blob: 8d3cb5c6fc631613e45e569eb1df4deffa5dba59 [file] [log] [blame]
/*
* 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.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import org.apache.openjpa.conf.Compatibility;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.datacache.DataCache;
import org.apache.openjpa.datacache.QueryCache;
import org.apache.openjpa.datacache.TypesChangedEvent;
import org.apache.openjpa.ee.ManagedRuntime;
import org.apache.openjpa.enhance.PCRegistry;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.enhance.Reflection;
import org.apache.openjpa.event.LifecycleEvent;
import org.apache.openjpa.event.LifecycleEventManager;
import org.apache.openjpa.event.RemoteCommitEventManager;
import org.apache.openjpa.event.TransactionEvent;
import org.apache.openjpa.event.TransactionEventManager;
import org.apache.openjpa.instrumentation.InstrumentationManager;
import org.apache.openjpa.kernel.exps.ExpressionParser;
import org.apache.openjpa.lib.conf.Configurations;
import org.apache.openjpa.lib.instrumentation.InstrumentationLevel;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.ReferenceHashMap;
import org.apache.openjpa.lib.util.ReferenceHashSet;
import org.apache.openjpa.lib.util.ReferenceMap;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.lib.util.collections.AbstractReferenceMap;
import org.apache.openjpa.lib.util.collections.IteratorChain;
import org.apache.openjpa.lib.util.collections.LinkedMap;
import org.apache.openjpa.lib.util.collections.MapBackedSet;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.meta.SequenceMetaData;
import org.apache.openjpa.meta.ValueMetaData;
import org.apache.openjpa.meta.ValueStrategies;
import org.apache.openjpa.util.ApplicationIds;
import org.apache.openjpa.util.CallbackException;
import org.apache.openjpa.util.Exceptions;
import org.apache.openjpa.util.GeneralException;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.InvalidStateException;
import org.apache.openjpa.util.NoTransactionException;
import org.apache.openjpa.util.ObjectExistsException;
import org.apache.openjpa.util.ObjectId;
import org.apache.openjpa.util.ObjectNotFoundException;
import org.apache.openjpa.util.OpenJPAException;
import org.apache.openjpa.util.OptimisticException;
import org.apache.openjpa.util.RuntimeExceptionTranslator;
import org.apache.openjpa.util.StoreException;
import org.apache.openjpa.util.UnsupportedException;
import org.apache.openjpa.util.UserException;
import org.apache.openjpa.util.WrappedException;
import org.apache.openjpa.validation.ValidatingLifecycleEventManager;
/**
* Concrete {@link Broker}. The broker handles object-level behavior,
* but leaves all interaction with the data store to a {@link StoreManager}
* that must be supplied at initialization.
*
* @author Abe White
*/
public class BrokerImpl implements Broker, FindCallbacks, Cloneable, Serializable {
private static final long serialVersionUID = 1L;
/**
* Incremental flush.
*/
protected static final int FLUSH_INC = 0;
/**
* Flush in preparation of commit.
*/
protected static final int FLUSH_COMMIT = 1;
/**
* Flush to check consistency of cache, then immediately rollback changes.
*/
protected static final int FLUSH_ROLLBACK = 2;
/**
* Run persistence-by-reachability and other flush-time operations without
* accessing the database.
*/
protected static final int FLUSH_LOGICAL = 3;
static final int STATUS_INIT = 0;
static final int STATUS_TRANSIENT = 1;
static final int STATUS_OID_ASSIGN = 2;
static final int STATUS_COMMIT_NEW = 3;
private static final int FLAG_ACTIVE = 2 << 0;
private static final int FLAG_STORE_ACTIVE = 2 << 1;
private static final int FLAG_CLOSE_INVOKED = 2 << 2;
private static final int FLAG_PRESTORING = 2 << 3;
private static final int FLAG_DEREFDELETING = 2 << 4;
private static final int FLAG_FLUSHING = 2 << 5;
private static final int FLAG_STORE_FLUSHING = 2 << 6;
private static final int FLAG_FLUSHED = 2 << 7;
private static final int FLAG_FLUSH_REQUIRED = 2 << 8;
private static final int FLAG_REMOTE_LISTENER = 2 << 9;
private static final int FLAG_RETAINED_CONN = 2 << 10;
private static final int FLAG_TRANS_ENDING = 2 << 11;
private static final Object[] EMPTY_OBJECTS = new Object[0];
private String _connectionFactoryName = "";
private String _connectionFactory2Name = "";
private static final Localizer _loc =
Localizer.forPackage(BrokerImpl.class);
// the store manager in use; this may be a decorator such as a
// data cache store manager around the native store manager
private transient DelegatingStoreManager _store = null;
private FetchConfiguration _fc = null;
private String _user = null;
private String _pass = null;
// these must be rebuilt by the facade layer during its deserialization
private transient Log _log = null;
private transient Compatibility _compat = null;
private transient ManagedRuntime _runtime = null;
private transient LockManager _lm = null;
private transient InverseManager _im = null;
private transient ReentrantLock _lock = null;
private transient OpCallbacks _call = null;
private transient RuntimeExceptionTranslator _extrans = null;
private transient InstrumentationManager _instm = null;
// ref to producing factory and configuration
private transient AbstractBrokerFactory _factory = null;
private transient OpenJPAConfiguration _conf = null;
private transient MetaDataRepository _repo = null;
// cache class loader associated with the broker
private transient ClassLoader _loader = null;
// user state
private Synchronization _sync = null;
private Map<Object, Object> _userObjects = null;
// managed object caches
private ManagedCache _cache = null;
private TransactionalCache _transCache = null;
private Set<StateManagerImpl> _transAdditions = null;
private Set<StateManagerImpl> _derefCache = null;
private Set<StateManagerImpl> _derefAdditions = null;
// these are used for method-internal state only
private transient Map<Object, StateManagerImpl> _loading = null;
private transient Set<Object> _operating = null;
private transient boolean _operatingDirty = true;
private Set<Class<?>> _persistedClss = null;
private Set<Class<?>> _updatedClss = null;
private Set<Class<?>> _deletedClss = null;
private Set<StateManagerImpl> _pending = null;
private int findAllDepth = 0;
// track instances that become transactional after the first savepoint
// (the first uses the transactional cache)
private Set<StateManagerImpl> _savepointCache = null;
private LinkedMap _savepoints = null;
private transient SavepointManager _spm = null;
// track open queries and extents so we can free their resources on close
private transient ReferenceHashSet _queries = null;
private transient ReferenceHashSet _extents = null;
// track operation stack depth. Transient because operations cannot
// span serialization.
private transient int _operationCount = 0;
// options
private boolean _nontransRead = false;
private boolean _nontransWrite = false;
private boolean _retainState = false;
private int _autoClear = CLEAR_DATASTORE;
private int _restoreState = RESTORE_IMMUTABLE;
private boolean _optimistic = false;
private boolean _ignoreChanges = false;
private boolean _multithreaded = false;
private boolean _managed = false;
private boolean _syncManaged = false;
private int _connRetainMode = CONN_RETAIN_DEMAND;
private boolean _evictDataCache = false;
private boolean _populateDataCache = true;
private boolean _largeTransaction = false;
private int _autoDetach = 0;
private int _detachState = DETACH_LOADED;
private boolean _detachedNew = true;
private boolean _orderDirty = false;
private boolean _cachePreparedQuery = true;
private boolean _cacheFinderQuery = true;
private boolean _suppressBatchOLELogging = false;
private boolean _allowReferenceToSiblingContext = false;
private boolean _postLoadOnMerge = false;
// status
private int _flags = 0;
// this is not in status because it should not be serialized
private transient boolean _isSerializing = false;
// transient because closed brokers can't be serialized
private transient boolean _closed = false;
private transient RuntimeException _closedException = null;
// event managers
private TransactionEventManager _transEventManager = null;
private int _transCallbackMode = 0;
private LifecycleEventManager _lifeEventManager = null;
private int _lifeCallbackMode = 0;
private transient DetachManagerLite _dmLite;
private transient boolean _initializeWasInvoked = false;
private transient boolean _fromWriteBehindCallback = false;
private LinkedList<FetchConfiguration> _fcs;
// Set of supported property keys. The keys in this set correspond to bean-style setter methods
// that can be set by reflection. The keys are not qualified by any prefix.
private static Set<String> _supportedPropertyNames;
static {
_supportedPropertyNames = new HashSet<>();
_supportedPropertyNames.addAll(Arrays.asList(new String[] {
"AutoClear",
"AutoDetach",
"CacheFinderQuery",
"CachePreparedQuery",
"DetachedNew",
"DetachState",
"EvictFromDataCache",
"IgnoreChanges",
"LifecycleListenerCallbackMode",
"Multithreaded",
"NontransactionalRead",
"NontransactionalWrite",
"Optimistic",
"PopulateDataCache",
"RestoreState",
"RetainState",
}));
}
private boolean _printParameters = false;
private static final String PRINT_PARAMETERS_CONFIG_STR = "PrintParameters";
/**
* Set the persistence manager's authentication. This is the first
* method called after construction.
*
* @param user the username this broker represents; used when pooling
* brokers to make sure that a request to the factory for
* a connection with an explicit user is delegated to a suitable broker
* @param pass the password for the above user
*/
public void setAuthentication(String user, String pass) {
_user = user;
_pass = pass;
}
/**
* Initialize the persistence manager. This method is called
* automatically by the factory before use.
*
* @param factory the factory used to create this broker
* @param sm a concrete StoreManager implementation to
* handle interaction with the data store
* @param managed the transaction mode
* @param connMode the connection retain mode
* @param fromDeserialization whether this call happened because of a
* deserialization or creation of a new BrokerImpl.
*/
public void initialize(AbstractBrokerFactory factory,
DelegatingStoreManager sm, boolean managed, int connMode,
boolean fromDeserialization) {
initialize(factory, sm, managed, connMode, fromDeserialization, false);
}
public void initialize(AbstractBrokerFactory factory,
DelegatingStoreManager sm, boolean managed, int connMode,
boolean fromDeserialization, boolean fromWriteBehindCallback) {
_fromWriteBehindCallback = fromWriteBehindCallback;
_initializeWasInvoked = true;
_loader = AccessController.doPrivileged(
J2DoPrivHelper.getContextClassLoaderAction());
if (!fromDeserialization){
_conf = factory.getConfiguration();
_repo = _conf.getMetaDataRepositoryInstance();
}
_compat = _conf.getCompatibilityInstance();
_factory = factory;
_log = _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME);
if (!fromDeserialization)
_cache = new ManagedCache(this);
// Force creation of a new operating set
_operatingDirty = true;
initializeOperatingSet();
_connRetainMode = connMode;
_managed = managed;
if (managed)
_runtime = _conf.getManagedRuntimeInstance();
else
_runtime = new LocalManagedRuntime(this);
if (!fromDeserialization) {
_lifeEventManager = _conf.getLifecycleEventManagerInstance();
_transEventManager = new TransactionEventManager();
int cmode = _repo.getMetaDataFactory().getDefaults().getCallbackMode();
setLifecycleListenerCallbackMode(cmode);
setTransactionListenerCallbackMode(cmode);
// setup default options
_factory.configureBroker(this);
}
// make sure to do this after configuring broker so that store manager
// can look to broker configuration; we set both store and lock managers
// before initializing them because they may each try to access the
// other in their initialization
_store = sm;
_lm = _conf.newLockManagerInstance();
_im = _conf.newInverseManagerInstance();
_spm = _conf.getSavepointManagerInstance();
_store.setContext(this);
_lm.setContext(this);
if (_connRetainMode == CONN_RETAIN_ALWAYS)
retainConnection();
if (!fromDeserialization) {
_fc = _store.newFetchConfiguration();
_fc.setContext(this);
}
_instm = _conf.getInstrumentationManagerInstance();
if (_instm != null) {
_instm.start(InstrumentationLevel.BROKER, this);
}
_dmLite = new DetachManagerLite(_conf);
_printParameters =
Boolean.parseBoolean(Configurations.parseProperties(_conf.getConnectionFactoryProperties()).getProperty(
PRINT_PARAMETERS_CONFIG_STR, "false"));
// do it before begin event otherwise transactional listeners can't use it, see @Auditable
if (!fromDeserialization)
_factory.addListeners(this);
// synch with the global transaction in progress, if any
if (_factory.syncWithManagedTransaction(this, false))
beginInternal();
}
@SuppressWarnings("unchecked")
private void initializeOperatingSet() {
if(_operatingDirty) {
_operatingDirty = false;
_operating = MapBackedSet.mapBackedSet(new IdentityHashMap<>());
}
}
/**
* Gets the unmodifiable set of instances being operated.
*/
protected Set<Object> getOperatingSet() {
return Collections.unmodifiableSet(_operating);
}
@Override
public Object clone()
throws CloneNotSupportedException {
if (_initializeWasInvoked)
throw new CloneNotSupportedException();
else {
return super.clone();
}
}
/**
* Create a {@link Map} to be used for the primary managed object cache.
* Maps oids to state managers. By default, this creates a
* {@link ReferenceMap} with soft values.
*/
protected Map<?,?> newManagedObjectCache() {
return new ReferenceHashMap(
AbstractReferenceMap.ReferenceStrength.HARD, AbstractReferenceMap.ReferenceStrength.SOFT);
}
//////////////////////////////////
// Implementation of StoreContext
//////////////////////////////////
@Override
public Broker getBroker() {
return this;
}
//////////////
// Properties
//////////////
@Override
public void setImplicitBehavior(OpCallbacks call,
RuntimeExceptionTranslator ex) {
if (_call == null)
_call = call;
if (_extrans == null)
_extrans = ex;
}
RuntimeExceptionTranslator getInstanceExceptionTranslator() {
return (_operationCount == 0) ? _extrans : null;
}
@Override
public BrokerFactory getBrokerFactory() {
return _factory;
}
@Override
public OpenJPAConfiguration getConfiguration() {
return _conf;
}
@Override
public FetchConfiguration getFetchConfiguration() {
return _fc;
}
@Override
public FetchConfiguration pushFetchConfiguration() {
return pushFetchConfiguration(null);
}
@Override
public FetchConfiguration pushFetchConfiguration(FetchConfiguration fc) {
if (_fcs == null)
_fcs = new LinkedList<>();
_fcs.add(_fc);
_fc = (FetchConfiguration) (fc != null ? fc : _fc).clone();
return _fc;
}
@Override
public void popFetchConfiguration() {
if (_fcs == null || _fcs.isEmpty())
throw new UserException(
_loc.get("fetch-configuration-stack-empty"));
_fc = _fcs.removeLast();
}
@Override
public int getConnectionRetainMode() {
return _connRetainMode;
}
@Override
public boolean isManaged() {
return _managed;
}
@Override
public ManagedRuntime getManagedRuntime() {
return _runtime;
}
@Override
public ClassLoader getClassLoader() {
return _loader;
}
@Override
public DelegatingStoreManager getStoreManager() {
return _store;
}
@Override
public LockManager getLockManager() {
return _lm;
}
@Override
public InverseManager getInverseManager() {
return _im;
}
@Override
public String getConnectionUserName() {
return _user;
}
@Override
public String getConnectionPassword() {
return _pass;
}
@Override
public boolean getMultithreaded() {
return _multithreaded;
}
@Override
public void setMultithreaded(boolean multithreaded) {
assertOpen();
_multithreaded = multithreaded;
if (multithreaded && _lock == null)
_lock = new ReentrantLock();
else if (!multithreaded)
_lock = null;
}
@Override
public boolean getIgnoreChanges() {
return _ignoreChanges;
}
@Override
public void setIgnoreChanges(boolean val) {
assertOpen();
_ignoreChanges = val;
}
@Override
public boolean getNontransactionalRead() {
return _nontransRead;
}
@Override
public void setNontransactionalRead(boolean val) {
assertOpen();
if ((_flags & FLAG_PRESTORING) != 0)
throw new UserException(_loc.get("illegal-op-in-prestore"));
// make sure the runtime supports it
if (val && !_conf.supportedOptions().contains
(OpenJPAConfiguration.OPTION_NONTRANS_READ))
throw new UnsupportedException(_loc.get
("nontrans-read-not-supported"));
_nontransRead = val;
}
@Override
public boolean getNontransactionalWrite() {
return _nontransWrite;
}
@Override
public void setNontransactionalWrite(boolean val) {
assertOpen();
if ((_flags & FLAG_PRESTORING) != 0)
throw new UserException(_loc.get("illegal-op-in-prestore"));
_nontransWrite = val;
}
@Override
public boolean getOptimistic() {
return _optimistic;
}
@Override
public void setOptimistic(boolean val) {
assertOpen();
if ((_flags & FLAG_ACTIVE) != 0)
throw new InvalidStateException(_loc.get("trans-active",
"Optimistic"));
// make sure the runtime supports it
if (val && !_conf.supportedOptions().contains(OpenJPAConfiguration.OPTION_OPTIMISTIC))
throw new UnsupportedException(_loc.get
("optimistic-not-supported"));
_optimistic = val;
}
@Override
public int getRestoreState() {
return _restoreState;
}
@Override
public void setRestoreState(int val) {
assertOpen();
if ((_flags & FLAG_ACTIVE) != 0)
throw new InvalidStateException(_loc.get("trans-active",
"Restore"));
_restoreState = val;
}
@Override
public boolean getRetainState() {
return _retainState;
}
@Override
public void setRetainState(boolean val) {
assertOpen();
if ((_flags & FLAG_PRESTORING) != 0)
throw new UserException(_loc.get("illegal-op-in-prestore"));
_retainState = val;
}
@Override
public int getAutoClear() {
return _autoClear;
}
@Override
public void setAutoClear(int val) {
assertOpen();
_autoClear = val;
}
@Override
public int getAutoDetach() {
return _autoDetach;
}
/**
* Sets automatic detachment option.
* <br>
* If the given flag contains {@link AutoDetach#DETACH_NONE} option,
* then no other option can be specified.
*/
@Override
public void setAutoDetach(int detachFlags) {
assertOpen();
assertAutoDetachValue(detachFlags);
_autoDetach = detachFlags;
}
@Override
public void setAutoDetach(int detachFlag, boolean on) {
assertOpen();
assertAutoDetachValue(on ? _autoDetach | detachFlag : _autoDetach & ~detachFlag);
if (on)
_autoDetach |= detachFlag;
else
_autoDetach &= ~detachFlag;
}
@Override
public int getDetachState() {
return _detachState;
}
@Override
public void setDetachState(int mode) {
assertOpen();
_detachState = mode;
}
@Override
public boolean isDetachedNew() {
return _detachedNew;
}
@Override
public void setDetachedNew(boolean isNew) {
assertOpen();
_detachedNew = isNew;
}
@Override
public boolean getSyncWithManagedTransactions() {
return _syncManaged;
}
@Override
public void setSyncWithManagedTransactions(boolean sync) {
assertOpen();
_syncManaged = sync;
}
@Override
public boolean getEvictFromDataCache() {
return _evictDataCache;
}
@Override
public void setEvictFromDataCache(boolean evict) {
assertOpen();
_evictDataCache = evict;
}
@Override
public boolean getPopulateDataCache() {
return _populateDataCache;
}
@Override
public void setPopulateDataCache(boolean cache) {
assertOpen();
_populateDataCache = cache;
}
@Override
public boolean isTrackChangesByType() {
return _largeTransaction;
}
@Override
public void setTrackChangesByType(boolean largeTransaction) {
assertOpen();
_largeTransaction = largeTransaction;
}
@Override
public Object getUserObject(Object key) {
beginOperation(false);
try {
return (_userObjects == null) ? null : _userObjects.get(key);
} finally {
endOperation();
}
}
@Override
public Object putUserObject(Object key, Object val) {
beginOperation(false);
try {
if (val == null)
return (_userObjects == null) ? null : _userObjects.remove(key);
if (_userObjects == null)
_userObjects = new HashMap<>();
return _userObjects.put(key, val);
} finally {
endOperation();
}
}
/**
* Get current configuration property values used by this instance.
* This values are combination of the current configuration values
* overwritten by values maintained by this instance such as
* Optimistic flag.
*/
@Override
public Map<String, Object> getProperties() {
Map<String, Object> props = _conf.toProperties(true);
for (String s : _supportedPropertyNames) {
final Object value = Reflection.getValue(this, s, !"CacheFinderQuery".equals(s));
if (value != null) {
props.put("openjpa." + s, value);
}
}
return props;
}
/**
* Gets the property names that can be used to corresponding setter methods of this receiver
* to set its value.
*/
@Override
public Set<String> getSupportedProperties() {
Set<String> keys = _conf.getPropertyKeys();
for (String s : _supportedPropertyNames)
keys.add("openjpa." + s);
return keys;
}
// ////////
// Events
// ////////
@Override
public void addLifecycleListener(Object listener, Class[] classes) {
beginOperation(false);
try {
_lifeEventManager.addListener(listener, classes);
} finally {
endOperation();
}
}
@Override
public void removeLifecycleListener(Object listener) {
beginOperation(false);
try {
_lifeEventManager.removeListener(listener);
} finally {
endOperation();
}
}
@Override
public int getLifecycleListenerCallbackMode() {
return _lifeCallbackMode;
}
@Override
public void setLifecycleListenerCallbackMode(int mode) {
beginOperation(false);
try {
_lifeCallbackMode = mode;
_lifeEventManager.setFailFast((mode & CALLBACK_FAIL_FAST) != 0);
} finally {
endOperation();
}
}
/**
* Give state managers access to the lifecycle event manager.
*/
@Override
public LifecycleEventManager getLifecycleEventManager() {
return _lifeEventManager;
}
/**
* Fire given lifecycle event, handling any exceptions appropriately.
*
* @return whether events are being processed at this time
*/
boolean fireLifecycleEvent(Object src, Object related, ClassMetaData meta,
int eventType) {
if (_lifeEventManager == null)
return false;
if (!_lifeEventManager.isActive(meta))
return false;
lock();
Exception[] exs;
try {
exs = _lifeEventManager.fireEvent(src, related, meta, eventType);
} finally {
unlock();
}
handleCallbackExceptions(exs, _lifeCallbackMode);
return true;
}
/**
* Take actions on callback exceptions depending on callback mode.
*/
private void handleCallbackExceptions(Exception[] exceps, int mode) {
if (exceps.length == 0 || (mode & CALLBACK_IGNORE) != 0)
return;
OpenJPAException ce;
if (exceps.length == 1) {
// If the exception is already a wrapped exception throw the
// exception instead of wrapping it with a callback exception
if (exceps[0] instanceof WrappedException)
throw (WrappedException)exceps[0];
else
ce = new CallbackException(exceps[0]);
} else {
ce = new CallbackException(_loc.get("callback-err")).
setNestedThrowables(exceps);
}
if ((mode & CALLBACK_ROLLBACK) != 0 && (_flags & FLAG_ACTIVE) != 0) {
ce.setFatal(true);
setRollbackOnlyInternal(ce);
}
if ((mode & CALLBACK_LOG) != 0 && _log.isWarnEnabled())
_log.warn(ce);
if ((mode & CALLBACK_RETHROW) != 0)
throw ce;
}
@Override
public void addTransactionListener(Object tl) {
beginOperation(false);
try {
_transEventManager.addListener(tl);
if (tl instanceof RemoteCommitEventManager)
_flags |= FLAG_REMOTE_LISTENER;
} finally {
endOperation();
}
}
@Override
public void removeTransactionListener(Object tl) {
beginOperation(false);
try {
if (_transEventManager.removeListener(tl)
&& (tl instanceof RemoteCommitEventManager))
_flags &= ~FLAG_REMOTE_LISTENER;
} finally {
endOperation();
}
}
@Override
public Collection<Object> getTransactionListeners() {
return _transEventManager.getListeners();
}
@Override
public int getTransactionListenerCallbackMode() {
return _transCallbackMode;
}
@Override
public void setTransactionListenerCallbackMode(int mode) {
beginOperation(false);
try {
_transCallbackMode = mode;
_transEventManager.setFailFast((mode & CALLBACK_FAIL_FAST) != 0);
} finally {
endOperation();
}
}
/**
* Fire given transaction event, handling any exceptions appropriately.
*/
private void fireTransactionEvent(TransactionEvent trans) {
if (_transEventManager != null && _transEventManager.hasListeners())
handleCallbackExceptions(_transEventManager.fireEvent(trans),
_transCallbackMode);
}
/**
* Set whether this Broker will generate verbose optimistic lock exceptions when batching
* operations. Defaults to true.
*/
public void setSuppressBatchOLELogging(boolean b) {
_suppressBatchOLELogging = b;
}
/**
* Return whether this Broker will generate verbose optimistic lock exceptions when batching
* operations.
*/
public boolean getSuppressBatchOLELogging() {
return _suppressBatchOLELogging;
}
///////////
// Lookups
///////////
@Override
public Object find(Object oid, boolean validate, FindCallbacks call) {
int flags = OID_COPY | OID_ALLOW_NEW | OID_NODELETED;
if (!validate)
flags |= OID_NOVALIDATE;
return find(oid, _fc, null, null, flags, call);
}
@Override
public Object find(Object oid, FetchConfiguration fetch, BitSet exclude,
Object edata, int flags) {
return find(oid, fetch, exclude, edata, flags, null);
}
/**
* Internal finder.
*/
protected Object find(Object oid, FetchConfiguration fetch, BitSet exclude,
Object edata, int flags, FindCallbacks call) {
if (call == null)
call = this;
oid = call.processArgument(oid);
if (oid == null) {
if ((flags & OID_NOVALIDATE) == 0)
throw new ObjectNotFoundException(_loc.get("null-oid"));
return call.processReturn(oid, null);
}
if (fetch == null)
fetch = _fc;
beginOperation(true);
try {
assertNontransactionalRead();
// cached instance?
StateManagerImpl sm = getStateManagerImplById(oid,
(flags & OID_ALLOW_NEW) != 0 || hasFlushed());
if (sm != null) {
if (!requiresLoad(sm, true, fetch, edata, flags))
return call.processReturn(oid, sm);
if (!sm.isLoading()) {
// make sure all the configured fields are loaded; do this
// after making instance transactional for locking
if (!sm.isTransactional() && useTransactionalState(fetch))
sm.transactional();
boolean loaded;
try {
loaded = sm.load(fetch, StateManagerImpl.LOAD_FGS,
exclude, edata, false);
} catch (ObjectNotFoundException onfe) {
if ((flags & OID_NODELETED) != 0
|| (flags & OID_NOVALIDATE) != 0)
throw onfe;
return call.processReturn(oid, null);
}
// if no data needed to be loaded and the user wants to
// validate, just make sure the object exists
if (!loaded && (flags & OID_NOVALIDATE) == 0
&& _compat.getValidateTrueChecksStore()
&& !sm.isTransactional()
&& !_store.exists(sm, edata)) {
if ((flags & OID_NODELETED) == 0)
return call.processReturn(oid, null);
throw new ObjectNotFoundException(_loc.get
("del-instance", sm.getManagedInstance(), oid)).
setFailedObject(sm.getManagedInstance());
}
}
// since the object was cached, we may need to upgrade lock
// if current level is higher than level of initial load
if ((_flags & FLAG_ACTIVE) != 0) {
int level = fetch.getReadLockLevel();
_lm.lock(sm, level, fetch.getLockTimeout(), edata);
sm.readLocked(level, fetch.getWriteLockLevel());
}
return call.processReturn(oid, sm);
}
// if there's no cached sm for a new/transient id type, we
// it definitely doesn't exist
if (oid instanceof StateManagerId)
return call.processReturn(oid, null);
// initialize a new state manager for the datastore instance
sm = newStateManagerImpl(oid, (flags & OID_COPY) != 0);
boolean load = requiresLoad(sm, false, fetch, edata, flags);
sm = initialize(sm, load, fetch, edata);
if (sm == null) {
if ((flags & OID_NOVALIDATE) != 0)
throw new ObjectNotFoundException(oid);
return call.processReturn(oid, null);
}
// make sure all configured fields were loaded
if (load) {
try {
sm.load(fetch, StateManagerImpl.LOAD_FGS, exclude,
edata, false);
} catch (ObjectNotFoundException onfe) {
if ((flags & OID_NODELETED) != 0
|| (flags & OID_NOVALIDATE) != 0)
throw onfe;
return call.processReturn(oid, null);
}
}
return call.processReturn(oid, sm);
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
/**
* Initialize a newly-constructed state manager.
*/
protected StateManagerImpl initialize(StateManagerImpl sm, boolean load,
FetchConfiguration fetch, Object edata) {
if (!load) {
sm.initialize(sm.getMetaData().getDescribedType(),
PCState.HOLLOW);
} else {
PCState state = (useTransactionalState(fetch))
? PCState.PCLEAN : PCState.PNONTRANS;
sm.setLoading(true);
try {
if (!_store.initialize(sm, state, fetch, edata))
return null;
} finally {
sm.setLoading(false);
}
}
return sm;
}
@Override
public Object[] findAll(Collection oids, boolean validate,
FindCallbacks call) {
int flags = OID_COPY | OID_ALLOW_NEW | OID_NODELETED;
if (!validate)
flags |= OID_NOVALIDATE;
return findAll(oids, _fc, null, null, flags, call);
}
@Override
public Object[] findAll(Collection oids, FetchConfiguration fetch,
BitSet exclude, Object edata, int flags) {
return findAll(oids, fetch, exclude, edata, flags, null);
}
/**
* Internal finder.
*/
protected Object[] findAll(Collection oids, FetchConfiguration fetch,
BitSet exclude, Object edata, int flags, FindCallbacks call) {
findAllDepth ++;
// throw any exceptions for null oids up immediately
if (oids == null)
throw new NullPointerException("oids == null");
if ((flags & OID_NOVALIDATE) != 0 && oids.contains(null))
throw new UserException(_loc.get("null-oids"));
// we have to use a map of oid->sm rather than a simple
// array, so that we make sure not to create multiple sms for equivalent
// oids if the user has duplicates in the given array
if (_loading == null)
_loading = new HashMap<>((int) (oids.size() * 1.33 + 1));
if (call == null)
call = this;
if (fetch == null)
fetch = _fc;
beginOperation(true);
try {
assertNontransactionalRead();
// collection of state managers to pass to store manager
List<OpenJPAStateManager> load = null;
StateManagerImpl sm;
boolean initialized;
boolean transState = useTransactionalState(fetch);
Object obj, oid;
int idx = 0;
for (Iterator<?> itr = oids.iterator(); itr.hasNext(); idx++) {
// if we've already seen this oid, skip repeats
obj = itr.next();
oid = call.processArgument(obj);
if (oid == null || _loading.containsKey(obj))
continue;
// if we don't have a cached instance or it is not transactional
// and is hollow or we need to validate, load it
sm = getStateManagerImplById(oid, (flags & OID_ALLOW_NEW) != 0
|| hasFlushed());
initialized = sm != null;
if (!initialized)
sm = newStateManagerImpl(oid, (flags & OID_COPY) != 0);
_loading.put(obj, sm);
if (requiresLoad(sm, initialized, fetch, edata, flags)) {
transState = transState || useTransactionalState(fetch);
if (initialized && !sm.isTransactional() && transState)
sm.transactional();
if (load == null)
load = new ArrayList<>(oids.size() - idx);
load.add(sm);
} else if (!initialized)
sm.initialize(sm.getMetaData().getDescribedType(),
PCState.HOLLOW);
}
// pass all state managers in need of loading or validation to the
// store manager
if (load != null) {
PCState state = (transState) ? PCState.PCLEAN
: PCState.PNONTRANS;
Collection<Object> failed = _store.loadAll(load, state,
StoreManager.FORCE_LOAD_NONE, fetch, edata);
// set failed instances to null
if (failed != null && !failed.isEmpty()) {
if ((flags & OID_NOVALIDATE) != 0)
throw newObjectNotFoundException(failed);
for (Object o : failed) {
_loading.put(o, null);
}
}
}
// create results array; make sure all configured fields are
// loaded in each instance
Object[] results = new Object[oids.size()];
boolean active = (_flags & FLAG_ACTIVE) != 0;
int level = fetch.getReadLockLevel();
idx = 0;
for (Iterator<?> itr = oids.iterator(); itr.hasNext(); idx++) {
oid = itr.next();
sm = _loading.get(oid);
if (sm != null && requiresLoad(sm, true, fetch, edata, flags)) {
try {
sm.load(fetch, StateManagerImpl.LOAD_FGS,
exclude, edata, false);
if (active) {
_lm.lock(sm, level, fetch.getLockTimeout(), edata);
sm.readLocked(level, fetch.getWriteLockLevel());
}
}
catch (ObjectNotFoundException onfe) {
if ((flags & OID_NODELETED) != 0
|| (flags & OID_NOVALIDATE) != 0)
throw onfe;
sm = null;
}
}
results[idx] = call.processReturn(oid, sm);
}
return results;
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
findAllDepth--;
if (findAllDepth == 0)
_loading = null;
endOperation();
}
}
public boolean isLoading(Object o) {
if(_loading == null ) {
return false;
}
return _loading.containsKey(o);
}
private boolean hasFlushed() {
return (_flags & FLAG_FLUSHED) != 0;
}
/**
* Return whether the given instance needs loading before being returned
* to the user.
*/
private boolean requiresLoad(OpenJPAStateManager sm, boolean initialized,
FetchConfiguration fetch, Object edata, int flags) {
if (!fetch.requiresLoad())
return false;
if ((flags & OID_NOVALIDATE) == 0)
return true;
if (edata != null) // take advantage of existing result
return true;
if (initialized && sm.getPCState() != PCState.HOLLOW)
return false;
if (!initialized && sm.getMetaData().getPCSubclasses().length > 0)
return true;
return !_compat.getValidateFalseReturnsHollow();
}
/**
* Return whether to use a transactional state.
*/
private boolean useTransactionalState(FetchConfiguration fetch) {
return (_flags & FLAG_ACTIVE) != 0 && (!_optimistic
|| _autoClear == CLEAR_ALL
|| fetch.getReadLockLevel() != LOCK_NONE);
}
@Override
public Object findCached(Object oid, FindCallbacks call) {
if (call == null)
call = this;
oid = call.processArgument(oid);
if (oid == null)
return call.processReturn(oid, null);
beginOperation(true);
try {
StateManagerImpl sm = getStateManagerImplById(oid, true);
return call.processReturn(oid, sm);
} finally {
endOperation();
}
}
@Override
public Class<?> getObjectIdType(Class<?> cls) {
if (cls == null)
return null;
beginOperation(false);
try {
ClassMetaData meta = _repo.getMetaData(cls, _loader, false);
if (meta == null
|| meta.getIdentityType() == ClassMetaData.ID_UNKNOWN)
return null;
if (meta.getIdentityType() == ClassMetaData.ID_APPLICATION)
return meta.getObjectIdType();
return _store.getDataStoreIdType(meta);
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
@Override
public Object newObjectId(Class<?> cls, Object val) {
if (val == null)
return null;
beginOperation(false);
try {
ClassMetaData meta = _repo.getMetaData(cls, _loader, true);
switch (meta.getIdentityType()) {
case ClassMetaData.ID_DATASTORE:
// delegate to store manager for datastore ids
if (val instanceof String
&& ((String) val).startsWith(StateManagerId.STRING_PREFIX))
return new StateManagerId((String) val);
return _store.newDataStoreId(val, meta);
case ClassMetaData.ID_APPLICATION:
if (ImplHelper.isAssignable(meta.getObjectIdType(),
val.getClass())) {
if (!meta.isOpenJPAIdentity()
&& meta.isObjectIdTypeShared())
return new ObjectId(cls, val);
return val;
}
// stringified app id?
if (val instanceof String
&& !_conf.getCompatibilityInstance().
getStrictIdentityValues()
&& !Modifier.isAbstract(cls.getModifiers()))
return PCRegistry.newObjectId(cls, (String) val);
Object[] arr = (val instanceof Object[]) ? (Object[]) val
: new Object[]{ val };
return ApplicationIds.fromPKValues(arr, meta);
default:
throw new UserException(_loc.get("meta-unknownid", cls));
}
} catch (IllegalArgumentException | ClassCastException iae) {
// OPENJPA-365
throw new UserException(_loc.get("bad-id-value", val,
val.getClass().getName(), cls)).setCause(iae);
} catch (OpenJPAException ke) {
throw ke;
}
catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
/**
* Create a new state manager for the given oid.
*/
private StateManagerImpl newStateManagerImpl(Object oid, boolean copy) {
// see if we're in the process of loading this oid in a loadAll call
StateManagerImpl sm;
if (_loading != null) {
sm = _loading.get(oid);
if (sm != null && sm.getPersistenceCapable() == null)
return sm;
}
// find metadata for the oid
Class<?> pcType = _store.getManagedType(oid);
ClassMetaData meta;
if (pcType != null)
meta = _repo.getMetaData(pcType, _loader, true);
else
meta = _repo.getMetaData(oid, _loader, true);
// copy the oid if needed
if (copy && _compat.getCopyObjectIds()) {
if (meta.getIdentityType() == ClassMetaData.ID_APPLICATION)
oid = ApplicationIds.copy(oid, meta);
else if (meta.getIdentityType() == ClassMetaData.ID_UNKNOWN)
throw new UserException(_loc.get("meta-unknownid", meta));
else
oid = _store.copyDataStoreId(oid, meta);
}
sm = newStateManagerImpl(oid, meta);
sm.setObjectId(oid);
return sm;
}
/**
* Create a state manager for the given oid and metadata.
*/
protected StateManagerImpl newStateManagerImpl(Object oid,
ClassMetaData meta) {
return new StateManagerImpl(oid, meta, this);
}
///////////////
// Transaction
///////////////
@Override
public void begin() {
beginOperation(true);
try {
if ((_flags & FLAG_ACTIVE) != 0)
throw new InvalidStateException(_loc.get("active"));
_factory.syncWithManagedTransaction(this, true);
beginInternal();
} finally {
endOperation();
}
}
/**
* Notify the store manager of a transaction.
*/
private void beginInternal() {
try {
beginStoreManagerTransaction(_optimistic);
_flags |= FLAG_ACTIVE;
// start locking
if (!_optimistic) {
_fc.setReadLockLevel(_conf.getReadLockLevelConstant());
_fc.setWriteLockLevel(_conf.getWriteLockLevelConstant());
_fc.setLockTimeout(_conf.getLockTimeout());
}
_lm.beginTransaction();
if (_transEventManager.hasBeginListeners())
fireTransactionEvent(new TransactionEvent(this,
TransactionEvent.AFTER_BEGIN, null, null, null, null));
} catch (OpenJPAException ke) {
// if we already started the transaction, don't let it commit
if ((_flags & FLAG_ACTIVE) != 0)
setRollbackOnlyInternal(ke);
throw ke.setFatal(true);
} catch (RuntimeException re) {
// if we already started the transaction, don't let it commit
if ((_flags & FLAG_ACTIVE) != 0)
setRollbackOnlyInternal(re);
throw new StoreException(re).setFatal(true);
}
if (_pending != null) {
StateManagerImpl sm;
for (StateManagerImpl stateManager : _pending) {
sm = stateManager;
sm.transactional();
if (sm.isDirty())
setDirty(sm, true);
}
_pending = null;
}
}
@Override
public void beginStore() {
beginOperation(true);
try {
assertTransactionOperation();
if ((_flags & FLAG_STORE_ACTIVE) == 0)
beginStoreManagerTransaction(false);
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new StoreException(re);
} finally {
endOperation();
}
}
/**
* Begin a store manager transaction.
*/
private void beginStoreManagerTransaction(boolean optimistic) {
if (!optimistic) {
retainConnection();
_store.begin();
_flags |= FLAG_STORE_ACTIVE;
} else {
if (_connRetainMode == CONN_RETAIN_TRANS)
retainConnection();
_store.beginOptimistic();
}
}
/**
* End the current store manager transaction. Throws an
* exception to signal a forced rollback after failed commit, otherwise
* returns any exception encountered during the end process.
*/
private RuntimeException endStoreManagerTransaction(boolean rollback) {
boolean forcedRollback = false;
boolean releaseConn = false;
RuntimeException err = null;
try {
if ((_flags & FLAG_STORE_ACTIVE) != 0) {
releaseConn = _connRetainMode != CONN_RETAIN_ALWAYS;
if (rollback)
_store.rollback();
else {
// and notify the query cache. notify in one batch to reduce synch
QueryCache queryCache = getConfiguration().
getDataCacheManagerInstance().getSystemQueryCache();
if (queryCache != null) {
Collection<Class<?>> pers = getPersistedTypes();
Collection<Class<?>> del = getDeletedTypes();
Collection<Class<?>> up = getUpdatedTypes();
int size = pers.size() + del.size() + up.size();
if (size > 0) {
Collection<Class<?>> types = new ArrayList<>(size);
types.addAll(pers);
types.addAll(del);
types.addAll(up);
queryCache.onTypesChanged(new TypesChangedEvent(this, types));
}
}
_store.commit();
}
} else {
releaseConn = _connRetainMode == CONN_RETAIN_TRANS;
_store.rollbackOptimistic();
}
}
catch (RuntimeException re) {
if (!rollback) {
forcedRollback = true;
try { _store.rollback(); } catch (RuntimeException re2) {}
}
err = re;
} finally {
_flags &= ~FLAG_STORE_ACTIVE;
}
if (releaseConn) {
try {
releaseConnection();
} catch (RuntimeException re) {
if (err == null)
err = re;
}
}
if (forcedRollback)
throw err;
return err;
}
@Override
public void commit() {
beginOperation(false);
try {
assertTransactionOperation();
javax.transaction.Transaction trans =
_runtime.getTransactionManager().getTransaction();
if (trans == null)
throw new InvalidStateException(_loc.get("null-trans"));
// this commit on the transaction will cause our
// beforeCompletion method to be invoked
trans.commit();
} catch (OpenJPAException ke) {
if (_log.isTraceEnabled())
_log.trace(_loc.get("end-trans-error"), ke);
throw ke;
} catch (Exception e) {
if (_log.isTraceEnabled())
_log.trace(_loc.get("end-trans-error"), e);
throw new StoreException(e);
} finally {
endOperation();
}
}
@Override
public void rollback() {
beginOperation(false);
try {
assertTransactionOperation();
javax.transaction.Transaction trans =
_runtime.getTransactionManager().getTransaction();
if (trans != null)
trans.rollback();
} catch (OpenJPAException ke) {
if (_log.isTraceEnabled())
_log.trace(_loc.get("end-trans-error"), ke);
throw ke;
} catch (Exception e) {
if (_log.isTraceEnabled())
_log.trace(_loc.get("end-trans-error"), e);
throw new StoreException(e);
} finally {
endOperation();
}
}
@Override
public boolean syncWithManagedTransaction() {
assertOpen();
lock();
try {
if ((_flags & FLAG_ACTIVE) != 0)
return true;
if (!_managed)
throw new InvalidStateException(_loc.get("trans-not-managed"));
if (_factory.syncWithManagedTransaction(this, false)) {
beginInternal();
return true;
}
return false;
} finally {
unlock();
}
}
@Override
public void commitAndResume() {
endAndResume(true);
}
@Override
public void rollbackAndResume() {
endAndResume(false);
}
private void endAndResume(boolean commit) {
beginOperation(false);
try {
if (commit)
commit();
else
rollback();
begin();
} finally {
endOperation();
}
}
@Override
public boolean getRollbackOnly() {
beginOperation(true);
try {
if ((_flags & FLAG_ACTIVE) == 0)
return false;
javax.transaction.Transaction trans =
_runtime.getTransactionManager().getTransaction();
if (trans == null)
return false;
return trans.getStatus() == Status.STATUS_MARKED_ROLLBACK;
} catch (OpenJPAException ke) {
throw ke;
} catch (Exception e) {
throw new GeneralException(e);
} finally {
endOperation();
}
}
@Override
public Throwable getRollbackCause() {
beginOperation(true);
try {
if ((_flags & FLAG_ACTIVE) == 0)
return null;
javax.transaction.Transaction trans =
_runtime.getTransactionManager().getTransaction();
if (trans == null)
return null;
if (trans.getStatus() == Status.STATUS_MARKED_ROLLBACK)
return _runtime.getRollbackCause();
return null;
} catch (OpenJPAException ke) {
throw ke;
} catch (Exception e) {
throw new GeneralException(e);
} finally {
endOperation();
}
}
@Override
public void setRollbackOnly() {
setRollbackOnly(new UserException());
}
@Override
public void setRollbackOnly(Throwable cause) {
beginOperation(true);
try {
assertTransactionOperation();
setRollbackOnlyInternal(cause);
} finally {
endOperation();
}
}
/**
* Mark the current transaction as rollback-only.
*/
private void setRollbackOnlyInternal(Throwable cause) {
try {
javax.transaction.Transaction trans =
_runtime.getTransactionManager().getTransaction();
if (trans == null)
throw new InvalidStateException(_loc.get("null-trans"));
// ensure tran is in a valid state to accept the setRollbackOnly
int tranStatus = trans.getStatus();
if ((tranStatus != Status.STATUS_NO_TRANSACTION)
&& (tranStatus != Status.STATUS_ROLLEDBACK)
&& (tranStatus != Status.STATUS_COMMITTED))
_runtime.setRollbackOnly(cause);
else if (_log.isTraceEnabled())
_log.trace(_loc.get("invalid-tran-status", tranStatus, "setRollbackOnly"));
} catch (OpenJPAException ke) {
throw ke;
} catch (Exception e) {
throw new GeneralException(e);
}
}
@Override
public void setSavepoint(String name) {
beginOperation(true);
try {
assertActiveTransaction();
if (_savepoints != null && _savepoints.containsKey(name))
throw new UserException(_loc.get("savepoint-exists", name));
if (hasFlushed() && !_spm.supportsIncrementalFlush())
throw new UnsupportedException(_loc.get
("savepoint-flush-not-supported"));
OpenJPASavepoint save = _spm.newSavepoint(name, this);
if (_savepoints == null || _savepoints.isEmpty()) {
save.save(getTransactionalStates());
_savepoints = new LinkedMap();
} else {
if (_savepointCache == null)
save.save(Collections.EMPTY_SET);
else {
save.save(_savepointCache);
_savepointCache.clear();
}
}
_savepoints.put(name, save);
} catch (OpenJPAException ke) {
throw ke;
} catch (Exception e) {
throw new GeneralException(e);
} finally {
endOperation();
}
}
@Override
public void releaseSavepoint() {
beginOperation(false);
try {
if (_savepoints == null || _savepoints.isEmpty())
throw new UserException(_loc.get("no-lastsavepoint"));
releaseSavepoint((String) _savepoints.get
(_savepoints.size() - 1));
} finally {
endOperation();
}
}
@Override
public void releaseSavepoint(String savepoint) {
beginOperation(false);
try {
assertActiveTransaction();
int index = (_savepoints == null) ? -1
: _savepoints.indexOf(savepoint);
if (index < 0)
throw new UserException(_loc.get("no-savepoint", savepoint));
// clear old in reverse
OpenJPASavepoint save;
while (_savepoints.size() > index + 1) {
save = (OpenJPASavepoint) _savepoints.remove
(_savepoints.size() - 1);
save.release(false);
}
save = (OpenJPASavepoint) _savepoints.remove(index);
save.release(true);
if (_savepointCache != null)
_savepointCache.clear();
} catch (OpenJPAException ke) {
throw ke;
} catch (Exception e) {
throw new GeneralException(e);
} finally {
endOperation();
}
}
@Override
public void rollbackToSavepoint() {
beginOperation(false);
try {
if (_savepoints == null || _savepoints.isEmpty())
throw new UserException(_loc.get("no-lastsavepoint"));
rollbackToSavepoint((String) _savepoints.get
(_savepoints.size() - 1));
} finally {
endOperation();
}
}
@Override
public void rollbackToSavepoint(String savepoint) {
beginOperation(false);
try {
assertActiveTransaction();
int index = (_savepoints == null) ? -1
: _savepoints.indexOf(savepoint);
if (index < 0)
throw new UserException(_loc.get("no-savepoint", savepoint));
// clear old in reverse
OpenJPASavepoint save;
while (_savepoints.size() > index + 1) {
save = (OpenJPASavepoint) _savepoints.remove
(_savepoints.size() - 1);
save.release(false);
}
save = (OpenJPASavepoint) _savepoints.remove(index);
Collection saved = save.rollback(_savepoints.values());
if (_savepointCache != null)
_savepointCache.clear();
if (hasTransactionalObjects()) {
// build up a new collection of states
TransactionalCache oldTransCache = _transCache;
TransactionalCache newTransCache = new TransactionalCache
(_orderDirty);
_transCache = null;
// currently there is the assumption that incremental
// flush is either a) not allowed, or b) required
// pre-savepoint. this solves a number of issues including
// storing flushed states as well as OID handling.
// if future plugins do not follow this, we need to cache
// more info per state
SavepointFieldManager fm;
StateManagerImpl sm;
for (Object value : saved) {
fm = (SavepointFieldManager) value;
sm = fm.getStateManager();
sm.rollbackToSavepoint(fm);
oldTransCache.remove(sm);
if (sm.isDirty())
newTransCache.addDirty(sm);
else
newTransCache.addClean(sm);
}
for (Object o : oldTransCache) {
sm = (StateManagerImpl) o;
sm.rollback();
removeFromTransaction(sm);
}
_transCache = newTransCache;
}
}
catch (OpenJPAException ke) {
throw ke;
} catch (Exception e) {
throw new GeneralException(e);
} finally {
endOperation();
}
}
/**
* Sets the given flag to the status.
*
* @since 2.3.0
*/
protected void setStatusFlag(int flag) {
_flags |= flag;
}
/**
* Clears the given flag from the status.
*
* @since 2.3.0
*/
protected void clearStatusFlag(int flag) {
_flags &= ~flag;
}
@Override
public void flush() {
beginOperation(true);
try {
// return silently if no trans is active, or if this is a reentrant
// call, which can happen if the store manager tries to get an
// auto-inc oid during flush
if ((_flags & FLAG_ACTIVE) == 0
|| (_flags & FLAG_STORE_FLUSHING) != 0)
return;
// make sure the runtime supports it
if (!_conf.supportedOptions().contains(OpenJPAConfiguration.OPTION_INC_FLUSH))
throw new UnsupportedException(_loc.get
("incremental-flush-not-supported"));
if (_savepoints != null && !_savepoints.isEmpty()
&& !_spm.supportsIncrementalFlush())
throw new UnsupportedException(_loc.get
("savepoint-flush-not-supported"));
try {
flushSafe(FLUSH_INC);
_flags |= FLAG_FLUSHED;
} catch (OpenJPAException ke) {
// rollback on flush error; objects may be in inconsistent state
setRollbackOnly(ke);
throw ke.setFatal(true);
} catch (RuntimeException re) {
// rollback on flush error; objects may be in inconsistent state
setRollbackOnly(re);
throw new StoreException(re).setFatal(true);
}
}
finally {
endOperation();
}
}
@Override
public void preFlush() {
beginOperation(true);
try {
if ((_flags & FLAG_ACTIVE) != 0)
flushSafe(FLUSH_LOGICAL);
} finally {
endOperation();
}
}
@Override
public void validateChanges() {
beginOperation(true);
try {
// if no trans, just return; if active datastore trans, flush
if ((_flags & FLAG_ACTIVE) == 0)
return;
if ((_flags & FLAG_STORE_ACTIVE) != 0) {
flush();
return;
}
// make sure the runtime supports inc flush
if (!_conf.supportedOptions().contains(OpenJPAConfiguration.OPTION_INC_FLUSH))
throw new UnsupportedException(_loc.get
("incremental-flush-not-supported"));
try {
flushSafe(FLUSH_ROLLBACK);
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new StoreException(re);
}
}
finally {
endOperation();
}
}
@Override
public boolean isActive() {
beginOperation(true);
try {
return (_flags & FLAG_ACTIVE) != 0;
} finally {
endOperation();
}
}
@Override
public boolean isStoreActive() {
// we need to lock here, because we might be in the middle of an
// atomic transaction process (e.g., commitAndResume)
beginOperation(true);
try {
return (_flags & FLAG_STORE_ACTIVE) != 0;
} finally {
endOperation();
}
}
/**
* Return whether the current transaction is ending, i.e. in the 2nd phase
* of a commit or rollback
*/
boolean isTransactionEnding() {
return (_flags & FLAG_TRANS_ENDING) != 0;
}
@Override
public boolean beginOperation(boolean syncTrans) {
lock();
try {
assertOpen();
if (syncTrans && _operationCount == 0 && _syncManaged
&& (_flags & FLAG_ACTIVE) == 0)
syncWithManagedTransaction();
return _operationCount++ == 1;
} catch (OpenJPAException ke) {
unlock();
throw ke;
} catch (RuntimeException re) {
unlock();
throw new GeneralException(re);
}
}
/**
* Mark the operation over. If outermost caller of stack, returns true
* and will detach managed instances if necessary.
*/
@Override
public boolean endOperation() {
try {
if (_operationCount == 1 && (_autoDetach & DETACH_NONTXREAD) != 0
&& (_flags & FLAG_ACTIVE) == 0) {
detachAllInternal(null);
}
if (_operationCount < 1)
throw new InternalException(_loc.get("multi-threaded-access"));
return _operationCount == 1;
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
_operationCount--;
if (_operationCount == 0)
initializeOperatingSet();
unlock();
}
}
public Synchronization getSynchronization() {
return _sync;
}
public void setSynchronization(Synchronization sync) {
assertOpen();
_sync = sync;
}
///////////////////////////////////////////////
// Implementation of Synchronization interface
///////////////////////////////////////////////
@Override
public void beforeCompletion() {
beginOperation(false);
try {
// user-supplied synchronization
if (_sync != null)
_sync.beforeCompletion();
flushSafe(FLUSH_COMMIT);
} catch (OpenJPAException ke) {
if (_log.isTraceEnabled())
_log.trace(_loc.get("end-trans-error"), ke);
throw translateManagedCompletionException(ke);
} catch (RuntimeException re) {
if (_log.isTraceEnabled())
_log.trace(_loc.get("end-trans-error"), re);
throw translateManagedCompletionException(new StoreException(re));
} finally {
endOperation();
}
}
@Override
public void afterCompletion(int status) {
beginOperation(false);
try {
assertActiveTransaction();
_flags |= FLAG_TRANS_ENDING;
endTransaction(status);
if (_sync != null)
_sync.afterCompletion(status);
if ((_autoDetach & DETACH_COMMIT) != 0)
detachAllInternal(null);
else if (status == Status.STATUS_ROLLEDBACK
&& (_autoDetach & DETACH_ROLLBACK) != 0) {
detachAllInternal(null);
}
// in an ee context, it's possible that the user tried to close
// us but we didn't actually close because we were waiting on this
// transaction; if that's true, then close now
if ((_flags & FLAG_CLOSE_INVOKED) != 0
&& _compat.getCloseOnManagedCommit())
free();
} catch (OpenJPAException ke) {
if (_log.isTraceEnabled())
_log.trace(_loc.get("end-trans-error"), ke);
throw translateManagedCompletionException(ke);
} catch (RuntimeException re) {
if (_log.isTraceEnabled())
_log.trace(_loc.get("end-trans-error"), re);
throw translateManagedCompletionException(new StoreException(re));
} finally {
_flags &= ~FLAG_ACTIVE;
_flags &= ~FLAG_FLUSHED;
_flags &= ~FLAG_TRANS_ENDING;
// event manager nulled if freed broker
if (_transEventManager != null
&& _transEventManager.hasEndListeners()) {
fireTransactionEvent(new TransactionEvent(this,
status == Status.STATUS_COMMITTED
? TransactionEvent.AFTER_COMMIT_COMPLETE
: TransactionEvent.AFTER_ROLLBACK_COMPLETE,
null, null, null, null));
}
endOperation();
}
}
/**
* If we're in a managed transaction, use our implicit behavior exception
* translator to translate before/afterCompletion callback errors.
*/
private RuntimeException translateManagedCompletionException
(RuntimeException re) {
return (!_managed || _extrans == null) ? re : _extrans.translate(re);
}
/**
* Flush safely, catching reentrant calls.
*/
private void flushSafe(int reason) {
if ((_flags & FLAG_FLUSHING) != 0)
throw new InvalidStateException(_loc.get("reentrant-flush"));
_flags |= FLAG_FLUSHING;
try {
flush(reason);
} finally {
_flags &= ~FLAG_FLUSHING;
}
}
/**
* Flush the transactional state to the data store. Subclasses that
* customize commit behavior should override this method. The method
* assumes that the persistence manager is locked, is not closed,
* and has an active transaction.
*
* @param reason one of {@link #FLUSH_INC}, {@link #FLUSH_COMMIT},
* {@link #FLUSH_ROLLBACK}, or {@link #FLUSH_LOGICAL}
* @since 0.2.5
*/
protected void flush(int reason) {
// this will enlist proxied states as necessary so we know whether we
// have anything to flush
Collection transactional = getTransactionalStates();
// do we actually have to flush? only if our flags say so, or if
// we have transaction listeners that need to be invoked for commit
// (no need to invoke them on inc flush if nothing is dirty). we
// special case the remote commit listener used by the datacache cause
// we know it doesn't require the commit event when nothing changes
boolean flush = (_flags & FLAG_FLUSH_REQUIRED) != 0;
boolean listeners = (_transEventManager.hasFlushListeners()
|| _transEventManager.hasEndListeners())
&& ((_flags & FLAG_REMOTE_LISTENER) == 0
|| _transEventManager.getListeners().size() > 1);
if (!flush && (reason != FLUSH_COMMIT || !listeners))
return;
Collection mobjs = null;
_flags |= FLAG_PRESTORING;
try {
if (flush) {
// call pre store on all currently transactional objs
for (Object o : transactional) {
((StateManagerImpl) o).beforeFlush(reason, _call);
}
flushAdditions(transactional, reason);
}
// hopefully now all dependent instances that are going to end
// up referenced have been marked as such; delete unrefed
// dependents
_flags |= FLAG_DEREFDELETING;
if (flush && _derefCache != null && !_derefCache.isEmpty()) {
// mark for delete all elements in deref, otherwise in some situations it
// throws ConcurrentModificationException
Set<StateManagerImpl> statesMarkedForDelete = new HashSet<>(_derefCache);
for (StateManagerImpl state: statesMarkedForDelete) {
deleteDeref(state);
}
flushAdditions(transactional, reason);
}
if (reason != FLUSH_LOGICAL) {
// if no datastore transaction, start one; even if we don't
// think we'll need to flush at this point, our transaction
// listeners might introduce some dirty objects or interact
// directly with the database
if ((_flags & FLAG_STORE_ACTIVE) == 0)
beginStoreManagerTransaction(false);
if ((_transEventManager.hasFlushListeners()
|| _transEventManager.hasEndListeners())
&& (flush || reason == FLUSH_COMMIT)) {
// fire events
mobjs = new ManagedObjectCollection(transactional);
if (reason == FLUSH_COMMIT
&& _transEventManager.hasEndListeners()) {
fireTransactionEvent(new TransactionEvent(this,
TransactionEvent.BEFORE_COMMIT, mobjs,
_persistedClss, _updatedClss, _deletedClss));
flushAdditions(transactional, reason);
flush = (_flags & FLAG_FLUSH_REQUIRED) != 0;
}
if (flush && _transEventManager.hasFlushListeners()) {
fireTransactionEvent(new TransactionEvent(this,
TransactionEvent.BEFORE_FLUSH, mobjs,
_persistedClss, _updatedClss, _deletedClss));
flushAdditions(transactional, reason);
}
}
}
}
finally {
_flags &= ~FLAG_PRESTORING;
_flags &= ~FLAG_DEREFDELETING;
_transAdditions = null;
_derefAdditions = null;
// also clear derefed set; the deletes have been recorded
if (_derefCache != null)
_derefCache = null;
}
// flush to store manager
List<Exception> exceps = null;
try {
if (flush && reason != FLUSH_LOGICAL) {
_flags |= FLAG_STORE_FLUSHING;
exceps = add(exceps,
newFlushException(_store.flush(transactional)));
}
} finally {
_flags &= ~FLAG_STORE_FLUSHING;
if (reason == FLUSH_ROLLBACK)
exceps = add(exceps, endStoreManagerTransaction(true));
else if (reason != FLUSH_LOGICAL)
_flags &= ~FLAG_FLUSH_REQUIRED;
// mark states as flushed
if (flush) {
StateManagerImpl sm;
for (Object o : transactional) {
sm = (StateManagerImpl) o;
try {
// the state may have become transient, such as if
// it is embedded and the owner has been deleted during
// this flush process; bug #1100
if (sm.getPCState() == PCState.TRANSIENT)
continue;
sm.afterFlush(reason);
if (reason == FLUSH_INC) {
// if not about to clear trans cache for commit
// anyway, re-cache dirty objects with default soft
// refs; we don't need hard refs now that the
// changes have been flushed
sm.proxyFields(true, false);
_transCache.flushed(sm);
}
}
catch (Exception e) {
exceps = add(exceps, e);
}
}
}
}
// throw any exceptions to shortcut listeners on fail
throwNestedExceptions(exceps, true);
if (flush && reason != FLUSH_ROLLBACK && reason != FLUSH_LOGICAL
&& _transEventManager.hasFlushListeners()) {
fireTransactionEvent(new TransactionEvent(this,
TransactionEvent.AFTER_FLUSH, mobjs, _persistedClss,
_updatedClss, _deletedClss));
}
}
/**
* Flush newly-transactional objects.
*/
private void flushAdditions(Collection transactional, int reason) {
boolean loop;
do {
// flush new transactional instances; note logical or
loop = flushTransAdditions(transactional, reason)
| deleteDerefAdditions(_derefCache);
} while (loop);
}
/**
* Flush transactional additions.
*/
private boolean flushTransAdditions(Collection transactional, int reason) {
if (_transAdditions == null || _transAdditions.isEmpty())
return false;
// keep local transactional list copy up to date
transactional.addAll(_transAdditions);
// copy the change set, then clear it for the next iteration
StateManagerImpl[] states = _transAdditions.
toArray(new StateManagerImpl[_transAdditions.size()]);
_transAdditions = null;
for (StateManagerImpl state : states) {
state.beforeFlush(reason, _call);
}
return true;
}
/**
* Delete new dereferenced objects.
*/
private boolean deleteDerefAdditions(Collection derefs) {
if (_derefAdditions == null || _derefAdditions.isEmpty())
return false;
// remember these additions in case one becomes derefed again later
derefs.addAll(_derefAdditions);
StateManagerImpl[] states = _derefAdditions.
toArray(new StateManagerImpl[_derefAdditions.size()]);
_derefAdditions = null;
for (StateManagerImpl state : states) {
deleteDeref(state);
}
return true;
}
/**
* Delete a dereferenced dependent.
*/
private void deleteDeref(StateManagerImpl sm) {
int action = processArgument(OpCallbacks.OP_DELETE,
sm.getManagedInstance(), sm, null);
if ((action & OpCallbacks.ACT_RUN) != 0)
sm.delete();
if ((action & OpCallbacks.ACT_CASCADE) != 0)
sm.cascadeDelete(_call);
}
/**
* Determine the action to take based on the user's given callbacks and
* our implicit behavior.
*/
private int processArgument(int op, Object obj, OpenJPAStateManager sm,
OpCallbacks call) {
if (call != null)
return call.processArgument(op, obj, sm);
if (_call != null)
return _call.processArgument(op, obj, sm);
return OpCallbacks.ACT_RUN | OpCallbacks.ACT_CASCADE;
}
/**
* Throw the proper exception based on the given set of flush errors, or
* do nothing if no errors occurred.
*/
private OpenJPAException newFlushException(Collection<Exception> exceps) {
if (exceps == null || exceps.isEmpty())
return null;
Throwable[] t = exceps.toArray(new Throwable[exceps.size()]);
List<Object> failed = new ArrayList<>(t.length);
// create fatal exception with nested exceptions for all the failed
// objects; if all OL exceptions, throw a top-level OL exception
boolean opt = true;
for (int i = 0; opt && i < t.length; i++) {
opt = t[i] instanceof OptimisticException;
if (opt) {
Object f = ((OptimisticException) t[i]).getFailedObject();
if (f != null)
failed.add(f);
}
}
if (opt && !failed.isEmpty()) {
if(_suppressBatchOLELogging){
return new OptimisticException(_loc.get("broker-suppressing-exceptions",t.length));
}else{
return new OptimisticException(failed, t);
}
}
if (opt)
return new OptimisticException(t);
Object failedObject = null;
if (t[0] instanceof OpenJPAException){
failedObject = ((OpenJPAException)t[0]).getFailedObject();
}
return new StoreException(_loc.get("rolled-back")).
setNestedThrowables(t).setFatal(true).setFailedObject(failedObject);
}
/**
* End the current transaction, making appropriate state transitions.
*/
protected void endTransaction(int status) {
// if a data store transaction was in progress, do the
// appropriate transaction change
boolean rollback = status != Status.STATUS_COMMITTED;
List<Exception> exceps = null;
try {
exceps = add(exceps, endStoreManagerTransaction(rollback));
} catch (RuntimeException re) {
rollback = true;
exceps = add(exceps, re);
}
// go back to default none lock level
_fc.setReadLockLevel(LOCK_NONE);
_fc.setWriteLockLevel(LOCK_NONE);
_fc.setLockTimeout(-1);
Collection transStates;
if (hasTransactionalObjects())
transStates = _transCache;
else
transStates = Collections.EMPTY_SET;
// fire after rollback/commit event
Collection mobjs = null;
if (_transEventManager.hasEndListeners()) {
mobjs = new ManagedObjectCollection(transStates);
int eventType = (rollback) ? TransactionEvent.AFTER_ROLLBACK
: TransactionEvent.AFTER_COMMIT;
fireTransactionEvent(new TransactionEvent(this, eventType, mobjs,
_persistedClss, _updatedClss, _deletedClss));
}
// null transactional caches now so that all the removeFromTransaction
// calls as we transition each object don't have to do any work; don't
// clear trans cache object because we still need the transStates
// reference to it below
_transCache = null;
if (_persistedClss != null)
_persistedClss = null;
if (_updatedClss != null)
_updatedClss = null;
if (_deletedClss != null)
_deletedClss = null;
// new cache would get cleared anyway during transitions, but doing so
// immediately saves us some lookups
_cache.clearNew();
// tell all derefed instances they're no longer derefed; we can't
// rely on rollback and commit calls below cause some instances might
// not be transactional
if (_derefCache != null && !_derefCache.isEmpty()) {
for (StateManagerImpl stateManager : _derefCache) {
stateManager.setDereferencedDependent(false, false);
}
_derefCache = null;
}
// perform commit or rollback state transitions on each instance
StateManagerImpl sm;
for (Object transState : transStates) {
sm = (StateManagerImpl) transState;
try {
if (rollback) {
// tell objects that may have been derefed then flushed
// (and therefore deleted) to un-deref
sm.setDereferencedDependent(false, false);
sm.rollback();
}
else {
if (sm.getPCState() == PCState.PNEWDELETED || sm.getPCState() == PCState.PDELETED) {
fireLifecycleEvent(sm.getPersistenceCapable(), null, sm.getMetaData(),
LifecycleEvent.AFTER_DELETE_PERFORMED);
}
sm.commit();
}
}
catch (RuntimeException re) {
exceps = add(exceps, re);
}
}
// notify the lock manager to clean up and release remaining locks
_lm.endTransaction();
// clear old savepoints in reverse
OpenJPASavepoint save;
while (_savepoints != null && _savepoints.size() > 0) {
save =
(OpenJPASavepoint) _savepoints.remove(_savepoints.size() - 1);
save.release(false);
}
_savepoints = null;
_savepointCache = null;
// fire after state change event
if (_transEventManager.hasEndListeners())
fireTransactionEvent(new TransactionEvent(this, TransactionEvent.
AFTER_STATE_TRANSITIONS, mobjs, null, null, null));
// now clear trans cache; keep cleared version rather than
// null to avoid having to re-create the set later; more efficient
if (transStates != Collections.EMPTY_SET) {
_transCache = (TransactionalCache) transStates;
_transCache.clear();
}
throwNestedExceptions(exceps, true);
}
////////////////////
// Object lifecycle
////////////////////
@Override
public void persist(Object obj, OpCallbacks call) {
persist(obj, null, true, call);
}
@Override
public OpenJPAStateManager persist(Object obj, Object id,
OpCallbacks call) {
return persist(obj, id, true, call);
}
@Override
public void persistAll(Collection objs, OpCallbacks call) {
persistAll(objs, true, call);
}
/**
* Persist the given objects. Indicate whether this was an explicit persist
* (PNEW) or a provisonal persist (PNEWPROVISIONAL).
*/
public void persistAll(Collection objs, boolean explicit,
OpCallbacks call) {
if (objs.isEmpty())
return;
beginOperation(true);
List<Exception> exceps = null;
try {
assertWriteOperation();
for (Object obj : objs) {
try {
if(obj == null)
continue;
persistInternal(obj, null, explicit, call, true);
} catch (UserException ue) {
exceps = add(exceps, ue);
}
catch (RuntimeException re) {
throw new GeneralException(re);
}
}
} finally {
endOperation();
}
throwNestedExceptions(exceps, false);
}
/**
* If the given element is not null, add it to the given list,
* creating the list if necessary.
*/
private List<Exception> add(List<Exception> l, Exception o) {
if (o == null)
return l;
if (l == null)
l = new LinkedList<>();
l.add(o);
return l;
}
/**
* Throw an exception wrapping the given nested exceptions.
*/
private void throwNestedExceptions(List<Exception> exceps, boolean datastore) {
if (exceps == null || exceps.isEmpty())
return;
if (datastore && exceps.size() == 1)
throw (RuntimeException) exceps.get(0);
boolean fatal = false;
Throwable[] t = exceps.toArray(new Throwable[exceps.size()]);
for (Throwable throwable : t) {
if (throwable instanceof OpenJPAException
&& ((OpenJPAException) throwable).isFatal())
fatal = true;
}
OpenJPAException err;
if (datastore)
err = new StoreException(_loc.get("nested-exceps"));
else
err = new UserException(_loc.get("nested-exceps"));
throw err.setNestedThrowables(t).setFatal(fatal);
}
/**
* Persist the given object. Indicate whether this was an explicit persist
* (PNEW) or a provisonal persist (PNEWPROVISIONAL)
*/
public void persist(Object obj, boolean explicit, OpCallbacks call) {
persist(obj, null, explicit, call);
}
/**
* Persist the given object. Indicate whether this was an explicit persist
* (PNEW) or a provisonal persist (PNEWPROVISIONAL).
* See {@link Broker} for details on this method.
*/
public OpenJPAStateManager persist(Object obj, Object id, boolean explicit,
OpCallbacks call) {
return persist(obj, id, explicit, call, true);
}
/**
* Persist the given object. Indicate whether this was an explicit persist
* (PNEW) or a provisonal persist (PNEWPROVISIONAL).
* See {@link Broker} for details on this method.
*/
public OpenJPAStateManager persist(Object obj, Object id, boolean explicit,
OpCallbacks call, boolean fireEvent) {
if (obj == null)
return null;
beginOperation(true);
try {
assertWriteOperation();
return persistInternal(obj, id, explicit, call, fireEvent);
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
private OpenJPAStateManager persistInternal(Object obj, Object id, boolean explicit, OpCallbacks call,
boolean fireEvent) {
StateManagerImpl sm = getStateManagerImpl(obj, true);
if (!operatingAdd(obj)) {
return sm;
}
int action = processArgument(OpCallbacks.OP_PERSIST, obj, sm, call);
if (action == OpCallbacks.ACT_NONE) {
return sm;
}
// ACT_CASCADE
if ((action & OpCallbacks.ACT_RUN) == 0) {
if (sm != null) {
sm.cascadePersist(call);
} else {
cascadeTransient(OpCallbacks.OP_PERSIST, obj, call, "persist");
}
return sm;
}
// ACT_RUN
PersistenceCapable pc;
if (sm != null) {
if (sm.isDetached()) {
throw new ObjectExistsException(_loc.get("persist-detached", Exceptions.toString(obj)))
.setFailedObject(obj);
}
if (!sm.isEmbedded()) {
sm.persist();
_cache.persist(sm);
if ((action & OpCallbacks.ACT_CASCADE) != 0) {
sm.cascadePersist(call);
}
return sm;
}
// an embedded field; notify the owner that the value has
// changed by becoming independently persistent
sm.getOwner().dirty(sm.getOwnerIndex());
_cache.persist(sm);
pc = sm.getPersistenceCapable();
} else {
pc = assertPersistenceCapable(obj);
if (pc.pcIsDetached() == Boolean.TRUE) {
throw new ObjectExistsException(_loc.get("persist-detached", Exceptions.toString(obj)))
.setFailedObject(obj);
}
}
ClassMetaData meta = _repo.getMetaData(obj.getClass(), _loader, true);
if (fireEvent) {
fireLifecycleEvent(obj, null, meta, LifecycleEvent.BEFORE_PERSIST);
}
// create id for instance
if (id == null) {
int idType = meta.getIdentityType();
if (idType == ClassMetaData.ID_APPLICATION) {
id = ApplicationIds.create(pc, meta);
} else if (idType == ClassMetaData.ID_UNKNOWN) {
throw new UserException(_loc.get("meta-unknownid", meta));
} else {
id = StateManagerId.newInstance(this);
}
}
// make sure we don't already have the instance cached
checkForDuplicateId(id, obj, meta);
// if had embedded sm, null it
if (sm != null) {
pc.pcReplaceStateManager(null);
}
// create new sm
sm = newStateManagerImpl(id, meta);
if ((_flags & FLAG_ACTIVE) != 0) {
if (explicit) {
sm.initialize(pc, PCState.PNEW);
} else {
sm.initialize(pc, PCState.PNEWPROVISIONAL);
}
} else {
sm.initialize(pc, PCState.PNONTRANSNEW);
}
if ((action & OpCallbacks.ACT_CASCADE) != 0) {
sm.cascadePersist(call);
}
return sm;
}
/**
* Temporarily manage the given instance in order to cascade the given
* operation through it.
*/
private void cascadeTransient(int op, Object obj, OpCallbacks call,
String errOp) {
PersistenceCapable pc = assertPersistenceCapable(obj);
// if using detached state manager, don't replace
if (pc.pcGetStateManager() != null)
throw newDetachedException(obj, errOp);
ClassMetaData meta = _repo.getMetaData(obj.getClass(), _loader, true);
StateManagerImpl sm = newStateManagerImpl(StateManagerId.
newInstance(this), meta);
sm.initialize(pc, PCState.TLOADED);
try {
switch (op) {
case OpCallbacks.OP_PERSIST:
sm.cascadePersist(call);
break;
case OpCallbacks.OP_DELETE:
sm.cascadeDelete(call);
break;
case OpCallbacks.OP_REFRESH:
sm.gatherCascadeRefresh(call);
break;
default:
throw new InternalException(String.valueOf(op));
}
}
finally {
sm.release(true);
}
}
@Override
public void deleteAll(Collection objs, OpCallbacks call) {
beginOperation(true);
try {
assertWriteOperation();
List<Exception> exceps = null;
Object obj;
for (Iterator<?> itr = objs.iterator(); itr.hasNext();) {
try {
obj = itr.next();
if (obj != null)
delete(obj, getStateManagerImpl(obj, true), call);
} catch (UserException ue) {
exceps = add(exceps, ue);
}
}
throwNestedExceptions(exceps, false);
} finally {
endOperation();
}
}
@Override
public void delete(Object obj, OpCallbacks call) {
if (obj == null)
return;
beginOperation(true);
try {
assertWriteOperation();
delete(obj, getStateManagerImpl(obj, true), call);
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
/**
* Internal delete.
*/
void delete(Object obj, StateManagerImpl sm, OpCallbacks call) {
if (!operatingAdd(obj))
return;
int action = processArgument(OpCallbacks.OP_DELETE, obj, sm, call);
if (action == OpCallbacks.ACT_NONE)
return;
// ACT_CASCADE
if ((action & OpCallbacks.ACT_RUN) == 0) {
if (sm != null) {
if (!sm.isEmbedded() || !sm.getDereferencedEmbedDependent()) {
sm.cascadeDelete(call);
}
}
else
cascadeTransient(OpCallbacks.OP_DELETE, obj, call, "delete");
return;
}
// ACT_RUN
if (sm != null) {
if (sm.isDetached())
throw newDetachedException(obj, "delete");
if ((action & OpCallbacks.ACT_CASCADE) != 0) {
if (!sm.isEmbedded() || !sm.getDereferencedEmbedDependent()) {
if (ValidatingLifecycleEventManager.class.isAssignableFrom(_lifeEventManager.getClass())) {
ValidatingLifecycleEventManager _validatingLCEventManager =
(ValidatingLifecycleEventManager) _lifeEventManager;
boolean saved = _validatingLCEventManager.setValidationEnabled(false);
try {
sm.cascadeDelete(call);
} finally {
_validatingLCEventManager.setValidationEnabled(saved);
}
} else {
sm.cascadeDelete(call);
}
}
}
sm.delete();
} else if (assertPersistenceCapable(obj).pcIsDetached() == Boolean.TRUE)
throw newDetachedException(obj, "delete");
}
/**
* Throw an exception indicating that the current action can't be
* performed on a detached object.
*/
private OpenJPAException newDetachedException(Object obj,
String operation) {
throw new UserException(_loc.get("bad-detached-op", operation,
Exceptions.toString(obj))).setFailedObject(obj);
}
@Override
public void releaseAll(Collection objs, OpCallbacks call) {
beginOperation(false);
try {
List<Exception> exceps = null;
for (Iterator<?> itr = objs.iterator(); itr.hasNext();) {
try {
release(itr.next(), call);
} catch (UserException ue) {
exceps = add(exceps, ue);
}
}
throwNestedExceptions(exceps, false);
} finally {
endOperation();
}
}
@Override
public void release(Object obj, OpCallbacks call) {
if (obj == null)
return;
beginOperation(false);
try {
StateManagerImpl sm = getStateManagerImpl(obj, true);
int action = processArgument(OpCallbacks.OP_RELEASE, obj, sm, call);
if (sm == null)
return;
if ((action & OpCallbacks.ACT_RUN) != 0 && sm.isPersistent()) {
boolean pending = sm.isPendingTransactional();
sm.release(true);
if (pending)
removeFromPendingTransaction(sm);
}
}
catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
@Override
public OpenJPAStateManager embed(Object obj, Object id,
OpenJPAStateManager owner, ValueMetaData ownerMeta) {
beginOperation(true);
try {
StateManagerImpl orig = getStateManagerImpl(obj, true);
if (orig != null) {
// if already embedded, nothing to do
if (orig.getOwner() == owner && orig.getMetaData().
getEmbeddingMetaData() == ownerMeta)
return orig;
// otherwise make sure pc is fully loaded for when we copy its
// data below
orig.load(_fc, StateManagerImpl.LOAD_ALL, null, null, false);
}
// create new state manager with embedded metadata
ClassMetaData meta = ownerMeta.getEmbeddedMetaData();
if (meta == null)
throw new InternalException(_loc.get("bad-embed", ownerMeta));
if (id == null)
id = StateManagerId.newInstance(this);
StateManagerImpl sm = newStateManagerImpl(id, meta);
sm.setOwner((StateManagerImpl) owner, ownerMeta);
PersistenceCapable copy;
PCState state;
Class<?> type = meta.getDescribedType();
if (obj != null) {
// give copy and the original instance the same state manager
// so that we can copy fields from one to the other
StateManagerImpl copySM;
PersistenceCapable pc;
if (orig == null) {
copySM = sm;
pc = assertPersistenceCapable(obj);
pc.pcReplaceStateManager(sm);
} else {
copySM = orig;
pc = orig.getPersistenceCapable();
}
try {
// copy the instance. we do this even if it doesn't already
// have a state manager in case it is later assigned to a
// PC field; at that point it's too late to copy
copy = PCRegistry.newInstance(type, copySM, false);
int[] fields = new int[meta.getFields().length];
for (int i = 0; i < fields.length; i++)
fields[i] = i;
copy.pcCopyFields(pc, fields);
state = PCState.ECOPY;
copy.pcReplaceStateManager(null);
} finally {
// if the instance didn't have a state manager to start,
// revert it to being transient
if (orig == null)
pc.pcReplaceStateManager(null);
}
} else {
copy = PCRegistry.newInstance(type, sm, false);
if ((_flags & FLAG_ACTIVE) != 0 && !_optimistic)
state = PCState.ECLEAN;
else
state = PCState.ENONTRANS;
}
sm.initialize(copy, state);
return sm;
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
/**
* If not already cached, create an empty copy of the given state
* manager in the given state.
*/
OpenJPAStateManager copy(OpenJPAStateManager copy, PCState state) {
beginOperation(true);
try {
assertOpen();
Object oid = copy.fetchObjectId();
Class<?> type = copy.getManagedInstance().getClass();
if (oid == null)
throw new InternalException();
// cached instance?
StateManagerImpl sm = null;
if (!copy.isEmbedded())
sm = getStateManagerImplById(oid, true);
if (sm == null) {
MetaDataRepository repos = _conf.
getMetaDataRepositoryInstance();
ClassMetaData meta = repos.getMetaData(type, _loader, true);
// construct a new state manager with all info known
sm = newStateManagerImpl(oid, meta);
sm.setObjectId(oid);
sm.initialize(sm.getMetaData().getDescribedType(), state);
}
return sm;
} finally {
endOperation();
}
}
@Override
public void refreshAll(Collection objs, OpCallbacks call) {
if (objs == null || objs.isEmpty())
return;
beginOperation(true);
try {
assertNontransactionalRead();
for (Object obj : objs) {
gatherCascadeRefresh(obj, call);
}
if (_operating.isEmpty())
return;
if (_operating.size() == 1)
refreshInternal(_operating.iterator().next(), call);
else
refreshInternal(_operating, call);
} finally {
endOperation();
}
}
@Override
public void refresh(Object obj, OpCallbacks call) {
if (obj == null)
return;
beginOperation(true);
try {
assertNontransactionalRead();
gatherCascadeRefresh(obj, call);
if (_operating.isEmpty())
return;
if (_operating.size() == 1)
refreshInternal(_operating.iterator().next(), call);
else
refreshInternal(_operating, call);
} finally {
endOperation();
}
}
/**
* Gathers all objects reachable through cascade-refresh relations
* into the operating set.
*/
void gatherCascadeRefresh(Object obj, OpCallbacks call) {
if (obj == null)
return;
if (!operatingAdd(obj))
return;
StateManagerImpl sm = getStateManagerImpl(obj, false);
int action = processArgument(OpCallbacks.OP_REFRESH, obj, sm, call);
if ((action & OpCallbacks.ACT_CASCADE) == 0)
return;
if (sm != null)
sm.gatherCascadeRefresh(call);
else
cascadeTransient(OpCallbacks.OP_REFRESH, obj, call, "refresh");
}
/**
* This method is called with the full set of objects reachable via
* cascade-refresh relations from the user-given instances.
*/
protected void refreshInternal(Collection objs, OpCallbacks call) {
if (objs == null || objs.isEmpty())
return;
List<Exception> exceps = null;
try {
// collect instances that need a refresh
Collection<OpenJPAStateManager> load = null;
StateManagerImpl sm;
Object obj;
for (Object o : objs) {
obj = o;
if (obj == null)
continue;
try {
sm = getStateManagerImpl(obj, true);
if ((processArgument(OpCallbacks.OP_REFRESH, obj, sm, call)
& OpCallbacks.ACT_RUN) == 0)
continue;
if (sm != null) {
if (sm.isDetached())
throw newDetachedException(obj, "refresh");
else if (sm.beforeRefresh(true)) {
if (load == null)
load = new ArrayList<>(objs.size());
load.add(sm);
}
int level = _fc.getReadLockLevel();
int timeout = _fc.getLockTimeout();
_lm.refreshLock(sm, level, timeout, null);
sm.readLocked(level, level);
}
else if (assertPersistenceCapable(obj).pcIsDetached()
== Boolean.TRUE)
throw newDetachedException(obj, "refresh");
}
catch (OpenJPAException ke) {
exceps = add(exceps, ke);
}
}
// refresh all
if (load != null) {
Collection<Object> failed = _store.loadAll(load, null,
StoreManager.FORCE_LOAD_REFRESH, _fc, null);
if (failed != null && !failed.isEmpty())
exceps = add(exceps, newObjectNotFoundException(failed));
// perform post-refresh transitions and make sure all fetch
// group fields are loaded
for (OpenJPAStateManager openJPAStateManager : load) {
sm = (StateManagerImpl) openJPAStateManager;
if (failed != null && failed.contains(sm.getId()))
continue;
try {
sm.afterRefresh();
sm.load(_fc, StateManagerImpl.LOAD_FGS, null, null,
false);
}
catch (OpenJPAException ke) {
exceps = add(exceps, ke);
}
}
}
// now invoke postRefresh on all the instances
for (Iterator<?> itr = objs.iterator(); itr.hasNext();) {
try {
sm = getStateManagerImpl(itr.next(), true);
if (sm != null && !sm.isDetached())
fireLifecycleEvent(sm.getManagedInstance(), null,
sm.getMetaData(), LifecycleEvent.AFTER_REFRESH);
} catch (OpenJPAException ke) {
exceps = add(exceps, ke);
}
}
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
}
throwNestedExceptions(exceps, false);
}
/**
* Optimization for single-object refresh.
*/
protected void refreshInternal(Object obj, OpCallbacks call) {
try {
StateManagerImpl sm = getStateManagerImpl(obj, true);
if ((processArgument(OpCallbacks.OP_REFRESH, obj, sm, call)
& OpCallbacks.ACT_RUN) == 0)
return;
if (sm != null) {
if (sm.isDetached())
throw newDetachedException(obj, "refresh");
else if (sm.beforeRefresh(false)) {
sm.load(_fc, StateManagerImpl.LOAD_FGS, null, null, false);
sm.afterRefresh();
}
int level = _fc.getReadLockLevel();
int timeout = _fc.getLockTimeout();
_lm.refreshLock(sm, level, timeout, null);
sm.readLocked(level, level);
fireLifecycleEvent(sm.getManagedInstance(), null,
sm.getMetaData(), LifecycleEvent.AFTER_REFRESH);
} else if (assertPersistenceCapable(obj).pcIsDetached()
== Boolean.TRUE)
throw newDetachedException(obj, "refresh");
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
}
}
@Override
public void retrieveAll(Collection objs, boolean dfgOnly,
OpCallbacks call) {
if (objs == null || objs.isEmpty())
return;
if (objs.size() == 1) {
retrieve(objs.iterator().next(), dfgOnly, call);
return;
}
List<Exception> exceps = null;
beginOperation(true);
try {
assertOpen();
assertNontransactionalRead();
// collect all hollow instances for load
Object obj;
Collection<OpenJPAStateManager> load = null;
StateManagerImpl sm;
Collection<StateManagerImpl> sms = new ArrayList<>(objs.size());
for (Object o : objs) {
obj = o;
if (obj == null)
continue;
try {
sm = getStateManagerImpl(obj, true);
if ((processArgument(OpCallbacks.OP_RETRIEVE, obj, sm, call)
& OpCallbacks.ACT_RUN) == 0)
continue;
if (sm != null) {
if (sm.isDetached())
throw newDetachedException(obj, "retrieve");
if (sm.isPersistent()) {
sms.add(sm);
if (sm.getPCState() == PCState.HOLLOW) {
if (load == null)
load = new ArrayList<>();
load.add(sm);
}
}
}
else if (assertPersistenceCapable(obj).pcIsDetached()
== Boolean.TRUE)
throw newDetachedException(obj, "retrieve");
}
catch (UserException ue) {
exceps = add(exceps, ue);
}
}
// load all hollow instances
Collection<Object> failed = null;
if (load != null) {
int mode = (dfgOnly) ? StoreManager.FORCE_LOAD_DFG
: StoreManager.FORCE_LOAD_ALL;
failed = _store.loadAll(load, null, mode, _fc, null);
if (failed != null && !failed.isEmpty())
exceps = add(exceps, newObjectNotFoundException(failed));
}
// retrieve all non-failed instances
for (StateManagerImpl stateManager : sms) {
sm = stateManager;
if (failed != null && failed.contains(sm.getId()))
continue;
int mode = (dfgOnly) ? StateManagerImpl.LOAD_FGS
: StateManagerImpl.LOAD_ALL;
try {
sm.beforeRead(-1);
sm.load(_fc, mode, null, null, false);
}
catch (OpenJPAException ke) {
exceps = add(exceps, ke);
}
}
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
throwNestedExceptions(exceps, false);
}
@Override
public void retrieve(Object obj, boolean dfgOnly, OpCallbacks call) {
if (obj == null)
return;
beginOperation(true);
try {
assertOpen();
assertNontransactionalRead();
StateManagerImpl sm = getStateManagerImpl(obj, true);
if ((processArgument(OpCallbacks.OP_RETRIEVE, obj, sm, call)
& OpCallbacks.ACT_RUN) == 0)
return;
if (sm != null) {
if (sm.isDetached())
throw newDetachedException(obj, "retrieve");
if (sm.isPersistent()) {
int mode = (dfgOnly) ? StateManagerImpl.LOAD_FGS
: StateManagerImpl.LOAD_ALL;
sm.beforeRead(-1);
sm.load(_fc, mode, null, null, false);
}
} else if (assertPersistenceCapable(obj).pcIsDetached()
== Boolean.TRUE)
throw newDetachedException(obj, "retrieve");
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
@Override
public void evictAll(OpCallbacks call) {
beginOperation(false);
try {
// evict all PClean and PNonTrans objects
Collection<StateManagerImpl> c = getManagedStates();
StateManagerImpl sm;
for (StateManagerImpl stateManager : c) {
sm = stateManager;
if (sm.isPersistent() && !sm.isDirty())
evict(sm.getManagedInstance(), call);
}
}
finally {
endOperation();
}
}
@Override
public void evictAll(Collection objs, OpCallbacks call) {
List<Exception> exceps = null;
beginOperation(false);
try {
for (Iterator<?> itr = objs.iterator(); itr.hasNext();) {
try {
evict(itr.next(), call);
} catch (UserException ue) {
exceps = add(exceps, ue);
}
}
} finally {
endOperation();
}
throwNestedExceptions(exceps, false);
}
@Override
public void evictAll(Extent extent, OpCallbacks call) {
if (extent == null)
return;
beginOperation(false);
try {
// evict all PClean and PNonTrans objects in extent
Collection<StateManagerImpl> c = getManagedStates();
StateManagerImpl sm;
Class<?> cls;
for (StateManagerImpl stateManager : c) {
sm = stateManager;
if (sm.isPersistent() && !sm.isDirty()) {
cls = sm.getMetaData().getDescribedType();
if (cls == extent.getElementType()
|| (extent.hasSubclasses()
&& extent.getElementType().isAssignableFrom(cls)))
evict(sm.getManagedInstance(), call);
}
}
} finally {
endOperation();
}
}
@Override
public void evict(Object obj, OpCallbacks call) {
if (obj == null)
return;
beginOperation(false);
try {
StateManagerImpl sm = getStateManagerImpl(obj, true);
if ((processArgument(OpCallbacks.OP_EVICT, obj, sm, call)
& OpCallbacks.ACT_RUN) == 0)
return;
if (sm == null)
return;
sm.evict();
if (_evictDataCache && sm.getObjectId() != null) {
DataCache cache = _conf.getDataCacheManagerInstance().selectCache(sm);
if (cache != null)
cache.remove(sm.getObjectId());
}
}
catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
@Override
public Object detach(Object obj, OpCallbacks call) {
if (obj == null)
return null;
if (call == null)
call = _call;
beginOperation(true);
try {
return new DetachManager(this, false, call).detach(obj);
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
@Override
public Object[] detachAll(Collection objs, OpCallbacks call) {
if (objs == null)
return null;
if (objs.isEmpty())
return EMPTY_OBJECTS;
if (call == null)
call = _call;
beginOperation(true);
try {
return new DetachManager(this, false, call).detachAll(objs);
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
@Override
public void detachAll(OpCallbacks call) {
detachAll(call, true);
}
@Override
public void detachAll(OpCallbacks call, boolean flush) {
beginOperation(true);
try {
// If a flush is desired (based on input parm), then check if the
// "dirty" flag is set before calling flush().
if (flush && (_flags & FLAG_FLUSH_REQUIRED) != 0)
flush();
detachAllInternal(call);
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
private void detachAllInternal(OpCallbacks call) {
if(_conf.getDetachStateInstance().getLiteAutoDetach()){
detachAllInternalLite();
return;
}
Collection<StateManagerImpl> states = getManagedStates();
StateManagerImpl sm;
for (Iterator<StateManagerImpl> itr = states.iterator(); itr.hasNext();) {
sm = itr.next();
if (!sm.isPersistent())
itr.remove();
else if (!sm.getMetaData().isDetachable()) {
sm.release(true);
itr.remove();
}
}
if (states.isEmpty())
return;
if (call == null)
call = _call;
// Make sure ALL entities are detached, even new ones that are loaded
// during the detach processing
boolean origCascade = _compat.getCascadeWithDetach();
_compat.setCascadeWithDetach(true);
try {
new DetachManager(this, true, call)
.detachAll(new ManagedObjectCollection(states));
}
finally {
_compat.setCascadeWithDetach(origCascade);
}
}
private void detachAllInternalLite() {
ManagedCache old = _cache;
_cache = new ManagedCache(this);
// TODO : should I call clear on old cache first? perhaps a memory leak?
Collection<StateManagerImpl> states = old.copy();
// Clear out all persistence context caches.
if (_transCache != null) {
_transCache.clear();
}
if (_transAdditions != null) {
_transAdditions.clear();
}
if (_pending != null) {
_pending = null;
}
if (_dmLite == null) {
_dmLite = new DetachManagerLite(_conf);
}
_dmLite.detachAll(states);
}
@Override
public Object attach(Object obj, boolean copyNew, OpCallbacks call) {
if (obj == null)
return null;
beginOperation(true);
try {
// make sure not to try to set rollback only if this fails
assertWriteOperation();
try {
return new AttachManager(this, copyNew, call).attach(obj);
} catch (OptimisticException oe) {
setRollbackOnly(oe);
throw oe.setFatal(true);
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
}
}
finally {
endOperation();
}
}
@Override
public Object[] attachAll(Collection objs, boolean copyNew,
OpCallbacks call) {
if (objs == null)
return null;
if (objs.isEmpty())
return EMPTY_OBJECTS;
beginOperation(true);
try {
// make sure not to try to set rollback only if this fails
assertWriteOperation();
try {
return new AttachManager(this, copyNew, call).attachAll(objs);
} catch (OptimisticException oe) {
setRollbackOnly(oe);
throw oe.setFatal(true);
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
}
}
finally {
endOperation();
}
}
@Override
public void nontransactionalAll(Collection objs, OpCallbacks call) {
beginOperation(true);
try {
List<Exception> exceps = null;
for (Iterator<?> itr = objs.iterator(); itr.hasNext();) {
try {
nontransactional(itr.next(), call);
} catch (UserException ue) {
exceps = add(exceps, ue);
}
}
throwNestedExceptions(exceps, false);
} finally {
endOperation();
}
}
@Override
public void nontransactional(Object obj, OpCallbacks call) {
if (obj == null)
return;
beginOperation(true);
try {
StateManagerImpl sm = getStateManagerImpl(obj, true);
if ((processArgument(OpCallbacks.OP_NONTRANSACTIONAL, obj, sm, call)
& OpCallbacks.ACT_RUN) == 0)
return;
if (sm != null)
sm.nontransactional();
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
/**
* Make the given instances transactional.
*/
@Override
public void transactionalAll(Collection objs, boolean updateVersion,
OpCallbacks call) {
if (objs.isEmpty())
return;
if (objs.size() == 1) {
transactional(objs.iterator().next(), updateVersion, call);
return;
}
beginOperation(true);
try {
// collect all hollow instances for load, and make unmananged
// instances transient-transactional
Collection<OpenJPAStateManager> load = null;
Object obj;
StateManagerImpl sm;
ClassMetaData meta;
Collection<StateManagerImpl> sms = new LinkedHashSet<>(objs.size());
List<Exception> exceps = null;
for (Object o : objs) {
obj = o;
if (obj == null)
continue;
try {
sm = getStateManagerImpl(obj, true);
if ((processArgument(OpCallbacks.OP_TRANSACTIONAL, obj, sm,
call) & OpCallbacks.ACT_RUN) == 0)
continue;
if (sm == null) {
// manage transient instance
meta = _repo.getMetaData(obj.getClass(), _loader, true);
sm = newStateManagerImpl
(StateManagerId.newInstance(this), meta);
sm.initialize(assertPersistenceCapable(obj),
PCState.TCLEAN);
}
else if (sm.isPersistent()) {
assertActiveTransaction();
sms.add(sm);
if (sm.getPCState() == PCState.HOLLOW) {
if (load == null)
load = new ArrayList<>();
load.add(sm);
}
sm.setCheckVersion(true);
if (updateVersion)
sm.setUpdateVersion(true);
_flags |= FLAG_FLUSH_REQUIRED; // version check/up
}
}
catch (UserException ue) {
exceps = add(exceps, ue);
}
}
// load all hollow instances
Collection<Object> failed = null;
if (load != null) {
failed = _store.loadAll(load, null, StoreManager.FORCE_LOAD_NONE,
_fc, null);
if (failed != null && !failed.isEmpty())
exceps = add(exceps,
newObjectNotFoundException(failed));
}
transactionalStatesAll(sms, failed, exceps);
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
/**
* Make the given instances transactional.
*/
@Override
public void transactional(Object obj, boolean updateVersion,
OpCallbacks call) {
if (obj == null)
return;
beginOperation(true);
try {
StateManagerImpl sm = getStateManagerImpl(obj, true);
if ((processArgument(OpCallbacks.OP_TRANSACTIONAL, obj, sm, call)
& OpCallbacks.ACT_RUN) == 0)
return;
if (sm != null && sm.isPersistent()) {
assertActiveTransaction();
sm.transactional();
sm.load(_fc, StateManagerImpl.LOAD_FGS, null, null, false);
sm.setCheckVersion(true);
if (updateVersion)
sm.setUpdateVersion(true);
_flags |= FLAG_FLUSH_REQUIRED; // version check/up
} else if (sm == null) {
// manage transient instance
ClassMetaData meta = _repo.getMetaData(obj.getClass(), _loader, true);
Object id = StateManagerId.newInstance(this);
sm = newStateManagerImpl(id, meta);
sm.initialize(assertPersistenceCapable(obj),
PCState.TCLEAN);
}
}
catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
/**
* Transition the given state managers to transactional.
*/
private void transactionalStatesAll(Collection sms, Collection failed,
List<Exception> exceps) {
// make instances transactional and make sure they are loaded
StateManagerImpl sm;
for (Object o : sms) {
sm = (StateManagerImpl) o;
if (failed != null && failed.contains(sm.getId()))
continue;
try {
sm.transactional();
sm.load(_fc, StateManagerImpl.LOAD_FGS, null, null, false);
}
catch (OpenJPAException ke) {
exceps = add(exceps, ke);
}
}
throwNestedExceptions(exceps, false);
}
/////////////////
// Extent, Query
/////////////////
@Override
public Extent newExtent(Class type, boolean subclasses) {
return newExtent(type, subclasses, null);
}
private Extent newExtent(Class type, boolean subclasses,
FetchConfiguration fetch) {
beginOperation(true);
try {
ExtentImpl extent = new ExtentImpl(this, type, subclasses, fetch);
if (_extents == null)
_extents = new ReferenceHashSet(AbstractReferenceMap.ReferenceStrength.WEAK);
_extents.add(extent);
return extent;
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
@Override
public Iterator extentIterator(Class type, boolean subclasses,
FetchConfiguration fetch, boolean ignoreChanges) {
Extent extent = newExtent(type, subclasses, fetch);
extent.setIgnoreChanges(ignoreChanges);
return extent.iterator();
}
@Override
public Query newQuery(String lang, Class cls, Object query) {
Query q = newQuery(lang, query);
q.setCandidateType(cls, true);
return q;
}
@Override
public Query newQuery(String lang, Object query) {
// common mistakes
if (query instanceof Extent || query instanceof Class)
throw new UserException(_loc.get("bad-new-query"));
beginOperation(false);
try {
StoreQuery sq = _store.newQuery(lang);
if (sq == null) {
ExpressionParser ep = QueryLanguages.parserForLanguage(lang);
if (ep != null)
sq = new ExpressionStoreQuery(ep);
else if (QueryLanguages.LANG_METHODQL.equals(lang))
sq = new MethodStoreQuery();
else
throw new UnsupportedException(lang);
}
Query q = newQueryImpl(lang, sq);
q.setIgnoreChanges(_ignoreChanges);
if (query != null)
q.setQuery(query);
// track queries
if (_queries == null)
_queries = new ReferenceHashSet(AbstractReferenceMap.ReferenceStrength.WEAK);
_queries.add(q);
return q;
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
/**
* Create a new query.
*/
protected QueryImpl newQueryImpl(String lang, StoreQuery sq) {
return new QueryImpl(this, lang, sq);
}
@Override
public Seq getIdentitySequence(ClassMetaData meta) {
if (meta == null)
return null;
return getSequence(meta, null);
}
@Override
public Seq getValueSequence(FieldMetaData fmd) {
if (fmd == null)
return null;
return getSequence(fmd.getDefiningMetaData(), fmd);
}
/**
* Return a sequence for the given class and optional field.
*/
private Seq getSequence(ClassMetaData meta, FieldMetaData fmd) {
// get sequence strategy from metadata
int strategy;
if (fmd == null)
strategy = meta.getIdentityStrategy();
else
strategy = fmd.getValueStrategy();
// we can handle non-native strategies without the store manager
switch (strategy) {
case ValueStrategies.UUID_HEX:
return UUIDHexSeq.getInstance();
case ValueStrategies.UUID_STRING:
return UUIDStringSeq.getInstance();
case ValueStrategies.UUID_TYPE4_HEX:
return UUIDType4HexSeq.getInstance();
case ValueStrategies.UUID_TYPE4_STRING:
return UUIDType4StringSeq.getInstance();
case ValueStrategies.SEQUENCE:
SequenceMetaData smd = (fmd == null)
? meta.getIdentitySequenceMetaData()
: fmd.getValueSequenceMetaData();
return smd.getInstance(_loader);
default:
// use store manager for native sequence
if (fmd == null) {
// This will return a sequence even for app id classes,
// which is what we want for backwards-compatibility.
// Even if user uses Application Identity,
// user might use custom sequence information.
// So, first, the sequence should be checked.
// Trying to get primary key field if it has
// sequence meta data.
FieldMetaData[] pks = meta.getPrimaryKeyFields();
if (pks != null && pks.length == 1) {
smd = pks[0].getValueSequenceMetaData();
} else {
smd = meta.getIdentitySequenceMetaData();
}
if (smd != null) {
return smd.getInstance(_loader);
} else {
return _store.getDataStoreIdSequence(meta);
}
}
return _store.getValueSequence(fmd);
}
}
///////////
// Locking
///////////
@Override
public void lock(Object obj, OpCallbacks call) {
if (obj == null)
return;
beginOperation(true); // have to sync or lock level always NONE
try {
lock(obj, _fc.getWriteLockLevel(), _fc.getLockTimeout(), call);
} finally {
endOperation();
}
}
@Override
public void lock(Object obj, int level, int timeout, OpCallbacks call) {
if (obj == null)
return;
beginOperation(true);
try {
assertActiveTransaction();
StateManagerImpl sm = getStateManagerImpl(obj, true);
if ((processArgument(OpCallbacks.OP_LOCK, obj, sm, call)
& OpCallbacks.ACT_RUN) == 0)
return;
if (sm == null || !sm.isPersistent())
return;
_lm.lock(sm, level, timeout, null);
sm.readLocked(level, level); // use same level for future write
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
@Override
public void lockAll(Collection objs, OpCallbacks call) {
if (objs.isEmpty())
return;
beginOperation(true); // have to sync or lock level always NONE
try {
lockAll(objs, _fc.getWriteLockLevel(), _fc.getLockTimeout(),
call);
} finally {
endOperation();
}
}
@Override
public void lockAll(Collection objs, int level, int timeout,
OpCallbacks call) {
if (objs.isEmpty())
return;
if (objs.size() == 1) {
lock(objs.iterator().next(), level, timeout, call);
return;
}
beginOperation(true);
try {
assertActiveTransaction();
Collection<StateManagerImpl> sms = new LinkedHashSet<>(objs.size());
Object obj;
StateManagerImpl sm;
for (Object o : objs) {
obj = o;
if (obj == null)
continue;
sm = getStateManagerImpl(obj, true);
if ((processArgument(OpCallbacks.OP_LOCK, obj, sm, call)
& OpCallbacks.ACT_RUN) == 0)
continue;
if (sm != null && sm.isPersistent())
sms.add(sm);
}
_lm.lockAll(sms, level, timeout, null);
for (StateManagerImpl stateManager : sms) {
stateManager.readLocked(level, level);
}
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new GeneralException(re);
} finally {
endOperation();
}
}
//////////////
// Connection
//////////////
@Override
public boolean cancelAll() {
// this method does not lock, since we want to allow a different
// thread to be able to cancel on a locked-up persistence manager
assertOpen();
try {
// if we're flushing, have to set rollback only -- do this before we
// attempt to cancel, because otherwise the cancel might case the
// transaction to complete before we have a chance to set the
// rollback only flag
if ((_flags & FLAG_STORE_FLUSHING) != 0)
setRollbackOnlyInternal(new UserException());
return _store.cancelAll();
} catch (OpenJPAException ke) {
throw ke;
} catch (RuntimeException re) {
throw new StoreException(re);
}
}
@Override
public Object getConnection() {
assertOpen();
if (!_conf.supportedOptions().contains
(OpenJPAConfiguration.OPTION_DATASTORE_CONNECTION))
throw new UnsupportedException(_loc.get("conn-not-supported"));
return _store.getClientConnection();
}
@Override
public boolean hasConnection() {
assertOpen();
return (_flags & FLAG_RETAINED_CONN) != 0;
}
/**
* Tell store to retain connection if we haven't already.
*/
private void retainConnection() {
if ((_flags & FLAG_RETAINED_CONN) == 0) {
_store.retainConnection();
_flags |= FLAG_RETAINED_CONN;
}
}
/**
* Tell store to release connection if we have retained one.
*/
private void releaseConnection() {
if ((_flags & FLAG_RETAINED_CONN) != 0) {
_store.releaseConnection();
_flags &= ~FLAG_RETAINED_CONN;
}
}
/////////
// Cache
/////////
@Override
public Collection getManagedObjects() {
beginOperation(false);
try {
return new ManagedObjectCollection(getManagedStates());
} finally {
endOperation();
}
}
@Override
public Collection getTransactionalObjects() {
beginOperation(false);
try {
return new ManagedObjectCollection(getTransactionalStates());
} finally {
endOperation();
}
}
@Override
public Collection getPendingTransactionalObjects() {
beginOperation(false);
try {
return new ManagedObjectCollection
(getPendingTransactionalStates());
} finally {
endOperation();
}
}
@Override
public Collection getDirtyObjects() {
beginOperation(false);
try {
return new ManagedObjectCollection(getDirtyStates());
} finally {
endOperation();
}
}
@Override
public boolean getOrderDirtyObjects() {
return _orderDirty;
}
@Override
public void setOrderDirtyObjects(boolean order) {
_orderDirty = order;
}
/**
* Return a copy of all managed state managers.
*/
protected Collection getManagedStates() {
return _cache.copy();
}
/**
* Return a copy of all transactional state managers.
*/
protected Collection<StateManagerImpl> getTransactionalStates() {
if (!hasTransactionalObjects()) {
// return a new empty set. Entities may be added by TransactionListeners
return new LinkedHashSet<>();
}
return _transCache.copy();
}
/**
* Whether or not there are any transactional objects in the current
* persistence context. If there are any instances with untracked state,
* this method will cause those instances to be scanned.
*/
private boolean hasTransactionalObjects() {
_cache.dirtyCheck();
return _transCache != null;
}
/**
* Return a copy of all dirty state managers.
*/
protected Collection getDirtyStates() {
if (!hasTransactionalObjects())
return Collections.EMPTY_SET;
return _transCache.copyDirty();
}
/**
* Return a copy of all state managers which will become
* transactional upon the next transaction.
*/
protected Collection getPendingTransactionalStates() {
if (_pending == null)
return Collections.EMPTY_SET;
return new LinkedHashSet<>(_pending);
}
/**
* Set the cached StateManager for the instance that had the given oid.
* This method must not be called multiple times for new instances.
*
* @param id the id previously used by the instance
* @param sm the state manager for the instance; if the state
* manager is transient, we'll stop managing the instance;
* if it has updated its oid, we'll re-cache under the new oid
* @param status one of our STATUS constants describing why we're
* setting the state manager
*/
protected void setStateManager(Object id, StateManagerImpl sm, int status) {
lock();
try {
switch (status) {
case STATUS_INIT:
// Only reset the flushed flag is this is a new instance.
if (sm.isNew() && _compat.getResetFlushFlagForCascadePersist()) {// OPENJPA-2051
_flags &= ~FLAG_FLUSHED;
}
_cache.add(sm);
break;
case STATUS_TRANSIENT:
_cache.remove(id, sm);
break;
case STATUS_OID_ASSIGN:
assignObjectId(_cache, id, sm);
break;
case STATUS_COMMIT_NEW:
_cache.commitNew(id, sm);
break;
default:
throw new InternalException();
}
}
finally {
unlock();
}
}
/**
* Notify the broker that the given state manager should
* be added to the set of instances involved in the current transaction.
*/
void addToTransaction(StateManagerImpl sm) {
// we only add clean instances now; dirty instances are added in
// the setDirty callback
if (sm.isDirty())
return;
lock();
try {
if (!hasTransactionalObjects())
_transCache = new TransactionalCache(_orderDirty);
_transCache.addClean(sm);
} finally {
unlock();
}
}
/**
* Notify the persistence manager that the given state manager should
* be removed from the set of instances involved in the current transaction.
*/
void removeFromTransaction(StateManagerImpl sm) {
lock();
try {
if (_transCache != null)
// intentional direct access; we don't want to recompute
// dirtiness while removing instances from the transaction
_transCache.remove(sm);
if (_derefCache != null && !sm.isPersistent())
_derefCache.remove(sm);
} finally {
unlock();
}
}
/**
* Notification that the given instance has been dirtied. This
* notification is given when an object first transitions to a dirty state,
* and every time the object is modified by the user thereafter.
*/
void setDirty(StateManagerImpl sm, boolean firstDirty) {
if (sm.isPersistent())
_flags |= FLAG_FLUSH_REQUIRED;
if (_savepoints != null && !_savepoints.isEmpty()) {
if (_savepointCache == null)
_savepointCache = new HashSet<>();
_savepointCache.add(sm);
}
if (firstDirty && sm.isTransactional()) {
lock();
try {
// cache dirty instance
if (!hasTransactionalObjects())
_transCache = new TransactionalCache(_orderDirty);
_transCache.addDirty(sm);
// also record that the class is dirty
if (sm.isNew()) {
if (_persistedClss == null)
_persistedClss = new HashSet<>();
_persistedClss.add(sm.getMetaData().getDescribedType());
} else if (sm.isDeleted()) {
if (_deletedClss == null)
_deletedClss = new HashSet<>();
_deletedClss.add(sm.getMetaData().getDescribedType());
} else {
if (_updatedClss == null)
_updatedClss = new HashSet<>();
_updatedClss.add(sm.getMetaData().getDescribedType());
}
// if tracking changes and this instance wasn't already dirty,
// add to changed set; we use this for detecting instances that
// enter the transaction during pre store
if ((_flags & FLAG_PRESTORING) != 0) {
if (_transAdditions == null)
_transAdditions = new HashSet<>();
_transAdditions.add(sm);
}
} finally {
unlock();
}
}
}
/**
* Notify the broker that the given state manager should
* be added to the set of instances that will become transactional
* on the next transaction
*/
void addToPendingTransaction(StateManagerImpl sm) {
lock();
try {
if (_pending == null)
_pending = new HashSet<>();
_pending.add(sm);
} finally {
unlock();
}
}
/**
* Notify the persistence manager that the given state manager should
* be removed from the set of instances involved in the next transaction.
*/
void removeFromPendingTransaction(StateManagerImpl sm) {
lock();
try {
if (_pending != null)
_pending.remove(sm);
if (_derefCache != null && !sm.isPersistent())
_derefCache.remove(sm);
} finally {
unlock();
}
}
/**
* Add a dereferenced dependent object to the persistence manager's cache.
* On flush, these objects will be deleted.
*/
void addDereferencedDependent(StateManagerImpl sm) {
lock();
try {
// if we're in the middle of flush and introducing more derefs
// via instance callbacks, add them to the special additions set
if ((_flags & FLAG_DEREFDELETING) != 0) {
if (_derefAdditions == null)
_derefAdditions = new HashSet<>();
_derefAdditions.add(sm);
} else {
if (_derefCache == null)
_derefCache = new HashSet<>();
_derefCache.add(sm);
}
}
finally {
unlock();
}
}
/**
* Remove the given previously dereferenced dependent object from the
* cache. It is now referenced.
*/
void removeDereferencedDependent(StateManagerImpl sm) {
lock();
try {
boolean removed = false;
if (_derefAdditions != null)
removed = _derefAdditions.remove(sm);
if (!removed && (_derefCache == null || !_derefCache.remove(sm)))
throw new InvalidStateException(_loc.get("not-derefed",
Exceptions.toString(sm.getManagedInstance()))).
setFailedObject(sm.getManagedInstance()).
setFatal(true);
} finally {
unlock();
}
}
@Override
public void dirtyType(Class cls) {
if (cls == null)
return;
beginOperation(false);
try {
if (_updatedClss == null)
_updatedClss = new HashSet<>();
_updatedClss.add(cls);
} finally {
endOperation();
}
}
@Override
public Collection getPersistedTypes() {
if (_persistedClss == null || _persistedClss.isEmpty())
return Collections.EMPTY_SET;
return Collections.unmodifiableCollection(_persistedClss);
}
@Override
public Collection getUpdatedTypes() {
if (_updatedClss == null || _updatedClss.isEmpty())
return Collections.EMPTY_SET;
return Collections.unmodifiableCollection(_updatedClss);
}
@Override
public Collection getDeletedTypes() {
if (_deletedClss == null || _deletedClss.isEmpty())
return Collections.EMPTY_SET;
return Collections.unmodifiableCollection(_deletedClss);
}
///////////
// Closing
///////////
@Override
public boolean isClosed() {
return _closed;
}
@Override
public boolean isCloseInvoked() {
return _closed || (_flags & FLAG_CLOSE_INVOKED) != 0;
}
@Override
public void close() {
beginOperation(false);
try {
// throw an exception if closing in an active local trans
if (!_managed && (_flags & FLAG_ACTIVE) != 0)
throw new InvalidStateException(_loc.get("active"));
// only close if not active; if active managed trans wait
// for completion
_flags |= FLAG_CLOSE_INVOKED;
if ((_flags & FLAG_ACTIVE) == 0)
free();
} finally {
endOperation();
}
}
/**
* Free the resources used by this persistence manager.
*/
protected void free() {
RuntimeException err = null;
if ((_autoDetach & DETACH_CLOSE) != 0) {
try {
detachAllInternal(_call);
} catch (RuntimeException re) {
err = re;
}
}
_sync = null;
_userObjects = null;
_cache.clear();
_transCache = null;
_persistedClss = null;
_updatedClss = null;
_deletedClss = null;
_derefCache = null;
_pending = null;
_loader = null;
_transEventManager = null;
_lifeEventManager = null;
OpenJPASavepoint save;
while (_savepoints != null && !_savepoints.isEmpty()) {
save =
(OpenJPASavepoint) _savepoints.remove(_savepoints.size() - 1);
save.release(false);
}
_savepoints = null;
_savepointCache = null;
if (_queries != null) {
for (Iterator<?> itr = _queries.iterator(); itr.hasNext();) {
try {
((Query) itr.next()).closeResources();
} catch (RuntimeException re) {
}
}
_queries = null;
}
if (_extents != null) {
Extent e;
for (Object extent : _extents) {
e = (Extent) extent;
try {
e.closeAll();
}
catch (RuntimeException re) {
}
}
_extents = null;
}
try { releaseConnection(); } catch (RuntimeException re) {}
_lm.close();
_store.close();
if (_instm != null) {
_instm.stop(InstrumentationLevel.BROKER, this);
}
_flags = 0;
_closed = true;
if (_log.isTraceEnabled())
_closedException = new IllegalStateException();
_factory.releaseBroker(this);
if (err != null)
throw err;
}
///////////////////
// Synchronization
///////////////////
@Override
public void lock() {
if (_lock != null)
_lock.lock();
}
@Override
public void unlock() {
if (_lock != null)
_lock.unlock();
}
////////////////////
// State management
////////////////////
@Override
public Object newInstance(Class cls) {
assertOpen();
if (!cls.isInterface() && Modifier.isAbstract(cls.getModifiers()))
throw new UnsupportedOperationException(_loc.get
("new-abstract", cls).getMessage());
// 1.5 doesn't initialize classes without a true Class.forName
if (!PCRegistry.isRegistered(cls)) {
try {
Class.forName(cls.getName(), true,
AccessController.doPrivileged(
J2DoPrivHelper.getClassLoaderAction(cls)));
} catch (Throwable t) {
}
}
if (_repo.getMetaData(cls, getClassLoader(), false) == null)
throw new IllegalArgumentException(
_loc.get("no-interface-metadata", cls.getName()).getMessage());
try {
return PCRegistry.newInstance(cls, null, false);
} catch (IllegalStateException ise) {
IllegalArgumentException iae =
new IllegalArgumentException(ise.getMessage());
iae.setStackTrace(ise.getStackTrace());
throw iae;
}
}
@Override
public Object getObjectId(Object obj) {
assertOpen();
if (ImplHelper.isManageable(obj)) {
PersistenceCapable pc = ImplHelper.toPersistenceCapable(obj, _conf);
if (pc != null) {
if (pc.pcGetStateManager() == null) {
// If the statemanager is null the call to pcFetchObjectId always returns null. Create a new object
// id.
return ApplicationIds.create(pc, _repo.getMetaData(pc.getClass(), null, true));
}
return pc.pcFetchObjectId();
}
}
return null;
}
@Override
public int getLockLevel(Object o) {
assertOpen();
if (o == null)
return LockLevels.LOCK_NONE;
OpenJPAStateManager sm = getStateManager(o);
if (sm == null)
return LockLevels.LOCK_NONE;
return getLockManager().getLockLevel(sm);
}
@Override
public Object getVersion(Object obj) {
assertOpen();
if (ImplHelper.isManageable(obj))
return (ImplHelper.toPersistenceCapable(obj, _conf)).pcGetVersion();
return null;
}
@Override
public boolean isDirty(Object obj) {
assertOpen();
if (ImplHelper.isManageable(obj)) {
PersistenceCapable pc = ImplHelper.toPersistenceCapable(obj, _conf);
return pc.pcIsDirty();
}
return false;
}
@Override
public boolean isTransactional(Object obj) {
assertOpen();
if (ImplHelper.isManageable(obj))
return (ImplHelper.toPersistenceCapable(obj, _conf))
.pcIsTransactional();
return false;
}
@Override
public boolean isPersistent(Object obj) {
assertOpen();
if (ImplHelper.isManageable(obj))
return (ImplHelper.toPersistenceCapable(obj, _conf)).
pcIsPersistent();
return false;
}
@Override
public boolean isNew(Object obj) {
assertOpen();
if (ImplHelper.isManageable(obj))
return (ImplHelper.toPersistenceCapable(obj, _conf)).pcIsNew();
return false;
}
@Override
public boolean isDeleted(Object obj) {
assertOpen();
if (ImplHelper.isManageable(obj))
return (ImplHelper.toPersistenceCapable(obj, _conf)).pcIsDeleted();
return false;
}
@Override
public boolean isDetached(Object obj) {
return isDetached(obj, true);
}
/**
* This method makes a best effort to determine if the provided object is detached.
*
* @param find
* - If true, as a last resort this method will check whether or not the provided object exists in the
* DB. If it is in the DB, the provided object is detached.
* @return - True if the provided obj is detached, false otherwise.
*/
public boolean isDetached(Object obj, boolean find) {
if (!(ImplHelper.isManageable(obj)))
return false;
PersistenceCapable pc = ImplHelper.toPersistenceCapable(obj, _conf);
if (pc.pcGetStateManager() instanceof DetachedStateManager)
return true;
Boolean detached = pc.pcIsDetached();
if (detached != null)
return detached;
if(!find){
return false;
}
// last resort: instance is detached if it has a store record
ClassMetaData meta = _repo.getMetaData(ImplHelper.getManagedInstance(pc).getClass(), _loader, true);
Object oid = ApplicationIds.create(pc, meta);
if (oid == null)
return false;
return find(oid, null, EXCLUDE_ALL, null, 0) != null;
}
@Override
public OpenJPAStateManager getStateManager(Object obj) {
assertOpen();
return getStateManagerImpl(obj, false);
}
/**
* Return the state manager for the given instance, or null.
*
* @param assertThisContext if true, thow an exception if the given
* object is managed by another broker
*/
protected StateManagerImpl getStateManagerImpl(Object obj,
boolean assertThisContext) {
if (ImplHelper.isManageable(obj)) {
PersistenceCapable pc = ImplHelper.toPersistenceCapable(obj, _conf);
BrokerImpl pcBroker = (BrokerImpl)pc.pcGetGenericContext();
if (pcBroker == this || isFromWriteBehindCallback())
return (StateManagerImpl) pc.pcGetStateManager();
if (assertThisContext && pcBroker != null)
throw new UserException(_loc.get("not-managed",
Exceptions.toString(obj))).setFailedObject(obj);
}
return null;
}
/**
* Return the state manager for the given oid.
*
* @param allowNew if true, objects made persistent in the current
* transaction will be included in the search; if
* multiple new objects match the given oid, it is
* undefined which will be returned
*/
protected StateManagerImpl getStateManagerImplById(Object oid,
boolean allowNew) {
return _cache.getById(oid, allowNew);
}
/**
* Return the given instance as a {@link PersistenceCapable}.
* If the instance is not manageable throw the proper exception.
*/
protected PersistenceCapable assertPersistenceCapable(Object obj) {
if (obj == null)
return null;
if (ImplHelper.isManageable(obj))
return ImplHelper.toPersistenceCapable(obj, _conf);
// check for different instances of the PersistenceCapable interface
// and throw a better error that mentions the class loaders
Class<?>[] intfs = obj.getClass().getInterfaces();
for (int i = 0; intfs != null && i < intfs.length; i++) {
if (intfs[i].getName().equals(PersistenceCapable.class.getName())) {
throw new UserException(_loc.get("pc-loader-different",
Exceptions.toString(obj),
AccessController.doPrivileged(
J2DoPrivHelper.getClassLoaderAction(
PersistenceCapable.class)),
AccessController.doPrivileged(
J2DoPrivHelper.getClassLoaderAction(intfs[i]))))
.setFailedObject(obj);
}
}
// not enhanced
throw new UserException(_loc.get("pc-cast",
Exceptions.toString(obj))).setFailedObject(obj);
}
/////////
// Utils
/////////
/**
* Throw an exception if the context is closed. The exact message and
* content of the exception varies whether TRACE is enabled or not.
*/
@Override
public void assertOpen() {
if (_closed) {
if (_closedException == null) // TRACE not enabled
throw new InvalidStateException(_loc.get("closed-notrace"))
.setFatal(true);
else {
OpenJPAException e = new InvalidStateException(
_loc.get("closed"), _closedException).setFatal(true);
e.setCause(_closedException);
throw e;
}
}
}
@Override
public void assertActiveTransaction() {
if ((_flags & FLAG_ACTIVE) == 0)
throw new NoTransactionException(_loc.get("not-active"));
}
/**
* Throw exception if a transaction-related operation is attempted and
* no transaction is active.
*/
private void assertTransactionOperation() {
if ((_flags & FLAG_ACTIVE) == 0)
throw new InvalidStateException(_loc.get("not-active"));
}
@Override
public void assertNontransactionalRead() {
if ((_flags & FLAG_ACTIVE) == 0 && !_nontransRead)
throw new InvalidStateException(_loc.get("non-trans-read"));
}
@Override
public void assertWriteOperation() {
if ((_flags & FLAG_ACTIVE) == 0 && (!_nontransWrite
|| (_autoDetach & DETACH_NONTXREAD) != 0))
throw new NoTransactionException(_loc.get("write-operation"));
}
/**
* Return an object not found exception containing nested exceptions
* for all of the given failed objects.
*/
private static ObjectNotFoundException newObjectNotFoundException
(Collection failed) {
Throwable[] t = new Throwable[failed.size()];
int idx = 0;
for (Iterator<?> itr = failed.iterator(); itr.hasNext(); idx++)
t[idx] = new ObjectNotFoundException(itr.next());
return new ObjectNotFoundException(failed, t);
}
////////////////////////////////
// FindCallbacks implementation
////////////////////////////////
@Override
public Object processArgument(Object oid) {
return oid;
}
@Override
public Object processReturn(Object oid, OpenJPAStateManager sm) {
return (sm == null) ? null : sm.getManagedInstance();
}
private void writeObject(ObjectOutputStream out) throws IOException {
assertOpen();
lock();
try {
if (isActive()) {
if (!getOptimistic())
throw new InvalidStateException(
_loc.get("cant-serialize-pessimistic-broker"));
if (hasFlushed())
throw new InvalidStateException(
_loc.get("cant-serialize-flushed-broker"));
if (hasConnection())
throw new InvalidStateException(
_loc.get("cant-serialize-connected-broker"));
}
try {
_isSerializing = true;
out.writeObject(_factory.getPoolKey());
out.defaultWriteObject();
} finally {
_isSerializing = false;
}
} finally {
unlock();
}
}
private void readObject(ObjectInputStream in)
throws ClassNotFoundException, IOException {
Object factoryKey = in.readObject();
AbstractBrokerFactory factory =
AbstractBrokerFactory.getPooledFactoryForKey(factoryKey);
// this needs to happen before defaultReadObject so that it's
// available for calls to broker.getConfiguration() during
// StateManager deserialization
_conf = factory.getConfiguration();
_repo = _conf.getMetaDataRepositoryInstance();
in.defaultReadObject();
factory.initializeBroker(_managed, _connRetainMode, this, true);
// re-initialize the lock if needed.
setMultithreaded(_multithreaded);
// force recreation of set
_operatingDirty = true;
initializeOperatingSet();
if (isActive() && _runtime instanceof LocalManagedRuntime)
((LocalManagedRuntime) _runtime).begin();
}
/**
* Whether or not this broker is in the midst of being serialized.
*
* @since 1.1.0
*/
boolean isSerializing() {
return _isSerializing;
}
/**
* @return The value of openjpa.ConnectionFactoryProperties.PrintParameters. Default is false.
*/
public boolean getPrintParameters() {
return _printParameters;
}
/**
* Transactional cache that holds soft refs to clean instances.
*/
static class TransactionalCache
implements Set, Serializable {
private static final long serialVersionUID = 1L;
private final boolean _orderDirty;
private Set<StateManagerImpl> _dirty = null;
private Set<StateManagerImpl> _clean = null;
public TransactionalCache(boolean orderDirty) {
_orderDirty = orderDirty;
}
/**
* Return a copy of all transactional state managers.
*/
public Collection copy() {
if (isEmpty()) {
// Transaction Listeners may add entities to the transaction.
return new LinkedHashSet();
}
// size may not be entirely accurate due to refs expiring, so
// manually copy each object; doesn't matter this way if size too
// big by some
Set copy = new LinkedHashSet(size());
if (_dirty != null)
for (StateManagerImpl stateManager : _dirty) {
copy.add(stateManager);
}
if (_clean != null)
for (StateManagerImpl stateManager : _clean) {
copy.add(stateManager);
}
return copy;
}
/**
* Return a copy of all dirty state managers.
*/
public Collection copyDirty() {
if (_dirty == null || _dirty.isEmpty())
return Collections.EMPTY_SET;
return new LinkedHashSet<>(_dirty);
}
/**
* Transfer the given instance from the dirty cache to the clean cache.
*/
public void flushed(StateManagerImpl sm) {
if (sm.isDirty() && _dirty != null && _dirty.remove(sm))
addCleanInternal(sm);
}
/**
* Add the given instance to the clean cache.
*/
public void addClean(StateManagerImpl sm) {
if (addCleanInternal(sm) && _dirty != null)
_dirty.remove(sm);
}
private boolean addCleanInternal(StateManagerImpl sm) {
if (_clean == null)
_clean = new ReferenceHashSet(AbstractReferenceMap.ReferenceStrength.SOFT);
return _clean.add(sm);
}
/**
* Add the given instance to the dirty cache.
*/
public void addDirty(StateManagerImpl sm) {
if (_dirty == null) {
if (_orderDirty)
_dirty = MapBackedSet.mapBackedSet(new LinkedMap());
else
_dirty = new HashSet<>();
}
if (_dirty.add(sm))
removeCleanInternal(sm);
}
/**
* Remove the given instance from the cache.
*/
public boolean remove(StateManagerImpl sm) {
return removeCleanInternal(sm)
|| (_dirty != null && _dirty.remove(sm));
}
private boolean removeCleanInternal(StateManagerImpl sm) {
return _clean != null && _clean.remove(sm);
}
@Override
public Iterator iterator() {
IteratorChain chain = new IteratorChain();
if (_dirty != null && !_dirty.isEmpty())
chain.addIterator(_dirty.iterator());
if (_clean != null && !_clean.isEmpty())
chain.addIterator(_clean.iterator());
return chain;
}
@Override
public boolean contains(Object obj) {
return (_dirty != null && _dirty.contains(obj))
|| (_clean != null && _clean.contains(obj));
}
@Override
public boolean containsAll(Collection coll) {
for (Object o : coll)
if (!contains(o))
return false;
return true;
}
@Override
public void clear() {
if (_dirty != null)
_dirty = null;
if (_clean != null)
_clean = null;
}
@Override
public boolean isEmpty() {
return (_dirty == null || _dirty.isEmpty())
&& (_clean == null || _clean.isEmpty());
}
@Override
public int size() {
int size = 0;
if (_dirty != null)
size += _dirty.size();
if (_clean != null)
size += _clean.size();
return size;
}
@Override
public boolean add(Object obj) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(Collection coll) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object obj) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(Collection coll) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(Collection c) {
throw new UnsupportedOperationException();
}
@Override
public Object[] toArray() {
throw new UnsupportedOperationException();
}
@Override
public Object[] toArray(Object[] arr) {
throw new UnsupportedOperationException();
}
}
/**
* Unique id for state managers of new datastore instances without assigned
* object ids.
*/
public static class StateManagerId
implements Serializable {
private static final long serialVersionUID = 1L;
public static final String STRING_PREFIX = "openjpasm:";
private static long _generator = 0;
private final int _bhash;
private final long _id;
public static StateManagerId newInstance(Broker b) {
return new StateManagerId(System.identityHashCode(b), _generator++);
}
private StateManagerId(int bhash, long id) {
_bhash = bhash;
_id = id;
}
public StateManagerId(String str) {
str = str.substring(STRING_PREFIX.length());
int idx = str.indexOf(':');
_bhash = Integer.parseInt(str.substring(0, idx));
_id = Long.parseLong(str.substring(idx + 1));
}
@Override
public boolean equals(Object other) {
if (other == this)
return true;
if ((other == null) || (other.getClass() != this.getClass()))
return false;
StateManagerId sid = (StateManagerId) other;
return _bhash == sid._bhash && _id == sid._id;
}
@Override
public int hashCode() {
return (int) (_id ^ (_id >>> 32));
}
@Override
public String toString() {
return STRING_PREFIX + _bhash + ":" + _id;
}
}
/**
* Collection type that holds state managers but whose interface deals
* with the corresponding managed objects.
*/
private static class ManagedObjectCollection
extends AbstractCollection {
private final Collection _states;
public ManagedObjectCollection(Collection states) {
_states = states;
}
public Collection getStateManagers() {
return _states;
}
@Override
public int size() {
return _states.size();
}
@Override
public Iterator iterator() {
return new Iterator() {
private final Iterator _itr = _states.iterator();
@Override
public boolean hasNext() {
return _itr.hasNext();
}
@Override
public Object next() {
return ((OpenJPAStateManager) _itr.next()).
getManagedInstance();
}
@Override
public void remove() {
throw new UnsupportedException();
}
};
}
}
/**
* Assign the object id to the cache. Exception will be
* thrown if the id already exists in the cache.
*/
protected void assignObjectId(Object cache, Object id,
StateManagerImpl sm) {
((ManagedCache) cache).assignObjectId(id, sm);
}
/**
* This method makes sure we don't already have the instance cached
*/
protected void checkForDuplicateId(Object id, Object obj, ClassMetaData meta) {
FieldMetaData[] pks = meta.getPrimaryKeyFields();
if (pks != null && pks.length == 1 && pks[0].getValueStrategy() == ValueStrategies.AUTOASSIGN) {
return;
}
StateManagerImpl other = getStateManagerImplById(id, false);
if (other != null && !other.isDeleted() && !other.isNew())
throw new ObjectExistsException(_loc.get("cache-exists",
obj.getClass().getName(), id)).setFailedObject(obj);
}
@Override
public boolean getCachePreparedQuery() {
lock();
try {
return _cachePreparedQuery
&& _conf.getQuerySQLCacheInstance() != null;
} finally {
unlock();
}
}
@Override
public void setCachePreparedQuery(boolean flag) {
lock();
try {
_cachePreparedQuery = flag;
} finally {
unlock();
}
}
public boolean getCacheFinderQuery() {
lock();
try {
return _cacheFinderQuery
&& _conf.getFinderCacheInstance() != null;
} finally {
unlock();
}
}
public void setCacheFinderQuery(boolean flag) {
lock();
try {
_cachePreparedQuery = flag;
} finally {
unlock();
}
}
public boolean isFromWriteBehindCallback() {
return _fromWriteBehindCallback;
}
/**
* Return the 'JTA' connectionFactoryName
*/
@Override
public String getConnectionFactoryName() {
return _connectionFactoryName;
}
/**
* Set the 'JTA' ConnectionFactoryName. Input will be trimmed to null before being stored.
*/
@Override
public void setConnectionFactoryName(String connectionFactoryName) {
this._connectionFactoryName = StringUtil.trimToNull(connectionFactoryName);
}
/**
* Return the 'NonJTA' ConnectionFactoryName.
*/
@Override
public String getConnectionFactory2Name() {
return _connectionFactory2Name;
}
/**
* Set the 'NonJTA' ConnectionFactoryName. Input will be trimmed to null before being stored.
*/
@Override
public void setConnectionFactory2Name(String connectionFactory2Name) {
this._connectionFactory2Name = StringUtil.trimToNull(connectionFactory2Name);
}
/**
* Return the 'JTA' ConnectionFactory, looking it up from JNDI if needed.
*
* @return the JTA connection factory or null if connectionFactoryName is blank.
*/
@Override
public Object getConnectionFactory() {
if(StringUtil.isNotBlank(_connectionFactoryName)) {
return Configurations.lookup(_connectionFactoryName, "openjpa.ConnectionFactory", _log );
}
else {
return null;
}
}
/**
* Return the 'NonJTA' ConnectionFactory, looking it up from JNDI if needed.
*
* @return the NonJTA connection factory or null if connectionFactoryName is blank.
*/
@Override
public Object getConnectionFactory2() {
if(StringUtil.isNotBlank(_connectionFactory2Name)) {
return Configurations.lookup(_connectionFactory2Name, "openjpa.ConnectionFactory2", _log);
}
else {
return null;
}
}
@Override
public boolean isCached(List<Object> oids) {
BitSet loaded = new BitSet(oids.size());
//check L1 cache first
for (int i = 0; i < oids.size(); i++) {
Object oid = oids.get(i);
if (_cache.getById(oid, false) != null) {
loaded.set(i);
}
}
if(loaded.cardinality()==oids.size()){
return true;
}
return _store.isCached(oids, loaded);
}
@Override
public boolean getAllowReferenceToSiblingContext() {
return _allowReferenceToSiblingContext;
}
@Override
public void setAllowReferenceToSiblingContext(boolean allow) {
_allowReferenceToSiblingContext = allow;
}
protected boolean isFlushing() {
return ((_flags & FLAG_FLUSHING) != 0);
}
@Override
public boolean getPostLoadOnMerge() {
return _postLoadOnMerge;
}
@Override
public void setPostLoadOnMerge(boolean allow) {
_postLoadOnMerge = allow;
}
/**
* Asserts consistencey of given automatic detachment option value.
*/
private void assertAutoDetachValue(int value) {
if (((value & AutoDetach.DETACH_NONE) != 0) && (value != AutoDetach.DETACH_NONE)) {
throw new UserException(_loc.get("detach-none-exclusive", toAutoDetachString(value)));
}
}
/**
* Generates a user-readable String from the given integral value of AutoDetach options.
*/
private String toAutoDetachString(int value) {
List<String> result = new ArrayList<>();
for (int i = 0; i < AutoDetach.values.length; i++) {
if ((value & AutoDetach.values[i]) != 0) {
result.add(AutoDetach.names[i]);
}
}
return Arrays.toString(result.toArray(new String[result.size()]));
}
private boolean operatingAdd(Object o){
_operatingDirty = true;
return _operating.add(o);
}
}