/*
 * 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);
    }

}
