/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.openjpa.kernel; import java.io.IOException; import java.io.NotSerializableException; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Modifier; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Calendar; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.TimeZone; import java.util.concurrent.locks.ReentrantLock; import org.apache.openjpa.conf.OpenJPAConfiguration; import org.apache.openjpa.enhance.DynamicPersistenceCapable; import org.apache.openjpa.enhance.FieldManager; import org.apache.openjpa.enhance.ManagedInstanceProvider; import org.apache.openjpa.enhance.PCRegistry; import org.apache.openjpa.enhance.PersistenceCapable; import org.apache.openjpa.enhance.RedefinitionHelper; import org.apache.openjpa.enhance.StateManager; import org.apache.openjpa.event.LifecycleEvent; import org.apache.openjpa.event.LifecycleEventManager; import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.meta.AccessCode; import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.FetchGroup; import org.apache.openjpa.meta.FieldMetaData; import org.apache.openjpa.meta.JavaTypes; import org.apache.openjpa.meta.UpdateStrategies; import org.apache.openjpa.meta.ValueMetaData; import org.apache.openjpa.meta.ValueStrategies; import org.apache.openjpa.util.ApplicationIds; import org.apache.openjpa.util.Exceptions; import org.apache.openjpa.util.ImplHelper; import org.apache.openjpa.util.InternalException; import org.apache.openjpa.util.InvalidStateException; import org.apache.openjpa.util.ObjectNotFoundException; import org.apache.openjpa.util.OpenJPAId; import org.apache.openjpa.util.ProxyManager; import org.apache.openjpa.util.RuntimeExceptionTranslator; import org.apache.openjpa.util.UserException; /** * Implementation of the {@link OpenJPAStateManager} interface for use * with this runtime. Each state manager manages the state of a single * persistence capable instance. The state manager is also responsible for * all communications about the instance to the {@link StoreManager}. * The state manager uses the State pattern in both its interaction with * the governed instance and its interaction with the broker. * In its interactions with the persistence capable instance, it uses the * {@link FieldManager} interface. Similarly, when interacting with the * broker, it uses the {@link PCState} singleton that represents * the current lifecycle state of the instance. * * @author Abe White */ public class StateManagerImpl implements OpenJPAStateManager, Serializable { public static final int LOAD_FGS = 0; public static final int LOAD_ALL = 1; public static final int LOAD_SERIALIZE = 2; private static final int FLAG_SAVE = 2 << 0; private static final int FLAG_DEREF = 2 << 1; private static final int FLAG_LOADED = 2 << 2; private static final int FLAG_READ_LOCKED = 2 << 3; private static final int FLAG_WRITE_LOCKED = 2 << 4; private static final int FLAG_OID_ASSIGNED = 2 << 5; private static final int FLAG_LOADING = 2 << 6; private static final int FLAG_PRE_DELETING = 2 << 7; private static final int FLAG_FLUSHED = 2 << 8; private static final int FLAG_PRE_FLUSHED = 2 << 9; private static final int FLAG_FLUSHED_DIRTY = 2 << 10; private static final int FLAG_IMPL_CACHE = 2 << 11; private static final int FLAG_INVERSES = 2 << 12; private static final int FLAG_NO_UNPROXY = 2 << 13; private static final int FLAG_VERSION_CHECK = 2 << 14; private static final int FLAG_VERSION_UPDATE = 2 << 15; private static final int FLAG_DETACHING = 2 << 16; private static final int FLAG_EMBED_DEREF = 2 << 17; private static final Localizer _loc = Localizer.forPackage (StateManagerImpl.class); // information about the instance private transient PersistenceCapable _pc = null; protected transient ClassMetaData _meta = null; protected BitSet _loaded = null; // Care needs to be taken when accessing these fields as they will can be null if no fields are // dirty, or have been flushed. private BitSet _dirty = null; private BitSet _flush = null; private BitSet _delayed = null; private int _flags = 0; // id is the state manager identity; oid is the persistent identity. oid // may be null for embedded and transient-transactional objects or new // instances that haven't been assigned an oid. id is reassigned to oid // on successful oid assignment (or flush completion if assignment is // during flush) private Object _id = null; private Object _oid = null; // the managing persistence manager and lifecycle state private transient BrokerImpl _broker; // this is serialized specially protected PCState _state = PCState.TRANSIENT; // the current and last loaded version indicators, and the lock object protected Object _version = null; protected Object _loadVersion = null; private Object _lock = null; private int _readLockLevel = -1; private int _writeLockLevel = -1; // delegates when providing/replacing instance data private SingleFieldManager _single = null; private SaveFieldManager _saved = null; private FieldManager _fm = null; // impldata; field impldata and intermediate data share the same array private Object _impl = null; protected Object[] _fieldImpl = null; // information about the owner of this instance, if it is embedded private StateManagerImpl _owner = null; // for embeddable object from query result private Object _ownerId = null; private int _ownerIndex = -1; private List<FieldMetaData> _mappedByIdFields = null; private transient ReentrantLock _instanceLock = null; private int _datePrecision = -1; /** * <p>set to <code>false</code> to prevent the postLoad method from * sending lifecycle callback events.</p> * <p>Callbacks are enabled by default</> */ private boolean postLoadCallback = true; /** * Constructor; supply id, type metadata, and owning persistence manager. */ protected StateManagerImpl(Object id, ClassMetaData meta, BrokerImpl broker) { _id = id; _meta = meta; _broker = broker; _single = new SingleFieldManager(this, broker); if (broker.getMultithreaded()) _instanceLock = new ReentrantLock(); if (_meta.getIdentityType() == ClassMetaData.ID_UNKNOWN && !_meta.isEmbeddable()) throw new UserException(_loc.get("meta-unknownid", _meta)); } /** * Create a new StateManager instance based on the StateManager provided. A * new PersistenceCapable instance will be created and associated with the * new StateManager. All fields will be copied into the ne PC instance as * well as the dirty, loaded, and flushed bitsets. * * @param sm A statemanager instance which will effectively be cloned. */ public StateManagerImpl(StateManagerImpl sm) { this(sm, sm.getPCState()); } /** * Create a new StateManager instance, optionally overriding the state * (FLUSHED, DELETED, etc) of the underlying PersistenceCapable instance). * * @param sm * A statemanager instance which will effectively be cloned. * @param newState * The new state of the underlying persistence capable object. */ public StateManagerImpl(StateManagerImpl sm, PCState newState) { this(sm.getId(), sm.getMetaData(), sm.getBroker()); PersistenceCapable origPC = sm.getPersistenceCapable(); _pc = origPC.pcNewInstance(sm, false); int[] fields = new int[sm.getMetaData().getFields().length]; for (int i = 0; i < fields.length; i++) { fields[i] = i; } _pc.pcCopyFields(origPC, fields); _pc.pcReplaceStateManager(this); _state = newState; // clone the field bitsets. _dirty=(BitSet)sm.getDirty().clone(); _loaded = (BitSet)sm.getLoaded().clone(); _flush = (BitSet) sm.getFlushed().clone(); _version = sm.getVersion(); _oid = sm.getObjectId(); _id = sm.getId(); // more data may need to be copied. } /** * Set the owning state and field if this is an embedded instance. */ void setOwner(StateManagerImpl owner, ValueMetaData ownerMeta) { _owner = owner; _ownerIndex = ownerMeta.getFieldMetaData().getIndex(); } /** * Whether this state manager is in the middle of a load. */ boolean isLoading() { return (_flags & FLAG_LOADING) > 0; } /** * Whether this state manager is in the middle of a load initiated * by outside code; for any internal methods that cause loading, the * loading flag is set automatically. */ void setLoading(boolean loading) { if (loading) _flags |= FLAG_LOADING; else _flags &= ~FLAG_LOADING; } /** * Set or reset the lifecycle state of the managed instance. If the * transactional state of the instance changes, it will be enlisted/ * delisted from the current transaction as necessary. The given * state will be initialized after being set. If the given state * is the same as the current state, this method will have no effect. */ private void setPCState(PCState state) { if (_state == state) return; PCState prev = _state; lock(); try { // notify the store manager that we're changing states; can veto _broker.getStoreManager().beforeStateChange(this, _state, state); // replace state boolean wasDeleted = _state.isDeleted(); boolean wasDirty = _state.isDirty(); boolean wasPending = _state.isPendingTransactional(); _state = state; // enlist/delist from transaction if (_state.isTransactional()) { _broker.addToTransaction(this); if (_state.isDeleted() != wasDeleted) _broker.setDirty(this, !wasDirty || isFlushed()); else if (_state.isDirty() && !wasDirty) _broker.setDirty(this, true); } else if (!wasPending && _state.isPendingTransactional()) _broker.addToPendingTransaction(this); else if (wasPending && !_state.isPendingTransactional()) _broker.removeFromPendingTransaction(this); else _broker.removeFromTransaction(this); _state.initialize(this, prev); if (_state.isDeleted() && !wasDeleted) fireLifecycleEvent(LifecycleEvent.AFTER_DELETE); } finally { unlock(); } } ////////////////////////////////////// // OpenJPAStateManager implementation ////////////////////////////////////// public void initialize(Class cls, PCState state) { // check to see if our current object id instance is the // correct id type for the specified class; this is for cases // when we have an application id hierarchy and we had set the // metadata to a superclass id -- the subclass' id may be a // different class, so we need to reset it if (_meta.getDescribedType() != cls) { ClassMetaData sub = _meta.getRepository().getMetaData (cls, _broker.getClassLoader(), true); if (_oid != null) { if (_meta.getIdentityType() == ClassMetaData.ID_DATASTORE) _oid = _broker.getStoreManager().copyDataStoreId(_oid, sub); else if (_meta.isOpenJPAIdentity()) _oid = ApplicationIds.copy(_oid, sub); else if (sub.getObjectIdType() != _meta.getObjectIdType()) { Object[] pkFields = ApplicationIds.toPKValues(_oid, _meta); _oid = ApplicationIds.fromPKValues(pkFields, sub); } } _meta = sub; } PersistenceCapable inst = PCRegistry.newInstance(cls, this, _oid, true); if (inst == null) { // the instance was null: check to see if the instance is // abstract (as can sometimes be the case when the // class discriminator strategy is not configured correctly) if (Modifier.isAbstract(cls.getModifiers())) throw new UserException(_loc.get("instantiate-abstract", cls.getName(), _oid)); throw new InternalException(); } initialize(inst, state); } /** * Initialize with the given instance and state. */ protected void initialize(PersistenceCapable pc, PCState state) { if (pc == null) throw new UserException(_loc.get("init-null-pc", _meta)); if (pc.pcGetStateManager() != null && pc.pcGetStateManager() != this) throw new UserException(_loc.get("init-sm-pc", Exceptions.toString(pc))).setFailedObject(pc); pc.pcReplaceStateManager(this); FieldMetaData[] fmds = _meta.getFields(); _loaded = new BitSet(fmds.length); // mark primary key and non-persistent fields as loaded for(int i : _meta.getPkAndNonPersistentManagedFmdIndexes()){ _loaded.set(i); } _mappedByIdFields = _meta.getMappyedByIdFields(); // record whether there are any managed inverse fields if (_broker.getInverseManager() != null && _meta.hasInverseManagedFields()) _flags |= FLAG_INVERSES; pc.pcSetDetachedState(null); _pc = pc; if (_oid instanceof OpenJPAId) ((OpenJPAId) _oid).setManagedInstanceType(_meta.getDescribedType()); // initialize our state and add ourselves to the broker's cache setPCState(state); if ( _oid == null || _broker.getStateManagerImplById(_oid, false) == null) { _broker.setStateManager(_id, this, BrokerImpl.STATUS_INIT); } if (state == PCState.PNEW) fireLifecycleEvent(LifecycleEvent.AFTER_PERSIST); // if this is a non-tracking PC, add a hard ref to the appropriate data // sets and give it an opportunity to make a state snapshot. if (!isIntercepting()) { saveFields(true); if (!isNew()) RedefinitionHelper.assignLazyLoadProxies(this); } } /** * Whether or not data access in this instance is intercepted. This differs * from {@link ClassMetaData#isIntercepting()} in that it checks for * property access + subclassing in addition to the redefinition / * enhancement checks. * * @since 1.0.0 */ public boolean isIntercepting() { if (getMetaData().isIntercepting()) return true; // TODO:JRB Intercepting if (AccessCode.isProperty(getMetaData().getAccessType()) && _pc instanceof DynamicPersistenceCapable) return true; return false; } /** * Fire the given lifecycle event to all listeners. */ private boolean fireLifecycleEvent(int type) { if (type == LifecycleEvent.AFTER_PERSIST && _broker.getConfiguration().getCallbackOptionsInstance().getPostPersistCallbackImmediate()) { fetchObjectId(); } return _broker.fireLifecycleEvent(getManagedInstance(), null, _meta, type); } public void load(FetchConfiguration fetch) { load(fetch, LOAD_FGS, null, null, false); } /** * Load the state of this instance based on the given fetch configuration * and load mode. Return true if any data was loaded, false otherwise. */ protected boolean load(FetchConfiguration fetch, int loadMode, BitSet exclude, Object sdata, boolean forWrite) { if (!forWrite && (!isPersistent() || (isNew() && !isFlushed()) || isDeleted())) return false; // if any fields being loaded, do state transitions for read BitSet fields = getUnloadedInternal(fetch, loadMode, exclude); boolean active = _broker.isActive(); if (!forWrite && fields != null) beforeRead(-1); // call load even if no fields are being loaded, because it takes // care of checking if the DFG is loaded, making sure version info // is loaded, etc int lockLevel = calculateLockLevel(active, forWrite, fetch); boolean ret = loadFields(fields, fetch, lockLevel, sdata); obtainLocks(active, forWrite, lockLevel, fetch, sdata); return ret; } public Object getManagedInstance() { if (_pc instanceof ManagedInstanceProvider) return ((ManagedInstanceProvider) _pc).getManagedInstance(); else return _pc; } public PersistenceCapable getPersistenceCapable() { return _pc; } public ClassMetaData getMetaData() { return _meta; } public OpenJPAStateManager getOwner() { return _owner; } public int getOwnerIndex() { return _ownerIndex; } public void setOwner(Object oid) { _ownerId = oid; } public boolean isEmbedded() { // _owner may not be set if embed object is from query result return _owner != null || _state instanceof ENonTransState; } public boolean isFlushed() { return (_flags & FLAG_FLUSHED) > 0; } public boolean isFlushedDirty() { return (_flags & FLAG_FLUSHED_DIRTY) > 0; } public BitSet getLoaded() { return _loaded; } public BitSet getUnloaded(FetchConfiguration fetch) { // collect fields to load from data store based on fetch configuration BitSet fields = getUnloadedInternal(fetch, LOAD_FGS, null); return (fields == null) ? new BitSet(0) : fields; } /** * Internal version of {@link OpenJPAStateManager#getUnloaded} that avoids * creating an empty bit set by returning null when there are no unloaded * fields. */ private BitSet getUnloadedInternal(FetchConfiguration fetch, int mode, BitSet exclude) { if (exclude == StoreContext.EXCLUDE_ALL) return null; BitSet fields = null; FieldMetaData[] fmds = _meta.getFields(); boolean load; for (int i = 0; i < fmds.length; i++) { if (_loaded.get(i) || (exclude != null && exclude.get(i))) continue; switch (mode) { case LOAD_SERIALIZE: load = !fmds[i].isTransient(); break; case LOAD_FGS: load = fetch == null || fetch.requiresFetch(fmds[i]) != FetchConfiguration.FETCH_NONE; break; default: // LOAD_ALL load = true; } if (load) { if (fields == null) fields = new BitSet(fmds.length); fields.set(i); } } return fields; } public StoreContext getContext() { return _broker; } /** * Managing broker. */ BrokerImpl getBroker() { return _broker; } public Object getId() { return _id; } public Object getObjectId() { StateManagerImpl sm = this; while (sm.getOwner() != null) sm = (StateManagerImpl) sm.getOwner(); if (sm.isEmbedded() && sm.getOwner() == null) return sm._ownerId; return sm._oid; } public void setObjectId(Object oid) { _oid = oid; if (_pc != null && oid instanceof OpenJPAId) ((OpenJPAId) oid).setManagedInstanceType(_meta.getDescribedType()); } public StateManagerImpl getObjectIdOwner() { StateManagerImpl sm = this; while (sm.getOwner() != null) sm = (StateManagerImpl) sm.getOwner(); return sm; } public boolean assignObjectId(boolean flush) { lock(); try { return assignObjectId(flush, false); } finally { unlock(); } } /** * Ask store manager to assign our oid, optionally flushing and * optionally recaching on the new oid. */ boolean assignObjectId(boolean flush, boolean preFlushing) { if (_oid != null || isEmbedded() || !isPersistent()) return true; if (_broker.getStoreManager().assignObjectId(this, preFlushing)) { if (!preFlushing) assertObjectIdAssigned(true); } else if (flush) _broker.flush(); else return false; return true; } /** * Make sure we were assigned an oid, and perform actions to make it * permanent. * * @param recache whether to recache ourself on the new oid */ private void assertObjectIdAssigned(boolean recache) { if (!isNew() || isDeleted() || isProvisional() || (_flags & FLAG_OID_ASSIGNED) != 0) return; if (_oid == null) { if (_meta.getIdentityType() == ClassMetaData.ID_DATASTORE) throw new InternalException(Exceptions.toString (getManagedInstance())); _oid = ApplicationIds.create(_pc, _meta); } Object orig = _id; _id = _oid; if (recache) { try { _broker.setStateManager(orig, this, BrokerImpl.STATUS_OID_ASSIGN); } catch (RuntimeException re) { _id = orig; _oid = null; throw re; } } _flags |= FLAG_OID_ASSIGNED; } /** * Assign the proper generated value to the given field based on its * value-strategy. */ private boolean assignField(int field, boolean preFlushing) { OpenJPAStateManager sm = this; while (sm != null && sm.isEmbedded()) sm = sm.getOwner(); if (sm == null) return false; if (!sm.isNew() || sm.isFlushed() || sm.isDeleted()) return false; // special-case oid fields, which require us to look inside the oid // object FieldMetaData fmd = _meta.getField(field); if (fmd.getDeclaredTypeCode() == JavaTypes.OID) { // try to shortcut if possible if (_oid != null || isEmbedded() || !isPersistent()) return true; // check embedded fields of oid for value strategy + default value FieldMetaData[] pks = fmd.getEmbeddedMetaData().getFields(); OpenJPAStateManager oidsm = null; boolean assign = false; for (int i = 0; !assign && i < pks.length; i++) { if (pks[i].getValueStrategy() == ValueStrategies.NONE) continue; if (oidsm == null) oidsm = new ObjectIdStateManager(fetchObjectField(field), this, fmd); assign = oidsm.isDefaultValue(i); } return assign && assignObjectId(!preFlushing, preFlushing); } // Just return if there's no value generation strategy if (fmd.getValueStrategy() == ValueStrategies.NONE) return false; // Throw exception if field already has a value assigned. // @GeneratedValue overrides POJO initial values and setter methods if (!fmd.isValueGenerated() && !isDefaultValue(field)) throw new InvalidStateException(_loc.get( "existing-value-override-excep", fmd.getFullName(false))); // for primary key fields, assign the object id and recache so that // to the user, so it looks like the oid always matches the pk fields if (fmd.isPrimaryKey() && !isEmbedded()) return assignObjectId(!preFlushing, preFlushing); // for other fields just assign the field or flush if needed if (_broker.getStoreManager().assignField(this, field, preFlushing)) { fmd.setValueGenerated(true); return true; } if (!preFlushing) _broker.flush(); return !preFlushing; } public Object getLock() { return _lock; } public void setLock(Object lock) { _lock = lock; } public Object getVersion() { return _version; } public void setVersion(Object version) { _loadVersion = version; assignVersionField(version); } Object getLoadVersion() { return _loadVersion; } public void setNextVersion(Object version) { assignVersionField(version); } public static Timestamp roundTimestamp(Timestamp val, int datePrecision) { // ensure that we do not insert dates at a greater precision than // that at which they will be returned by a SELECT int rounded = (int) Math.round(val.getNanos() / (double) datePrecision); long time = val.getTime(); int nanos = rounded * datePrecision; if (nanos > 999999999) { // rollover to next second time = time + 1000; nanos = 0; } val = new Timestamp(time); val.setNanos(nanos); return val; } private void assignVersionField(Object version) { if (version instanceof Timestamp) { if (_datePrecision == -1) { try { OpenJPAConfiguration conf = _broker.getConfiguration(); Class confCls = Class.forName("org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl"); if (confCls.isAssignableFrom(conf.getClass())) { Object o = conf.getClass().getMethod("getDBDictionaryInstance").invoke(conf, (Object[]) null); _datePrecision = o.getClass().getField("datePrecision").getInt(o); } else { _datePrecision = 1000; } } catch (Throwable e) { _datePrecision = 1000; } } version = roundTimestamp((Timestamp) version, _datePrecision); } _version = version; FieldMetaData vfield = _meta.getVersionField(); if (vfield != null) store(vfield.getIndex(), JavaTypes.convert(version, vfield.getTypeCode())); } public PCState getPCState() { return _state; } public synchronized Object getImplData() { return _impl; } public synchronized Object setImplData(Object data, boolean cacheable) { Object old = _impl; _impl = data; if (cacheable && data != null) _flags |= FLAG_IMPL_CACHE; else _flags &= ~FLAG_IMPL_CACHE; return old; } public boolean isImplDataCacheable() { return (_flags & FLAG_IMPL_CACHE) != 0; } public Object getImplData(int field) { return getExtraFieldData(field, true); } public Object setImplData(int field, Object data) { return setExtraFieldData(field, data, true); } public synchronized boolean isImplDataCacheable(int field) { if (_fieldImpl == null || !_loaded.get(field)) return false; if (_meta.getField(field).usesImplData() != null) return false; int idx = _meta.getExtraFieldDataIndex(field); return idx != -1 && _fieldImpl[idx] != null; } public Object getIntermediate(int field) { return getExtraFieldData(field, false); } public void setIntermediate(int field, Object data) { setExtraFieldData(field, data, false); } /** * Return the data from the proper index of the extra field data array. */ protected synchronized Object getExtraFieldData(int field, boolean isLoaded) { // only return the field data if the field is in the right loaded // state; otherwise we might return intermediate for impl data or // vice versa if (_fieldImpl == null || _loaded.get(field) != isLoaded) return null; int idx = _meta.getExtraFieldDataIndex(field); return (idx == -1) ? null : _fieldImpl[idx]; } /** * Set the data from to proper index of the extra field data array. */ private synchronized Object setExtraFieldData(int field, Object data, boolean loaded) { int idx = _meta.getExtraFieldDataIndex(field); if (idx == -1) throw new InternalException(String.valueOf(_meta.getField(field))); Object old = (_fieldImpl == null) ? null : _fieldImpl[idx]; if (data != null) { // cannot set if field in wrong loaded state if (_loaded.get(field) != loaded) throw new InternalException(String.valueOf(_meta.getField (field))); // set data if (_fieldImpl == null) _fieldImpl = new Object[_meta.getExtraFieldDataLength()]; _fieldImpl[idx] = data; } else if (_fieldImpl != null && _loaded.get(field) == loaded) _fieldImpl[idx] = null; return old; } public Object fetch(int field) { Object val = fetchField(field, false); return _meta.getField(field).getExternalValue(val, _broker); } public Object fetchField(int field, boolean transitions) { FieldMetaData fmd = _meta.getField(field); if (fmd == null) throw new UserException(_loc.get("no-field", String.valueOf(field), getManagedInstance().getClass())). setFailedObject(getManagedInstance()); // do normal state transitions if (!fmd.isPrimaryKey() && transitions) accessingField(field); switch (fmd.getDeclaredTypeCode()) { case JavaTypes.STRING: return fetchStringField(field); case JavaTypes.OBJECT: return fetchObjectField(field); case JavaTypes.BOOLEAN: return (fetchBooleanField(field)) ? Boolean.TRUE : Boolean.FALSE; case JavaTypes.BYTE: return Byte.valueOf(fetchByteField(field)); case JavaTypes.CHAR: return Character.valueOf(fetchCharField(field)); case JavaTypes.DOUBLE: return Double.valueOf(fetchDoubleField(field)); case JavaTypes.FLOAT: return Float.valueOf(fetchFloatField(field)); case JavaTypes.INT: return fetchIntField(field); case JavaTypes.LONG: return fetchLongField(field); case JavaTypes.SHORT: return Short.valueOf(fetchShortField(field)); default: return fetchObjectField(field); } } public void store(int field, Object val) { val = _meta.getField(field).getFieldValue(val, _broker); storeField(field, val); } public void storeField(int field, Object val) { storeField(field, val, this); } /** * <p>Checks whether or not <code>_pc</code> is dirty. In the cases where * field tracking is not happening (see below), this method will do a * state comparison to find whether <code>_pc</code> is dirty, and will * update this instance with this information. In the cases where field * tracking is happening, this method is a no-op.</p> * * <p>Fields are tracked for all classes that are run through the OpenJPA * enhancer prior to or during deployment, and all classes (enhanced or * unenhanced) in a Java 6 environment or newer.</p> * * <p>In a Java 5 VM or older: * <br>- instances of unenhanced classes that use * property access and obey the property access limitations are tracked * when the instances are loaded from the database by OpenJPA, and are * not tracked when the instances are created by application code. * <br>- instances of unenhanced classes that use field access are * never tracked.</p> * * @since 1.0.0 */ public void dirtyCheck() { if (!needsDirtyCheck()) return; SaveFieldManager saved = getSaveFieldManager(); if (saved == null) throw new InternalException(_loc.get("no-saved-fields", getMetaData().getDescribedType().getName())); FieldMetaData[] fmds = getMetaData().getFields(); for (int i = 0; i < fmds.length; i++) { // pk and version fields cannot be mutated; don't mark them // as such. ##### validate? if (!fmds[i].isPrimaryKey() && !fmds[i].isVersion() && _loaded.get(i)) { if (!saved.isFieldEqual(i, fetch(i))) { dirty(i); } } } } private boolean needsDirtyCheck() { if (isIntercepting()) return false; if (isDeleted()) return false; if (isNew() && !isFlushed()) return false; return true; } public Object fetchInitialField(int field) { FieldMetaData fmd = _meta.getField(field); if (_broker.getRestoreState() == RestoreState.RESTORE_NONE && ((_flags & FLAG_INVERSES) == 0 || fmd.getInverseMetaDatas().length == 0)) throw new InvalidStateException(_loc.get("restore-unset")); switch (fmd.getDeclaredTypeCode()) { case JavaTypes.DATE: case JavaTypes.CALENDAR: case JavaTypes.ARRAY: case JavaTypes.COLLECTION: case JavaTypes.MAP: case JavaTypes.OBJECT: // if we're not saving mutable types, throw an exception if (_broker.getRestoreState() != RestoreState.RESTORE_ALL && ((_flags & FLAG_INVERSES) == 0 || fmd.getInverseMetaDatas().length == 0)) throw new InvalidStateException(_loc.get ("mutable-restore-unset")); } lock(); try { if (_saved == null || !_loaded.get(field) || !isFieldDirty(field)) return fetchField(field, false); // if the field is dirty but we never loaded it, we can't restore it if (_saved.getUnloaded().get(field)) throw new InvalidStateException(_loc.get("initial-unloaded", fmd)); provideField(_saved.getState(), _single, field); return fetchField(_single, fmd); } finally { unlock(); } } /** * Fetch the specified field from the specified field manager, wrapping it * in an object if it's a primitive. A field should be provided to the * field manager before this call is made. */ private static Object fetchField(FieldManager fm, FieldMetaData fmd) { int field = fmd.getIndex(); switch (fmd.getDeclaredTypeCode()) { case JavaTypes.BOOLEAN: return (fm.fetchBooleanField(field)) ? Boolean.TRUE : Boolean.FALSE; case JavaTypes.BYTE: return Byte.valueOf(fm.fetchByteField(field)); case JavaTypes.CHAR: return Character.valueOf(fm.fetchCharField(field)); case JavaTypes.DOUBLE: return Double.valueOf(fm.fetchDoubleField(field)); case JavaTypes.FLOAT: return Float.valueOf(fm.fetchFloatField(field)); case JavaTypes.INT: return fm.fetchIntField(field); case JavaTypes.LONG: return fm.fetchLongField(field); case JavaTypes.SHORT: return Short.valueOf(fm.fetchShortField(field)); case JavaTypes.STRING: return fm.fetchStringField(field); default: return fm.fetchObjectField(field); } } public void setRemote(int field, Object value) { lock(); try { Boolean stat = dirty(field, Boolean.FALSE, false); storeField(field, value, _single); replaceField(_pc, _single, field); postDirty(stat); } finally { unlock(); } } //////////////////////// // Lifecycle operations //////////////////////// /** * Notification that the object is about to be accessed. * * @param field the field number being read, or -1 if not a single * field read */ void beforeRead(int field) { // allow unmediated reads of primary key fields if (field != -1 && _meta.getField(field).isPrimaryKey()) return; if (_broker.isActive() && !_broker.isTransactionEnding()) { if (_broker.getOptimistic()) setPCState(_state.beforeOptimisticRead(this, field)); else setPCState(_state.beforeRead(this, field)); } else if (_broker.getNontransactionalRead()) setPCState(_state.beforeNontransactionalRead(this, field)); else throw new InvalidStateException(_loc.get("non-trans-read")). setFailedObject(getManagedInstance()); } /** * Delegates to the current state. * * @see PCState#beforeFlush */ void beforeFlush(int reason, OpCallbacks call) { _state.beforeFlush(this, reason == BrokerImpl.FLUSH_LOGICAL, call); } /** * Delegates to the current state. * * @see PCState#flush */ void afterFlush(int reason) { // nothing happens when we flush non-persistent states if (!isPersistent()) return; if (reason != BrokerImpl.FLUSH_ROLLBACK && reason != BrokerImpl.FLUSH_LOGICAL) { // analyze previous state for later boolean wasNew = isNew(); boolean wasFlushed = isFlushed(); boolean wasDeleted = isDeleted(); boolean needPostUpdate = !(wasNew && !wasFlushed) && (ImplHelper.getUpdateFields(this) != null); // all dirty fields were flushed, we are referencing the _dirty BitSet directly here // because we don't want to instantiate it if we don't have to. if (_dirty != null) { getFlushed().or(_dirty); } // important to set flushed bit after calling _state.flush so // that the state can tell whether this is the first flush setPCState(_state.flush(this)); _flags |= FLAG_FLUSHED; _flags &= ~FLAG_FLUSHED_DIRTY; _flags &= ~FLAG_VERSION_CHECK; _flags &= ~FLAG_VERSION_UPDATE; // if this was an inc flush during which we had our identity // assigned, tell the broker to cache us under our final oid if (reason == BrokerImpl.FLUSH_INC) assertObjectIdAssigned(true); // if this object was stored with preFlush, do post-store callback if ((_flags & FLAG_PRE_FLUSHED) > 0) fireLifecycleEvent(LifecycleEvent.AFTER_STORE); // do post-update as needed if (wasNew && !wasFlushed) fireLifecycleEvent(LifecycleEvent.AFTER_PERSIST_PERFORMED); else if (wasDeleted) fireLifecycleEvent(LifecycleEvent.AFTER_DELETE_PERFORMED); else if (needPostUpdate) // updates and new-flushed with changes fireLifecycleEvent(LifecycleEvent.AFTER_UPDATE_PERFORMED); } else if (reason == BrokerImpl.FLUSH_ROLLBACK) { // revert to last loaded version and original oid assignVersionField(_loadVersion); if (isNew() && (_flags & FLAG_OID_ASSIGNED) == 0) _oid = null; } _flags &= ~FLAG_PRE_FLUSHED; } /** * Delegates to the current state after checking the value * of the RetainState flag. * * @see PCState#commit * @see PCState#commitRetain */ void commit() { // release locks before oid updated releaseLocks(); // update version and oid information setVersion(_version); _flags &= ~FLAG_FLUSHED; _flags &= ~FLAG_FLUSHED_DIRTY; Object orig = _id; assertObjectIdAssigned(false); boolean wasNew = isNew() && !isDeleted() && !isProvisional(); if (_broker.getRetainState()) setPCState(_state.commitRetain(this)); else setPCState(_state.commit(this)); // ask the broker to re-cache us if we were new previously if (wasNew) _broker.setStateManager(orig, this, BrokerImpl.STATUS_COMMIT_NEW); } /** * Delegates to the current state after checking the value * of the RetainState flag. * * @see PCState#rollback * @see PCState#rollbackRestore */ void rollback() { // release locks releaseLocks(); _flags &= ~FLAG_FLUSHED; _flags &= ~FLAG_FLUSHED_DIRTY; afterFlush(BrokerImpl.FLUSH_ROLLBACK); if (_broker.getRestoreState() != RestoreState.RESTORE_NONE) setPCState(_state.rollbackRestore(this)); else setPCState(_state.rollback(this)); } /** * Rollback state of the managed instance to the given savepoint. */ void rollbackToSavepoint(SavepointFieldManager savepoint) { _state = savepoint.getPCState(); BitSet loaded = savepoint.getLoaded(); for (int i = 0, len = loaded.length(); i < len; i++) { if (loaded.get(i) && savepoint.restoreField(i)) { provideField(savepoint.getCopy(), savepoint, i); replaceField(_pc, savepoint, i); } } _loaded = loaded; _dirty = savepoint.getDirty(); _flush = savepoint.getFlushed(); _version = savepoint.getVersion(); _loadVersion = savepoint.getLoadVersion(); } /** * Delegates to the current state. * * @see PCState#persist * @see Broker#persist */ void persist() { setPCState(_state.persist(this)); } /** * Delegates to the current state. * * @see PCState#delete * @see Broker#delete */ void delete() { setPCState(_state.delete(this)); } /** * Delegates to the current state. * * @see PCState#nontransactional * @see Broker#nontransactional */ void nontransactional() { setPCState(_state.nontransactional(this)); } /** * Delegates to the current state. * * @see PCState#transactional * @see Broker#transactional */ void transactional() { setPCState(_state.transactional(this)); } /** * Delegates to the current state. * * @see PCState#nonprovisional */ void nonprovisional(boolean logical, OpCallbacks call) { setPCState(_state.nonprovisional(this, logical, call)); } /** * Delegates to the current state. * * @see PCState#release * @see Broker#release */ void release(boolean unproxy) { release(unproxy, false); } void release(boolean unproxy, boolean force) { // optimization for detach-in-place special case when fields are // already (un)proxied correctly if (!unproxy) _flags |= FLAG_NO_UNPROXY; try { if (force) setPCState(PCState.TRANSIENT); else setPCState(_state.release(this)); } finally { _flags &= ~FLAG_NO_UNPROXY; } } /** * Delegates to the current state. * * @see PCState#evict * @see Broker#evict */ void evict() { setPCState(_state.evict(this)); } /** * Gather relations reachable from values using * {@link ValueMetaData#CASCADE_IMMEDIATE}. */ void gatherCascadeRefresh(OpCallbacks call) { FieldMetaData[] fmds = _meta.getFields(); for (int i = 0; i < fmds.length; i++) { if (!_loaded.get(i)) continue; if (fmds[i].getCascadeRefresh() == ValueMetaData.CASCADE_IMMEDIATE || fmds[i].getKey().getCascadeRefresh() == ValueMetaData.CASCADE_IMMEDIATE || fmds[i].getElement().getCascadeRefresh() == ValueMetaData.CASCADE_IMMEDIATE) { _single.storeObjectField(i, fetchField(i, false)); _single.gatherCascadeRefresh(call); _single.clear(); } } } public boolean beforeRefresh(boolean refreshAll) { // note: all logic placed here rather than in the states for // optimization; this method public b/c used by remote package // nothing to do for non persistent or new unflushed instances if (!isPersistent() || (isNew() && !isFlushed())) return false; lock(); try { // if dirty need to clear fields if (isDirty()) { clearFields(); return true; } // if some fields have been loaded but the instance is out of // date or this is part of a refreshAll() and we don't want to // take the extra hit to see if the instance is out of date, clear if (_loaded.length() > 0 && (refreshAll || isEmbedded() || !syncVersion(null))) { Object version = _version; clearFields(); // if syncVersion just replaced the version, reset it if (!refreshAll && !isEmbedded()) setVersion(version); return true; } return false; } finally { unlock(); } } /** * Perform state transitions after refresh. This method is only * called if {@link #beforeRefresh} returns true. */ void afterRefresh() { lock(); try { // transition to clean or nontransactional depending on trans status if (!_broker.isActive()) setPCState(_state.afterNontransactionalRefresh()); else if (_broker.getOptimistic()) setPCState(_state.afterOptimisticRefresh()); else setPCState(_state.afterRefresh()); } finally { unlock(); } } /** * Mark this object as a dereferenced dependent object. */ void setDereferencedDependent(boolean deref, boolean notify) { if (!deref && (_flags & FLAG_DEREF) > 0) { if (notify) _broker.removeDereferencedDependent(this); _flags &= ~FLAG_DEREF; } else if (deref && (_flags & FLAG_DEREF) == 0) { _flags |= FLAG_DEREF; if (notify) _broker.addDereferencedDependent(this); } } void setDereferencedEmbedDependent(boolean deref) { if (!deref && (_flags & FLAG_EMBED_DEREF) > 0) { _flags &= ~FLAG_EMBED_DEREF; } else if (deref && (_flags & FLAG_EMBED_DEREF) == 0) { _flags |= FLAG_EMBED_DEREF; } } public boolean getDereferencedEmbedDependent() { return ((_flags & FLAG_EMBED_DEREF) == 0 ? false : true); } /////////// // Locking /////////// /** * Notification that we've been read-locked. Pass in the level at which * we were locked and the level at which we should write lock ourselves * on dirty. */ void readLocked(int readLockLevel, int writeLockLevel) { // make sure object is added to transaction so lock will get // cleared on commit/rollback if (readLockLevel != LockLevels.LOCK_NONE) transactional(); _readLockLevel = readLockLevel; _writeLockLevel = writeLockLevel; _flags |= FLAG_READ_LOCKED; _flags &= ~FLAG_WRITE_LOCKED; } /** * Return the lock level to use when loading state. */ private int calculateLockLevel(boolean active, boolean forWrite, FetchConfiguration fetch) { if (!active) return LockLevels.LOCK_NONE; if (fetch == null) fetch = _broker.getFetchConfiguration(); if (_readLockLevel == -1 || _readLockLevel < fetch.getReadLockLevel()) _readLockLevel = fetch.getReadLockLevel(); if (_writeLockLevel == -1 || _writeLockLevel < fetch.getWriteLockLevel()) _writeLockLevel = fetch.getWriteLockLevel(); return (forWrite) ? _writeLockLevel : _readLockLevel; } /** * Make sure we're locked at the given level. */ private void obtainLocks(boolean active, boolean forWrite, int lockLevel, FetchConfiguration fetch, Object sdata) { if (!active) return; // if we haven't been locked yet, lock now at the given level int flag = (forWrite) ? FLAG_WRITE_LOCKED : FLAG_READ_LOCKED; if ((_flags & flag) == 0) { // make sure object is added to transaction so lock will get // cleared on commit/rollback if (lockLevel != LockLevels.LOCK_NONE) transactional(); if (fetch == null) fetch = _broker.getFetchConfiguration(); _broker.getLockManager().lock(this, lockLevel, fetch.getLockTimeout(), sdata); _flags |= FLAG_READ_LOCKED; _flags |= flag; } } /** * Release locks. */ private void releaseLocks() { if (_lock != null) _broker.getLockManager().release(this); _readLockLevel = -1; _writeLockLevel = -1; _flags &= ~FLAG_READ_LOCKED; _flags &= ~FLAG_WRITE_LOCKED; } //////////////////////////////////////////// // Implementation of StateManager interface //////////////////////////////////////////// /** * @return whether or not unloaded fields should be closed. */ public boolean serializing() { // if the broker is in the midst of a serialization, then no special // handling should be performed on the instance, and no subsequent // load should happen if (_broker.isSerializing()) return false; try { if (_meta.isDetachable()) return DetachManager.preSerialize(this); load(_broker.getFetchConfiguration(), LOAD_SERIALIZE, null, null, false); return false; } catch (RuntimeException re) { throw translate(re); } } public boolean writeDetached(ObjectOutput out) throws IOException { BitSet idxs = new BitSet(_meta.getFields().length); lock(); try { boolean detsm = DetachManager.writeDetachedState(this, out, idxs); if (detsm) _flags |= FLAG_DETACHING; FieldMetaData[] fmds = _meta.getFields(); for (int i = 0; i < fmds.length; i++) { if (fmds[i].isTransient()) continue; provideField(_pc, _single, i); _single.serialize(out, !idxs.get(i)); _single.clear(); } return true; } catch (RuntimeException re) { throw translate(re); } finally { _flags &= ~FLAG_DETACHING; unlock(); } } public void proxyDetachedDeserialized(int idx) { // we don't serialize state manager impls throw new InternalException(); } public boolean isTransactional() { // special case for TCLEAN, which we want to appear non-trans to // internal code, but which publicly should be transactional return _state == PCState.TCLEAN || _state.isTransactional(); } public boolean isPendingTransactional() { return _state.isPendingTransactional(); } public boolean isProvisional() { return _state.isProvisional(); } public boolean isPersistent() { return _state.isPersistent(); } public boolean isNew() { return _state.isNew(); } public boolean isDeleted() { return _state.isDeleted(); } public boolean isDirty() { return _state.isDirty(); } public boolean isDetached() { return (_flags & FLAG_DETACHING) != 0; } public Object getGenericContext() { return _broker; } public Object fetchObjectId() { try { if (hasGeneratedKey() && _state instanceof PNewState && _oid == null) return _oid; assignObjectId(true); if (_oid == null || !_broker.getConfiguration(). getCompatibilityInstance().getCopyObjectIds()) return _oid; if (_meta.getIdentityType() == ClassMetaData.ID_DATASTORE) return _broker.getStoreManager().copyDataStoreId(_oid, _meta); return ApplicationIds.copy(_oid, _meta); } catch (RuntimeException re) { throw translate(re); } } private boolean hasGeneratedKey() { FieldMetaData[] pkFields = _meta.getPrimaryKeyFields(); for (int i = 0; i < pkFields.length; i++) { if (pkFields[i].getValueStrategy() == ValueStrategies.AUTOASSIGN) return true; } return false; } public Object getPCPrimaryKey(Object oid, int field) { FieldMetaData fmd = _meta.getField(field); Object pk = ApplicationIds.get(oid, fmd); if (pk == null) return null; ClassMetaData relmeta = fmd.getDeclaredTypeMetaData(); pk = ApplicationIds.wrap(relmeta, pk); if (relmeta.getIdentityType() == ClassMetaData.ID_DATASTORE && fmd.getObjectIdFieldTypeCode() == JavaTypes.LONG) pk = _broker.getStoreManager().newDataStoreId(pk, relmeta); else if (relmeta.getIdentityType() == ClassMetaData.ID_APPLICATION && fmd.getObjectIdFieldType() != relmeta.getObjectIdType()) pk = ApplicationIds.fromPKValues(new Object[] { pk }, relmeta); return _broker.find(pk, false, null); } public byte replaceFlags() { // we always use load required so that we can detect when objects // are touched for locking or making transactional return PersistenceCapable.LOAD_REQUIRED; } public StateManager replaceStateManager(StateManager sm) { return sm; } public void accessingField(int field) { // possibly change state try { // If this field is loaded, and not a PK field allow pass through // TODO -- what about version fields? Could probably UT this if(_loaded.get(field) && !_meta.getField(field).isPrimaryKey()) return; beforeRead(field); beforeAccessField(field); } catch (RuntimeException re) { throw translate(re); } } public boolean isDelayed(int field) { if (_delayed == null) { return false; } return _delayed.get(field); } public void setDelayed(int field, boolean delay) { if (_delayed == null) { _delayed = new BitSet(); } if (delay) { _delayed.set(field); } else { _delayed.clear(field); } } /** * Loads a delayed access field. */ public void loadDelayedField(int field) { if (!isDelayed(field)) { return; } try { beforeRead(field); } catch (RuntimeException re) { throw translate(re); } lock(); try { boolean active = _broker.isActive(); int lockLevel = calculateLockLevel(active, false, null); BitSet fields = new BitSet(); fields.set(field); if (!_broker.getStoreManager().load(this, fields, _broker.getFetchConfiguration(), lockLevel, null)) { throw new ObjectNotFoundException(_loc.get("del-instance", _meta.getDescribedType(), _oid)). setFailedObject(getManagedInstance()); } // Cleared the delayed bit _delayed.clear(field); obtainLocks(active, false, lockLevel, null, null); } catch (RuntimeException re) { throw translate(re); } finally { unlock(); } } /** * Load the given field before access. */ protected void beforeAccessField(int field) { lock(); try { boolean active = _broker.isActive(); int lockLevel = calculateLockLevel(active, false, null); if (!_loaded.get(field)) loadField(field, lockLevel, false, true); else assignField(field, false); obtainLocks(active, false, lockLevel, null, null); } catch (RuntimeException re) { throw translate(re); } finally { unlock(); } } public void dirty(String field) { FieldMetaData fmd = _meta.getField(field); if (fmd == null) throw translate(new UserException(_loc.get("no-field", field, ImplHelper.getManagedInstance(_pc).getClass())) .setFailedObject(getManagedInstance())); dirty(fmd.getIndex(), null, true); } public void dirty(int field) { dirty(field, null, true); } private boolean isEmbeddedNotUpdatable() { // embeddable object returned from query result is not uptable return (_owner == null && _ownerId != null); } /** * Make the given field dirty. * * @param mutate if null, may be an SCO mutation; if true, is certainly * a mutation (or at least treat as one) * @return {@link Boolean#FALSE} if this instance was already dirty, * <code>null</code> if it was dirty but not since flush, and * {@link Boolean#TRUE} if it was not dirty */ private Boolean dirty(int field, Boolean mutate, boolean loadFetchGroup) { boolean locked = false; boolean newFlush = false; boolean clean = false; try { FieldMetaData fmd = _meta.getField(field); if (!isNew() || isFlushed()) { if (fmd.getUpdateStrategy() == UpdateStrategies.RESTRICT) throw new InvalidStateException(_loc.get ("update-restrict", fmd)); if (fmd.getUpdateStrategy() == UpdateStrategies.IGNORE) return Boolean.FALSE; } if (isEmbedded()) { if (isEmbeddedNotUpdatable()) throw new UserException(_loc.get ("cant-update-embed-in-query-result")).setFailedObject (getManagedInstance()); else // notify owner of change _owner.dirty(_ownerIndex, Boolean.TRUE, loadFetchGroup); } // is this a direct mutation of an sco field? if (mutate == null) { switch (fmd.getDeclaredTypeCode()) { case JavaTypes.COLLECTION: case JavaTypes.MAP: case JavaTypes.ARRAY: case JavaTypes.DATE: case JavaTypes.CALENDAR: case JavaTypes.OBJECT: mutate = Boolean.TRUE; break; case JavaTypes.PC: mutate = (fmd.isEmbedded()) ? Boolean.TRUE : Boolean.FALSE; break; default: mutate = Boolean.FALSE; // not sco } } // possibly change state boolean active = _broker.isActive(); clean = !_state.isDirty(); // intentional direct access // fire event fast before state change. if (clean) fireLifecycleEvent(LifecycleEvent.BEFORE_DIRTY); if (active) { if (_broker.getOptimistic()) setPCState(_state.beforeOptimisticWrite(this, field, mutate.booleanValue())); else setPCState(_state.beforeWrite(this, field, mutate.booleanValue())); } else if (fmd.getManagement() == FieldMetaData.MANAGE_PERSISTENT) { if (isPersistent() && !_broker.getNontransactionalWrite()) throw new InvalidStateException(_loc.get ("non-trans-write")).setFailedObject (getManagedInstance()); setPCState(_state.beforeNontransactionalWrite(this, field, mutate.booleanValue())); } if ((_flags & FLAG_FLUSHED) != 0) { newFlush = (_flags & FLAG_FLUSHED_DIRTY) == 0; _flags |= FLAG_FLUSHED_DIRTY; } lock(); locked = true; // note that the field is in need of flushing again, and tell the // broker too clearFlushField(field); _broker.setDirty(this, newFlush && !clean); // save the field for rollback if needed saveField(field); // dirty the field and mark loaded; load fetch group if needed int lockLevel = calculateLockLevel(active, true, null); if (!isFieldDirty(field)) { setLoaded(field, true); setFieldDirty(field); // make sure the field's fetch group is loaded if (loadFetchGroup && isPersistent() && fmd.getManagement() == fmd.MANAGE_PERSISTENT) loadField(field, lockLevel, true, true); } obtainLocks(active, true, lockLevel, null, null); } catch (RuntimeException re) { throw translate(re); } finally { if (locked) unlock(); } if (clean) return Boolean.TRUE; if (newFlush) { // this event can be fired later cause we're already dirty. fireLifecycleEvent(LifecycleEvent.BEFORE_DIRTY_FLUSHED); return null; } return Boolean.FALSE; } /** * Fire post-dirty events after field value changes. * * @param status return value from {@link #dirty(int, Boolean, boolean)} */ private void postDirty(Boolean status) { if (Boolean.TRUE.equals(status)) fireLifecycleEvent(LifecycleEvent.AFTER_DIRTY); else if (status == null) fireLifecycleEvent(LifecycleEvent.AFTER_DIRTY_FLUSHED); } public void removed(int field, Object removed, boolean key) { if (removed == null) return; try { // dereference dependent fields, delete embedded FieldMetaData fmd = _meta.getField(field); ValueMetaData vmd = (key) ? fmd.getKey() : fmd.getElement(); if (vmd.isEmbeddedPC()) _single.delete(vmd, removed, null); else if (vmd.getCascadeDelete() == ValueMetaData.CASCADE_AUTO) _single.dereferenceDependent(removed); } catch (RuntimeException re) { throw translate(re); } } public Object newProxy(int field) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) return newFieldProxy(field); switch (fmd.getTypeCode()) { case JavaTypes.DATE: if (fmd.getDeclaredType() == java.sql.Date.class) return new java.sql.Date(System.currentTimeMillis()); if (fmd.getDeclaredType() == java.sql.Timestamp.class) return new java.sql.Timestamp(System.currentTimeMillis()); if (fmd.getDeclaredType() == java.sql.Time.class) return new java.sql.Time(System.currentTimeMillis()); return new Date(); case JavaTypes.CALENDAR: return Calendar.getInstance(); case JavaTypes.COLLECTION: return new ArrayList(); case JavaTypes.MAP: return new HashMap(); } return null; } public Object newFieldProxy(int field) { FieldMetaData fmd = _meta.getField(field); ProxyManager mgr = _broker.getConfiguration(). getProxyManagerInstance(); Object init = fmd.getInitializer(); switch (fmd.getDeclaredTypeCode()) { case JavaTypes.DATE: return mgr.newDateProxy(fmd.getDeclaredType()); case JavaTypes.CALENDAR: return mgr.newCalendarProxy(fmd.getDeclaredType(), init instanceof TimeZone ? (TimeZone) init : null); case JavaTypes.COLLECTION: return mgr.newCollectionProxy(fmd.getProxyType(), fmd.getElement().getDeclaredType(), init instanceof Comparator ? (Comparator) init : null, _broker.getConfiguration().getCompatibilityInstance().getAutoOff()); case JavaTypes.MAP: return mgr.newMapProxy(fmd.getProxyType(), fmd.getKey().getDeclaredType(), fmd.getElement().getDeclaredType(), init instanceof Comparator ? (Comparator) init : null, _broker.getConfiguration().getCompatibilityInstance().getAutoOff()); } return null; } public boolean isDefaultValue(int field) { lock(); try { _single.clear(); provideField(_pc, _single, field); boolean ret = _single.isDefaultValue(); _single.clear(); return ret; } finally { unlock(); } } ///////////////////////////////////////////////////////// // Record that the field is dirty (which might load DFG) ///////////////////////////////////////////////////////// public void settingBooleanField(PersistenceCapable pc, int field, boolean curVal, boolean newVal, int set) { if (set != SET_REMOTE) { if (newVal == curVal && _loaded.get(field)) return; assertNoPrimaryKeyChange(field); } lock(); try { Boolean stat = dirty(field, Boolean.FALSE, set == SET_USER); _single.storeBooleanField(field, newVal); replaceField(pc, _single, field); postDirty(stat); } finally { unlock(); } } public void settingByteField(PersistenceCapable pc, int field, byte curVal, byte newVal, int set) { if (set != SET_REMOTE) { if (newVal == curVal && _loaded.get(field)) return; assertNoPrimaryKeyChange(field); } lock(); try { Boolean stat = dirty(field, Boolean.FALSE, set == SET_USER); _single.storeByteField(field, newVal); replaceField(pc, _single, field); postDirty(stat); } finally { unlock(); } } public void settingCharField(PersistenceCapable pc, int field, char curVal, char newVal, int set) { if (set != SET_REMOTE) { if (newVal == curVal && _loaded.get(field)) return; assertNoPrimaryKeyChange(field); } lock(); try { Boolean stat = dirty(field, Boolean.FALSE, set == SET_USER); _single.storeCharField(field, newVal); replaceField(pc, _single, field); postDirty(stat); } finally { unlock(); } } public void settingDoubleField(PersistenceCapable pc, int field, double curVal, double newVal, int set) { if (set != SET_REMOTE) { if (newVal == curVal && _loaded.get(field)) return; assertNoPrimaryKeyChange(field); } lock(); try { Boolean stat = dirty(field, Boolean.FALSE, set == SET_USER); _single.storeDoubleField(field, newVal); replaceField(pc, _single, field); postDirty(stat); } finally { unlock(); } } public void settingFloatField(PersistenceCapable pc, int field, float curVal, float newVal, int set) { if (set != SET_REMOTE) { if (newVal == curVal && _loaded.get(field)) return; assertNoPrimaryKeyChange(field); } lock(); try { Boolean stat = dirty(field, Boolean.FALSE, set == SET_USER); _single.storeFloatField(field, newVal); replaceField(pc, _single, field); postDirty(stat); } finally { unlock(); } } public void settingIntField(PersistenceCapable pc, int field, int curVal, int newVal, int set) { if (set != SET_REMOTE) { if (newVal == curVal && _loaded.get(field)) return; assertNoPrimaryKeyChange(field); } lock(); try { Boolean stat = dirty(field, Boolean.FALSE, set == SET_USER); _single.storeIntField(field, newVal); replaceField(pc, _single, field); postDirty(stat); } finally { unlock(); } } public void settingLongField(PersistenceCapable pc, int field, long curVal, long newVal, int set) { if (set != SET_REMOTE) { if (newVal == curVal && _loaded.get(field)) return; assertNoPrimaryKeyChange(field); } lock(); try { Boolean stat = dirty(field, Boolean.FALSE, set == SET_USER); _single.storeLongField(field, newVal); replaceField(pc, _single, field); postDirty(stat); } finally { unlock(); } } public void settingObjectField(PersistenceCapable pc, int field, Object curVal, Object newVal, int set) { if (set != SET_REMOTE) { FieldMetaData fmd = _meta.getField(field); if (_loaded.get(field)) { if (newVal == curVal) return; // only compare new to old values if the comparison is going to // be cheap -- don't compare collections, maps, UDTs switch (fmd.getDeclaredTypeCode()) { case JavaTypes.ARRAY: case JavaTypes.COLLECTION: case JavaTypes.MAP: case JavaTypes.PC_UNTYPED: break; case JavaTypes.PC: if (_meta.getField(field).isPrimaryKey()) { // this field is a derived identity //if (newVal != null && newVal.equals(curVal)) // return; //else { if (curVal != null && newVal != null && curVal instanceof PersistenceCapable && newVal instanceof PersistenceCapable) { PersistenceCapable curPc = (PersistenceCapable) curVal; PersistenceCapable newPc = (PersistenceCapable) newVal; if (curPc.pcFetchObjectId().equals(newPc.pcFetchObjectId())) return; } //} } else break; default: if (newVal != null && newVal.equals(curVal)) return; } } else { // if this is a dependent unloaded field, make sure to load // it now if (fmd.getCascadeDelete() == ValueMetaData.CASCADE_AUTO || fmd.getKey().getCascadeDelete() == ValueMetaData.CASCADE_AUTO || fmd.getElement().getCascadeDelete() == ValueMetaData.CASCADE_AUTO) curVal = fetchObjectField(field); } assertNoPrimaryKeyChange(field); if (fmd.getDeclaredTypeCode() == JavaTypes.OID) assertNotManagedObjectId(newVal); } lock(); try { Boolean stat = dirty(field, Boolean.FALSE, set == SET_USER); if (set != SET_REMOTE) { _single.storeObjectField(field, curVal); _single.unproxy(); _single.dereferenceDependent(); _single.clear(); } _single.storeObjectField(field, newVal); replaceField(pc, _single, field); postDirty(stat); } finally { unlock(); } } public void settingShortField(PersistenceCapable pc, int field, short curVal, short newVal, int set) { if (set != SET_REMOTE) { if (newVal == curVal && _loaded.get(field)) return; assertNoPrimaryKeyChange(field); } lock(); try { Boolean stat = dirty(field, Boolean.FALSE, set == SET_USER); _single.storeShortField(field, newVal); replaceField(pc, _single, field); postDirty(stat); } finally { unlock(); } } public void settingStringField(PersistenceCapable pc, int field, String curVal, String newVal, int set) { if (set != SET_REMOTE) { if (Objects.equals(newVal, curVal) && _loaded.get(field)) return; assertNoPrimaryKeyChange(field); } lock(); try { Boolean stat = dirty(field, Boolean.FALSE, set == SET_USER); _single.storeStringField(field, newVal); replaceField(pc, _single, field); postDirty(stat); } finally { unlock(); } } /** * Disallows changing primary key fields for instances. */ private void assertNoPrimaryKeyChange(int field) { if (_oid != null && _meta.getField(field).isPrimaryKey()) throw translate(new InvalidStateException(_loc.get ("change-identity")).setFailedObject(getManagedInstance())); } /** * Disallows setting an object id field to a managed instance. */ void assertNotManagedObjectId(Object val) { if (val != null && (ImplHelper.toPersistenceCapable(val, getContext().getConfiguration())).pcGetGenericContext()!= null) throw translate(new InvalidStateException(_loc.get ("managed-oid", Exceptions.toString(val), Exceptions.toString(getManagedInstance()))). setFailedObject(getManagedInstance())); } //////////////////////////// // Delegate to FieldManager //////////////////////////// public void providedBooleanField(PersistenceCapable pc, int field, boolean curVal) { _fm.storeBooleanField(field, curVal); } public void providedByteField(PersistenceCapable pc, int field, byte curVal) { _fm.storeByteField(field, curVal); } public void providedCharField(PersistenceCapable pc, int field, char curVal) { _fm.storeCharField(field, curVal); } public void providedDoubleField(PersistenceCapable pc, int field, double curVal) { _fm.storeDoubleField(field, curVal); } public void providedFloatField(PersistenceCapable pc, int field, float curVal) { _fm.storeFloatField(field, curVal); } public void providedIntField(PersistenceCapable pc, int field, int curVal) { _fm.storeIntField(field, curVal); } public void providedLongField(PersistenceCapable pc, int field, long curVal) { _fm.storeLongField(field, curVal); } public void providedObjectField(PersistenceCapable pc, int field, Object curVal) { _fm.storeObjectField(field, curVal); } public void providedShortField(PersistenceCapable pc, int field, short curVal) { _fm.storeShortField(field, curVal); } public void providedStringField(PersistenceCapable pc, int field, String curVal) { _fm.storeStringField(field, curVal); } public boolean replaceBooleanField(PersistenceCapable pc, int field) { return _fm.fetchBooleanField(field); } public byte replaceByteField(PersistenceCapable pc, int field) { return _fm.fetchByteField(field); } public char replaceCharField(PersistenceCapable pc, int field) { return _fm.fetchCharField(field); } public double replaceDoubleField(PersistenceCapable pc, int field) { return _fm.fetchDoubleField(field); } public float replaceFloatField(PersistenceCapable pc, int field) { return _fm.fetchFloatField(field); } public int replaceIntField(PersistenceCapable pc, int field) { return _fm.fetchIntField(field); } public long replaceLongField(PersistenceCapable pc, int field) { return _fm.fetchLongField(field); } public Object replaceObjectField(PersistenceCapable pc, int field) { return _fm.fetchObjectField(field); } public short replaceShortField(PersistenceCapable pc, int field) { return _fm.fetchShortField(field); } public String replaceStringField(PersistenceCapable pc, int field) { return _fm.fetchStringField(field); } ////////////////////////////////// // Implementation of FieldManager ////////////////////////////////// public boolean fetchBoolean(int field) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) return fetchBooleanField(field); Object val = fetchField(field, false); return ((Boolean) fmd.getExternalValue(val, _broker)).booleanValue(); } public boolean fetchBooleanField(int field) { lock(); try { if (!_loaded.get(field)) loadField(field, LockLevels.LOCK_NONE, false, false); provideField(_pc, _single, field); return _single.fetchBooleanField(field); } finally { unlock(); } } public byte fetchByte(int field) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) return fetchByteField(field); Object val = fetchField(field, false); return ((Number) fmd.getExternalValue(val, _broker)).byteValue(); } public byte fetchByteField(int field) { lock(); try { if (!_loaded.get(field)) loadField(field, LockLevels.LOCK_NONE, false, false); provideField(_pc, _single, field); return _single.fetchByteField(field); } finally { unlock(); } } public char fetchChar(int field) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) return fetchCharField(field); Object val = fetchField(field, false); return ((Character) fmd.getExternalValue(val, _broker)).charValue(); } public char fetchCharField(int field) { lock(); try { if (!_loaded.get(field)) loadField(field, LockLevels.LOCK_NONE, false, false); provideField(_pc, _single, field); return _single.fetchCharField(field); } finally { unlock(); } } public double fetchDouble(int field) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) return fetchDoubleField(field); Object val = fetchField(field, false); return ((Number) fmd.getExternalValue(val, _broker)).doubleValue(); } public double fetchDoubleField(int field) { lock(); try { if (!_loaded.get(field)) loadField(field, LockLevels.LOCK_NONE, false, false); provideField(_pc, _single, field); return _single.fetchDoubleField(field); } finally { unlock(); } } public float fetchFloat(int field) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) return fetchFloatField(field); Object val = fetchField(field, false); return ((Number) fmd.getExternalValue(val, _broker)).floatValue(); } public float fetchFloatField(int field) { lock(); try { if (!_loaded.get(field)) loadField(field, LockLevels.LOCK_NONE, false, false); provideField(_pc, _single, field); return _single.fetchFloatField(field); } finally { unlock(); } } public int fetchInt(int field) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) return fetchIntField(field); Object val = fetchField(field, false); return ((Number) fmd.getExternalValue(val, _broker)).intValue(); } public int fetchIntField(int field) { lock(); try { if (!_loaded.get(field)) loadField(field, LockLevels.LOCK_NONE, false, false); provideField(_pc, _single, field); return _single.fetchIntField(field); } finally { unlock(); } } public long fetchLong(int field) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) return fetchLongField(field); Object val = fetchField(field, false); return ((Number) fmd.getExternalValue(val, _broker)).longValue(); } public long fetchLongField(int field) { lock(); try { if (!_loaded.get(field)) loadField(field, LockLevels.LOCK_NONE, false, false); provideField(_pc, _single, field); return _single.fetchLongField(field); } finally { unlock(); } } public Object fetchObject(int field) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) return fetchObjectField(field); Object val = fetchField(field, false); return fmd.getExternalValue(val, _broker); } public Object fetchObjectField(int field) { lock(); try { if (!_loaded.get(field)) loadField(field, LockLevels.LOCK_NONE, false, false); provideField(_pc, _single, field); return _single.fetchObjectField(field); } finally { unlock(); } } public short fetchShort(int field) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) return fetchShortField(field); Object val = fetchField(field, false); return ((Number) fmd.getExternalValue(val, _broker)).shortValue(); } public short fetchShortField(int field) { lock(); try { if (!_loaded.get(field)) loadField(field, LockLevels.LOCK_NONE, false, false); provideField(_pc, _single, field); return _single.fetchShortField(field); } finally { unlock(); } } public String fetchString(int field) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) return fetchStringField(field); Object val = fetchField(field, false); return (String) fmd.getExternalValue(val, _broker); } public String fetchStringField(int field) { lock(); try { if (!_loaded.get(field)) loadField(field, LockLevels.LOCK_NONE, false, false); provideField(_pc, _single, field); return _single.fetchStringField(field); } finally { unlock(); } } public void storeBoolean(int field, boolean externalVal) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) storeBooleanField(field, externalVal); else { Object val = (externalVal) ? Boolean.TRUE : Boolean.FALSE; storeField(field, fmd.getFieldValue(val, _broker)); } } public void storeBooleanField(int field, boolean curVal) { lock(); try { _single.storeBooleanField(field, curVal); replaceField(_pc, _single, field); setLoaded(field, true); postLoad(field, null); } finally { unlock(); } } public void storeByte(int field, byte externalVal) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) storeByteField(field, externalVal); else storeField(field, fmd.getFieldValue(Byte.valueOf(externalVal), _broker)); } public void storeByteField(int field, byte curVal) { lock(); try { _single.storeByteField(field, curVal); replaceField(_pc, _single, field); setLoaded(field, true); postLoad(field, null); } finally { unlock(); } } public void storeChar(int field, char externalVal) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) storeCharField(field, externalVal); else storeField(field, fmd.getFieldValue(Character.valueOf(externalVal), _broker)); } public void storeCharField(int field, char curVal) { lock(); try { _single.storeCharField(field, curVal); replaceField(_pc, _single, field); setLoaded(field, true); postLoad(field, null); } finally { unlock(); } } public void storeDouble(int field, double externalVal) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) storeDoubleField(field, externalVal); else storeField(field, fmd.getFieldValue(Double.valueOf(externalVal), _broker)); } public void storeDoubleField(int field, double curVal) { lock(); try { _single.storeDoubleField(field, curVal); replaceField(_pc, _single, field); setLoaded(field, true); postLoad(field, null); } finally { unlock(); } } public void storeFloat(int field, float externalVal) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) storeFloatField(field, externalVal); else storeField(field, fmd.getFieldValue(Float.valueOf(externalVal), _broker)); } public void storeFloatField(int field, float curVal) { lock(); try { _single.storeFloatField(field, curVal); replaceField(_pc, _single, field); setLoaded(field, true); postLoad(field, null); } finally { unlock(); } } public void storeInt(int field, int externalVal) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) storeIntField(field, externalVal); else storeField(field, fmd.getFieldValue(externalVal, _broker)); } public void storeIntField(int field, int curVal) { lock(); try { _single.storeIntField(field, curVal); replaceField(_pc, _single, field); setLoaded(field, true); postLoad(field, null); } finally { unlock(); } } public void storeLong(int field, long externalVal) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) storeLongField(field, externalVal); else storeField(field, fmd.getFieldValue(externalVal, _broker)); } public void storeLongField(int field, long curVal) { lock(); try { _single.storeLongField(field, curVal); replaceField(_pc, _single, field); setLoaded(field, true); postLoad(field, null); } finally { unlock(); } } public void storeObject(int field, Object externalVal) { FieldMetaData fmd = _meta.getField(field); externalVal = fmd.order(externalVal); if (!fmd.isExternalized()) storeObjectField(field, externalVal); else storeField(field, fmd.getFieldValue(externalVal, _broker)); } public void storeObjectField(int field, Object curVal) { lock(); try { _single.storeObjectField(field, curVal); _single.proxy(true, false); replaceField(_pc, _single, field); setLoaded(field, true); postLoad(field, null); } finally { unlock(); } } public void storeShort(int field, short externalVal) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) storeShortField(field, externalVal); else storeField(field, fmd.getFieldValue(Short.valueOf(externalVal), _broker)); } public void storeShortField(int field, short curVal) { lock(); try { _single.storeShortField(field, curVal); replaceField(_pc, _single, field); setLoaded(field, true); postLoad(field, null); } finally { unlock(); } } public void storeString(int field, String externalVal) { FieldMetaData fmd = _meta.getField(field); if (!fmd.isExternalized()) storeStringField(field, externalVal); else storeField(field, fmd.getFieldValue(externalVal, _broker)); } public void storeStringField(int field, String curVal) { lock(); try { _single.storeStringField(field, curVal); replaceField(_pc, _single, field); setLoaded(field, true); postLoad(field, null); } finally { unlock(); } } /** * Store the given field value into the given field manager. */ private void storeField(int field, Object val, FieldManager fm) { FieldMetaData fmd = _meta.getField(field); if (fmd == null) throw new UserException(_loc.get("no-field-index", String.valueOf(field), _meta.getDescribedType())). setFailedObject(getManagedInstance()); switch (fmd.getDeclaredTypeCode()) { case JavaTypes.BOOLEAN: boolean bool = val != null && ((Boolean) val).booleanValue(); fm.storeBooleanField(field, bool); break; case JavaTypes.BYTE: byte b = (val == null) ? 0 : ((Number) val).byteValue(); fm.storeByteField(field, b); break; case JavaTypes.CHAR: char c = (val == null) ? 0 : ((Character) val).charValue(); fm.storeCharField(field, c); break; case JavaTypes.DOUBLE: double d = (val == null) ? 0 : ((Number) val).doubleValue(); fm.storeDoubleField(field, d); break; case JavaTypes.FLOAT: float f = (val == null) ? 0 : ((Number) val).floatValue(); fm.storeFloatField(field, f); break; case JavaTypes.INT: int i = (val == null) ? 0 : ((Number) val).intValue(); fm.storeIntField(field, i); break; case JavaTypes.LONG: long l = (val == null) ? 0 : ((Number) val).longValue(); fm.storeLongField(field, l); break; case JavaTypes.SHORT: short s = (val == null) ? 0 : ((Number) val).shortValue(); fm.storeShortField(field, s); break; case JavaTypes.STRING: fm.storeStringField(field, (String) val); break; default: fm.storeObjectField(field, val); } } ///////////// // Utilities ///////////// /** * Erase the fact that this instance has been flushed. */ void eraseFlush() { _flags &= ~FLAG_FLUSHED; _flags &= ~FLAG_FLUSHED_DIRTY; _flush = null; } /** * Records that all instance fields are/are not loaded. * Primary key and non-persistent fields are not affected. */ void setLoaded(boolean val) { FieldMetaData[] fmds = _meta.getFields(); for (int i = 0; i < fmds.length; i++) { if (!fmds[i].isPrimaryKey() && fmds[i].getManagement() == fmds[i].MANAGE_PERSISTENT) setLoaded(i, val); } if (!val) { _flags &= ~FLAG_LOADED; setDirty(false); } else _flags |= FLAG_LOADED; } /** * Records that all instance fields are/are not dirty, * and changes the flags of the instance accordingly. */ void setDirty(boolean val) { FieldMetaData[] fmds = _meta.getFields(); boolean update = !isNew() || isFlushed(); for (int i = 0; i < fmds.length; i++) { if (val && (!update || fmds[i].getUpdateStrategy() != UpdateStrategies.IGNORE)) setFieldDirty(i); else if (!val) { // we never consider clean fields flushed; this also takes // care of clearing the flushed fields on commit/rollback clearFlushField(i); clearDirty(i); } } if (val) _flags |= FLAG_LOADED; } /** * Executes pre-clear callbacks, clears all managed fields, and calls the * {@link #setLoaded} method with a value of false. Primary key fields * are not cleared. */ void clearFields() { if (!isIntercepting()) return; fireLifecycleEvent(LifecycleEvent.BEFORE_CLEAR); // unproxy all fields unproxyFields(); lock(); try { // clear non-pk fields FieldMetaData[] fmds = _meta.getFields(); for (int i = 0; i < fmds.length; i++) { if (!fmds[i].isPrimaryKey() && fmds[i].getManagement() == FieldMetaData.MANAGE_PERSISTENT) replaceField(_pc, ClearFieldManager.getInstance(), i); } // forget version info and impl data so we re-read next time setLoaded(false); _version = null; _loadVersion = null; if (_fieldImpl != null) Arrays.fill(_fieldImpl, null); } finally { unlock(); } fireLifecycleEvent(LifecycleEvent.AFTER_CLEAR); } /** * Record that we should save any fields that change from this point * forward. */ void saveFields(boolean immediate) { if (_broker.getRestoreState() == RestoreState.RESTORE_NONE && (_flags & FLAG_INVERSES) == 0) return; _flags |= FLAG_SAVE; if (immediate) { for (int i = 0, len = _loaded.length(); i < len; i++) saveField(i); _flags &= ~FLAG_SAVE; // OPENJPA-659 // record a saved field manager even if no field is currently loaded // as existence of a SaveFieldManager is critical for a dirty check if (_saved == null) _saved = new SaveFieldManager(this, getPersistenceCapable(), getDirty()); } } /** * If the field isn't already saved, saves the currently loaded field * state of the instance. The saved values can all be restored via * {@link #restoreFields}. */ private void saveField(int field) { if ((_flags & FLAG_SAVE) == 0) return; // if this is a managed inverse field, load it so we're sure to have // the original value if (!_loaded.get(field) && ((_flags & FLAG_INVERSES) != 0 && _meta.getField(field).getInverseMetaDatas().length > 0)) loadField(field, LockLevels.LOCK_NONE, false, false); // don't bother creating the save field manager if we're not going to // save the old field value anyway if (_saved == null) { if (_loaded.get(field)) _saved = new SaveFieldManager(this, null, getDirty()); else return; } // copy the field to save field manager; if the field is not directly // copyable, immediately provide and replace it via the save field // manager, which will copy the mutable value to prevent by-ref mods if (_saved.saveField(field)) { provideField(_pc, _saved, field); replaceField(_saved.getState(), _saved, field); } } /** * Notification that the state will not need to be rolled back * to that of the last call to {@link #saveFields}. */ void clearSavedFields() { if (isIntercepting()) { _flags &= ~FLAG_SAVE; _saved = null; } } public SaveFieldManager getSaveFieldManager() { return _saved; } /** * Rollback the state of the instance to the saved state from the * last call to {@link #saveFields}, or to default values if never saved. */ void restoreFields() { lock(); try { if (_saved == null) { if ((_flags & FLAG_SAVE) == 0) clearFields(); else // only unloaded fields were dirtied _loaded.andNot(_loaded); } // we direct state transitions based on our own getRestoreState // method, but to decide whether to actually rollback field // values, we consult the broker for the user's setting else if (_broker.getRestoreState() != RestoreState.RESTORE_NONE) { // rollback all currently-loaded fields for (int i = 0, len = _loaded.length(); i < len; i++) if (_loaded.get(i) && _saved.restoreField(i)) replaceField(_pc, _saved, i); // rollback loaded set _loaded.andNot(_saved.getUnloaded()); } } finally { unlock(); } } /** * Replaces all second class object fields with fresh proxied instances * containing the same information as the originals. * <br> * <B>Note:</B> Proxying is bypassed if {@link AutoDetach#DETACH_NONE} option is set. */ void proxyFields(boolean reset, boolean replaceNull) { if (getBroker().getAutoDetach() == AutoDetach.DETACH_NONE) return; // we only replace nulls if the runtime can't differentiate between // null and empty containers. we replace nulls in this case to // maintain consistency whether values are being retained or not if (replaceNull) replaceNull = !_broker.getConfiguration().supportedOptions(). contains(OpenJPAConfiguration.OPTION_NULL_CONTAINER); lock(); try { for (FieldMetaData fmd : _meta.getProxyFields()) { int index = fmd.getIndex(); // only reload if dirty if (_loaded.get(index) && isFieldDirty(index)) { provideField(_pc, _single, index); if (_single.proxy(reset, replaceNull)) { replaceField(_pc, _single, index); } else { _single.clear(); } } } } finally { unlock(); } } /** * Unproxy all fields. */ void unproxyFields() { if ((_flags & FLAG_NO_UNPROXY) != 0) return; lock(); try { for (int i = 0, len = _loaded.length(); i < len; i++) { provideField(_pc, _single, i); _single.unproxy(); _single.releaseEmbedded(); _single.clear(); } } finally { unlock(); } } /** * Get ready for a flush. Persists all persistence-capable object fields, * and checks for illegal null values. Also assigns oids and field values * for all strategies that don't require flushing. */ void preFlush(boolean logical, OpCallbacks call) { if ((_flags & FLAG_PRE_FLUSHED) != 0) return; if (isPersistent()) { fireLifecycleEvent(LifecycleEvent.BEFORE_STORE); // BEFORE_PERSIST is handled during Broker.persist and Broker.attach if (isDeleted()) fireLifecycleEvent(LifecycleEvent.BEFORE_DELETE); else if (!(isNew() && !isFlushed()) && (ImplHelper.getUpdateFields(this) != null)) fireLifecycleEvent(LifecycleEvent.BEFORE_UPDATE); _flags |= FLAG_PRE_FLUSHED; } lock(); try { if (!logical) assignObjectId(false, true); for (int i = 0, len = _meta.getFields().length; i < len; i++) { if ((logical || !assignField(i, true)) && !isFieldFlushed(i) && isFieldDirty(i)) { provideField(_pc, _single, i); if (_single.preFlush(logical, call)) replaceField(_pc, _single, i); else _single.clear(); } } dirtyCheck(); } finally { unlock(); } } /** * Make callbacks for deletion. */ void preDelete() { // set a flag while call pre delete callback so that user can't // get into infinite recursion by calling delete(this) // within his callback method if ((_flags & FLAG_PRE_DELETING) == 0) { _flags |= FLAG_PRE_DELETING; try { fireLifecycleEvent(LifecycleEvent.BEFORE_DELETE); } finally { _flags &= ~FLAG_PRE_DELETING; } } } /** * Cascade deletes and dereference dependent fields. */ void cascadeDelete(OpCallbacks call) { FieldMetaData[] fmds = _meta.getFields(); for (int i = 0; i < fmds.length; i++) { if (fmds[i].getCascadeDelete() != ValueMetaData.CASCADE_NONE || fmds[i].getKey().getCascadeDelete() != ValueMetaData.CASCADE_NONE || fmds[i].getElement().getCascadeDelete() != ValueMetaData.CASCADE_NONE) { _single.storeObjectField(i, fetchField(i, false)); _single.delete(call); _single.clear(); } } } /** * Called after an instance is persisted by a user through the broker. * Cascades the persist operation to fields marked * {@link ValueMetaData#CASCADE_IMMEDIATE}. */ void cascadePersist(OpCallbacks call) { FieldMetaData[] fmds = _meta.getFields(); for (int i = 0; i < fmds.length; i++) { if (!_loaded.get(i)) continue; if (fmds[i].getCascadePersist() == ValueMetaData.CASCADE_IMMEDIATE || fmds[i].getKey().getCascadePersist() == ValueMetaData.CASCADE_IMMEDIATE || fmds[i].getElement().getCascadePersist() == ValueMetaData.CASCADE_IMMEDIATE) { _single.storeObjectField(i, fetchField(i, false)); _single.persist(call); _single.clear(); } } } /** * Load the given field set from the data store into the instance. * Return true if any data is loaded, false otherwise. */ boolean loadFields(BitSet fields, FetchConfiguration fetch, int lockLevel, Object sdata) { // can't load version field from store if (fields != null) { FieldMetaData vfield = _meta.getVersionField(); if (vfield != null) fields.clear(vfield.getIndex()); } boolean ret = false; setLoading(true); try { // if any fields given, load them int len = (fields == null) ? 0 : fields.length(); if (len > 0) { if (fetch == null) fetch = _broker.getFetchConfiguration(); if (!_broker.getStoreManager().load(this, fields, fetch, lockLevel, sdata)) { throw new ObjectNotFoundException(_loc.get("del-instance", _meta.getDescribedType(), _oid)). setFailedObject(getManagedInstance()); } ret = true; } // make sure version information has been set; version info must // always be set after the first state load or set (which is why // we do this even if no fields were loaded -- could be that this // method is being called after a field is set) // If the _loadVersion field is null AND the version field has been loaded, skip calling sync version. // This indicates that the DB has a null value for the version column. FieldMetaData versionMeta = _meta != null ? _meta.getVersionField() : null; if (_loadVersion == null && (versionMeta != null && !_loaded.get(versionMeta.getIndex()))) { syncVersion(sdata); ret = ret || _loadVersion != null; } } finally { setLoading(false); } // see if the dfg is now loaded; do this regardless of whether we // loaded any fields, cause may already have been loaded by // StoreManager during initialization postLoad(-1, fetch); return ret; } /** * Load the given field's fetch group; the field itself may already be * loaded if it is being set by the user. */ protected void loadField(int field, int lockLevel, boolean forWrite, boolean fgs) { FetchConfiguration fetch = _broker.getFetchConfiguration(); FieldMetaData fmd = _meta.getField(field); BitSet fields = null; boolean unloadedDFGFieldMarked = false; // if this is a dfg field or we need to load our dfg, do so if (fgs && (_flags & FLAG_LOADED) == 0){ fields = getUnloadedInternal(fetch, LOAD_FGS, null); unloadedDFGFieldMarked = true; } // check for load fetch group String lfg = fmd.getLoadFetchGroup(); boolean lfgAdded = false; if (lfg != null) { FieldMetaData[] fmds = _meta.getFields(); for (int i = 0; i < fmds.length; i++) { if (!_loaded.get(i) && (i == field || fmds[i].isInFetchGroup(lfg))) { if (fields == null) fields = new BitSet(fmds.length); fields.set(i); } } // relation field is loaded with the load-fetch-group // but this addition must be reverted once the load is over if (!fetch.hasFetchGroup(lfg)) { fetch.addFetchGroup(lfg); lfgAdded = true; } } else if (fetch.hasFetchGroup(FetchGroup.NAME_DEFAULT) && fmd.isInDefaultFetchGroup() && fields == null) { // no load group but dfg: add dfg fields if we haven't already if (!unloadedDFGFieldMarked) fields = getUnloadedInternal(fetch, LOAD_FGS, null); } else if (!_loaded.get(fmd.getIndex())) { // no load group or dfg: load individual field if (fields == null) fields = new BitSet(); fields.set(fmd.getIndex()); } // call this method even if there are no unloaded fields; loadFields // takes care of things like loading version info and setting PC flags try { loadFields(fields, fetch, lockLevel, null); } finally { if (lfgAdded) fetch.removeFetchGroup(lfg); } } /** * Helper method to provide the given field number to the given * field manager. */ void provideField(PersistenceCapable pc, FieldManager store, int field) { if (pc != null) { FieldManager beforeFM = _fm; _fm = store; pc.pcProvideField(field); // Retaining original FM because of the possibility of reentrant calls if (beforeFM != null) _fm = beforeFM; } } /** * Helper method to replace the given field number to the given * field manager. */ void replaceField(PersistenceCapable pc, FieldManager load, int field) { FieldManager beforeFM = _fm; _fm = load; pc.pcReplaceField(field); // Retaining original FM because of the possibility of reentrant calls if (beforeFM != null) _fm = beforeFM; } /** * Mark the field as loaded or unloaded. */ private void setLoaded(int field, boolean isLoaded) { // don't continue if loaded state is already correct; otherwise we // can end up clearing _fieldImpl when we shouldn't if (_loaded.get(field) == isLoaded) return; // if loading, clear intermediate data; if unloading, clear impl data if (_fieldImpl != null) { int idx = _meta.getExtraFieldDataIndex(field); if (idx != -1) _fieldImpl[idx] = null; } if (isLoaded) _loaded.set(field); else _loaded.clear(field); } /** * Set to <code>false</code> to prevent the postLoad method from * sending lifecycle callback events. */ public void setPostLoadCallback(boolean enabled) { this.postLoadCallback = enabled; } /** * Perform post-load steps, including the post load callback. * We have to check the dfg after all field loads because it might be * loaded in multiple steps when paging is involved; the initial load * might exclude some fields which are then immediately loaded in a * separate step before being returned to the user. * * @param field the field index that was loaded, or -1 to indicate * that a group of possibly unknown fields was loaded */ private void postLoad(int field, FetchConfiguration fetch) { // no need for postLoad callback? if ((_flags & FLAG_LOADED) != 0) return; // in the middle of a group load, after which this method will be // called again? if (field != -1 && isLoading()) return; // no listeners? LifecycleEventManager mgr = _broker.getLifecycleEventManager(); if (mgr == null || !mgr.isActive(_meta) || !mgr.hasLoadListeners(getManagedInstance(), _meta)) return; if (fetch == null) fetch = _broker.getFetchConfiguration(); // is this field a post-load field? if (field != -1) { FieldMetaData fmd = _meta.getField(field); if (fmd.isInDefaultFetchGroup() && fetch.hasFetchGroup(FetchGroup.NAME_DEFAULT) && postLoad(FetchGroup.NAME_DEFAULT, fetch)) return; String[] fgs = fmd.getCustomFetchGroups(); for (int i = 0; i < fgs.length; i++) if (fetch.hasFetchGroup(fgs[i]) && postLoad(fgs[i], fetch)) return; } else { for (Iterator itr = fetch.getFetchGroups().iterator(); itr.hasNext();) { if (postLoad((String) itr.next(), fetch)) return; } } } /** * Perform post-load actions if the given fetch group is a post-load group * and is fully loaded. */ private boolean postLoad(String fgName, FetchConfiguration fetch) { FetchGroup fg = _meta.getFetchGroup(fgName); if (fg == null || !fg.isPostLoad()) return false; FieldMetaData[] fmds = _meta.getFields(); for (int i = 0; i < fmds.length; i++) if (!_loaded.get(i) && fmds[i].isInFetchGroup(fgName)) return false; _flags |= FLAG_LOADED; if (postLoadCallback) _broker.fireLifecycleEvent(getManagedInstance(), fetch, _meta, LifecycleEvent.AFTER_LOAD); return true; } /** * Synchronize our version object with the datastore. */ private boolean syncVersion(Object sdata) { return _broker.getStoreManager().syncVersion(this, sdata); } /** * Returns whether this instance needs a version check. */ public boolean isVersionCheckRequired() { // explicit flag for version check if ((_flags & FLAG_VERSION_CHECK) != 0) return true; if (!_broker.getOptimistic() && !_broker.getConfiguration(). getCompatibilityInstance().getNonOptimisticVersionCheck()) return false; return _state.isVersionCheckRequired(this); } /** * Set whether this instance requires a version check on the next flush. */ void setCheckVersion(boolean versionCheck) { if (versionCheck) _flags |= FLAG_VERSION_CHECK; else _flags &= ~FLAG_VERSION_CHECK; } /** * Returns whether this instance needs a version update. */ public boolean isVersionUpdateRequired() { return (_flags & FLAG_VERSION_UPDATE) > 0; } /** * Set whether this instance requires a version update on the next flush. */ void setUpdateVersion(boolean versionUpdate) { if (versionUpdate) _flags |= FLAG_VERSION_UPDATE; else _flags &= ~FLAG_VERSION_UPDATE; } /** * Translate the given exception based on the broker's implicit behavior. * Translation only occurs if the exception is initiated by a user action * on an instance, and therefore will not be caught and translated by the * broker. */ protected RuntimeException translate(RuntimeException re) { RuntimeExceptionTranslator trans = _broker.getInstanceExceptionTranslator(); return (trans == null) ? re : trans.translate(re); } /** * Lock the state manager if the multithreaded option is set. */ protected void lock() { if (_instanceLock != null) _instanceLock.lock(); } /** * Unlock the state manager. */ protected void unlock () { if (_instanceLock != null) _instanceLock.unlock(); } private void writeObject(ObjectOutputStream oos) throws IOException { oos.writeObject(_broker); oos.defaultWriteObject(); oos.writeObject(_meta.getDescribedType()); writePC(oos, _pc); } /** * Write <code>pc</code> to <code>oos</code>, handling internal-form * serialization. <code>pc</code> must be of the same type that this * state manager manages. * * @since 1.1.0 */ void writePC(ObjectOutputStream oos, PersistenceCapable pc) throws IOException { if (!Serializable.class.isAssignableFrom(_meta.getDescribedType())) throw new NotSerializableException(_meta.getDescribedType().getName()); oos.writeObject(pc); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { _broker = (BrokerImpl) in.readObject(); in.defaultReadObject(); // we need to store the class before the pc instance so that we can // create _meta before calling readPC(), which relies on _meta being // non-null when reconstituting ReflectingPC instances. Sadly, this // penalizes the serialization footprint of non-ReflectingPC SMs also. Class managedType = (Class) in.readObject(); _meta = _broker.getConfiguration().getMetaDataRepositoryInstance() .getMetaData(managedType, null, true); _pc = readPC(in); } /** * Converts the deserialized <code>o</code> to a {@link PersistenceCapable} * instance appropriate for storing in <code>_pc</code>. * * @since 1.1.0 */ PersistenceCapable readPC(ObjectInputStream in) throws ClassNotFoundException, IOException { Object o = in.readObject(); if (o == null) return null; PersistenceCapable pc; if (!(o instanceof PersistenceCapable)) pc = ImplHelper.toPersistenceCapable(o, this); else pc = (PersistenceCapable) o; pc.pcReplaceStateManager(this); return pc; } public List<FieldMetaData> getMappedByIdFields() { return _mappedByIdFields; } public boolean requiresFetch(FieldMetaData fmd) { return (_broker.getFetchConfiguration().requiresFetch(fmd) != FetchConfiguration.FETCH_NONE); } public void setPc(PersistenceCapable pc) { _pc = pc; } public void setBroker(BrokerImpl ctx) { _broker = ctx; } public BitSet getFlushed() { if (_flush == null) { _flush = new BitSet(_meta.getFields().length); } return _flush; } private boolean isFieldFlushed(int index) { if (_flush == null) { return false; } return _flush.get(index); } /** * Will clear the bit at the specified if the _flush BetSet has been created. */ private void clearFlushField(int index) { if (_flush != null) { getFlushed().clear(index); } } public BitSet getDirty() { if (_dirty == null) { _dirty = new BitSet(_meta.getFields().length); } return _dirty; } private boolean isFieldDirty(int index) { if (_dirty == null) { return false; } return _dirty.get(index); } private void setFieldDirty(int index) { getDirty().set(index); } /** * Will clear the bit at the specified index if the _dirty BetSet has been created. */ private void clearDirty(int index) { if (_dirty != null) { getDirty().clear(index); } } public String toString() { return "SM[" + _meta.getDescribedType().getSimpleName() + "]:" + getObjectId(); } }