blob: 8e4347a770071bb05625aa5f861833786b3f2aaf [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2021 IBM Corporation and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
//
// 05/28/2008-1.0M8 Andrei Ilitchev
// - 224964: Provide support for Proxy Authentication through JPA.
// Added setProperties method to be used in case properties couldn't be passed to createEM method.
// The properties now set to the uow's parent - not to the uow itself.
// In case there's no active transaction, close method now releases uow.
// UowImpl was amended to allow value holders instantiation even after it has been released,
// the parent ClientSession is released, too.
// 03/19/2009-2.0 Michael O'Brien
// - 266912: JPA 2.0 Metamodel API (part of the JSR-317 EJB 3.1 Criteria API)
// 07/13/2009-2.0 Guy Pelletier
// - 277039: JPA 2.0 Cache Usage Settings
// 02/08/2012-2.4 Guy Pelletier
// - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
// 14/05/2012-2.4 Guy Pelletier
// - 376603: Provide for table per tenant support for multitenant applications
// 06/20/2012-2.5 Guy Pelletier
// - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
// 08/24/2012-2.5 Guy Pelletier
// - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
// 09/13/2012-2.5 Guy Pelletier
// - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
// 08/11/2012-2.5 Guy Pelletier
// - 393867: Named queries do not work when using EM level Table Per Tenant Multitenancy.
// 02/16/2017-2.6 Jody Grassel
// - 512255: Eclipselink JPA/Auditing capablity in EE Environment fails with JNDI name parameter type
// 07/16/2019-2.7 Jody Grassel
// - 547173: EntityManager.unwrap(Connection.class) returns null
// 09/02/2019-2.7 Alexandre Jacob
// - 527415: Fix code when locale is tr, az or lt
package org.eclipse.persistence.internal.jpa;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import jakarta.persistence.CacheStoreMode;
import jakarta.persistence.EntityExistsException;
import jakarta.persistence.EntityGraph;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.LockModeType;
import jakarta.persistence.LockTimeoutException;
import jakarta.persistence.OptimisticLockException;
import jakarta.persistence.PersistenceException;
import jakarta.persistence.PessimisticLockException;
import jakarta.persistence.Query;
import jakarta.persistence.StoredProcedureQuery;
import jakarta.persistence.SynchronizationType;
import jakarta.persistence.TransactionRequiredException;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaDelete;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.CriteriaUpdate;
import jakarta.persistence.metamodel.Metamodel;
import javax.sql.DataSource;
import org.eclipse.persistence.annotations.CacheKeyType;
import org.eclipse.persistence.config.EntityManagerProperties;
import org.eclipse.persistence.config.QueryHints;
import org.eclipse.persistence.config.ReferenceMode;
import org.eclipse.persistence.descriptors.CMPPolicy;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.VersionLockingPolicy;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.EclipseLinkException;
import org.eclipse.persistence.exceptions.JPQLException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.internal.databaseaccess.Accessor;
import org.eclipse.persistence.internal.descriptors.OptimisticLockingPolicy;
import org.eclipse.persistence.internal.helper.BasicTypeHelperImpl;
import org.eclipse.persistence.internal.identitymaps.CacheId;
import org.eclipse.persistence.internal.jpa.querydef.CriteriaDeleteImpl;
import org.eclipse.persistence.internal.jpa.querydef.CriteriaQueryImpl;
import org.eclipse.persistence.internal.jpa.querydef.CriteriaUpdateImpl;
import org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl;
import org.eclipse.persistence.internal.jpa.transaction.EntityTransactionWrapper;
import org.eclipse.persistence.internal.jpa.transaction.JTATransactionWrapper;
import org.eclipse.persistence.internal.jpa.transaction.TransactionWrapper;
import org.eclipse.persistence.internal.jpa.transaction.TransactionWrapperImpl;
import org.eclipse.persistence.internal.localization.ExceptionLocalization;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.DatabaseSessionImpl;
import org.eclipse.persistence.internal.sessions.MergeManager;
import org.eclipse.persistence.internal.sessions.PropertiesHandler;
import org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.jpa.JpaEntityManager;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.queries.AttributeGroup;
import org.eclipse.persistence.queries.Call;
import org.eclipse.persistence.queries.DataReadQuery;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.queries.FetchGroupTracker;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.ReadAllQuery;
import org.eclipse.persistence.queries.ReadObjectQuery;
import org.eclipse.persistence.queries.ResultSetMappingQuery;
import org.eclipse.persistence.queries.SQLResultSetMapping;
import org.eclipse.persistence.queries.StoredProcedureCall;
import org.eclipse.persistence.sessions.DatabaseSession;
import org.eclipse.persistence.sessions.DatasourceLogin;
import org.eclipse.persistence.sessions.DefaultConnector;
import org.eclipse.persistence.sessions.JNDIConnector;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.UnitOfWork;
import org.eclipse.persistence.sessions.UnitOfWork.CommitOrderType;
import org.eclipse.persistence.sessions.broker.SessionBroker;
import org.eclipse.persistence.sessions.factories.SessionManager;
import org.eclipse.persistence.sessions.server.ConnectionPolicy;
import org.eclipse.persistence.sessions.server.Server;
import org.eclipse.persistence.sessions.server.ServerSession;
/**
* <p>
* <b>Purpose</b>: Contains the implementation of the EntityManager.
* <p>
* <b>Description</b>: This class provides the implementation for the combined
* EclipseLink and JPA EntityManager class.
* <p>
* <b>Responsibilities</b>: It is responsible for tracking transaction state and
* the objects within that transaction.
*
* @see jakarta.persistence.EntityManager
* @see org.eclipse.persistence.jpa.JpaEntityManager
*
* @author gyorke
* @since TopLink Essentials - JPA 1.0
*/
public class EntityManagerImpl implements org.eclipse.persistence.jpa.JpaEntityManager {
protected enum OperationType {FIND, REFRESH, LOCK};
/** Allows transparent transactions across JTA and local transactions. */
private TransactionWrapperImpl transaction;
/** Store if this entity manager has been closed. */
protected boolean isOpen;
/** Stores the UnitOfWork representing the persistence context. */
protected RepeatableWriteUnitOfWork extendedPersistenceContext;
/** Stores a session used for read-only queries. */
protected AbstractSession readOnlySession;
/**
* References the DatabaseSession that this deployment is using.
*/
protected AbstractSession databaseSession;
/**
* References to the parent factory that has created this entity manager.
* Ensures that the factory is not garbage collected.
*/
protected EntityManagerFactoryDelegate factory;
/**
* Join existing transaction property, allows reading through write
* connection.
*/
protected boolean beginEarlyTransaction;
/** Local properties passed from createEntityManager. */
protected Map<String, Object> properties;
/** Flush mode property, allows flush before query to be avoided. */
protected FlushModeType flushMode;
/**
* Reference mode property, allows weak unit of work references to allow
* garbage collection during a transaction.
*/
protected ReferenceMode referenceMode;
/**
* Connection policy used to create ClientSession, allows using a different
* pool/connection/exclusive connections.
* Not used in SessionBroker case (composite persistence unit case).
*/
protected ConnectionPolicy connectionPolicy;
/**
* In case of composite persistence unit this map is used instead of connectionPolicy attribute.
* Member sessions' ConnectionPolicies keyed by sessions' names (composite members' persistence unit names).
* Used only in SessionBroker case (composite persistence unit case): in that case guaranteed to be always non null.
*/
protected Map<String, ConnectionPolicy> connectionPolicies;
/**
* Keep a list of openQueries that are executed in this entity manager.
*/
protected WeakHashMap<QueryImpl, QueryImpl> openQueriesMap;
/**
* Property to avoid resuming unit of work if going to be closed on commit
* anyway.
*/
protected boolean closeOnCommit;
/**
* Property to avoid discover new objects in unit of work if application
* always uses persist.
*/
protected boolean persistOnCommit;
/**
* Property to avoid writing to the cache on commit (merge)
*/
protected boolean cacheStoreBypass;
/**
* The FlashClearCache mode to be used. Relevant only in case call to flush
* method followed by call to clear method.
*
* @see org.eclipse.persistence.config.FlushClearCache
*/
protected String flushClearCache;
/** Determine if does-exist should be performed on persist. */
protected boolean shouldValidateExistence;
/** Allow updates to be ordered by id to avoid possible deadlocks. */
protected org.eclipse.persistence.sessions.UnitOfWork.CommitOrderType commitOrder;
protected boolean commitWithoutPersistRules;
/** Tracks if this EntityManager should automatically associate with the transaction or not*/
protected SynchronizationType syncType;
abstract static class PropertyProcessor {
abstract void process(String name, Object value, EntityManagerImpl em);
}
static Map<String, PropertyProcessor> processors = new HashMap<>() {
{
put(EntityManagerProperties.JOIN_EXISTING_TRANSACTION, new PropertyProcessor() {
@Override
void process(String name, Object value, EntityManagerImpl em) {
em.beginEarlyTransaction = "true".equalsIgnoreCase(getPropertiesHandlerProperty(name, (String)value));
}});
put(EntityManagerProperties.PERSISTENCE_CONTEXT_REFERENCE_MODE, new PropertyProcessor() {
@Override
void process(String name, Object value, EntityManagerImpl em) {
em.referenceMode = ReferenceMode.valueOf(getPropertiesHandlerProperty(name, (String)value));
if (em.hasActivePersistenceContext()) {
em.extendedPersistenceContext.log(SessionLog.WARNING, SessionLog.PROPERTIES, "entity_manager_sets_property_while_context_is_active", new Object[]{name});
}
}});
put(EntityManagerProperties.PERSISTENCE_CONTEXT_FLUSH_MODE, new PropertyProcessor() {
@Override
void process(String name, Object value, EntityManagerImpl em) {
em.flushMode = FlushModeType.valueOf(getPropertiesHandlerProperty(name, (String)value));
}});
put(EntityManagerProperties.PERSISTENCE_CONTEXT_CLOSE_ON_COMMIT, new PropertyProcessor() {
@Override
void process(String name, Object value, EntityManagerImpl em) {
em.closeOnCommit = "true".equalsIgnoreCase(getPropertiesHandlerProperty(name, (String)value));
if(em.hasActivePersistenceContext()) {
em.extendedPersistenceContext.setResumeUnitOfWorkOnTransactionCompletion(!em.closeOnCommit);
}
}});
put(EntityManagerProperties.PERSISTENCE_CONTEXT_PERSIST_ON_COMMIT, new PropertyProcessor() {
@Override
void process(String name, Object value, EntityManagerImpl em) {
em.persistOnCommit = "true".equalsIgnoreCase(getPropertiesHandlerProperty(name, (String)value));
if(em.hasActivePersistenceContext()) {
em.extendedPersistenceContext.setShouldDiscoverNewObjects(em.persistOnCommit);
}
}});
put(EntityManagerProperties.PERSISTENCE_CONTEXT_COMMIT_WITHOUT_PERSIST_RULES, new PropertyProcessor() {
@Override
void process(String name, Object value, EntityManagerImpl em) {
em.commitWithoutPersistRules = "true".equalsIgnoreCase(getPropertiesHandlerProperty(name, (String)value));
if (em.hasActivePersistenceContext()) {
em.extendedPersistenceContext.setDiscoverUnregisteredNewObjectsWithoutPersist(em.commitWithoutPersistRules);
}
}});
put(EntityManagerProperties.VALIDATE_EXISTENCE, new PropertyProcessor() {
@Override
void process(String name, Object value, EntityManagerImpl em) {
em.shouldValidateExistence = "true".equalsIgnoreCase(getPropertiesHandlerProperty(name, (String)value));
if (em.hasActivePersistenceContext()) {
em.extendedPersistenceContext.setShouldValidateExistence(em.shouldValidateExistence);
}
}});
put(EntityManagerProperties.ORDER_UPDATES, new PropertyProcessor() {
@Override
void process(String name, Object value, EntityManagerImpl em) {
if ("true".equalsIgnoreCase(getPropertiesHandlerProperty(name, (String)value))) {
em.commitOrder = CommitOrderType.ID;
} else {
em.commitOrder = CommitOrderType.NONE;
}
if (em.hasActivePersistenceContext()) {
em.extendedPersistenceContext.setCommitOrder(em.commitOrder);
}
}});
put(EntityManagerProperties.PERSISTENCE_CONTEXT_COMMIT_ORDER, new PropertyProcessor() {
@Override
void process(String name, Object value, EntityManagerImpl em) {
em.commitOrder = CommitOrderType.valueOf(getPropertiesHandlerProperty(name, (String)value).toUpperCase(Locale.ROOT));
if (em.hasActivePersistenceContext()) {
em.extendedPersistenceContext.setCommitOrder(em.commitOrder);
}
}});
put(EntityManagerProperties.FLUSH_CLEAR_CACHE, new PropertyProcessor() {
@Override
void process(String name, Object value, EntityManagerImpl em) {
em.flushClearCache = getPropertiesHandlerProperty(name, (String)value);
if( em.hasActivePersistenceContext()) {
em.extendedPersistenceContext.setFlushClearCache(em.flushClearCache);
}
}});
put(QueryHints.CACHE_STORE_MODE, new PropertyProcessor() {
@Override
void process(String name, Object value, EntityManagerImpl em) {
// This property could be a string or an enum.
em.cacheStoreBypass = value.equals(CacheStoreMode.BYPASS) || value.equals(CacheStoreMode.BYPASS.name());
if(em.hasActivePersistenceContext()) {
em.extendedPersistenceContext.setShouldStoreByPassCache(em.cacheStoreBypass);
}
}});
PropertyProcessor connectionPolicyPropertyProcessor = new PropertyProcessor() {
@Override
void process(String name, Object value, EntityManagerImpl em) {
// Property used to create ConnectionPolicy has changed - already existing ConnectionPolicy should be removed.
// A new one will be created when the new active persistence context is created.
em.connectionPolicy = null;
if (em.hasActivePersistenceContext()) {
em.extendedPersistenceContext.log(SessionLog.WARNING, SessionLog.PROPERTIES, "entity_manager_sets_property_while_context_is_active", new Object[]{name});
}
}};
put(EntityManagerProperties.EXCLUSIVE_CONNECTION_MODE, connectionPolicyPropertyProcessor);
put(EntityManagerProperties.EXCLUSIVE_CONNECTION_IS_LAZY, connectionPolicyPropertyProcessor);
put(EntityManagerProperties.JTA_DATASOURCE, connectionPolicyPropertyProcessor);
put(EntityManagerProperties.NON_JTA_DATASOURCE, connectionPolicyPropertyProcessor);
put(EntityManagerProperties.JDBC_DRIVER, connectionPolicyPropertyProcessor);
put(EntityManagerProperties.JDBC_URL, connectionPolicyPropertyProcessor);
put(EntityManagerProperties.JDBC_USER, connectionPolicyPropertyProcessor);
put(EntityManagerProperties.JDBC_PASSWORD, connectionPolicyPropertyProcessor);
put(EntityManagerProperties.CONNECTION_POLICY, connectionPolicyPropertyProcessor);
put(EntityManagerProperties.ORACLE_PROXY_TYPE, new PropertyProcessor() {
@Override
void process(String name, Object value, EntityManagerImpl em) {
if (em.hasActivePersistenceContext()) {
em.extendedPersistenceContext.log(SessionLog.WARNING, SessionLog.PROPERTIES, "entity_manager_sets_property_while_context_is_active", new Object[]{name});
}
}});
put(EntityManagerProperties.COMPOSITE_UNIT_PROPERTIES, new PropertyProcessor() {
@Override
void process(String name, Object value, EntityManagerImpl em) {
if( em.connectionPolicies != null) {
Map<String, ?> mapOfProperties = (Map<String, ?>) value;
Iterator<String> it = mapOfProperties.keySet().iterator();
while (it.hasNext()) {
String sessionName = it.next();
if (em.connectionPolicies.containsKey(sessionName)) {
// Property used to create ConnectionPolicy has changed - already existing ConnectionPolicy should be removed.
// A new one will be created when the new active persistence context is created.
em.connectionPolicies.put(sessionName, null);
}
}
if (em.hasActivePersistenceContext()) {
em.extendedPersistenceContext.log(SessionLog.WARNING, SessionLog.PROPERTIES, "entity_manager_sets_property_while_context_is_active", new Object[]{name});
}
}
}});
}
};
/**
* Constructor returns an EntityManager assigned to the a particular
* DatabaseSession.
*
* @param sessionName
* the DatabaseSession name that should be used. This constructor
* can potentially throw EclipseLink exceptions regarding the
* existence, or errors with the specified session.
*/
public EntityManagerImpl(String sessionName) {
this(SessionManager.getManager().getSession(sessionName), null, SynchronizationType.SYNCHRONIZED);
}
/**
* Return the weak reference to the open queries.
*/
protected Map<QueryImpl, QueryImpl> getOpenQueriesMap() {
if (openQueriesMap == null) {
openQueriesMap = new WeakHashMap<QueryImpl, QueryImpl>();
}
return openQueriesMap;
}
/**
* Return the weak reference to the open queries.
*/
protected Set<QueryImpl> getOpenQueriesSet() {
return getOpenQueriesMap().keySet();
}
/**
* Queries that leave the connection and are executed against this entity
* manager will be added here. On rollback or commit any left over open
* queries should be closed.
*
*/
public void addOpenQuery(QueryImpl query) {
getOpenQueriesMap().put(query, query);
// If there is an open entity transaction, tag the query to it to be closed
// on commit or rollback.
Object transaction = checkForTransaction(false);
if (transaction != null && transaction instanceof EntityTransactionImpl) {
((EntityTransactionImpl) transaction).addOpenQuery(query);
}
}
/**
* Constructor called from the EntityManagerFactory to create an
* EntityManager
*
* @param databaseSession
* the databaseSession assigned to this deployment.
*/
public EntityManagerImpl(AbstractSession databaseSession, SynchronizationType syncType) {
this(databaseSession, null, syncType);
}
/**
* Constructor called from the EntityManagerFactory to create an
* EntityManager
*
* @param databaseSession
* the databaseSession assigned to this deployment. Note: The
* properties argument is provided to allow properties to be
* passed into this EntityManager, but there are currently no
* such properties implemented
*/
public EntityManagerImpl(AbstractSession databaseSession, Map properties, SynchronizationType syncType) {
this(new EntityManagerFactoryImpl(databaseSession).unwrap(), properties, syncType);
}
/**
* Constructor called from the EntityManagerFactory to create an
* EntityManager
*
* @param factory
* the EntityMangerFactoryImpl that created this entity manager.
* Note: The properties argument is provided to allow properties
* to be passed into this EntityManager, but there are currently
* no such properties implemented
*/
public EntityManagerImpl(EntityManagerFactoryDelegate factory, Map properties, SynchronizationType syncType) {
this.factory = factory;
this.databaseSession = factory.getAbstractSession();
this.beginEarlyTransaction = factory.getBeginEarlyTransaction();
this.closeOnCommit = factory.getCloseOnCommit();
this.flushMode = factory.getFlushMode();
this.persistOnCommit = factory.getPersistOnCommit();
this.commitWithoutPersistRules = factory.getCommitWithoutPersistRules();
this.referenceMode = factory.getReferenceMode();
this.flushClearCache = factory.getFlushClearCache();
this.shouldValidateExistence = factory.shouldValidateExistence();
this.commitOrder = factory.getCommitOrder();
this.isOpen = true;
this.cacheStoreBypass = false;
this.syncType = syncType;
initialize(properties);
}
/**
* Initialize the state after construction.
*/
protected void initialize(Map properties) {
detectTransactionWrapper();
// Cache default ConnectionPolicy. If ConnectionPolicy property(ies) specified
// then connectionPolicy will be set to null and re-created when when active persistence context is created.
if(this.databaseSession.isServerSession()) {
this.connectionPolicy = ((ServerSession)this.databaseSession).getDefaultConnectionPolicy();
} else if (this.databaseSession.isBroker()) {
SessionBroker broker = (SessionBroker)this.databaseSession;
this.connectionPolicies = new HashMap<>(broker.getSessionsByName().size());
Iterator<Map.Entry<String, AbstractSession>> it = broker.getSessionsByName().entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, AbstractSession> entry = it.next();
this.connectionPolicies.put(entry.getKey(), ((ServerSession)entry.getValue()).getDefaultConnectionPolicy());
}
}
// bug 236249: In JPA session.setProperty() throws
// UnsupportedOperationException.
if (properties != null) {
this.properties = new HashMap<>(properties);
if(!properties.isEmpty()) {
processProperties();
}
}
}
/**
* Clear the persistence context, causing all managed entities to become
* detached. Changes made to entities that have not been flushed to the
* database will not be persisted.
*/
@Override
public void clear() {
try {
verifyOpen();
if (this.extendedPersistenceContext != null) {
if (checkForTransaction(false) == null) {
// 259993: WebSphere 7.0 during a JPAEMPool.putEntityManager() afterCompletion callback
// may attempt to clear an entityManager in lifecyle/state 4 with a transaction commit active
// that is in the middle of a commit for an insert or update by calling em.clear(true).
// We only clear the entityManager if we are in the states
// (Birth == 0, WriteChangesFailed==3, Death==5 or AfterExternalTransactionRolledBack==6).
// If we are in one of the following *Pending states (1,2 and 4) we defer the clear() to the release() call later.
// Note: the single state CommitTransactionPending==2 may never happen as a result of an em.clear
if(this.extendedPersistenceContext.isSynchronized() &&
( this.extendedPersistenceContext.isCommitPending()
|| this.extendedPersistenceContext.isAfterWriteChangesButBeforeCommit()
|| this.extendedPersistenceContext.isMergePending())) {
// when jta transaction afterCompleteion callback will have completed merge,
// the uow will be released.
// Change sets will be cleared, but the cache will be kept.
// uow still could be used for instantiating of ValueHolders
// after it's released.
extendedPersistenceContext.setResumeUnitOfWorkOnTransactionCompletion(false);
} else {
// clear all change sets and cache
this.extendedPersistenceContext.clearForClose(true);
this.extendedPersistenceContext.release();
this.extendedPersistenceContext.getParent().release();
}
this.extendedPersistenceContext = null;
} else {
// clear all change sets created after the last flush and
// cache
this.extendedPersistenceContext.clear(true);
}
}
} catch (RuntimeException exception) {
setRollbackOnly();
throw exception;
}
}
/**
* Internal method called by EntityTransactionImpl class in case of
* transaction rollback. The caller is responsible for releasing
* extendedPersistenceContext and it's parent.
*/
public void removeExtendedPersistenceContext() {
this.extendedPersistenceContext = null;
}
/**
* If in a transaction this method will check for existence and register the
* object if it is new. The instance of the entity provided will become
* managed.
*
* @throws IllegalArgumentException
* if the given Object is not an entity.
*/
@Override
public void persist(Object entity) {
try {
verifyOpen();
if (entity == null) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { null }));
}
try {
getActivePersistenceContext(checkForTransaction(false)).registerNewObjectForPersist(entity, new IdentityHashMap());
} catch (RuntimeException exception) {
if (exception instanceof ValidationException) {
throw new EntityExistsException(exception.getLocalizedMessage(), exception);
}
throw exception;
}
} catch (RuntimeException exception) {
setRollbackOnly();
throw exception;
}
}
/**
* Merge the state of the given entity into the current persistence context,
* using the unqualified class name as the entity name.
*
* @return the instance that the state was merged to
*/
@Override
public <T> T merge(T entity) {
try {
verifyOpen();
return (T) mergeInternal(entity);
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Merge the state of the given entity into the current persistence context,
* using the unqualified class name as the entity name.
*
* @return the instance that the state was merged to
* @throws IllegalArgumentException
* if given Object is not an entity or is a removed entity
*/
protected Object mergeInternal(Object entity) {
if (entity == null) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { null }));
}
Object merged = null;
UnitOfWorkImpl context = getActivePersistenceContext(checkForTransaction(false));
try {
merged = context.mergeCloneWithReferences(entity, MergeManager.CASCADE_BY_MAPPING, true);
} catch (org.eclipse.persistence.exceptions.OptimisticLockException ole) {
throw new jakarta.persistence.OptimisticLockException(ole);
}
return merged;
}
/**
* Remove the instance.
*
* @throws IllegalArgumentException
* if Object passed in is not an entity
*/
@Override
public void remove(Object entity) {
try {
verifyOpen();
if (entity == null) { // gf732 - check for null
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { null }));
}
try {
getActivePersistenceContext(checkForTransaction(false)).performRemove(entity, new IdentityHashMap());
} catch (RuntimeException e) {
throw e;
}
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Find by primary key.
*
* @param entityClass
* - the entity class to find.
* @param primaryKey
* - the entity primary key value, or primary key class, or a
* List of primary key values.
* @return the found entity instance or null if the entity does not exist
* @throws IllegalArgumentException
* if the first argument does not denote an entity type or the
* second argument is not a valid type for that entity's primary
* key.
*/
@Override
public <T> T find(Class<T> entityClass, Object primaryKey) {
return find(entityClass, primaryKey, null, getQueryHints(entityClass, OperationType.FIND));
}
/**
* Find by primary key, using the specified properties. Search for an entity
* of the specified class and primary key. If the entity instance is
* contained in the persistence context it is returned from there. If a
* vendor-specific property or hint is not recognized, it is silently
* ignored.
*
* @param properties
* standard and vendor-specific properties
* @return the found entity instance or null if the entity does not exist
* @throws IllegalArgumentException
* if the first argument does not denote an entity type or the
* second argument is is not a valid type for that entity's
* primary key or is null
*
* @since Java Persistence API 2.0
*/
@Override
public <T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties) {
return find(entityClass, primaryKey, null, properties);
}
/**
* Find by primary key and lock. Search for an entity of the specified class
* and primary key and lock it with respect to the specified lock type. If
* the entity instance is contained in the persistence context it is
* returned from there. If the entity is found within the persistence
* context and the lock mode type is pessimistic and the entity has a
* version attribute, the persistence provider must perform optimistic
* version checks when obtaining the database lock. If these checks fail,
* the OptimisticLockException will be thrown. If the lock mode type is
* pessimistic and the entity instance is found but cannot be locked: - the
* PessimisticLockException will be thrown if the database locking failure
* causes transaction-level rollback. - the LockTimeoutException will be
* thrown if the database locking failure causes only statement-level
* rollback
*
* @return the found entity instance or null if the entity does not exist
* @throws IllegalArgumentException
* if the first argument does not denote an entity type or the
* second argument is not a valid type for that entity's primary
* key or is null
* @throws TransactionRequiredException
* if there is no transaction and a lock mode other than NONE is
* set
* @throws OptimisticLockException
* if the optimistic version check fails
* @throws PessimisticLockException
* if pessimistic locking fails and the transaction is rolled
* back
* @throws LockTimeoutException
* if pessimistic locking fails and only the statement is rolled
* back
* @throws PersistenceException
* if an unsupported lock call is made
*/
@Override
public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode) {
return find(entityClass, primaryKey, lockMode, getQueryHints(entityClass, OperationType.FIND));
}
/**
* Find by primary key and lock. Search for an entity of the specified class
* and primary key and lock it with respect to the specified lock type. If
* the entity instance is contained in the persistence context it is
* returned from there. If the entity is found within the persistence
* context and the lock mode type is pessimistic and the entity has a
* version attribute, the persistence provider must perform optimistic
* version checks when obtaining the database lock. If these checks fail,
* the OptimisticLockException will be thrown. If the lock mode type is
* pessimistic and the entity instance is found but cannot be locked: - the
* PessimisticLockException will be thrown if the database locking failure
* causes transaction-level rollback. - the LockTimeoutException will be
* thrown if the database locking failure causes only statement-level
* rollback If a vendor-specific property or hint is not recognized, it is
* silently ignored. Portable applications should not rely on the standard
* timeout hint. Depending on the database in use and the locking mechanisms
* used by the provider, the hint may or may not be observed.
*
* @param properties
* standard and vendor-specific properties and hints
* @return the found entity instance or null if the entity does not exist
* @throws IllegalArgumentException
* if the first argument does not denote an entity type or the
* second argument is not a valid type for that entity's primary
* key or is null
* @throws TransactionRequiredException
* if there is no transaction and a lock mode other than NONE is
* set
* @throws OptimisticLockException
* if the optimistic version check fails
* @throws PessimisticLockException
* if pessimistic locking fails and the transaction is rolled
* back
* @throws LockTimeoutException
* if pessimistic locking fails and only the statement is rolled
* back
* @throws PersistenceException
* if an unsupported lock call is made
*/
@Override
public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode, Map<String, Object> properties) {
try {
verifyOpen();
if (lockMode != null && !lockMode.equals(LockModeType.NONE)){
checkForTransaction(true);
}
AbstractSession session = this.databaseSession;
ClassDescriptor descriptor = session.getDescriptor(entityClass);
// PERF: Avoid uow creation for read-only.
if (descriptor == null || descriptor.isDescriptorTypeAggregate()) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("unknown_bean_class", new Object[] { entityClass }));
}
if (!descriptor.shouldBeReadOnly() || !descriptor.isSharedIsolation()) {
session = (AbstractSession) getActiveSession();
} else {
session = (AbstractSession) getReadOnlySession();
}
// Be sure to use the descriptor from the active session.
if (descriptor.hasTablePerMultitenantPolicy()) {
descriptor = session.getDescriptor(entityClass);
}
return (T) findInternal(descriptor, session, primaryKey, lockMode, properties);
} catch (LockTimeoutException e) {
throw e;
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Find by primary key.
*
* @param entityName
* - the entity class to find.
* @param primaryKey
* - the entity primary key value, or primary key class, or a
* List of primary key values.
* @return the found entity instance or null, if the entity does not exist.
* @throws IllegalArgumentException
* if the first argument does not indicate an entity or if the
* second argument is not a valid type for that entity's
* primaryKey.
*/
public Object find(String entityName, Object primaryKey) {
try {
verifyOpen();
AbstractSession session = (AbstractSession) getActiveSession();
ClassDescriptor descriptor = session.getDescriptorForAlias(entityName);
if (descriptor == null || descriptor.isDescriptorTypeAggregate()) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("unknown_entitybean_name", new Object[] { entityName }));
}
return findInternal(descriptor, session, primaryKey, null, null);
} catch (LockTimeoutException e) {
throw e;
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Find by primary key.
*
* @param descriptor
* - the entity class to find.
* @param id
* - the entity primary key value, or primary key class, or a
* List of primary key values.
* @return the found entity instance or null, if the entity does not exist.
* @throws IllegalArgumentException
* if the first argument does not denote an entity type or the
* second argument is not a valid type for that entity's primary
* key.
*/
protected Object findInternal(ClassDescriptor descriptor, AbstractSession session, Object id, LockModeType lockMode, Map<String, Object> properties) {
if (id == null) { // gf721 - check for null PK
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("null_pk"));
}
Object primaryKey;
if (id instanceof List) {
if (descriptor.getCacheKeyType() == CacheKeyType.ID_VALUE) {
if (((List)id).isEmpty()) {
primaryKey = null;
} else {
primaryKey = ((List)id).get(0);
}
} else {
primaryKey = new CacheId(((List)id).toArray());
}
} else if (id instanceof CacheId) {
primaryKey = id;
} else {
CMPPolicy policy = descriptor.getCMPPolicy();
Class<Object> pkClass = policy.getPKClass();
if ((pkClass != null) && (pkClass != id.getClass()) && (!BasicTypeHelperImpl.getInstance().isStrictlyAssignableFrom(pkClass, id.getClass()))) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("invalid_pk_class", new Object[] { descriptor.getCMPPolicy().getPKClass(), id.getClass() }));
}
primaryKey = policy.createPrimaryKeyFromId(id, session);
}
// If the LockModeType is PESSIMISTIC*, check the unitofwork cache and return the entity if it has previously been locked
// Must avoid using the new JPA 2.0 Enum values directly to allow JPA 1.0 jars to still work.
if (lockMode != null && (lockMode.name().equals(ObjectLevelReadQuery.PESSIMISTIC_READ) || lockMode.name().equals(ObjectLevelReadQuery.PESSIMISTIC_WRITE)
|| lockMode.name().equals(ObjectLevelReadQuery.PESSIMISTIC_FORCE_INCREMENT))) {
// PERF: check if the UnitOfWork has pessimistically locked objects to avoid a cache query
if (session.isUnitOfWork() && ((UnitOfWorkImpl)session).hasPessimisticLockedObjects()) {
ReadObjectQuery query = new ReadObjectQuery();
query.setReferenceClass(descriptor.getJavaClass());
query.setSelectionId(primaryKey);
query.checkCacheOnly();
Object cachedEntity = session.executeQuery(query);
if (cachedEntity != null && ((UnitOfWorkImpl)session).isPessimisticLocked(cachedEntity)) {
return cachedEntity;
}
}
}
// Get the read object query and apply the properties to it.
// PERF: use descriptor defined query to avoid extra query creation.
ReadObjectQuery query = descriptor.getQueryManager().getReadObjectQuery();
if (query == null) {
// The properties/query hints and setIsExecutionClone etc. is set
// in the getReadObjectQuery.
query = getReadObjectQuery(descriptor.getJavaClass(), primaryKey, properties);
} else {
query.checkPrepare(session, null);
query = (ReadObjectQuery) query.clone();
// Apply the properties if there are some.
QueryHintsHandler.apply(properties, query, session.getLoader(), session);
query.setIsExecutionClone(true);
query.setSelectionId(primaryKey);
}
// Apply any EclipseLink defaults if they haven't been set through
// the properties.
if (properties == null || ( !properties.containsKey(QueryHints.CACHE_USAGE) && !properties.containsKey(QueryHints.CACHE_RETRIEVE_MODE) && !properties.containsKey(QueryHints.CACHE_STORE_MODE)
&& !properties.containsKey("jakarta.persistence.cacheRetrieveMode") && !properties.containsKey("jakarta.persistence.cacheStoreMode"))) {
query.conformResultsInUnitOfWork();
}
return executeQuery(query, lockMode, session);
}
/**
* Synchronize the persistence context with the underlying database.
*/
@Override
public void flush() {
try {
// Based on spec definition 3 possible exceptions are thrown
// IllegalState by verifyOpen,
// TransactionRequired by check for transaction
// PersistenceException for all others.
// but there is a tck test that checks for illegal state exception
// and the
// official statement is that the spec 'intended' for
// IllegalStateException to be raised.
verifyOpen();
try {
try {
getActivePersistenceContext(checkForTransaction(true)).writeChanges();
} catch (org.eclipse.persistence.exceptions.OptimisticLockException eclipselinkOLE) {
throw new OptimisticLockException(eclipselinkOLE);
}
} catch (EclipseLinkException e) {
throw new PersistenceException(e);
}
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
protected void detectTransactionWrapper() {
if (this.databaseSession.hasExternalTransactionController()) {
setJTATransactionWrapper();
} else {
setEntityTransactionWrapper();
}
}
/**
* Execute the locking query.
*/
private Object executeQuery(ReadObjectQuery query, LockModeType lockMode, AbstractSession session) {
// Make sure we set the lock mode type if there is one. It will
// be handled in the query prepare statement. Setting the lock mode
// will validate that a valid locking policy is in place if needed. If
// a true value is returned it indicates that we were unable to set the
// lock mode, throw an exception.
if (lockMode != null && query.setLockModeType(lockMode.name(), session)) {
throw new PersistenceException(ExceptionLocalization.buildMessage("ejb30-wrong-lock_called_without_version_locking-index", null));
}
Object result = null;
try {
result = session.executeQuery(query);
} catch (DatabaseException e) {
// If we catch a database exception as a result of executing a
// pessimistic locking query we need to ask the platform which
// JPA 2.0 locking exception we should throw. It will be either
// be a PessimisticLockException or a LockTimeoutException (if
// the query was executed using a wait timeout value)
if (lockMode != null && lockMode.name().contains(ObjectLevelReadQuery.PESSIMISTIC_)) {
// ask the platform if it is a lock timeout
if (query.getExecutionSession().getPlatform().isLockTimeoutException(e)) {
throw new LockTimeoutException(e);
} else {
throw new PessimisticLockException(e);
}
} else {
throw e;
}
}
return result;
}
/**
* Refresh the state of the instance from the database.
*
* @param entity
* instance registered in the current persistence context.
*/
@Override
public void refresh(Object entity) {
refresh(entity, null, getQueryHints(entity, OperationType.REFRESH));
}
/**
* Refresh the state of the instance from the database, using the specified
* properties, and overwriting changes made to the entity, if any. If a
* vendor-specific property or hint is not recognized, it is silently
* ignored.
*
* @param properties
* standard and vendor-specific properties
* @throws IllegalArgumentException
* if the instance is not an entity or the entity is not managed
* @throws TransactionRequiredException
* if invoked on a container-managed entity manager of type
* PersistenceContextType.TRANSACTION and there is no
* transaction.
* @throws EntityNotFoundException
* if the entity no longer exists in the database
*
* @since Java Persistence API 2.0
*/
@Override
public void refresh(Object entity, Map<String, Object> properties) {
refresh(entity, null, properties);
}
/**
* Refresh the state of the instance from the database, overwriting changes
* made to the entity, if any, and lock it with respect to given lock mode
* type. If the lock mode type is pessimistic and the entity instance is
* found but cannot be locked: - the PessimisticLockException will be thrown
* if the database locking failure causes transaction-level rollback. - the
* LockTimeoutException will be thrown if the database locking failure
* causes only statement-level rollback.
*
* @throws IllegalArgumentException
* if the instance is not an entity or the entity is not managed
* @throws TransactionRequiredException
* if there is no transaction
* @throws EntityNotFoundException
* if the entity no longer exists in the database
* @throws PessimisticLockException
* if pessimistic locking fails and the transaction is rolled
* back
* @throws LockTimeoutException
* if pessimistic locking fails and only the statement is rolled
* back
* @throws PersistenceException
* if an unsupported lock call is made
*/
@Override
public void refresh(Object entity, LockModeType lockMode) {
refresh(entity, lockMode, getQueryHints(entity, OperationType.REFRESH));
}
/**
* Refresh the state of the instance from the database, overwriting changes
* made to the entity, if any, and lock it with respect to given lock mode
* type. If the lock mode type is pessimistic and the entity instance is
* found but cannot be locked: - the PessimisticLockException will be thrown
* if the database locking failure causes transaction-level rollback. - the
* LockTimeoutException will be thrown if the database locking failure
* causes only statement-level rollback If a vendor-specific property or
* hint is not recognized, it is silently ignored. Portable applications
* should not rely on the standard timeout hint. Depending on the database
* in use and the locking mechanisms used by the provider, the hint may or
* may not be observed.
*
* @param properties
* standard and vendor-specific properties and hints
* @throws IllegalArgumentException
* if the instance is not an entity or the entity is not managed
* @throws TransactionRequiredException
* if there is no transaction
* @throws EntityNotFoundException
* if the entity no longer exists in the database
* @throws PessimisticLockException
* if pessimistic locking fails and the transaction is rolled
* back
* @throws LockTimeoutException
* if pessimistic locking fails and only the statement is rolled
* back
* @throws PersistenceException
* if an unsupported lock call is made
*/
@Override
public void refresh(Object entity, LockModeType lockMode, Map<String, Object> properties) {
try {
verifyOpen();
boolean validateExistence = (lockMode != null && !lockMode.equals(LockModeType.NONE));
UnitOfWorkImpl uow = getActivePersistenceContext(checkForTransaction(validateExistence));
if (!contains(entity, uow)) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cant_refresh_not_managed_object", new Object[] { entity }));
}
// Get the read object query and apply the properties to it.
ReadObjectQuery query = getReadObjectQuery(entity, properties);
// Apply any EclipseLink defaults if they haven't been set through
// the properties.
if (properties == null || !properties.containsKey(QueryHints.REFRESH)) {
query.refreshIdentityMapResult();
}
if (properties == null || !properties.containsKey(QueryHints.REFRESH_CASCADE)) {
query.cascadeByMapping();
}
Object refreshedEntity = executeQuery(query, lockMode, uow);
if (refreshedEntity == null) {
// bug5955326, ReadObjectQuery will now ensure the object is
// invalidated if refresh returns null.
throw new EntityNotFoundException(ExceptionLocalization.buildMessage("entity_no_longer_exists_in_db", new Object[] { entity }));
}
} catch (LockTimeoutException e) {
throw e;
} catch (RuntimeException exception) {
setRollbackOnly();
throw exception;
}
}
/**
* Check if the instance belongs to the current persistence context.
*
* @throws IllegalArgumentException
* if given Object is not an entity
*/
@Override
public boolean contains(Object entity) {
try {
verifyOpen();
if (entity == null) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { null }));
}
ClassDescriptor descriptor = this.databaseSession.getDescriptors().get(entity.getClass());
if (descriptor == null || descriptor.isDescriptorTypeAggregate()) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { entity }));
}
if ((!hasActivePersistenceContext())) {
return false;
}
return contains(entity, getActivePersistenceContext(checkForTransaction(false)));
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Check if the instance belongs to the current persistence context.
*/
protected boolean contains(Object entity, UnitOfWork uow) {
return uow.isObjectRegistered(entity) && !((UnitOfWorkImpl) uow).isObjectDeleted(entity);
}
@Override
public jakarta.persistence.Query createDescriptorNamedQuery(String queryName, Class descriptorClass) {
return createDescriptorNamedQuery(queryName, descriptorClass, null);
}
@Override
public jakarta.persistence.Query createDescriptorNamedQuery(String queryName, Class descriptorClass, List argumentTypes) {
try {
verifyOpen();
ClassDescriptor descriptor = this.databaseSession.getDescriptor(descriptorClass);
if (descriptor != null) {
DatabaseQuery query = descriptor.getQueryManager().getLocalQueryByArgumentTypes(queryName, argumentTypes);
if (query != null) {
return new EJBQueryImpl(query, this);
}
}
return null;
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Create an instance of Query for executing a named query (in EJBQL or
* native SQL).
*
* @param name
* the name of a query defined in metadata
* @return the new query instance
*/
@Override
public Query createNamedQuery(String name) {
try {
verifyOpen();
EJBQueryImpl query = new EJBQueryImpl(name, this, true);
query.getDatabaseQueryInternal();
return query;
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Create an instance of TypedQuery for executing a
* named query (in the Java Persistence query language
* or in native SQL).
* @param name the name of a query defined in metadata
* @param resultClass the type of the query result
* @return the new query instance
* @throws IllegalArgumentException if a query has not been
* defined with the given name or if the query string is
* found to be invalid
*/
@Override
public <T> TypedQuery<T> createNamedQuery(String name, Class<T> resultClass){
return (TypedQuery<T>) createNamedQuery(name);
}
/**
* Create an instance of StoredProcedureQuery for executing a
* stored procedure in the database.
* @param name name assigned to the stored procedure query
* in metadata
* @return the new stored procedure query instance
* @throws IllegalArgumentException if a query has not been
* defined with the given name
*/
@Override
public StoredProcedureQuery createNamedStoredProcedureQuery(String name) {
try {
verifyOpen();
StoredProcedureQueryImpl query = new StoredProcedureQueryImpl(name, this);
query.getDatabaseQueryInternal();
return query;
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Create an instance of Query for executing a native SQL query.
*
* @param sqlString
* a native SQL query string
* @return the new query instance
*/
@Override
public Query createNativeQuery(String sqlString) {
try {
verifyOpen();
return new EJBQueryImpl(EJBQueryImpl.buildSQLDatabaseQuery(sqlString, this.databaseSession.getLoader(), this.databaseSession), this);
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* This method is used to create a query using SQL. The class, must be the
* expected return type.
*/
@Override
public Query createNativeQuery(String sqlString, Class resultType) {
try {
verifyOpen();
return new EJBQueryImpl(EJBQueryImpl.buildSQLDatabaseQuery(resultType, sqlString, this.databaseSession.getLoader(), this.databaseSession), this);
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Create an instance of Query for executing a native SQL query.
*
* @param sqlString
* a native SQL query string
* @param resultSetMapping
* the name of the result set mapping
* @return the new query instance
* @throws IllegalArgumentException
* if query string is not valid
*/
@Override
public Query createNativeQuery(String sqlString, String resultSetMapping) {
try {
verifyOpen();
ResultSetMappingQuery query = new ResultSetMappingQuery();
query.setSQLResultSetMappingName(resultSetMapping);
query.setSQLString(sqlString);
query.setIsUserDefined(true);
return new EJBQueryImpl(query, this);
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* This method returns the current session to the requestor. The current
* session will be a the active UnitOfWork within a transaction and will be
* a 'scrap' UnitOfWork outside of a transaction. The caller is concerned
* about the results then the getSession() or getUnitOfWork() API should be
* called.
*/
@Override
public Session getActiveSession() {
return getActivePersistenceContext(checkForTransaction(false));
}
/**
* This method returns the current session to the requestor. The current
* session will be a the active UnitOfWork within a transaction and will be
* a 'scrap' UnitOfWork outside of a transaction. The caller is concerned
* about the results then the getSession() or getUnitOfWork() API should be
* called.
*/
public AbstractSession getActiveSessionIfExists() {
// When requesting an active session, if there isn't one but we have
// table per tenant descriptors, make sure we return one. The 'scrap'
// session will not be initialized for table per tenant multitenancy.
if (hasActivePersistenceContext() || getAbstractSession().hasTablePerTenantDescriptors()) {
return (AbstractSession) getActiveSession();
} else {
return getAbstractSession();
}
}
/**
* Return the underlying provider object for the EntityManager, if
* available. The result of this method is implementation specific.
*/
@Override
public Object getDelegate() {
try {
verifyOpen();
return this;
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Get the flush mode that applies to all objects contained in the
* persistence context.
*
* @return flushMode
*/
@Override
public FlushModeType getFlushMode() {
try {
verifyOpen();
return flushMode;
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* This method will return the active UnitOfWork
*/
@Override
public UnitOfWork getUnitOfWork() {
return getActivePersistenceContext(checkForTransaction(false));
}
/**
* This method will return a Session outside of a transaction and null
* within a transaction.
*/
@Override
public Session getSession() {
if (checkForTransaction(false) == null) {
return this.databaseSession.acquireNonSynchronizedUnitOfWork(this.referenceMode);
}
return null;
}
/**
* Returns the resource-level transaction object. The EntityTransaction
* instance may be used serially to begin and commit multiple transactions.
*
* @return EntityTransaction instance
* @throws IllegalStateException
* if invoked on a JTA EntityManager.
*/
@Override
public jakarta.persistence.EntityTransaction getTransaction() {
try {
return ((TransactionWrapper) transaction).getTransaction();
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* The method search for user defined property passed in from EntityManager,
* if it is not found then search for it from EntityManagerFactory
* properties.
*
*/
public Object getProperty(String name) {
Object propertyValue = null;
if (name == null) {
return null;
}
if (this.properties != null) {
propertyValue = this.properties.get(name);
}
if (propertyValue == null) {
propertyValue = this.factory.getAbstractSession().getProperty(name);
}
return propertyValue;
}
/**
* Build a selection query for the primary key values.
*/
protected ReadObjectQuery getReadObjectQuery(Class referenceClass, Object primaryKey, Map properties) {
ReadObjectQuery query = getReadObjectQuery(properties);
query.setReferenceClass(referenceClass);
query.setSelectionId(primaryKey);
return query;
}
/**
* Build a selection query using the given properties.
*/
protected ReadObjectQuery getReadObjectQuery(Map properties) {
ReadObjectQuery query = new ReadObjectQuery();
// Apply the properties if there are some.
QueryHintsHandler.apply(properties, query, this.databaseSession.getDatasourcePlatform().getConversionManager().getLoader(), this.databaseSession);
query.setIsExecutionClone(true);
return query;
}
/**
* Build a selection query for the given entity.
*/
protected ReadObjectQuery getReadObjectQuery(Object entity, Map properties) {
ReadObjectQuery query = getReadObjectQuery(properties);
query.setSelectionObject(entity);
return query;
}
/**
* Get an instance, whose state may be lazily fetched. If the requested
* instance does not exist in the database, throws EntityNotFoundException
* when the instance state is first accessed. (The container is permitted to
* throw EntityNotFoundException when get is called.) The application should
* not expect that the instance state will be available upon detachment,
* unless it was accessed by the application while the entity manager was
* open.
*
* @return the found entity instance.
* @throws IllegalArgumentException
* if the first argument does not denote an entity type or the
* second argument is not a valid type for that entity's primary
* key.
* @throws EntityNotFoundException
* if the entity state cannot be accessed.
*/
@Override
public <T> T getReference(Class<T> entityClass, Object primaryKey) {
try {
verifyOpen();
UnitOfWork session = (UnitOfWork) getActiveSession();
Object reference = session.getReference(entityClass, primaryKey);
if (reference == null) {
Object[] args = { primaryKey };
String message = ExceptionLocalization.buildMessage("no_entities_retrieved_for_get_reference", args);
throw new jakarta.persistence.EntityNotFoundException(message);
}
return (T) reference;
} catch (RuntimeException exception) {
setRollbackOnly();
throw exception;
}
}
/**
* Return a read-only session (client session) for read-only operations.
*/
public Session getReadOnlySession() {
if (this.extendedPersistenceContext != null && this.extendedPersistenceContext.isActive()) {
return this.extendedPersistenceContext.getParent();
}
if (this.readOnlySession != null) {
return this.readOnlySession;
}
if (this.databaseSession.isServerSession()) {
this.readOnlySession = ((ServerSession)this.databaseSession).acquireClientSession(connectionPolicy, properties);
return this.readOnlySession;
} else if(this.databaseSession.isBroker()) {
this.readOnlySession = ((SessionBroker)this.databaseSession).acquireClientSessionBroker(this.connectionPolicies, (Map)this.properties.get(EntityManagerProperties.COMPOSITE_UNIT_PROPERTIES));
return this.readOnlySession;
} else {
// currently this can't happen - the databaseSession is either ServerSession or SessionBroker.
return this.databaseSession;
}
}
/**
* Return the underlying database session
*/
@Override
public DatabaseSessionImpl getDatabaseSession() {
return (DatabaseSessionImpl)this.databaseSession;
}
/**
* Return the underlying database session
*/
@Override
public AbstractSession getAbstractSession() {
return this.databaseSession;
}
/**
* INTERNAL:
* Set the underlying database session
*/
public void setAbstractSession(AbstractSession session) {
this.databaseSession = session;
}
/**
* Return the underlying server session, throws ClassCastException if it's not a ServerSession.
*/
@Override
public ServerSession getServerSession() {
return (ServerSession)this.databaseSession;
}
/**
* Return the underlying session broker, throws ClassCastException if it's not a SessionBroker.
*/
@Override
public SessionBroker getSessionBroker() {
return (SessionBroker)this.databaseSession;
}
/**
* Return the member DatabaseSessionImpl that maps cls in session broker.
* Return null if either not a session broker or cls is not mapped.
* Session broker implement composite persistence unit.
*/
@Override
public DatabaseSessionImpl getMemberDatabaseSession(Class cls) {
if(this.databaseSession.isBroker()) {
return (DatabaseSessionImpl) this.databaseSession.getSessionForClass(cls);
} else {
return null;
}
}
/**
* Return the member ServerSession that maps cls in session broker.
* Return null if either not a session broker or cls is not mapped.
* Session broker implement composite persistence unit.
*/
@Override
public ServerSession getMemberServerSession(Class cls) {
if(this.databaseSession.isBroker()) {
return (ServerSession) this.databaseSession.getSessionForClass(cls);
} else {
return null;
}
}
/**
* Return the name of member session that maps cls.
* Return null if either not a session broker or cls is not mapped.
* Session broker implement composite persistence unit.
*/
@Override
public String getMemberSessionName(Class cls) {
if(this.databaseSession.isBroker()) {
return this.databaseSession.getSessionForClass(cls).getName();
} else {
return null;
}
}
/**
* This method is used to create a query using a EclipseLink Expression and
* the return type.
*/
@Override
public jakarta.persistence.Query createQuery(Expression expression, Class resultType) {
try {
verifyOpen();
DatabaseQuery query = createQueryInternal(expression, resultType);
return new EJBQueryImpl(query, this);
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* This method is used to create a query using a EclipseLink DatabaseQuery.
*/
@Override
public jakarta.persistence.Query createQuery(DatabaseQuery databaseQuery) {
try {
verifyOpen();
return new EJBQueryImpl(databaseQuery, this);
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* @see EntityManager#createQuery(jakarta.persistence.criteria.CriteriaQuery)
* @since Java Persistence 2.0
*/
@Override
public <T> TypedQuery<T> createQuery(CriteriaQuery<T> criteriaQuery) {
try{
verifyOpen();
return new EJBQueryImpl<T>(((CriteriaQueryImpl<T>)criteriaQuery).translate(), this);
}catch (RuntimeException e){
setRollbackOnly();
throw e;
}
}
/**
* This method is used to create a query using a EclipseLink by example.
*/
@Override
public jakarta.persistence.Query createQueryByExample(Object exampleObject) {
try {
verifyOpen();
ReadAllQuery query = new ReadAllQuery(exampleObject.getClass());
query.setExampleObject(exampleObject);
return new EJBQueryImpl(query, this);
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* This method is used to create a query using a EclipseLink Call.
*/
@Override
public jakarta.persistence.Query createQuery(Call call) {
try {
verifyOpen();
DataReadQuery query = new DataReadQuery(call);
return new EJBQueryImpl(query, this);
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* This method is used to create a query using a EclipseLink Call.
*/
@Override
public jakarta.persistence.Query createQuery(Call call, Class entityClass) {
try {
verifyOpen();
ReadAllQuery query = new ReadAllQuery(entityClass, call);
return new EJBQueryImpl(query, this);
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Create an instance of Query for executing an JPQL query.
*
* @param jpqlString
* an JPQL query string
* @return the new query instance
*/
@Override
public Query createQuery(String jpqlString) {
try {
verifyOpen();
EJBQueryImpl ejbqImpl;
try {
ejbqImpl = new EJBQueryImpl(jpqlString, this);
} catch (JPQLException exception) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("wrap_ejbql_exception") + ": " + exception.getLocalizedMessage(), exception);
}
return ejbqImpl;
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Create an instance of TypedQuery for executing a
* Java Persistence query language statement.
* @param qlString a Java Persistence query string
* @param resultClass the type of the query result
* @return the new query instance
* @throws IllegalArgumentException if the query string is found
* to be invalid
*/
@Override
public <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass){
return (TypedQuery<T>) this.createQuery(qlString);
}
/**
* This method is used to create a query using a EclipseLink Expression and
* the return type.
*/
protected DatabaseQuery createQueryInternal(Expression expression, Class resultType) {
ReadAllQuery query = new ReadAllQuery(resultType);
query.setSelectionCriteria(expression);
return query;
}
/**
* Create an instance of <code>StoredProcedureQuery</code> for executing a
* stored procedure in the database.
* <p>Parameters must be registered before the stored procedure can be
* executed.
* <p>If the stored procedure returns one or more result sets, any result
* set will be returned as a list of type Object[].
*
* @param procedureName name of the stored procedure in the database
* @return the new stored procedure query instance
* @throws IllegalArgumentException if a stored procedure of the given name
* does not exist (or the query execution will fail)
* @since EclipseLink 2.5/Java Persistence 2.1
*/
@Override
public StoredProcedureQuery createStoredProcedureQuery(String procedureName) {
try {
verifyOpen();
StoredProcedureCall call = new StoredProcedureCall();
call.setProcedureName(procedureName);
return new StoredProcedureQueryImpl(StoredProcedureQueryImpl.buildResultSetMappingQuery(new ArrayList<SQLResultSetMapping>(), call), this);
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Create an instance of <code>StoredProcedureQuery</code> for executing a
* stored procedure in the database.
* <p>Parameters must be registered before the stored procedure can be
* executed.
* <p>The <code>resultClass</code> arguments must be specified in the order
* in which the result sets will be returned by the stored procedure
* invocation.
*
* @param procedureName name of the stored procedure in the database
* @param resultClasses classes to which the result sets produced by the
* stored procedure are to be mapped
* @return the new stored procedure query instance
* @throws IllegalArgumentException if a stored procedure of the given name
* does not exist (or the query execution will fail)
* @since EclipseLink 2.5/Java Persistence 2.1
*/
@Override
public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class... resultClasses) {
try {
verifyOpen();
StoredProcedureCall call = new StoredProcedureCall();
call.setProcedureName(procedureName);
call.setHasMultipleResultSets(resultClasses.length > 1);
List<SQLResultSetMapping> sqlResultSetMappings = new ArrayList<SQLResultSetMapping>();
for (Class resultClass : resultClasses) {
sqlResultSetMappings.add(new SQLResultSetMapping(resultClass));
}
return new StoredProcedureQueryImpl(StoredProcedureQueryImpl.buildResultSetMappingQuery(sqlResultSetMappings, call), this);
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Create an instance of <code>StoredProcedureQuery</code> for executing a
* stored procedure in the database.
* <p>Parameters must be registered before the stored procedure can be
* executed.
* <p>The <code>resultSetMapping</code> arguments must be specified in the
* order in which the result sets will be returned by the stored procedure
* invocation.
*
* @param procedureName name of the stored procedure in the database
* @param resultSetMappings the names of the result set mappings
* to be used in mapping result sets
* returned by the stored procedure
* @return the new stored procedure query instance
* @throws IllegalArgumentException if a stored procedure or
* result set mapping of the given name does not exist
* (or the query execution will fail)
* @since EclipseLink 2.5/Java Persistence 2.1
*/
@Override
public StoredProcedureQuery createStoredProcedureQuery(String procedureName, String... resultSetMappings) {
try {
verifyOpen();
StoredProcedureCall call = new StoredProcedureCall();
call.setProcedureName(procedureName);
call.setHasMultipleResultSets(resultSetMappings.length > 1);
List<String> sqlResultSetMappingNames = new ArrayList<String>();
for (String resultSetMapping : resultSetMappings) {
sqlResultSetMappingNames.add(resultSetMapping);
}
return new StoredProcedureQueryImpl(StoredProcedureQueryImpl.buildResultSetMappingNameQuery(sqlResultSetMappingNames, call), this);
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* <p>
* Closes this EntityManager.
*
* <p>
* After invoking this method, all methods on the instance will throw an
* {@link IllegalStateException} except for {@link #isOpen}, which will
* return <code>false</code> .
* </p>
*
* <p>
* This should be called when a method is finished with the EntityManager in
* a bean-managed transaction environment or when executed outside a
* container. Closing of the EntityManager is handled by the container when
* using container-managed transactions.
* </p>
*/
@Override
public void close() {
try {
verifyOpen();
closeOpenQueries();
this.isOpen = false;
this.factory = null;
this.databaseSession = null;
// Do not invalidate the metaModel field
// (a reopened emf will re-populate the same metaModel)
// (a new persistence unit will generate a new metaModel)
if (this.extendedPersistenceContext != null) {
// bug210677, checkForTransaction returns null in
// afterCompletion - in this case check for uow being
// synchronized.
if (checkForTransaction(false) == null && !this.extendedPersistenceContext.isSynchronized()) {
// uow.release clears change sets but keeps the cache.
// uow still could be used for instantiating of ValueHolders
// after it's released.
this.extendedPersistenceContext.release();
this.extendedPersistenceContext.getParent().release();
} else {
// when commit will be called uow will be released, all
// change sets will be cleared, but the cache will be kept.
// uow still could be used for instantiating of ValueHolders
// after it's released.
this.extendedPersistenceContext.setResumeUnitOfWorkOnTransactionCompletion(false);
}
this.extendedPersistenceContext = null;
}
if (this.readOnlySession != null) {
this.readOnlySession.release();
this.readOnlySession = null;
}
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Close any open queries executed against this entity manager.0
*/
protected void closeOpenQueries() {
for (QueryImpl openQuery : getOpenQueriesSet()) {
openQuery.close();
}
}
/**
* Internal method. Indicates whether flushMode is AUTO.
*
* @return boolean
*/
public boolean isFlushModeAUTO() {
return flushMode == FlushModeType.AUTO;
}
/**
* Indicates whether or not this entity manager and its entity manager factory
* are open. Returns
* <code>true</code> until a call to {@link #close} is made.
*/
@Override
public boolean isOpen() {
return isOpen && factory.isOpen();
}
/**
* Set the lock mode for an entity object contained in the persistence
* context.
*
* @throws PersistenceException
* if an unsupported lock call is made
* @throws IllegalArgumentException
* if the instance is not an entity or is a detached entity
* @throws jakarta.persistence.TransactionRequiredException
* if there is no transaction
*/
@Override
public void lock(Object entity, LockModeType lockMode) {
lock(entity, lockMode, getQueryHints(entity, OperationType.LOCK));
}
/**
* Set the lock mode for an entity object contained in the persistence
* context.
*
* @throws PersistenceException
* if an unsupported lock call is made
* @throws IllegalArgumentException
* if the instance is not an entity or is a detached entity
* @throws jakarta.persistence.TransactionRequiredException
* if there is no transaction
*/
@Override
public void lock(Object entity, LockModeType lockMode, Map<String, Object> properties) {
try {
verifyOpen();
if (entity == null) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { null }));
}
UnitOfWorkImpl uow = getActivePersistenceContext(checkForTransaction(true)); // Throws TransactionRequiredException if no active transaction.
if (!contains(entity, uow)) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cant_lock_not_managed_object", new Object[] { entity }));
}
if (lockMode == null || lockMode.name().equals(ObjectLevelReadQuery.NONE)) {
// Nothing to do
return;
}
// Must avoid using the new JPA 2.0 Enum values directly to allow JPA 1.0 jars to still work.
if (lockMode.name().equals(ObjectLevelReadQuery.PESSIMISTIC_READ) || lockMode.name().equals(ObjectLevelReadQuery.PESSIMISTIC_WRITE )
|| lockMode.name().equals(ObjectLevelReadQuery.PESSIMISTIC_FORCE_INCREMENT)) {
// return if the entity has previously been pessimistically locked
if (uow.isPessimisticLocked(entity)) {
return;
}
// Get the read object query and apply the properties to it.
ReadObjectQuery query = getReadObjectQuery(entity, properties);
// Apply any EclipseLink defaults if they haven't been set
// through
// the properties.
if (properties == null || !properties.containsKey(QueryHints.REFRESH)) {
query.refreshIdentityMapResult();
}
if (properties == null || !properties.containsKey(QueryHints.REFRESH_CASCADE)) {
query.cascadePrivateParts();
}
executeQuery(query, lockMode, getActivePersistenceContext(checkForTransaction(false)));
} else {
RepeatableWriteUnitOfWork context = getActivePersistenceContext(checkForTransaction(false));
ClassDescriptor descriptor = context.getDescriptor(entity);
OptimisticLockingPolicy lockingPolicy = descriptor.getOptimisticLockingPolicy();
if ((lockingPolicy == null) || !(lockingPolicy instanceof VersionLockingPolicy)) {
throw new PersistenceException(ExceptionLocalization.buildMessage("ejb30-wrong-lock_called_without_version_locking-index", null));
}
context.forceUpdateToVersionField(entity, (lockMode == LockModeType.WRITE || lockMode.name().equals(ObjectLevelReadQuery.OPTIMISTIC_FORCE_INCREMENT)));
}
} catch (LockTimeoutException e) {
throw e;
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
public void verifyOpen() {
if (!this.isOpen || !this.factory.isOpen()) {
throw new IllegalStateException(ExceptionLocalization.buildMessage("operation_on_closed_entity_manager"));
}
}
/**
* used to save having to constantly use a try/catch to call setRollbackOnly
*/
public void verifyOpenWithSetRollbackOnly() {
if (!this.isOpen || !this.factory.isOpen()) {
setRollbackOnly();
throw new IllegalStateException(ExceptionLocalization.buildMessage("operation_on_closed_entity_manager"));
}
}
public RepeatableWriteUnitOfWork getActivePersistenceContext(Object txn) {
// use local uow as it will be local to this EM and not on the txn
if (this.extendedPersistenceContext == null || !this.extendedPersistenceContext.isActive()) {
AbstractSession client;
if(this.databaseSession.isServerSession()) {
createConnectionPolicy();
client = ((ServerSession)this.databaseSession).acquireClientSession(this.connectionPolicy, this.properties);
} else if(this.databaseSession.isBroker()) {
Map mapOfProperties = null;
if (properties != null) {
mapOfProperties = (Map)this.properties.get(EntityManagerProperties.COMPOSITE_UNIT_PROPERTIES);
}
createConnectionPolicies(mapOfProperties);
client = ((SessionBroker)this.databaseSession).acquireClientSessionBroker(this.connectionPolicies, mapOfProperties);
} else {
// currently this can't happen - the databaseSession is either ServerSession or SessionBroker.
client = this.databaseSession;
}
this.extendedPersistenceContext = client.acquireRepeatableWriteUnitOfWork(this.referenceMode);
this.extendedPersistenceContext.setResumeUnitOfWorkOnTransactionCompletion(!this.closeOnCommit);
this.extendedPersistenceContext.setShouldDiscoverNewObjects(this.persistOnCommit);
this.extendedPersistenceContext.setDiscoverUnregisteredNewObjectsWithoutPersist(this.commitWithoutPersistRules);
this.extendedPersistenceContext.setFlushClearCache(this.flushClearCache);
this.extendedPersistenceContext.setShouldValidateExistence(this.shouldValidateExistence);
this.extendedPersistenceContext.setCommitOrder(this.commitOrder);
this.extendedPersistenceContext.setShouldCascadeCloneToJoinedRelationship(true);
this.extendedPersistenceContext.setShouldStoreByPassCache(this.cacheStoreBypass);
if (txn != null) {
// if there is a txn, it means we have been marked to join with it.
// All that is left is to register the UOW with the transaction
transaction.registerIfRequired(this.extendedPersistenceContext);
}
if (client.shouldLog(SessionLog.FINER, SessionLog.TRANSACTION)) {
client.log(SessionLog.FINER, SessionLog.TRANSACTION, "acquire_unit_of_work_with_argument", String.valueOf(System.identityHashCode(this.extendedPersistenceContext)));
}
}
if (this.beginEarlyTransaction && txn != null && !this.extendedPersistenceContext.isInTransaction()) {
// gf3334, force persistence context early transaction
this.extendedPersistenceContext.beginEarlyTransaction();
}
return this.extendedPersistenceContext;
}
/**
* Use this method to set properties into existing EntityManager that are
* normally passed to createEntityManager method. Note that if the method
* called when active persistence context already exists then properties
* used to create persistence context will be ignored until the new
* persistence context is created (that happens either after transaction
* rolled back or after clear method was called).
*/
public void setProperties(Map properties) {
verifyOpenWithSetRollbackOnly();
this.properties = properties;
if(this.hasActivePersistenceContext()) {
this.extendedPersistenceContext.getParent().setProperties(properties);
}
if(properties == null || properties.isEmpty()) {
return;
}
processProperties();
}
/**
* @see EntityManager#setProperty(java.lang.String, java.lang.Object)
*/
@Override
public void setProperty(String propertyName, Object value) {
verifyOpenWithSetRollbackOnly();
if(propertyName == null || value == null) {
return;
}
if (this.properties == null) {
if(this.hasActivePersistenceContext()) {
// getProperties method always returns non-null Map
this.properties = this.extendedPersistenceContext.getParent().getProperties();
} else {
this.properties = new HashMap<>();
}
}
properties.put(propertyName, value);
// If there is an extended persistence context update table per tenant descriptors.
if (hasActivePersistenceContext()) {
extendedPersistenceContext.updateTablePerTenantDescriptors(propertyName, value);
}
// Re-process the property.
PropertyProcessor processor = processors.get(propertyName);
if(processor != null) {
processor.process(propertyName, value, this);
}
}
/**
* This method is used in contains to check if we already have a persistence
* context. If there is no active persistence context the method returns
* false
*/
public boolean hasActivePersistenceContext() {
if (this.extendedPersistenceContext == null || !this.extendedPersistenceContext.isActive()) {
return false;
} else {
return true;
}
}
/**
* Return the current, joined transaction object. If validateExistence is true throw
* an error if there is no joined transaction, otherwise return null.
*/
protected Object checkForTransaction(boolean validateExistence) {
Object txn = this.transaction.checkForTransaction(validateExistence);
//use transaction.isJoinedToTransaction EM is open verification.
if ((txn != null) && !transaction.isJoinedToTransaction(this.extendedPersistenceContext)) {
if (validateExistence) {
throw new TransactionRequiredException(ExceptionLocalization.buildMessage("cannot_use_transaction_on_unsynced_pc"));
}
return null;
}
return txn;
}
public boolean shouldFlushBeforeQuery() {
return (checkForTransaction(false)!= null);
}
/**
* Indicate the early transaction should be forced to start. This allows for
* reading through the write connection. As a side effect, this will also
* prevent anything from being cached.
*/
public boolean shouldBeginEarlyTransaction() {
return this.beginEarlyTransaction;
}
/**
* Indicate to the EntityManager that a JTA transaction is active. This
* method should be called on a JTA application managed EntityManager that
* was created outside the scope of the active transaction to associate it
* with the current JTA transaction.
*
* @throws jakarta.persistence.TransactionRequiredException
* if there is no transaction.
*/
@Override
public void joinTransaction() {
try {
verifyOpen();
//An EntityTransactionWrapper throws an exception, while
//if using JTA and extendedPersistenceContext is active, then this will have the UOW register with the transaction.
//If there is no context, the JTATransactionWrapper will register a listener with the transaction to keep track of when
//it completes. Any UOW created while the transaction is still active will then automatically register/join with it.
transaction.registerIfRequired(this.extendedPersistenceContext);
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* Internal method. Sets transaction to rollback only.
*/
protected void setRollbackOnly() {
this.transaction.setRollbackOnlyInternal();
}
/**
* Process the local EntityManager properties only. The persistence unit
* properties are processed by the factory.
*/
protected void processProperties() {
Iterator<Map.Entry<String, Object>> it = this.properties.entrySet().iterator();
while(it.hasNext()) {
Map.Entry<String, Object> entry = it.next();
PropertyProcessor processor = processors.get(entry.getKey());
if(processor != null) {
processor.process(entry.getKey(), entry.getValue(), this);
}
}
}
/**
* Get the local EntityManager property from the properties Map. This only
* searches the local Map. The persistence unit properties are processed by
* the EntityManagerFactory.
*/
protected String getPropertiesHandlerProperty(String name) {
return PropertiesHandler.getPropertyValue(name, this.properties, false);
}
/**
* Verifies and (if required) translates the value.
*/
protected static String getPropertiesHandlerProperty(String name, String value) {
return PropertiesHandler.getPropertyValue(name, value);
}
protected void setEntityTransactionWrapper() {
transaction = new EntityTransactionWrapper(this);
}
/**
* Set the flush mode that applies to all objects contained in the
* persistence context.
*
*/
@Override
public void setFlushMode(FlushModeType flushMode) {
try {
verifyOpen();
this.flushMode = flushMode;
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
protected void setJTATransactionWrapper() {
transaction = new JTATransactionWrapper(this);
//if this is not unsynchronized and there is a transaction, this EM needs to join it
if (syncType == null || syncType.equals(SynchronizationType.SYNCHRONIZED)) {
//use the wrapper's checkForTransaction as this.checkForTransaction does an unnecessary isJoined check
if (transaction.checkForTransaction(false) != null) {
//extendedPersistenceContext should be null, which will force the wrapper to register with the transaction
transaction.registerIfRequired(this.extendedPersistenceContext);
}
}
}
/**
* Create connection policy using properties.
* Default connection policy created if no connection properties specified.
* Should be called only in case this.databaseSession is a ServerSession.
*/
protected void createConnectionPolicy() {
if (this.connectionPolicy == null) {
this.connectionPolicy = createConnectionPolicy((ServerSession)this.databaseSession, this.properties);
}
}
/**
* Create connection policy using properties.
* Default connection policy created if no connection properties specified.
* Should be called only in case this.databaseSession is a SessionBroker.
*/
protected void createConnectionPolicies(Map mapOfProperties) {
// Because the method called only in SessionBroker case this.connectionPolicies is guaranteed to be non null.
Iterator<Map.Entry<String, ConnectionPolicy>> it = this.connectionPolicies.entrySet().iterator();
while (it.hasNext()) {
// key - sessionName, value - ConnectionPolicy
Map.Entry<String, ConnectionPolicy> entry = it.next();
if (entry.getValue() == null) {
// ConnectionPolicy is null - should be recreated
Map properties = null;
if (mapOfProperties != null) {
properties = (Map)mapOfProperties.get(entry.getKey());
}
ConnectionPolicy connectionPolicy = createConnectionPolicy((ServerSession)this.databaseSession.getSessionForName(entry.getKey()), properties);
this.connectionPolicies.put(entry.getKey(), connectionPolicy);
}
}
}
/**
* Create connection policy using properties.
* Default connection policy created if no connection properties specified.
*/
protected static ConnectionPolicy createConnectionPolicy(ServerSession serverSession, Map properties) {
ConnectionPolicy policy = serverSession.getDefaultConnectionPolicy();
if (properties == null || properties.isEmpty()) {
return policy;
}
// Search only the properties map - serverSession's properties have been
// already processed.
ConnectionPolicy policyFromProperties = (ConnectionPolicy) properties.get(EntityManagerProperties.CONNECTION_POLICY);
if (policyFromProperties != null) {
policy = policyFromProperties;
}
// Note that serverSession passed into the methods below only because it
// carries the SessionLog into which the debug info should be written.
// The property is search for in the passed properties map only (not in
// serverSession, not in System.properties).
ConnectionPolicy newPolicy = null;
String isLazyString = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(EntityManagerProperties.EXCLUSIVE_CONNECTION_IS_LAZY, properties, serverSession, false);
if (isLazyString != null) {
boolean isLazy = Boolean.parseBoolean(isLazyString);
if (policy.isLazy() != isLazy) {
if (newPolicy == null) {
newPolicy = (ConnectionPolicy) policy.clone();
}
newPolicy.setIsLazy(isLazy);
}
}
ConnectionPolicy.ExclusiveMode exclusiveMode = EntityManagerSetupImpl.getConnectionPolicyExclusiveModeFromProperties(properties, serverSession, false);
if (exclusiveMode != null) {
if (!exclusiveMode.equals(policy.getExclusiveMode())) {
if (newPolicy == null) {
newPolicy = (ConnectionPolicy) policy.clone();
}
newPolicy.setExclusiveMode(exclusiveMode);
}
}
String user = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(EntityManagerProperties.JDBC_USER, properties, serverSession, false);
String password = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(EntityManagerProperties.JDBC_PASSWORD, properties, serverSession, false);
String driver = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(EntityManagerProperties.JDBC_DRIVER, properties, serverSession, false);
String connectionString = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(EntityManagerProperties.JDBC_URL, properties, serverSession, false);
// find the jta datasource
Object jtaDataSourceObj = EntityManagerFactoryProvider.getConfigPropertyLogDebug(EntityManagerProperties.JTA_DATASOURCE, properties, serverSession, false);
DataSource jtaDataSource = null;
String jtaDataSourceName = null;
if (jtaDataSourceObj != null) {
if (jtaDataSourceObj instanceof DataSource) {
jtaDataSource = (DataSource) jtaDataSourceObj;
} else if (jtaDataSourceObj instanceof String) {
jtaDataSourceName = (String) jtaDataSourceObj;
}
}
// find the non jta datasource
Object nonjtaDataSourceObj = EntityManagerFactoryProvider.getConfigPropertyLogDebug(EntityManagerProperties.NON_JTA_DATASOURCE, properties, serverSession, false);
DataSource nonjtaDataSource = null;
String nonjtaDataSourceName = null;
if (nonjtaDataSourceObj != null) {
if (nonjtaDataSourceObj instanceof DataSource) {
nonjtaDataSource = (DataSource) nonjtaDataSourceObj;
} else if (nonjtaDataSourceObj instanceof String) {
nonjtaDataSourceName = (String) nonjtaDataSourceObj;
}
}
if (user != null || password != null || driver != null || connectionString != null || jtaDataSourceObj != null || nonjtaDataSourceObj != null) {
// Validation: Can't specify jdbcDriver, connectionString with a
// DataSource
boolean isDefaultConnectorRequired = isPropertyToBeAdded(driver) || isPropertyToBeAdded(connectionString);
boolean isJNDIConnectorRequired = isPropertyToBeAdded(jtaDataSource, jtaDataSourceName) || isPropertyToBeAdded(nonjtaDataSource, nonjtaDataSourceName);
if (isDefaultConnectorRequired && isJNDIConnectorRequired) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("entity_manager_properties_conflict_default_connector_vs_jndi_connector", new Object[] {}));
}
DatasourceLogin login = (DatasourceLogin) policy.getLogin();
if (login == null) {
if (policy.getPoolName() != null) {
login = (DatasourceLogin) serverSession.getConnectionPool(policy.getPoolName()).getLogin();
} else {
login = (DatasourceLogin) serverSession.getDatasourceLogin();
}
}
// Validation: Can't specify jdbcDriver, connectionString if
// externalTransactionController is used - this requires
// externalConnectionPooling
if (login.shouldUseExternalTransactionController() && isDefaultConnectorRequired) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("entity_manager_properties_conflict_default_connector_vs_external_transaction_controller", new Object[] {}));
}
javax.sql.DataSource dataSource = null;
String dataSourceName = null;
if (isJNDIConnectorRequired) {
if (login.shouldUseExternalTransactionController()) {
if (isPropertyToBeAdded(jtaDataSource, jtaDataSourceName)) {
dataSource = jtaDataSource;
dataSourceName = jtaDataSourceName;
}
// validation: Can't change externalTransactionController
// state - will ignore data source that doesn't match the
// flag.
if (isPropertyToBeAdded(nonjtaDataSource, nonjtaDataSourceName)) {
serverSession.log(SessionLog.WARNING, SessionLog.PROPERTIES, "entity_manager_ignores_nonjta_data_source");
}
} else {
if (isPropertyToBeAdded(nonjtaDataSource, nonjtaDataSourceName)) {
dataSource = nonjtaDataSource;
dataSourceName = nonjtaDataSourceName;
}
// validation: Can't change externalTransactionController
// state - will ignore data source that doesn't match the
// flag.
if (isPropertyToBeAdded(jtaDataSource, jtaDataSourceName)) {
serverSession.log(SessionLog.WARNING, SessionLog.PROPERTIES, "entity_manager_ignores_jta_data_source");
}
}
}
// isNew...Required == null means no change required; TRUE -
// newValue substitute oldValue by newValue; FALSE - remove
// oldValue.
Boolean isNewUserRequired = isPropertyValueToBeUpdated(login.getUserName(), user);
// if isNewUserRequired==null then isNewPasswordRequired==null, too:
// don't create a new ConnectionPolicy if the same user/password passed to both createEMF and createEM
Boolean isNewPasswordRequired = null;
// if user name should be removed from properties then password
// should be removed, too.
if (isNewUserRequired != null) {
if(isNewUserRequired) {
if(password != null) {
// can't compare the passed (un-encrypted) password with the existing encrypted one, therefore
// use the new password if it's not an empty string.
isNewPasswordRequired = password.length() > 0;
}
} else {
// user should be removed -> remove password as well
isNewPasswordRequired = Boolean.FALSE;
}
}
DefaultConnector oldDefaultConnector = null;
if (login.getConnector() instanceof DefaultConnector) {
oldDefaultConnector = (DefaultConnector) login.getConnector();
}
boolean isNewDefaultConnectorRequired = oldDefaultConnector == null && isDefaultConnectorRequired;
JNDIConnector oldJNDIConnector = null;
if (login.getConnector() instanceof JNDIConnector) {
oldJNDIConnector = (JNDIConnector) login.getConnector();
}
boolean isNewJNDIConnectorRequired = oldJNDIConnector == null && isJNDIConnectorRequired;
Boolean isNewDriverRequired = null;
Boolean isNewConnectionStringRequired = null;
if (isNewDefaultConnectorRequired) {
isNewDriverRequired = isPropertyValueToBeUpdated(null, driver);
isNewConnectionStringRequired = isPropertyValueToBeUpdated(null, connectionString);
} else {
if (oldDefaultConnector != null) {
isNewDriverRequired = isPropertyValueToBeUpdated(oldDefaultConnector.getDriverClassName(), driver);
isNewConnectionStringRequired = isPropertyValueToBeUpdated(oldDefaultConnector.getConnectionString(), connectionString);
}
}
Boolean isNewDataSourceRequired = null;
if (isNewJNDIConnectorRequired) {
isNewDataSourceRequired = Boolean.TRUE;
} else {
if (oldJNDIConnector != null) {
if (dataSource != null) {
if (!dataSource.equals(oldJNDIConnector.getDataSource())) {
isNewDataSourceRequired = Boolean.TRUE;
}
} else if (dataSourceName != null) {
if (!dataSourceName.equals(oldJNDIConnector.getName())) {
isNewDataSourceRequired = Boolean.TRUE;
}
}
}
}
if (isNewUserRequired != null || isNewPasswordRequired != null || isNewDriverRequired != null || isNewConnectionStringRequired != null || isNewDataSourceRequired != null) {
// a new login required - so a new policy required, too.
if (newPolicy == null) {
newPolicy = (ConnectionPolicy) policy.clone();
}
// the new policy must have a new login - not to override the
// existing one in the original ConnectionPolicy that is likely
// shared.
DatasourceLogin newLogin = (DatasourceLogin) newPolicy.getLogin();
// sometimes ConnectionPolicy.clone clones the login , too -
// sometimes it doesn't.
if (newPolicy.getLogin() == null || newPolicy.getLogin() == policy.getLogin()) {
newLogin = login.clone();
newPolicy.setLogin(newLogin);
}
// because it uses a new login the connection policy should not
// be pooled.
newPolicy.setPoolName(null);
if (isNewUserRequired != null) {
if (isNewUserRequired) {
newLogin.setProperty("user", user);
} else {
newLogin.getProperties().remove("user");
}
}
if (isNewPasswordRequired != null) {
if (isNewPasswordRequired) {
newLogin.setProperty("password", password);
} else {
newLogin.getProperties().remove("password");
}
}
if (isNewDefaultConnectorRequired) {
newLogin.setConnector(new DefaultConnector());
newLogin.setUsesExternalConnectionPooling(false);
} else if (isNewJNDIConnectorRequired) {
newLogin.setConnector(new JNDIConnector());
newLogin.setUsesExternalConnectionPooling(true);
}
if (isDefaultConnectorRequired) {
DefaultConnector defaultConnector = (DefaultConnector) newLogin.getConnector();
if (isNewDriverRequired != null) {
if (isNewDriverRequired) {
defaultConnector.setDriverClassName(driver);
} else {
defaultConnector.setDriverClassName(null);
}
}
if (isNewConnectionStringRequired != null) {
if (isNewConnectionStringRequired) {
defaultConnector.setDatabaseURL(connectionString);
} else {
defaultConnector.setDatabaseURL(null);
}
}
} else if (isNewDataSourceRequired != null) {
JNDIConnector jndiConnector = (JNDIConnector) newLogin.getConnector();
if (isNewDataSourceRequired) {
if (dataSource != null) {
jndiConnector.setDataSource(dataSource);
} else {
// dataSourceName != null
jndiConnector.setDataSource(null);
jndiConnector.setName(dataSourceName);
}
}
}
}
}
if (newPolicy != null) {
return newPolicy;
} else {
return policy;
}
}
/**
* Indicates whether the underlying session is a session broker.
* Session broker implement composite persistence unit.
*/
@Override
public boolean isBroker() {
return this.databaseSession.isBroker();
}
/**
* Property value is to be added if it's non null and not an empty string.
*/
protected static boolean isPropertyToBeAdded(String value) {
return value != null && value.length() > 0;
}
protected static boolean isPropertyToBeAdded(DataSource ds, String dsName) {
return ds != null || (dsName != null && dsName.length() > 0);
}
/**
* Property value of an empty string indicates that the existing property
* should be removed.
*/
protected static boolean isPropertyToBeRemoved(String value) {
return value != null && value.length() == 0;
}
/**
* @return null: no change; TRUE: substitute oldValue by newValue; FALSE:
* remove oldValue
*/
protected static Boolean isPropertyValueToBeUpdated(String oldValue, String newValue) {
if (newValue == null) {
// no new value - no change
return null;
} else {
// new value is a non empty string
if (newValue.length() > 0) {
if (oldValue != null) {
if (newValue.equals(oldValue)) {
// new and old values are equal - no change.
return null;
} else {
// new and old values are different - change old value
// for new value.
return Boolean.TRUE;
}
} else {
// no old value - change for new value.
return Boolean.TRUE;
}
} else {
// new value is an empty string - if old value exists it should
// be substituted with new value..
if (oldValue != null) {
return Boolean.FALSE;
} else {
return null;
}
}
}
}
/**
* Remove the given entity from the persistence context, causing a managed
* entity to become detached. Unflushed changes made to the entity if any
* (including removal of the entity), will not be synchronized to the
* database. Entities which previously referenced the detached entity will
* continue to reference it.
*
* @throws IllegalArgumentException
* if the instance is not an entity
*
* @since Java Persistence 2.0
*/
@Override
public void detach(Object entity) {
try {
verifyOpen();
if (entity == null) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { null }));
}
ClassDescriptor descriptor = this.databaseSession.getDescriptors().get(entity.getClass());
if (descriptor == null || descriptor.isDescriptorTypeAggregate()) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { entity }));
}
UnitOfWorkImpl uowImpl = (UnitOfWorkImpl) getUnitOfWork();
uowImpl.unregisterObject(entity, 0, true);
} catch (RuntimeException exception) {
setRollbackOnly();
throw exception;
}
}
/**
* Return an instance of CriteriaBuilder for the creation of
* Criteria API Query objects.
* @return CriteriaBuilder instance
* @throws IllegalStateException if the entity manager has
* been closed.
* @see jakarta.persistence.EntityManager#getCriteriaBuilder()
* @since Java Persistence 2.0
*/
@Override
public CriteriaBuilder getCriteriaBuilder() {
verifyOpenWithSetRollbackOnly();
// defer to the parent entityManagerFactory
return this.factory.getCriteriaBuilder();
}
/**
* Before any find or refresh operation, gather any persistence unit
* properties that should be applied to the query.
*/
protected HashMap<String, Object> getQueryHints(Object entity, OperationType operation) {
HashMap<String, Object> queryHints = null;
// If the entity is null or there are no properties just return null.
// Individual methods will handle the entity = null case, although we
// could likely do it here as well.
if (entity != null && properties != null) {
queryHints = new HashMap<String, Object>();
if (properties.containsKey(QueryHints.PESSIMISTIC_LOCK_TIMEOUT)) {
queryHints.put(QueryHints.PESSIMISTIC_LOCK_TIMEOUT, properties.get(QueryHints.PESSIMISTIC_LOCK_TIMEOUT));
}
if (properties.containsKey(QueryHints.PESSIMISTIC_LOCK_TIMEOUT_UNIT)) {
queryHints.put(QueryHints.PESSIMISTIC_LOCK_TIMEOUT_UNIT, properties.get(QueryHints.PESSIMISTIC_LOCK_TIMEOUT_UNIT));
}
// Ignore the JPA cache settings if the eclipselink setting has
// been specified.
if (! properties.containsKey(QueryHints.CACHE_USAGE)) {
// If the descriptor is isolated then it is not cacheable so ignore
// the properties. A null descriptor case will be handled in the
// individual operation methods so no need to worry about it here.
Class cls = entity instanceof Class ? (Class) entity : entity.getClass();
ClassDescriptor descriptor = getActiveSession().getDescriptor(cls);
if (descriptor != null && ! descriptor.isIsolated()) {
if (operation != OperationType.LOCK) {
// For a find operation, apply the jakarta.persistence.cache.retrieveMode
if (operation == OperationType.FIND) {
if (properties.containsKey(QueryHints.CACHE_RETRIEVE_MODE)) {
queryHints.put(QueryHints.CACHE_RETRIEVE_MODE, properties.get(QueryHints.CACHE_RETRIEVE_MODE));
} else if (properties.containsKey("jakarta.persistence.cacheRetrieveMode")) { // support legacy property
Session activeSession = getActiveSession();
if (activeSession != null) {
// log deprecation info
String[] properties = new String[] { QueryHints.CACHE_RETRIEVE_MODE, "jakarta.persistence.cacheRetrieveMode" };
((AbstractSession)activeSession).log(SessionLog.INFO, SessionLog.TRANSACTION, "deprecated_property", properties);
}
queryHints.put(QueryHints.CACHE_RETRIEVE_MODE, properties.get("jakarta.persistence.cacheRetrieveMode"));
}
}
// For both find and refresh operations, apply jakarta.persistence.cache.storeMode
if (properties.containsKey(QueryHints.CACHE_STORE_MODE)) {
queryHints.put(QueryHints.CACHE_STORE_MODE, properties.get(QueryHints.CACHE_STORE_MODE));
} else if (properties.containsKey("jakarta.persistence.cacheStoreMode")) { // support legacy property
Session activeSession = getActiveSession();
if (activeSession != null) {
// log deprecation info
String[] properties = new String[] { QueryHints.CACHE_STORE_MODE, "jakarta.persistence.cacheStoreMode" };
((AbstractSession)activeSession).log(SessionLog.INFO, SessionLog.TRANSACTION, "deprecated_property", properties);
}
queryHints.put(QueryHints.CACHE_STORE_MODE, properties.get("jakarta.persistence.cacheStoreMode"));
}
}
}
}
}
return queryHints;
}
/**
* Return an instance of Metamodel interface for access to the
* metamodel of the persistence unit.
* @return Metamodel instance
* @throws IllegalStateException if the entity manager has
* been closed.
* @see jakarta.persistence.EntityManager#getMetamodel()
* @since Java Persistence 2.0
*/
@Override
public Metamodel getMetamodel() {
verifyOpenWithSetRollbackOnly();
// defer to the parent entityManagerFactory
return this.factory.getMetamodel();
}
/**
* Return the entity manager factory for the entity manager.
*
* @return EntityManagerFactory instance
* @throws IllegalStateException
* if the entity manager has been closed.
*
* @since Java Persistence API 2.0
*/
@Override
public EntityManagerFactory getEntityManagerFactory() {
try {
verifyOpen();
return factory;
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
}
}
/**
* @see jakarta.persistence.EntityManager#getLockMode(java.lang.Object)
* @since Java Persistence API 2.0
*/
@Override
public LockModeType getLockMode(Object entity) {
try {
verifyOpen();
checkForTransaction(true);
UnitOfWorkImpl uowImpl = getActivePersistenceContext(checkForTransaction(false));
LockModeType lockMode = LockModeType.NONE;
if (!contains(entity, uowImpl)) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cant_getLockMode_of_not_managed_object", new Object[] { entity }));
}
Boolean optimistickLock = (Boolean) uowImpl.getOptimisticReadLockObjects().get(entity);
if (optimistickLock != null) {
if (optimistickLock.equals(Boolean.FALSE)) {
lockMode = LockModeType.OPTIMISTIC;
} else {
// The entity is present in the map and its version is
// marked for increment.
// The lockMode can be OPTIMISTIC_FORCE_INCREMENT ||
// PESSIMISTIC_FORCE_INCREMENT
if (uowImpl.getPessimisticLockedObjects().get(entity) != null) {
lockMode = LockModeType.PESSIMISTIC_FORCE_INCREMENT;
} else {
lockMode = LockModeType.OPTIMISTIC_FORCE_INCREMENT;
}
}
} else { // Not optimistically locked
if (uowImpl.getPessimisticLockedObjects().get(entity) != null) {
lockMode = LockModeType.PESSIMISTIC_WRITE;
}
}
return lockMode;
} catch (RuntimeException exception) {
setRollbackOnly();
throw exception;
}
}
/**
* Get the properties and associated values that are in effect for the
* entity manager. Changing the contents of the map does not change the
* configuration in effect.
*
* @since Java Persistence API 2.0
*/
@Override
public Map<String, Object> getProperties() {
Map<String, Object> sessionMap = new HashMap<>(getAbstractSession().getProperties());
if (this.properties != null) {
sessionMap.putAll(this.properties);
}
return Collections.unmodifiableMap(sessionMap);
}
/**
* Get the names of the properties that are supported for use with the
* entity manager. These correspond to properties and hints that may be
* passed to the methods of the EntityManager interface that take a
* properties argument or used with the PersistenceContext annotation. These
* properties include all standard entity manager hints and properties as
* well as vendor-specific ones supported by the provider. These properties
* may or may not currently be in effect.
*
* @return property names
*
* @since Java Persistence API 2.0
*/
public Set<String> getSupportedProperties() {
return EntityManagerProperties.getSupportedProperties();
}
/**
* Return an object of the specified type to allow access to the
* provider-specific API. If the provider's EntityManager implementation
* does not support the specified class, the PersistenceException is thrown.
*
* @param cls
* the class of the object to be returned. This is normally
* either the underlying EntityManager implementation class or an
* interface that it implements.
* @return an instance of the specified class
* @throws PersistenceException
* if the provider does not support the call.
*
* @since Java Persistence API 2.0
*/
@Override
public <T> T unwrap(Class<T> cls) {
try {
if (cls.equals(UnitOfWork.class) || cls.equals(UnitOfWorkImpl.class) || cls.equals(RepeatableWriteUnitOfWork.class)) {
return (T) this.getUnitOfWork();
} else if (cls.equals(JpaEntityManager.class) || cls.equals(EntityManagerImpl.class)) {
return (T) this;
} else if (cls.equals(Session.class) || cls.equals(AbstractSession.class)) {
return (T) this.getAbstractSession();
} else if (cls.equals(DatabaseSession.class) || cls.equals(DatabaseSessionImpl.class)) {
return (T) this.getDatabaseSession();
} else if (cls.equals(Server.class) || cls.equals(ServerSession.class)) {
return (T) this.getServerSession();
} else if (cls.equals(SessionBroker.class)) {
return (T) this.getSessionBroker();
} else if (cls.equals(java.sql.Connection.class)) {
final UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl) this.getUnitOfWork();
Accessor accessor = unitOfWork.getAccessor();
if (unitOfWork.getParent().isExclusiveIsolatedClientSession()) {
// If the ExclusiveIsolatedClientSession hasn't serviced a query prior to the unwrap,
// there will be no available Connection.
java.sql.Connection conn = accessor.getConnection();
if (conn == null) {
final boolean uowInTran = unitOfWork.isInTransaction();
final boolean activeTran = checkForTransaction(false) != null;
if (uowInTran || activeTran) {
if (activeTran) {
unitOfWork.beginEarlyTransaction();
}
accessor.incrementCallCount(unitOfWork.getParent());
accessor.decrementCallCount();
conn = accessor.getConnection();
}
// if not in a tx, still return null
}
return (T) conn;
} else if (unitOfWork.isInTransaction()) {
return (T) unitOfWork.getAccessor().getConnection();
}
if (checkForTransaction(false) != null) {
unitOfWork.beginEarlyTransaction();
accessor = unitOfWork.getAccessor();
// Ensure external connection is acquired.
accessor.incrementCallCount(unitOfWork.getParent());
accessor.decrementCallCount();
return (T) accessor.getConnection();
}
return null;
} else if (cls.getName().equals("jakarta.resource.cci.Connection")) {
UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl) this.getUnitOfWork();
if(unitOfWork.isInTransaction() || unitOfWork.getParent().isExclusiveIsolatedClientSession()) {
return (T) unitOfWork.getAccessor().getConnection();
}
if (checkForTransaction(false) != null) {
unitOfWork.beginEarlyTransaction();
Accessor accessor = unitOfWork.getAccessor();
// Ensure external connection is acquired.
accessor.incrementCallCount(unitOfWork.getParent());
accessor.decrementCallCount();
return (T) accessor.getDatasourceConnection();
}
return null;
}
throw new PersistenceException(ExceptionLocalization.buildMessage("unable_to_unwrap_jpa", new String[]{EntityManager.class.getName(), cls.getName()}));
} catch (RuntimeException e) {
throw e;
}
}
/**
* This method will load the passed entity or collection of entities using the passed AttributeGroup.
* In case of collection all members should be either entities of the same type
* or have a common inheritance hierarchy mapped root class.
* The AttributeGroup should correspond to the entity type.
*
*/
@Override
public void load(Object entityOrEntities, AttributeGroup group) {
verifyOpen();
getActivePersistenceContext(checkForTransaction(false)).load(entityOrEntities, group);
}
/**
* This method will return copy the passed entity using the passed AttributeGroup.
* In case of collection all members should be either entities of the same type
* or have a common inheritance hierarchy mapped root class.
* The AttributeGroup should correspond to the entity type.
*
*/
@Override
public Object copy(Object entityOrEntities, AttributeGroup group) {
verifyOpen();
return getActivePersistenceContext(checkForTransaction(false)).copy(entityOrEntities, group);
}
/**
* INTERNAL:
* Load/fetch the unfetched object. This method is used by the ClassWaver..
*/
public static void processUnfetchedAttribute(FetchGroupTracker entity, String attributeName) {
String errorMsg = entity._persistence_getFetchGroup().onUnfetchedAttribute(entity, attributeName);
if(errorMsg != null) {
throw new jakarta.persistence.EntityNotFoundException(errorMsg);
}
}
/**
* INTERNAL:
* Load/fetch the unfetched object. This method is used by the ClassWeaver.
*/
public static void processUnfetchedAttributeForSet(FetchGroupTracker entity, String attributeName) {
String errorMsg = entity._persistence_getFetchGroup().onUnfetchedAttributeForSet(entity, attributeName);
if(errorMsg != null) {
throw new jakarta.persistence.EntityNotFoundException(errorMsg);
}
}
@Override
public Query createQuery(CriteriaUpdate updateQuery) {
try{
verifyOpen();
return new EJBQueryImpl(((CriteriaUpdateImpl)updateQuery).translate(), this);
}catch (RuntimeException e){
setRollbackOnly();
throw e;
}
}
@Override
public Query createQuery(CriteriaDelete deleteQuery) {
try{
verifyOpen();
return new EJBQueryImpl(((CriteriaDeleteImpl)deleteQuery).translate(), this);
}catch (RuntimeException e){
setRollbackOnly();
throw e;
}
}
@Override
public boolean isJoinedToTransaction() {
verifyOpen();
return transaction.isJoinedToTransaction(this.extendedPersistenceContext);
}
@Override
public <T> EntityGraph<T> createEntityGraph(Class<T> rootType) {
ClassDescriptor descriptor = getAbstractSession().getDescriptor(rootType);
if (descriptor == null || descriptor.isAggregateDescriptor()){
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("unknown_bean_class", new Object[]{rootType.getName()}));
}
return new EntityGraphImpl<T>(new AttributeGroup(null, rootType, true), descriptor);
}
@Override
public EntityGraph createEntityGraph(String graphName) {
AttributeGroup group = this.getAbstractSession().getAttributeGroups().get(graphName);
if (group == null){
return null;
}
ClassDescriptor descriptor = this.getAbstractSession().getDescriptor(group.getType());
return new EntityGraphImpl(group.clone(), descriptor);
}
@Override
public EntityGraph getEntityGraph(String graphName) {
AttributeGroup group = this.getAbstractSession().getAttributeGroups().get(graphName);
if (group == null){
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("no_entity_graph_of_name", new Object[]{graphName}));
}
return new EntityGraphImpl(group);
}
@Override
public <T> List<EntityGraph<? super T>> getEntityGraphs(Class<T> entityClass) {
ClassDescriptor descriptor = getAbstractSession().getDescriptor(entityClass);
if (descriptor == null || descriptor.isAggregateDescriptor()){
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("unknown_bean_class", new Object[]{entityClass.getName()}));
}
List<EntityGraph<? super T>> result = new ArrayList<EntityGraph<? super T>>();
for (AttributeGroup group : descriptor.getAttributeGroups().values()){
result.add(new EntityGraphImpl<>(group));
}
if (descriptor.hasInheritance()){
while(descriptor.getInheritancePolicy().getParentDescriptor() != null){
descriptor = descriptor.getInheritancePolicy().getParentDescriptor();
for (AttributeGroup group : descriptor.getAttributeGroups().values()){
result.add(new EntityGraphImpl<>(group));
}
}
}
return result;
}
/**
* INTERNAL:
* Tracks if this EntityManager should automatically associate with the transaction or not
* @return the syncType
*/
public SynchronizationType getSyncType() {
return syncType;
}
}