| /* |
| * 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.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 { |
| |
| private static final long serialVersionUID = 1L; |
| 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 |
| ////////////////////////////////////// |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| public Object getManagedInstance() { |
| if (_pc instanceof ManagedInstanceProvider) |
| return ((ManagedInstanceProvider) _pc).getManagedInstance(); |
| else |
| return _pc; |
| } |
| |
| @Override |
| public PersistenceCapable getPersistenceCapable() { |
| return _pc; |
| } |
| |
| @Override |
| public ClassMetaData getMetaData() { |
| return _meta; |
| } |
| |
| @Override |
| public OpenJPAStateManager getOwner() { |
| return _owner; |
| } |
| |
| @Override |
| public int getOwnerIndex() { |
| return _ownerIndex; |
| } |
| |
| public void setOwner(Object oid) { |
| _ownerId = oid; |
| } |
| |
| @Override |
| public boolean isEmbedded() { |
| // _owner may not be set if embed object is from query result |
| return _owner != null || _state instanceof ENonTransState; |
| } |
| |
| @Override |
| public boolean isFlushed() { |
| return (_flags & FLAG_FLUSHED) > 0; |
| } |
| |
| @Override |
| public boolean isFlushedDirty() { |
| return (_flags & FLAG_FLUSHED_DIRTY) > 0; |
| } |
| |
| @Override |
| public BitSet getLoaded() { |
| return _loaded; |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| public StoreContext getContext() { |
| return _broker; |
| } |
| |
| /** |
| * Managing broker. |
| */ |
| BrokerImpl getBroker() { |
| return _broker; |
| } |
| |
| @Override |
| public Object getId() { |
| return _id; |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| 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; |
| } |
| @Override |
| 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; |
| } |
| |
| @Override |
| public Object getLock() { |
| return _lock; |
| } |
| |
| @Override |
| public void setLock(Object lock) { |
| _lock = lock; |
| } |
| |
| @Override |
| public Object getVersion() { |
| return _version; |
| } |
| |
| @Override |
| public void setVersion(Object version) { |
| _loadVersion = version; |
| assignVersionField(version); |
| } |
| |
| Object getLoadVersion() { |
| return _loadVersion; |
| } |
| |
| @Override |
| 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())); |
| } |
| |
| @Override |
| public PCState getPCState() { |
| return _state; |
| } |
| |
| @Override |
| public synchronized Object getImplData() { |
| return _impl; |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| public boolean isImplDataCacheable() { |
| return (_flags & FLAG_IMPL_CACHE) != 0; |
| } |
| |
| @Override |
| public Object getImplData(int field) { |
| return getExtraFieldData(field, true); |
| } |
| |
| @Override |
| public Object setImplData(int field, Object data) { |
| return setExtraFieldData(field, data, true); |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| public Object getIntermediate(int field) { |
| return getExtraFieldData(field, false); |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| public Object fetch(int field) { |
| Object val = fetchField(field, false); |
| return _meta.getField(field).getExternalValue(val, _broker); |
| } |
| |
| @Override |
| 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 fetchByteField(field); |
| case JavaTypes.CHAR: |
| return fetchCharField(field); |
| case JavaTypes.DOUBLE: |
| return fetchDoubleField(field); |
| case JavaTypes.FLOAT: |
| return fetchFloatField(field); |
| case JavaTypes.INT: |
| return fetchIntField(field); |
| case JavaTypes.LONG: |
| return fetchLongField(field); |
| case JavaTypes.SHORT: |
| return fetchShortField(field); |
| default: |
| return fetchObjectField(field); |
| } |
| } |
| |
| @Override |
| public void store(int field, Object val) { |
| val = _meta.getField(field).getFieldValue(val, _broker); |
| storeField(field, val); |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| 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 fm.fetchByteField(field); |
| case JavaTypes.CHAR: |
| return fm.fetchCharField(field); |
| case JavaTypes.DOUBLE: |
| return fm.fetchDoubleField(field); |
| case JavaTypes.FLOAT: |
| return fm.fetchFloatField(field); |
| case JavaTypes.INT: |
| return fm.fetchIntField(field); |
| case JavaTypes.LONG: |
| return fm.fetchLongField(field); |
| case JavaTypes.SHORT: |
| return fm.fetchShortField(field); |
| case JavaTypes.STRING: |
| return fm.fetchStringField(field); |
| default: |
| return fm.fetchObjectField(field); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| } |
| |
| @Override |
| 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. |
| */ |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| public void proxyDetachedDeserialized(int idx) { |
| // we don't serialize state manager impls |
| throw new InternalException(); |
| } |
| |
| @Override |
| 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(); |
| } |
| |
| @Override |
| public boolean isProvisional() { |
| return _state.isProvisional(); |
| } |
| |
| @Override |
| public boolean isPersistent() { |
| return _state.isPersistent(); |
| } |
| |
| @Override |
| public boolean isNew() { |
| return _state.isNew(); |
| } |
| |
| @Override |
| public boolean isDeleted() { |
| return _state.isDeleted(); |
| } |
| |
| @Override |
| public boolean isDirty() { |
| return _state.isDirty(); |
| } |
| |
| @Override |
| public boolean isDetached() { |
| return (_flags & FLAG_DETACHING) != 0; |
| } |
| |
| @Override |
| public Object getGenericContext() { |
| return _broker; |
| } |
| |
| @Override |
| 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 (FieldMetaData pkField : pkFields) { |
| if (pkField.getValueStrategy() == ValueStrategies.AUTOASSIGN) |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| public StateManager replaceStateManager(StateManager sm) { |
| return sm; |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public boolean isDelayed(int field) { |
| if (_delayed == null) { |
| return false; |
| } |
| return _delayed.get(field); |
| } |
| |
| @Override |
| 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. |
| */ |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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)); |
| else |
| setPCState(_state.beforeWrite(this, field, |
| mutate)); |
| } 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)); |
| } |
| |
| 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() == FieldMetaData.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); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| 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) |
| ///////////////////////////////////////////////////////// |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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 |
| //////////////////////////// |
| |
| @Override |
| public void providedBooleanField(PersistenceCapable pc, int field, |
| boolean curVal) { |
| _fm.storeBooleanField(field, curVal); |
| } |
| |
| @Override |
| public void providedByteField(PersistenceCapable pc, int field, |
| byte curVal) { |
| _fm.storeByteField(field, curVal); |
| } |
| |
| @Override |
| public void providedCharField(PersistenceCapable pc, int field, |
| char curVal) { |
| _fm.storeCharField(field, curVal); |
| } |
| |
| @Override |
| public void providedDoubleField(PersistenceCapable pc, int field, |
| double curVal) { |
| _fm.storeDoubleField(field, curVal); |
| } |
| |
| @Override |
| public void providedFloatField(PersistenceCapable pc, int field, |
| float curVal) { |
| _fm.storeFloatField(field, curVal); |
| } |
| |
| @Override |
| public void providedIntField(PersistenceCapable pc, int field, |
| int curVal) { |
| _fm.storeIntField(field, curVal); |
| } |
| |
| @Override |
| public void providedLongField(PersistenceCapable pc, int field, |
| long curVal) { |
| _fm.storeLongField(field, curVal); |
| } |
| |
| @Override |
| public void providedObjectField(PersistenceCapable pc, int field, |
| Object curVal) { |
| _fm.storeObjectField(field, curVal); |
| } |
| |
| @Override |
| public void providedShortField(PersistenceCapable pc, int field, |
| short curVal) { |
| _fm.storeShortField(field, curVal); |
| } |
| |
| @Override |
| public void providedStringField(PersistenceCapable pc, int field, |
| String curVal) { |
| _fm.storeStringField(field, curVal); |
| } |
| |
| @Override |
| public boolean replaceBooleanField(PersistenceCapable pc, int field) { |
| return _fm.fetchBooleanField(field); |
| } |
| |
| @Override |
| public byte replaceByteField(PersistenceCapable pc, int field) { |
| return _fm.fetchByteField(field); |
| } |
| |
| @Override |
| public char replaceCharField(PersistenceCapable pc, int field) { |
| return _fm.fetchCharField(field); |
| } |
| |
| @Override |
| public double replaceDoubleField(PersistenceCapable pc, int field) { |
| return _fm.fetchDoubleField(field); |
| } |
| |
| @Override |
| public float replaceFloatField(PersistenceCapable pc, int field) { |
| return _fm.fetchFloatField(field); |
| } |
| |
| @Override |
| public int replaceIntField(PersistenceCapable pc, int field) { |
| return _fm.fetchIntField(field); |
| } |
| |
| @Override |
| public long replaceLongField(PersistenceCapable pc, int field) { |
| return _fm.fetchLongField(field); |
| } |
| |
| @Override |
| public Object replaceObjectField(PersistenceCapable pc, int field) { |
| return _fm.fetchObjectField(field); |
| } |
| |
| @Override |
| public short replaceShortField(PersistenceCapable pc, int field) { |
| return _fm.fetchShortField(field); |
| } |
| |
| @Override |
| public String replaceStringField(PersistenceCapable pc, int field) { |
| return _fm.fetchStringField(field); |
| } |
| |
| ////////////////////////////////// |
| // Implementation of FieldManager |
| ////////////////////////////////// |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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)); |
| } |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| public void storeByte(int field, byte externalVal) { |
| FieldMetaData fmd = _meta.getField(field); |
| if (!fmd.isExternalized()) |
| storeByteField(field, externalVal); |
| else |
| storeField(field, fmd.getFieldValue(externalVal, |
| _broker)); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| public void storeChar(int field, char externalVal) { |
| FieldMetaData fmd = _meta.getField(field); |
| if (!fmd.isExternalized()) |
| storeCharField(field, externalVal); |
| else |
| storeField(field, fmd.getFieldValue(externalVal, |
| _broker)); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| public void storeDouble(int field, double externalVal) { |
| FieldMetaData fmd = _meta.getField(field); |
| if (!fmd.isExternalized()) |
| storeDoubleField(field, externalVal); |
| else |
| storeField(field, fmd.getFieldValue(externalVal, _broker)); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| public void storeFloat(int field, float externalVal) { |
| FieldMetaData fmd = _meta.getField(field); |
| if (!fmd.isExternalized()) |
| storeFloatField(field, externalVal); |
| else |
| storeField(field, fmd.getFieldValue(externalVal, _broker)); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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)); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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)); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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)); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| public void storeShort(int field, short externalVal) { |
| FieldMetaData fmd = _meta.getField(field); |
| if (!fmd.isExternalized()) |
| storeShortField(field, externalVal); |
| else |
| storeField(field, fmd.getFieldValue(externalVal, |
| _broker)); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| |
| @Override |
| 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)); |
| } |
| |
| @Override |
| 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; |
| 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; |
| 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() == FieldMetaData.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 (String fg : fgs) |
| if (fetch.hasFetchGroup(fg) && postLoad(fg, fetch)) |
| return; |
| } else { |
| for (String s : fetch.getFetchGroups()) { |
| if (postLoad(s, 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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; |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return "SM[" + _meta.getDescribedType().getSimpleName() + "]:" + getObjectId(); |
| } |
| |
| } |