/******************************************************************************* * Copyright (c) 1998, 2016 Oracle and/or its affiliates, IBM Corporation. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink * 10/15/2010-2.2 Guy Pelletier * - 322008: Improve usability of additional criteria applied to queries at the session/EM * 06/30/2011-2.3.1 Guy Pelletier * - 341940: Add disable/enable allowing native queries * 09/09/2011-2.3.1 Guy Pelletier * - 356197: Add new VPD type to MultitenantType * 09/14/2011-2.3.1 Guy Pelletier * - 357533: Allow DDL queries to execute even when Multitenant entities are part of the PU * 05/14/2012-2.4 Guy Pelletier * - 376603: Provide for table per tenant support for multitenant applications * 08/11/2012-2.5 Guy Pelletier * - 393867: Named queries do not work when using EM level Table Per Tenant Multitenancy. * 11/29/2012-2.5 Guy Pelletier * - 395406: Fix nightly static weave test errors * 08/11/2014-2.5 Rick Curtis * - 440594: Tolerate invalid NamedQuery at EntityManager creation. * 09/03/2015 - Will Dazey * - 456067: Added support for defining query timeout units * 05/26/2016-2.7 Tomas Kraus * - 494610: Session Properties map should be Map<String, Object> ******************************************************************************/ package org.eclipse.persistence.internal.sessions; import java.io.Serializable; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Constructor; import java.security.AccessController; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import java.util.concurrent.TimeUnit; import org.eclipse.persistence.config.PersistenceUnitProperties; import org.eclipse.persistence.config.ReferenceMode; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.descriptors.DescriptorEvent; import org.eclipse.persistence.descriptors.DescriptorQueryManager; import org.eclipse.persistence.descriptors.TablePerMultitenantPolicy; import org.eclipse.persistence.descriptors.invalidation.CacheInvalidationPolicy; import org.eclipse.persistence.descriptors.partitioning.PartitioningPolicy; import org.eclipse.persistence.exceptions.ConcurrencyException; import org.eclipse.persistence.exceptions.DatabaseException; import org.eclipse.persistence.exceptions.EclipseLinkException; import org.eclipse.persistence.exceptions.ExceptionHandler; import org.eclipse.persistence.exceptions.IntegrityChecker; import org.eclipse.persistence.exceptions.IntegrityException; import org.eclipse.persistence.exceptions.OptimisticLockException; import org.eclipse.persistence.exceptions.QueryException; import org.eclipse.persistence.exceptions.ValidationException; import org.eclipse.persistence.expressions.Expression; import org.eclipse.persistence.history.AsOfClause; import org.eclipse.persistence.indirection.ValueHolderInterface; import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession; import org.eclipse.persistence.internal.databaseaccess.Accessor; import org.eclipse.persistence.internal.databaseaccess.Platform; import org.eclipse.persistence.internal.descriptors.ObjectBuilder; import org.eclipse.persistence.internal.helper.ConcurrencyManager; import org.eclipse.persistence.internal.helper.Helper; import org.eclipse.persistence.internal.helper.QueryCounter; import org.eclipse.persistence.internal.helper.linkedlist.ExposedNodeLinkedList; import org.eclipse.persistence.internal.history.HistoricalSession; import org.eclipse.persistence.internal.identitymaps.CacheKey; import org.eclipse.persistence.internal.identitymaps.IdentityMapManager; import org.eclipse.persistence.internal.indirection.DatabaseValueHolder; import org.eclipse.persistence.internal.indirection.ProtectedValueHolder; import org.eclipse.persistence.internal.indirection.ProxyIndirectionPolicy; import org.eclipse.persistence.internal.localization.ExceptionLocalization; import org.eclipse.persistence.internal.queries.JoinedAttributeManager; import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; import org.eclipse.persistence.internal.security.PrivilegedClassForName; import org.eclipse.persistence.internal.security.PrivilegedGetConstructorFor; import org.eclipse.persistence.internal.security.PrivilegedInvokeConstructor; import org.eclipse.persistence.internal.security.PrivilegedNewInstanceFromClass; import org.eclipse.persistence.internal.sequencing.Sequencing; import org.eclipse.persistence.internal.sessions.cdi.DisabledInjectionManager; import org.eclipse.persistence.internal.sessions.cdi.InjectionManager; import org.eclipse.persistence.logging.DefaultSessionLog; import org.eclipse.persistence.logging.SessionLog; import org.eclipse.persistence.logging.SessionLogEntry; import org.eclipse.persistence.mappings.ForeignReferenceMapping; import org.eclipse.persistence.mappings.foundation.AbstractTransformationMapping; import org.eclipse.persistence.platform.database.DatabasePlatform; import org.eclipse.persistence.platform.server.ServerPlatform; import org.eclipse.persistence.queries.AttributeGroup; import org.eclipse.persistence.queries.Call; import org.eclipse.persistence.queries.DataModifyQuery; import org.eclipse.persistence.queries.DataReadQuery; import org.eclipse.persistence.queries.DatabaseQuery; import org.eclipse.persistence.queries.DeleteObjectQuery; import org.eclipse.persistence.queries.DoesExistQuery; import org.eclipse.persistence.queries.InsertObjectQuery; import org.eclipse.persistence.queries.JPAQueryBuilder; import org.eclipse.persistence.queries.JPQLCall; import org.eclipse.persistence.queries.ObjectBuildingQuery; import org.eclipse.persistence.queries.ObjectLevelReadQuery; import org.eclipse.persistence.queries.ReadAllQuery; import org.eclipse.persistence.queries.ReadObjectQuery; import org.eclipse.persistence.queries.SQLCall; import org.eclipse.persistence.queries.UpdateObjectQuery; import org.eclipse.persistence.queries.WriteObjectQuery; import org.eclipse.persistence.sessions.CopyGroup; import org.eclipse.persistence.sessions.DatabaseLogin; import org.eclipse.persistence.sessions.ExternalTransactionController; import org.eclipse.persistence.sessions.Login; import org.eclipse.persistence.sessions.ObjectCopyingPolicy; import org.eclipse.persistence.sessions.Project; import org.eclipse.persistence.sessions.SessionEventManager; import org.eclipse.persistence.sessions.SessionProfiler; import org.eclipse.persistence.sessions.coordination.Command; import org.eclipse.persistence.sessions.coordination.CommandManager; import org.eclipse.persistence.sessions.coordination.CommandProcessor; import org.eclipse.persistence.sessions.coordination.MetadataRefreshListener; import org.eclipse.persistence.sessions.serializers.Serializer; /** * Implementation of org.eclipse.persistence.sessions.Session * The public interface should be used. * @see org.eclipse.persistence.sessions.Session * * <p> * <b>Purpose</b>: Define the interface and common protocol of an EclipseLink compliant session. * <p> * <b>Description</b>: The session is the primary interface into EclipseLink, * the application should do all of its reading and writing of objects through the session. * The session also manages transactions and units of work. Normally the session * is passed and used by the application controller objects. Controller objects normally * sit behind the GUI and perform the business processes required for the application, * they should perform all explicit database access and database access should be avoided from * the domain object model. Do not use a globally accessible session instance, doing so does * not allow for multiple sessions. Multiple sessions may required when performing things like * data migration or multiple database access, as well the unit of work feature requires the usage * of multiple session instances. Although session is abstract, any users of its subclasses * should only cast the variables to Session to allow usage of any of its subclasses. * <p> * <b>Responsibilities</b>: * <ul> * <li> Connecting/disconnecting. * <li> Reading and writing objects. * <li> Transaction and unit of work support. * <li> Identity maps and caching. * </ul> * @see DatabaseSessionImpl */ public abstract class AbstractSession extends CoreAbstractSession<ClassDescriptor, Login, Platform, Project, SessionEventManager> implements org.eclipse.persistence.sessions.Session, CommandProcessor, Serializable, Cloneable { /** ExceptionHandler handles database exceptions. */ transient protected ExceptionHandler exceptionHandler; /** IntegrityChecker catch all the descriptor Exceptions. */ transient protected IntegrityChecker integrityChecker; /** The project stores configuration information, such as the descriptors and login. */ transient protected Project project; /** Ensure mutual exclusion of the session's transaction state across multiple threads.*/ transient protected ConcurrencyManager transactionMutex; /** Manages the live object cache.*/ protected IdentityMapAccessor identityMapAccessor; /** If Transactions were externally started */ protected boolean wasJTSTransactionInternallyStarted; /** The connection to the data store. */ transient protected Collection<Accessor> accessors; /** Allow the datasource platform to be cached. */ transient protected Platform platform; /** Stores predefine reusable queries.*/ transient protected Map<String, List<DatabaseQuery>> queries; /** * Stores predefined reusable AttributeGroups. */ protected Map<String, AttributeGroup> attributeGroups; /** Stores predefined not yet parsed JPQL queries.*/ protected boolean jpaQueriesProcessed = false; /** Resolves referential integrity on commits. */ transient protected CommitManager commitManager; /** Tool that log performance information. */ transient protected SessionProfiler profiler; /** Support being owned by a session broker. */ transient protected AbstractSession broker; /** Used to identify a session when using the session broker. */ protected String name; /** Keep track of active units of work. */ transient protected int numberOfActiveUnitsOfWork; /** * This collection will be used to store those objects that are currently locked * for the clone process. It should be populated with an EclipseLinkIdentityHashMap */ protected Map objectsLockedForClone; /** Destination for logged messages and SQL. */ transient protected SessionLog sessionLog; /** When logging the name of the session is typed: class name + system hashcode. */ transient protected String logSessionString; /** Stores the event listeners for this session. */ transient protected SessionEventManager eventManager; /** Allow for user defined properties. */ protected Map<String, Object> properties; /** Delegate that handles synchronizing a UnitOfWork with an external transaction. */ transient protected ExternalTransactionController externalTransactionController; /** Last descriptor accessed, use to optimize descriptor lookup. */ transient protected ClassDescriptor lastDescriptorAccessed; /** PERF: cache descriptors from project. */ transient protected Map<Class, ClassDescriptor> descriptors; /** PERF: cache table per tenant descriptors needing to be initialized per EM */ transient protected List<ClassDescriptor> tablePerTenantDescriptors; /** PERF: cache table per tenant queries needing to be initialized per EM */ transient protected List<DatabaseQuery> tablePerTenantQueries; // bug 3078039: move EJBQL alias > descriptor map from Session to Project (MWN) /** Used to determine If a session is in a Broker or not */ protected boolean isInBroker; /** * Used to connect this session to EclipseLink cluster for distributed command */ transient protected CommandManager commandManager; /** * PERF: Cache the write-lock check to avoid cost of checking in every register/clone. */ protected boolean shouldCheckWriteLock; /** * Determined whether changes should be propagated to an EclipseLink cluster */ protected boolean shouldPropagateChanges; /** Used to determine If a session is in a profile or not */ protected boolean isInProfile; /** PERF: Quick check if logging is OFF entirely. */ protected boolean isLoggingOff; /** PERF: Allow for finalizers to be enabled, currently enables client-session finalize. */ protected boolean isFinalizersEnabled; /** List of active command threads. */ transient protected ExposedNodeLinkedList activeCommandThreads; /** * Indicates whether the session is synchronized. * In case external transaction controller is used isSynchronized==true means * the session's jta connection will be freed during external transaction callback. */ protected boolean isSynchronized; /** * Stores the default reference mode that a UnitOfWork will use when referencing * managed objects. * @see org.eclipse.persistence.config.ReferenceMode */ protected ReferenceMode defaultReferenceMode; /** * Default pessimistic lock timeout value. */ protected Integer pessimisticLockTimeoutDefault; protected int queryTimeoutDefault; protected TimeUnit queryTimeoutUnitDefault; /** Allow a session to enable concurrent processing. */ protected boolean isConcurrent; /** * This map will hold onto class to static metamodel class references from JPA. */ protected Map<String, String> staticMetamodelClasses; /** temporarily holds a list of events that must be fired after the current operation completes. * Initialy created for postClone events. */ protected List<DescriptorEvent> deferredEvents; /** records that the UOW is executing deferred events. Events could cause operations to occur that may attempt to restart the event execution. This must be avoided*/ protected boolean isExecutingEvents; /** Allow queries to be targeted at specific connection pools. */ protected PartitioningPolicy partitioningPolicy; /** the MetadataRefreshListener is used with RCM to force a refresh of the metadata used within EntityManagerFactoryWrappers */ protected MetadataRefreshListener metadatalistener; /** Stores the set of multitenant context properties this session requires **/ protected Set<String> multitenantContextProperties; /** Store the query builder used to parse JPQL. */ transient protected JPAQueryBuilder queryBuilder; /** Set the Serializer to use by default for serialization. */ transient protected Serializer serializer; /** Allow CDI injection of entity listeners **/ transient protected InjectionManager injectionManager; /** * Indicates whether ObjectLevelReadQuery should by default use ResultSet Access optimization. * Optimization specified by the session is ignored if incompatible with other query settings. */ protected boolean shouldOptimizeResultSetAccess; /** * Indicates whether Session creation should tolerate an invalid NamedQuery. If true, an exception * will be thrown on .createNamedQuery(..) rather than at init time. */ protected boolean tolerateInvalidJPQL = false; /** * INTERNAL: * Create and return a new session. * This should only be called if the database login information is not know at the time of creation. * Normally it is better to call the constructor that takes the login information as an argument * so that the session can initialize itself to the platform information given in the login. */ protected AbstractSession() { this.name = ""; this.queryTimeoutUnitDefault = DescriptorQueryManager.DefaultTimeoutUnit; initializeIdentityMapAccessor(); // PERF - move to lazy init (3286091) } /** * INTERNAL: * Create a blank session, used for proxy session. */ protected AbstractSession(int nothing) { } /** * PUBLIC: * Create and return a new session. * By giving the login information on creation this allows the session to initialize itself * to the platform given in the login. This constructor does not return a connected session. * To connect the session to the database login() must be sent to it. The login(userName, password) * method may also be used to connect the session, this allows for the user name and password * to be given at login but for the other database information to be provided when the session is created. */ public AbstractSession(Login login) { this(new org.eclipse.persistence.sessions.Project(login)); } /** * PUBLIC: * Create and return a new session. * This constructor does not return a connected session. * To connect the session to the database login() must be sent to it. The login(userName, password) * method may also be used to connect the session, this allows for the user name and password * to be given at login but for the other database information to be provided when the session is created. */ public AbstractSession(org.eclipse.persistence.sessions.Project project) { this(); this.project = project; if (project.getDatasourceLogin() == null) { throw ValidationException.projectLoginIsNull(this); } // add the Project's queries as session queries for (DatabaseQuery query : project.getQueries()) { addQuery(query.getName(), query); } } /** * Return the Serializer to use by default for serialization. */ @Override public Serializer getSerializer() { return serializer; } /** * Set the Serializer to use by default for serialization. */ @Override public void setSerializer(Serializer serializer) { this.serializer = serializer; } /** * INTERNAL * Return the query builder used to parser JPQL. */ public JPAQueryBuilder getQueryBuilder() { if (this.queryBuilder == null) { AbstractSession parent = getParent(); if (parent != null) { this.queryBuilder = parent.getQueryBuilder(); } else { this.queryBuilder = buildDefaultQueryBuilder(); } } return this.queryBuilder; } /** * INTERNAL * Set the query builder used to parser JPQL. */ public void setQueryBuilder(JPAQueryBuilder queryBuilder) { this.queryBuilder = queryBuilder; } /** * INTERNAL * Build the JPQL builder based on session properties. */ protected JPAQueryBuilder buildDefaultQueryBuilder() { String queryBuilderClassName = (String)getProperty(PersistenceUnitProperties.JPQL_PARSER); if (queryBuilderClassName == null) { queryBuilderClassName = "org.eclipse.persistence.internal.jpa.jpql.HermesParser"; //queryBuilderClassName = "org.eclipse.persistence.queries.ANTLRQueryBuilder"; } String validation = (String)getProperty(PersistenceUnitProperties.JPQL_VALIDATION); JPAQueryBuilder builder = null; try { Class parserClass = null; // use class.forName() to avoid loading parser classes for JAXB // Use Class.forName not thread class loader to avoid class loader issues. if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){ parserClass = AccessController.doPrivileged(new PrivilegedClassForName(queryBuilderClassName)); builder = (JPAQueryBuilder)AccessController.doPrivileged(new PrivilegedNewInstanceFromClass(parserClass)); } else { parserClass = PrivilegedAccessHelper.getClassForName(queryBuilderClassName); builder = (JPAQueryBuilder)PrivilegedAccessHelper.newInstanceFromClass(parserClass); } } catch (Exception e) { throw new IllegalStateException("Could not load the JPQL parser class." /* TODO: Localize string */, e); } if (validation != null) { builder.setValidationLevel(validation); } return builder; } /** * INTERNAL: * PERF: Used for quick turning logging ON/OFF entirely. * @param loggingOff Logging is turned off when <code>true</code> * and turned on when <code>false</code>. */ public void setLoggingOff(final boolean loggingOff) { isLoggingOff = loggingOff; } /** * INTERNAL: * PERF: Used for quick check if logging is OFF entirely. */ public boolean isLoggingOff() { return isLoggingOff; } /** * INTERNAL: * Called by a sessions queries to obtain individual query ids. * CR #2698903 */ public long getNextQueryId() { return QueryCounter.getCount(); } /** * INTERNAL: * Return a unit of work for this session not registered with the JTS transaction. */ public UnitOfWorkImpl acquireNonSynchronizedUnitOfWork() { return acquireNonSynchronizedUnitOfWork(null); } /** * INTERNAL: * Return a unit of work for this session not registered with the JTS transaction. */ public UnitOfWorkImpl acquireNonSynchronizedUnitOfWork(ReferenceMode referenceMode) { setNumberOfActiveUnitsOfWork(getNumberOfActiveUnitsOfWork() + 1); UnitOfWorkImpl unitOfWork = new UnitOfWorkImpl(this, referenceMode); if (shouldLog(SessionLog.FINER, SessionLog.TRANSACTION)) { log(SessionLog.FINER, SessionLog.TRANSACTION, "acquire_unit_of_work_with_argument", String.valueOf(System.identityHashCode(unitOfWork))); } return unitOfWork; } /** * INTERNAL: * Constructs a HistoricalSession given a valid AsOfClause. */ @Override public org.eclipse.persistence.sessions.Session acquireHistoricalSession(AsOfClause clause) throws ValidationException { if ((clause == null) || (clause.getValue() == null)) { throw ValidationException.cannotAcquireHistoricalSession(); } if (!getProject().hasGenericHistorySupport() && !hasBroker() && ((getPlatform() == null) || !getPlatform().isOracle())) { throw ValidationException.historicalSessionOnlySupportedOnOracle(); } return new HistoricalSession(this, clause); } /** * PUBLIC: * Return a unit of work for this session. * The unit of work is an object level transaction that allows * a group of changes to be applied as a unit. * * @see UnitOfWorkImpl */ @Override public UnitOfWorkImpl acquireUnitOfWork() { UnitOfWorkImpl unitOfWork = acquireNonSynchronizedUnitOfWork(getDefaultReferenceMode()); unitOfWork.registerWithTransactionIfRequired(); return unitOfWork; } /** * PUBLIC: * Return a repeatable write unit of work for this session. * A repeatable write unit of work allows multiple writeChanges (flushes). * * @see RepeatableWriteUnitOfWork */ public RepeatableWriteUnitOfWork acquireRepeatableWriteUnitOfWork(ReferenceMode referenceMode) { return new RepeatableWriteUnitOfWork(this, referenceMode); } /** * PUBLIC: * Return a unit of work for this session. * The unit of work is an object level transaction that allows * a group of changes to be applied as a unit. * * @see UnitOfWorkImpl * @param referenceMode The reference type the UOW should use internally when * referencing Working clones. Setting this to WEAK means the UOW will use * weak references to reference clones that support active object change * tracking and hard references for deferred change tracked objects. * Setting to FORCE_WEAK means that all objects will be referenced by weak * references and if the application no longer references the clone the * clone may be garbage collected. If the clone * has uncommitted changes then those changes will be lost. */ @Override public UnitOfWorkImpl acquireUnitOfWork(ReferenceMode referenceMode) { UnitOfWorkImpl unitOfWork = acquireNonSynchronizedUnitOfWork(referenceMode); unitOfWork.registerWithTransactionIfRequired(); return unitOfWork; } /** * PUBLIC: * Add an alias for the descriptor */ public void addAlias(String alias, ClassDescriptor descriptor) { project.addAlias(alias, descriptor); } /** * INTERNAL: * Return all pre-defined not yet parsed EJBQL queries. */ @Override public void addJPAQuery(DatabaseQuery query) { getProject().addJPAQuery(query); } /** * INTERNAL: * Return all pre-defined not yet parsed EJBQL multitenant queries. */ public void addJPATablePerTenantQuery(DatabaseQuery query) { getProject().addJPATablePerTenantQuery(query); } /** * PUBLIC: * Return a set of multitenant context properties this session */ public void addMultitenantContextProperty(String contextProperty) { getMultitenantContextProperties().add(contextProperty); } /** * INTERNAL: * Add the query to the session queries. */ protected synchronized void addQuery(DatabaseQuery query, boolean nameMustBeUnique) { Vector queriesByName = (Vector)getQueries().get(query.getName()); if (queriesByName == null) { // lazily create Vector in Hashtable. queriesByName = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(); getQueries().put(query.getName(), queriesByName); } if (nameMustBeUnique){ // JPA addNamedQuery if (queriesByName.size() <= 1){ queriesByName.clear(); }else{ throw new IllegalStateException(ExceptionLocalization.buildMessage("argument_keyed_named_query_with_JPA", new Object[]{query.getName()})); } }else{ // Check that we do not already have a query that matched it for (Iterator enumtr = queriesByName.iterator(); enumtr.hasNext();) { DatabaseQuery existingQuery = (DatabaseQuery)enumtr.next(); if (Helper.areTypesAssignable(query.getArgumentTypes(), existingQuery.getArgumentTypes())) { throw ValidationException.existingQueryTypeConflict(query, existingQuery); } } } queriesByName.add(query); } /** * PUBLIC: * Add the query to the session queries with the given name. * This allows for common queries to be pre-defined, reused and executed by name. */ @Override public void addQuery(String name, DatabaseQuery query) { query.setName(name); addQuery(query, false); } /** * PUBLIC: * Add the query to the session queries with the given name. * This allows for common queries to be pre-defined, reused and executed by name. */ public void addQuery(String name, DatabaseQuery query, boolean replace) { query.setName(name); addQuery(query, replace); } /** * INTERNAL: * Add a metamodel class to model class reference. */ public void addStaticMetamodelClass(String modelClassName, String metamodelClassName) { if (staticMetamodelClasses == null) { staticMetamodelClasses = new HashMap<String, String>(); } staticMetamodelClasses.put(modelClassName, metamodelClassName); } /** * INTERNAL: * Add a descriptor that is uses a table per tenant multitenant policy. */ protected void addTablePerTenantDescriptor(ClassDescriptor descriptor) { getTablePerTenantDescriptors().add(descriptor); } /** * INTERNAL: * Add a query that queries a table per tenant entity */ protected void addTablePerTenantQuery(DatabaseQuery query) { getTablePerTenantQueries().add(query); } /** * INTERNAL: * Called by beginTransaction() to start a transaction. * This starts a real database transaction. * Allows retry if the connection is dead. */ protected void basicBeginTransaction() throws DatabaseException { Collection<Accessor> accessors = getAccessors(); if (accessors == null) { return; } Accessor failedAccessor = null; try { for (Accessor accessor : accessors) { failedAccessor = accessor; basicBeginTransaction(accessor); } } catch (RuntimeException exception) { // If begin failed, rollback ones already started. for (Accessor accessor : accessors) { if (accessor == failedAccessor) { break; } try { accessor.rollbackTransaction(this); } catch (RuntimeException ignore) { } } throw exception; } } /** * INTERNAL: * Called by beginTransaction() to start a transaction. * This starts a real database transaction. * Allows retry if the connection is dead. */ protected void basicBeginTransaction(Accessor accessor) throws DatabaseException { try { accessor.beginTransaction(this); } catch (DatabaseException databaseException) { // Retry if the failure was communication based? (i.e. timeout, database down, can no longer ping) if ((!getDatasourceLogin().shouldUseExternalTransactionController()) && databaseException.isCommunicationFailure()) { DatabaseException exceptionToThrow = databaseException; Object[] args = new Object[1]; args[0] = databaseException; log(SessionLog.INFO, SessionLog.TRANSACTION, "communication_failure_attempting_begintransaction_retry", args, null); // Attempt to reconnect connection. exceptionToThrow = retryTransaction(accessor, databaseException, 0, this); if (exceptionToThrow == null) { // Retry was a success. return; } handleException(exceptionToThrow); } else { handleException(databaseException); } } catch (RuntimeException exception) { handleException(exception); } } /** * INTERNAL: * A begin transaction failed. * Re-connect and retry the begin transaction. */ public DatabaseException retryTransaction(Accessor accessor, DatabaseException databaseException, int retryCount, AbstractSession executionSession) { DatabaseException exceptionToThrow = databaseException; // Attempt to reconnect connection. int count = getLogin().getQueryRetryAttemptCount(); while (retryCount < count) { try { // if database session then re-establish // connection // else the session will just get a new // connection from the pool accessor.reestablishConnection(this); accessor.beginTransaction(this); return null; } catch (DatabaseException newException) { // Need to use last exception, as may not be a communication exception. exceptionToThrow = newException; // failed to get connection because of // database error. ++retryCount; try { // Give the failover time to recover. Thread.currentThread().sleep(getLogin().getDelayBetweenConnectionAttempts()); Object[] args = new Object[1]; args[0] = databaseException; log(SessionLog.INFO, SessionLog.TRANSACTION, "communication_failure_attempting_begintransaction_retry", args, null); } catch (InterruptedException intEx) { break; } } } return exceptionToThrow; } /** * INTERNAL: * Called in the end of beforeCompletion of external transaction synchronization listener. * Close the managed sql connection corresponding to the external transaction, * if applicable releases accessor. */ public void releaseJTSConnection() { } /** * INTERNAL: * Called by commitTransaction() to commit a transaction. * This commits the active transaction. */ protected void basicCommitTransaction() throws DatabaseException { Collection<Accessor> accessors = getAccessors(); if (accessors == null) { return; } try { for (Accessor accessor : accessors) { accessor.commitTransaction(this); } } catch (RuntimeException exception) { // Leave transaction uncommitted as rollback should be called. handleException(exception); } } /** * INTERNAL: * Called by rollbackTransaction() to rollback a transaction. * This rolls back the active transaction. */ protected void basicRollbackTransaction() throws DatabaseException { Collection<Accessor> accessors = getAccessors(); if (accessors == null) { return; } RuntimeException exception = null; for (Accessor accessor : accessors) { try { accessor.rollbackTransaction(this); } catch (RuntimeException failure) { exception = failure; } } if (exception != null) { handleException(exception); } } /** * INTERNAL: * Attempts to begin an external transaction. * Returns true only in one case - * external transaction has been internally started during this method call: * wasJTSTransactionInternallyStarted()==false in the beginning of this method and * wasJTSTransactionInternallyStarted()==true in the end of this method. */ public boolean beginExternalTransaction() { boolean externalTransactionHasBegun = false; if (hasExternalTransactionController() && !wasJTSTransactionInternallyStarted()) { try { getExternalTransactionController().beginTransaction(this); } catch (RuntimeException exception) { handleException(exception); } if (wasJTSTransactionInternallyStarted()) { externalTransactionHasBegun = true; log(SessionLog.FINER, SessionLog.TRANSACTION, "external_transaction_has_begun_internally"); } } return externalTransactionHasBegun; } /** * PUBLIC: * Begin a transaction on the database. * This allows a group of database modification to be committed or rolled back as a unit. * All writes/deletes will be sent to the database be will not be visible to other users until commit. * Although databases do not allow nested transaction, * EclipseLink supports nesting through only committing to the database on the outer commit. * * @exception DatabaseException if the database connection is lost or the begin is rejected. * @exception ConcurrencyException if this session's transaction is acquired by another thread and a timeout occurs. * * @see #isInTransaction() */ public void beginTransaction() throws DatabaseException, ConcurrencyException { ConcurrencyManager mutex = getTransactionMutex(); // If there is no db transaction in progress // beginExternalTransaction() starts an external transaction - // provided externalTransactionController is used, and there is // no active external transaction - so we have to start one internally. if (!mutex.isAcquired()) { beginExternalTransaction(); } // For unit of work and client session multi threading is allowed as they are a context, // this is required for JTS/RMI/CORBA/EJB stuff where the server thread can be different across calls. if (isClientSession()) { mutex.setActiveThread(Thread.currentThread()); } // Ensure mutual exclusion and call subclass specific begin. mutex.acquire(); if (!mutex.isNested()) { if (this.eventManager != null) { this.eventManager.preBeginTransaction(); } basicBeginTransaction(); if (this.eventManager != null) { this.eventManager.postBeginTransaction(); } } } /** * Check to see if the descriptor of a superclass can be used to describe this class * * @param theClass * @return ClassDescriptor */ protected ClassDescriptor checkHierarchyForDescriptor(Class theClass){ return getDescriptor(theClass.getSuperclass()); } /** * allow the entity listener injection manager to clean itself up. */ public void cleanUpInjectionManager(){ if (injectionManager != null){ injectionManager.cleanUp(this); } } /** * PUBLIC: * clear the integrityChecker. IntegrityChecker holds all the ClassDescriptor Exceptions. */ @Override public void clearIntegrityChecker() { setIntegrityChecker(null); } /** * INTERNAL: * Clear the the lastDescriptorAccessed cache. */ public void clearLastDescriptorAccessed() { this.lastDescriptorAccessed = null; } /** * INTERNAL: * Clear the the descriptors cache. */ public void clearDescriptors() { this.descriptors = null; } /** * PUBLIC: * Clear the profiler, this will end the current profile operation. */ @Override public void clearProfile() { setProfiler(null); } /** * INTERNAL: * Clones the descriptor */ @Override public Object clone() { // An alternative to this process should be found try { return super.clone(); } catch (Exception exception) { throw new AssertionError(exception); } } /** * INTERNAL: * Attempts to commit the running internally started external transaction. * Returns true only in one case - * external transaction has been internally committed during this method call: * wasJTSTransactionInternallyStarted()==true in the beginning of this method and * wasJTSTransactionInternallyStarted()==false in the end of this method. */ public boolean commitExternalTransaction() { boolean externalTransactionHasCommitted = false; if (hasExternalTransactionController() && wasJTSTransactionInternallyStarted()) { try { getExternalTransactionController().commitTransaction(this); } catch (RuntimeException exception) { handleException(exception); } if (!wasJTSTransactionInternallyStarted()) { externalTransactionHasCommitted = true; log(SessionLog.FINER, SessionLog.TRANSACTION, "external_transaction_has_committed_internally"); } } return externalTransactionHasCommitted; } /** * PUBLIC: * Commit the active database transaction. * This allows a group of database modification to be committed or rolled back as a unit. * All writes/deletes will be sent to the database be will not be visible to other users until commit. * Although databases do not allow nested transaction, * EclipseLink supports nesting through only committing to the database on the outer commit. * * @exception DatabaseException most databases validate changes as they are done, * normally errors do not occur on commit unless the disk fails or the connection is lost. * @exception ConcurrencyException if this session is not within a transaction. */ public void commitTransaction() throws DatabaseException, ConcurrencyException { ConcurrencyManager mutex = getTransactionMutex(); // Release mutex and call subclass specific commit. if (!mutex.isNested()) { if (this.eventManager != null) { this.eventManager.preCommitTransaction(); } basicCommitTransaction(); if (this.eventManager != null) { this.eventManager.postCommitTransaction(); } } // This MUST not be in a try catch or finally as if the commit failed the transaction is still open. mutex.release(); // If there is no db transaction in progress // if there is an active external transaction // which was started internally - it should be committed internally, too. if (!mutex.isAcquired()) { commitExternalTransaction(); } } /** * INTERNAL: * Return if the two object match completely. * This checks the objects attributes and their private parts. */ public boolean compareObjects(Object firstObject, Object secondObject) { if ((firstObject == null) && (secondObject == null)) { return true; } if ((firstObject == null) || (secondObject == null)) { return false; } if (!(firstObject.getClass().equals(secondObject.getClass()))) { return false; } ObjectBuilder builder = getDescriptor(firstObject.getClass()).getObjectBuilder(); return builder.compareObjects(builder.unwrapObject(firstObject, this), builder.unwrapObject(secondObject, this), this); } /** * TESTING: * Return true if the object do not match. * This checks the objects attributes and their private parts. */ public boolean compareObjectsDontMatch(Object firstObject, Object secondObject) { return !this.compareObjects(firstObject, secondObject); } /** * PUBLIC: * Return true if the pre-defined query is defined on the session. */ @Override public boolean containsQuery(String queryName) { return getQueries().containsKey(queryName); } /** * PUBLIC: * Return a complete copy of the object or of collection of objects. * In case of collection all members should be either entities of the same type * or have a common inheritance hierarchy mapped root class. * This can be used to obtain a scratch copy of an object, * or for templatizing an existing object into another new object. * The object and all of its privately owned parts will be copied. * * @see #copy(Object, AttributeGroup) */ @Override public Object copy(Object originalObjectOrObjects) { return copy(originalObjectOrObjects, new CopyGroup()); } /** * PUBLIC: * Return a complete copy of the object or of collection of objects. * In case of collection all members should be either entities of the same type * or have a common inheritance hierarchy mapped root class. * This can be used to obtain a scratch copy of an object, * or for templatizing an existing object into another new object. * If there are no attributes in the group * then the object and all of its privately owned parts will be copied. * Otherwise only the attributes included into the group will be copied. */ @Override public Object copy(Object originalObjectOrObjects, AttributeGroup group) { if (originalObjectOrObjects == null) { return null; } CopyGroup copyGroup = group.toCopyGroup(); copyGroup.setSession(this); if(originalObjectOrObjects instanceof Collection) { // it's a collection - make sure all elements use the same instance of CopyGroup. Collection originalCollection = (Collection)originalObjectOrObjects; Collection copies; if(originalCollection instanceof List) { copies = new ArrayList(); } else { copies = new HashSet(); } Iterator it = originalCollection.iterator(); while(it.hasNext()) { copies.add(copyInternal(it.next(), copyGroup)); } return copies; } // it's not a collection return copyInternal(originalObjectOrObjects, copyGroup); } /** * INTERNAL: */ public Object copyInternal(Object originalObject, CopyGroup copyGroup) { if (originalObject == null) { return null; } ClassDescriptor descriptor = getDescriptor(originalObject); if (descriptor == null) { return originalObject; } return descriptor.getObjectBuilder().copyObject(originalObject, copyGroup); } /** * PUBLIC: * Return a complete copy of the object. * This can be used to obtain a scratch copy of an object, * or for templatizing an existing object into another new object. * The object and all of its privately owned parts will be copied, the object's primary key will be reset to null. * * @see #copyObject(Object, ObjectCopyingPolicy) * @deprecated since EclipseLink 2.1, replaced by copy(Object) * @see #copy(Object) */ @Deprecated @Override public Object copyObject(Object original) { CopyGroup copyGroup = new CopyGroup(); copyGroup.setShouldResetPrimaryKey(true); return copy(original, copyGroup); } /** * PUBLIC: * Return a complete copy of the object. * This can be used to obtain a scratch copy of an object, * or for templatizing an existing object into another new object. * The object copying policy allow for the depth, and reseting of the primary key to null, to be specified. * @deprecated since EclipseLink 2.1, replaced by copy(Object, AttributeGroup) * @see #copy(Object, AttributeGroup) */ @Deprecated @Override public Object copyObject(Object original, ObjectCopyingPolicy policy) { return copy(original, policy); } /** * INTERNAL: * Copy the read only classes from the unit of work * * Added Nov 8, 2000 JED for Patch 2.5.1.8 * Ref: Prs 24502 */ public Vector copyReadOnlyClasses() { return getDefaultReadOnlyClasses(); } public DatabaseValueHolder createCloneQueryValueHolder(ValueHolderInterface attributeValue, Object clone, AbstractRecord row, ForeignReferenceMapping mapping) { return new ProtectedValueHolder(attributeValue, mapping, this); } public DatabaseValueHolder createCloneTransformationValueHolder(ValueHolderInterface attributeValue, Object original, Object clone, AbstractTransformationMapping mapping) { return new ProtectedValueHolder(attributeValue, mapping, this); } public <T> InjectionManager<T> createInjectionManager(Object beanManager){ try{ if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){ Class elim = AccessController.doPrivileged(new PrivilegedClassForName(InjectionManager.DEFAULT_CDI_INJECTION_MANAGER, true, getLoader())); Constructor constructor = AccessController.doPrivileged(new PrivilegedGetConstructorFor(elim, new Class[] {String.class}, false)); return (InjectionManager<T>) AccessController.doPrivileged(new PrivilegedInvokeConstructor(constructor, new Object[] {beanManager})); } else { Class elim = org.eclipse.persistence.internal.security.PrivilegedAccessHelper.getClassForName(InjectionManager.DEFAULT_CDI_INJECTION_MANAGER, true, getLoader()); Constructor constructor = PrivilegedAccessHelper.getConstructorFor(elim, new Class[] {Object.class}, false); return (InjectionManager<T>) PrivilegedAccessHelper.invokeConstructor(constructor, new Object[] {beanManager}); } } catch (Exception e){ logThrowable(SessionLog.FINEST, SessionLog.JPA, e); } return new DisabledInjectionManager<>(); } /** * INTERNAL: * This method is similar to getAndCloneCacheKeyFromParent. It purpose is to get protected cache data from the shared cache and * build/return a protected instance. */ public Object createProtectedInstanceFromCachedData(Object cached, Integer refreshCascade, ClassDescriptor descriptor){ CacheKey localCacheKey = getIdentityMapAccessorInstance().getCacheKeyForObject(cached); if (localCacheKey != null && localCacheKey.getObject() != null){ return localCacheKey.getObject(); } boolean identityMapLocked = this.shouldCheckWriteLock && getParent().getIdentityMapAccessorInstance().acquireWriteLock(); boolean rootOfCloneRecursion = false; CacheKey cacheKey = getParent().getIdentityMapAccessorInstance().getCacheKeyForObject(cached); try{ Object key = null; Object lockValue = null; long readTime = 0; if (cacheKey != null){ if (identityMapLocked) { checkAndRefreshInvalidObject(cached, cacheKey, descriptor); } else { // Check if we have locked all required objects already. if (this.objectsLockedForClone == null) { // PERF: If a simple object just acquire a simple read-lock. if (descriptor.shouldAcquireCascadedLocks()) { this.objectsLockedForClone = getParent().getIdentityMapAccessorInstance().getWriteLockManager().acquireLocksForClone(cached, descriptor, cacheKey, this); } else { checkAndRefreshInvalidObject(cached, cacheKey, descriptor); cacheKey.acquireReadLock(); } rootOfCloneRecursion = true; } } key = cacheKey.getKey(); lockValue = cacheKey.getWriteLockValue(); readTime = cacheKey.getReadTime(); } if (descriptor.hasInheritance()){ descriptor = this.getClassDescriptor(cached.getClass()); } ObjectBuilder builder = descriptor.getObjectBuilder(); Object workingClone = builder.instantiateWorkingCopyClone(cached, this); // PERF: Cache the primary key if implements PersistenceEntity. builder.populateAttributesForClone(cached, cacheKey, workingClone, refreshCascade, this); getIdentityMapAccessorInstance().putInIdentityMap(workingClone, key, lockValue, readTime, descriptor); return workingClone; }finally{ if (rootOfCloneRecursion){ if (this.objectsLockedForClone == null && cacheKey != null) { cacheKey.releaseReadLock(); } else { for (Iterator iterator = this.objectsLockedForClone.values().iterator(); iterator.hasNext();) { ((CacheKey)iterator.next()).releaseReadLock(); } this.objectsLockedForClone = null; } executeDeferredEvents(); } } } /** * INTERNAL: * Check if the object is invalid and refresh it. * This is used to ensure that no invalid objects are registered. */ public void checkAndRefreshInvalidObject(Object object, CacheKey cacheKey, ClassDescriptor descriptor) { if (isConsideredInvalid(object, cacheKey, descriptor)) { ReadObjectQuery query = new ReadObjectQuery(); query.setReferenceClass(object.getClass()); query.setSelectionId(cacheKey.getKey()); query.refreshIdentityMapResult(); query.setIsExecutionClone(true); this.executeQuery(query); } } /** * INTERNAL: * Check if the object is invalid and *should* be refreshed. * This is used to ensure that no invalid objects are cloned. */ public boolean isConsideredInvalid(Object object, CacheKey cacheKey, ClassDescriptor descriptor) { if (cacheKey.getObject() != null) { CacheInvalidationPolicy cachePolicy = descriptor.getCacheInvalidationPolicy(); // BUG#6671556 refresh invalid objects when accessed in the unit of work. return (cachePolicy.shouldRefreshInvalidObjectsOnClone() && cachePolicy.isInvalidated(cacheKey)); } return false; } /** * INTERNAL: * Add an event to the deferred list. Events will be fired after the operation completes */ public void deferEvent(DescriptorEvent event){ if (this.deferredEvents == null){ this.deferredEvents = new ArrayList<DescriptorEvent>(); } this.deferredEvents.add(event); } /** * PUBLIC: * delete all of the objects and all of their privately owned parts in the database. * The allows for a group of objects to be deleted as a unit. * The objects will be deleted through a single transactions. * * @exception DatabaseException if an error occurs on the database, * these include constraint violations, security violations and general database errors. * @exception OptimisticLockException if the object's descriptor is using optimistic locking and * the object has been updated or deleted by another user since it was last read. */ public void deleteAllObjects(Collection domainObjects) throws DatabaseException, OptimisticLockException { for (Iterator objectsEnum = domainObjects.iterator(); objectsEnum.hasNext();) { deleteObject(objectsEnum.next()); } } /** * PUBLIC: * delete all of the objects and all of their privately owned parts in the database. * The allows for a group of objects to be deleted as a unit. * The objects will be deleted through a single transactions. * * @exception DatabaseException if an error occurs on the database, * these include constraint violations, security violations and general database errors. * @exception OptimisticLockException if the object's descriptor is using optimistic locking and * the object has been updated or deleted by another user since it was last read. */ @Deprecated public void deleteAllObjects(Vector domainObjects) throws DatabaseException, OptimisticLockException { for (Enumeration objectsEnum = domainObjects.elements(); objectsEnum.hasMoreElements();) { deleteObject(objectsEnum.nextElement()); } } /** * PUBLIC: * Delete the object and all of its privately owned parts from the database. * The delete operation can be customized through using a delete query. * * @exception DatabaseException if an error occurs on the database, * these include constraint violations, security violations and general database errors. * An database error is not raised if the object is already deleted or no rows are effected. * @exception OptimisticLockException if the object's descriptor is using optimistic locking and * the object has been updated or deleted by another user since it was last read. * * @see DeleteObjectQuery */ public Object deleteObject(Object domainObject) throws DatabaseException, OptimisticLockException { DeleteObjectQuery query = new DeleteObjectQuery(); query.setObject(domainObject); query.setIsExecutionClone(true); return executeQuery(query); } /** * PUBLIC: * Return if the object exists on the database or not. * This always checks existence on the database. */ @Override public boolean doesObjectExist(Object object) throws DatabaseException { DoesExistQuery query = new DoesExistQuery(); query.setObject(object); query.checkDatabaseForDoesExist(); query.setIsExecutionClone(true); return ((Boolean)executeQuery(query)).booleanValue(); } /** * PUBLIC: * Turn off logging */ @Override public void dontLogMessages() { setLogLevel(SessionLog.OFF); } /** * INTERNAL: * End the operation timing. */ @Override public void endOperationProfile(String operationName) { if (this.isInProfile) { getProfiler().endOperationProfile(operationName); } } /** * INTERNAL: * End the operation timing. */ public void endOperationProfile(String operationName, DatabaseQuery query, int weight) { if (this.isInProfile) { getProfiler().endOperationProfile(operationName, query, weight); } } /** * INTERNAL: * Updates the value of SessionProfiler state */ @Override public void updateProfile(String operationName, Object value) { if (this.isInProfile) { getProfiler().update(operationName, value); } } /** * INTERNAL: * Set the table per tenant. This should be called per client session after * the start of a transaction. From JPA this method is called on the entity * manager by setting the multitenant table per tenant property. */ public void updateTablePerTenantDescriptors(String property, Object value) { // When all the table per tenant descriptors are set, we should initialize them. boolean shouldInitializeDescriptors = hasTablePerTenantDescriptors(); for (ClassDescriptor descriptor : getTablePerTenantDescriptors()) { TablePerMultitenantPolicy policy = (TablePerMultitenantPolicy) descriptor.getMultitenantPolicy(); if ((! policy.hasContextTenant()) && policy.usesContextProperty(property)) { policy.setContextTenant((String) value); } shouldInitializeDescriptors = shouldInitializeDescriptors && policy.hasContextTenant(); } if (shouldInitializeDescriptors) { // Now that the correct tables are set on all table per tenant // descriptors, we can go through the initialization phases safely. try { // First initialize basic properties (things that do not depend on anything else) for (ClassDescriptor descriptor : tablePerTenantDescriptors) { descriptor.preInitialize(this); } // Second initialize basic mappings for (ClassDescriptor descriptor : tablePerTenantDescriptors) { descriptor.initialize(this); } // Third initialize child dependencies for (ClassDescriptor descriptor : tablePerTenantDescriptors) { descriptor.postInitialize(this); } if (getIntegrityChecker().hasErrors()) { handleSevere(new IntegrityException(getIntegrityChecker())); } } finally { clearIntegrityChecker(); } getCommitManager().initializeCommitOrder(); // If we have table per tenant queries, initialize and add them now // once all the descriptors have been initialized. if (hasTablePerTenantQueries()) { for (DatabaseQuery query : getTablePerTenantQueries()) { processJPAQuery(query); } } } } /** * INTERNAL: * Updates the count of SessionProfiler event */ @Override public void incrementProfile(String operationName) { if (this.isInProfile) { getProfiler().occurred(operationName, this); } } /** * INTERNAL: * Updates the count of SessionProfiler event */ public void incrementProfile(String operationName, DatabaseQuery query) { if (this.isInProfile) { getProfiler().occurred(operationName, query, this); } } /** * INTERNAL: * Causes any deferred events to be fired. Called after operation completes */ public void executeDeferredEvents(){ if (!this.isExecutingEvents && this.deferredEvents != null) { this.isExecutingEvents = true; try { for (int i = 0; i < this.deferredEvents.size(); ++i) { // the size is checked every time here because the list may grow DescriptorEvent event = this.deferredEvents.get(i); event.getDescriptor().getEventManager().executeEvent(event); } this.deferredEvents.clear(); } finally { this.isExecutingEvents = false; } } } /** * INTERNAL: * Overridden by subclasses that do more than just execute the call. * Executes the call directly on this session and does not check which * session it should have executed on. */ public Object executeCall(Call call, AbstractRecord translationRow, DatabaseQuery query) throws DatabaseException { if (query.getAccessors() == null) { query.setAccessors(getAccessors()); } try { return basicExecuteCall(call, translationRow, query); } finally { if (call.isFinished()) { query.setAccessors(null); } } } /** * INTERNAL: * Release (if required) connection after call. * @param query * @return */ public void releaseConnectionAfterCall(DatabaseQuery query) { } /** * PUBLIC: * Execute the call on the database. * The row count is returned. * The call can be a stored procedure call, SQL call or other type of call. * <p>Example: * <p>session.executeNonSelectingCall(new SQLCall("Delete from Employee"); * * @see #executeSelectingCall(Call) */ @Override public int executeNonSelectingCall(Call call) throws DatabaseException { DataModifyQuery query = new DataModifyQuery(); query.setIsExecutionClone(true); query.setCall(call); Integer value = (Integer)executeQuery(query); if (value == null) { return 0; } else { return value.intValue(); } } /** * PUBLIC: * Execute the sql on the database. * <p>Example: * <p>session.executeNonSelectingSQL("Delete from Employee"); * @see #executeNonSelectingCall(Call) * Warning: Allowing an unverified SQL string to be passed into this * method makes your application vulnerable to SQL injection attacks. */ @Override public void executeNonSelectingSQL(String sqlString) throws DatabaseException { executeNonSelectingCall(new SQLCall(sqlString)); } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * * @see #addQuery(String, DatabaseQuery) */ @Override public Object executeQuery(String queryName) throws DatabaseException { DatabaseQuery query = getQuery(queryName); if (query == null) { throw QueryException.queryNotDefined(queryName); } return executeQuery(query); } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * The class is the descriptor in which the query was pre-defined. * * @see DescriptorQueryManager#addQuery(String, DatabaseQuery) */ @Override public Object executeQuery(String queryName, Class domainClass) throws DatabaseException { ClassDescriptor descriptor = getDescriptor(domainClass); if (descriptor == null) { throw QueryException.descriptorIsMissingForNamedQuery(domainClass, queryName); } DatabaseQuery query = descriptor.getQueryManager().getQuery(queryName); if (query == null) { throw QueryException.queryNotDefined(queryName, domainClass); } return executeQuery(query); } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * The class is the descriptor in which the query was pre-defined. * * @see DescriptorQueryManager#addQuery(String, DatabaseQuery) */ @Override public Object executeQuery(String queryName, Class domainClass, Object arg1) throws DatabaseException { Vector argumentValues = new Vector(); argumentValues.addElement(arg1); return executeQuery(queryName, domainClass, argumentValues); } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * The class is the descriptor in which the query was pre-defined. * * @see DescriptorQueryManager#addQuery(String, DatabaseQuery) */ @Override public Object executeQuery(String queryName, Class domainClass, Object arg1, Object arg2) throws DatabaseException { Vector argumentValues = new Vector(); argumentValues.addElement(arg1); argumentValues.addElement(arg2); return executeQuery(queryName, domainClass, argumentValues); } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * The class is the descriptor in which the query was pre-defined. * * @see DescriptorQueryManager#addQuery(String, DatabaseQuery) */ @Override public Object executeQuery(String queryName, Class domainClass, Object arg1, Object arg2, Object arg3) throws DatabaseException { Vector argumentValues = new Vector(); argumentValues.addElement(arg1); argumentValues.addElement(arg2); argumentValues.addElement(arg3); return executeQuery(queryName, domainClass, argumentValues); } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * The class is the descriptor in which the query was pre-defined. * * @see DescriptorQueryManager#addQuery(String, DatabaseQuery) */ @Override public Object executeQuery(String queryName, Class domainClass, List argumentValues) throws DatabaseException { if (argumentValues instanceof Vector) { return executeQuery(queryName, domainClass, (Vector)argumentValues); } else { return executeQuery(queryName, domainClass, new Vector(argumentValues)); } } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * The class is the descriptor in which the query was pre-defined. * * @see DescriptorQueryManager#addQuery(String, DatabaseQuery) */ public Object executeQuery(String queryName, Class domainClass, Vector argumentValues) throws DatabaseException { ClassDescriptor descriptor = getDescriptor(domainClass); if (descriptor == null) { throw QueryException.descriptorIsMissingForNamedQuery(domainClass, queryName); } DatabaseQuery query = descriptor.getQueryManager().getQuery(queryName, argumentValues); if (query == null) { throw QueryException.queryNotDefined(queryName, domainClass); } return executeQuery(query, argumentValues); } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * * @see #addQuery(String, DatabaseQuery) */ @Override public Object executeQuery(String queryName, Object arg1) throws DatabaseException { Vector argumentValues = new Vector(); argumentValues.addElement(arg1); return executeQuery(queryName, argumentValues); } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * * @see #addQuery(String, DatabaseQuery) */ @Override public Object executeQuery(String queryName, Object arg1, Object arg2) throws DatabaseException { Vector argumentValues = new Vector(); argumentValues.addElement(arg1); argumentValues.addElement(arg2); return executeQuery(queryName, argumentValues); } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * * @see #addQuery(String, DatabaseQuery) */ @Override public Object executeQuery(String queryName, Object arg1, Object arg2, Object arg3) throws DatabaseException { Vector argumentValues = new Vector(); argumentValues.addElement(arg1); argumentValues.addElement(arg2); argumentValues.addElement(arg3); return executeQuery(queryName, argumentValues); } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * * @see #addQuery(String, DatabaseQuery) */ @Override public Object executeQuery(String queryName, List argumentValues) throws DatabaseException { if (argumentValues instanceof Vector) { return executeQuery(queryName, (Vector)argumentValues); } else { return executeQuery(queryName, new Vector(argumentValues)); } } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * * @see #addQuery(String, DatabaseQuery) */ public Object executeQuery(String queryName, Vector argumentValues) throws DatabaseException { DatabaseQuery query = getQuery(queryName, argumentValues); if (query == null) { throw QueryException.queryNotDefined(queryName); } return executeQuery(query, argumentValues); } /** * PUBLIC: * Execute the database query. * A query is a database operation such as reading or writing. * The query allows for the operation to be customized for such things as, * performance, depth, caching, etc. * * @see DatabaseQuery */ @Override public Object executeQuery(DatabaseQuery query) throws DatabaseException { return executeQuery(query, EmptyRecord.getEmptyRecord()); } /** * PUBLIC: * Return the results from executing the database query. * The query arguments are passed in as a List of argument values in the same order as the query arguments. */ @Override public Object executeQuery(DatabaseQuery query, List argumentValues) throws DatabaseException { if (query == null) { throw QueryException.queryNotDefined(); } AbstractRecord row = query.rowFromArguments(argumentValues, this); return executeQuery(query, row); } /** * INTERNAL: * Return the results from executing the database query. * the arguments should be a database row with raw data values. */ public Object executeQuery(DatabaseQuery query, AbstractRecord row) throws DatabaseException { if (hasBroker()) { if (!((query.isDataModifyQuery() || query.isDataReadQuery()) && (query.getSessionName() == null))) { return getBroker().executeQuery(query, row); } } if (query == null) { throw QueryException.queryNotDefined(); } // Check for disabled native queries. if (query.isUserDefinedSQLCall() && query.isSQLCallQuery() && ! query.isJPQLCallQuery()) { if (! query.shouldAllowNativeSQLQuery(getProject().allowNativeSQLQueries())) { // If the session/project says no to SQL queries and the database // query doesn't ask to bypass this decision then throw an exception. throw QueryException.nativeSQLQueriesAreDisabled(query); } } //CR#2272 log(SessionLog.FINEST, SessionLog.QUERY, "execute_query", query); //Make a call to the internal method with a retry count of 0. This will //initiate a retry call stack if required and supported. The separation between the //calling stack and the target method is made because the target method may call itself //recursively. return this.executeQuery(query, row, 0); } /** * INTERNAL: * Return the results from executing the database query. * the arguments should be a database row with raw data values. */ public Object executeQuery(DatabaseQuery query, AbstractRecord row, int retryCount) throws DatabaseException { try { if (this.eventManager != null) { this.eventManager.preExecuteQuery(query); } Object result; if (isInProfile()) { result = getProfiler().profileExecutionOfQuery(query, row, this); } else { result = internalExecuteQuery(query, row); } if (this.eventManager != null) { this.eventManager.postExecuteQuery(query, result); } return result; } catch (RuntimeException exception) { if (exception instanceof QueryException) { QueryException queryException = (QueryException)exception; if (queryException.getQuery() == null) { queryException.setQuery(query); } if (queryException.getQueryArgumentsRecord() == null) { queryException.setQueryArguments(row); } if (queryException.getSession() == null) { queryException.setSession(this); } } else if (exception instanceof DatabaseException) { DatabaseException databaseException = (DatabaseException)exception; if (databaseException.getQuery() == null) { databaseException.setQuery(query); } if (databaseException.getQueryArgumentsRecord() == null) { databaseException.setQueryArguments(row); } if (databaseException.getSession() == null) { databaseException.setSession(this); } //if this query is a read query outside of a transaction then we may be able to retry the query if (!isInTransaction() && query.isReadQuery()) { final int count = getLogin().getQueryRetryAttemptCount(); //was the failure communication based? (ie timeout) if (databaseException.isCommunicationFailure() && retryCount < count) { Object[] args = new Object[1]; args[0] = databaseException; log(SessionLog.INFO, SessionLog.QUERY, "communication_failure_attempting_query_retry", args, null); Object result = retryQuery(query, row, databaseException, retryCount, this); if (result instanceof DatabaseException) { exception = (DatabaseException)result; } else { return result; } } } } return handleException(exception); } } /** * INTERNAL: * A query execution failed due to an invalid query. * Re-connect and retry the query. */ public Object retryQuery(DatabaseQuery query, AbstractRecord row, DatabaseException databaseException, int retryCount, AbstractSession executionSession) { DatabaseException exception = databaseException; //retry if (retryCount <= getLogin().getQueryRetryAttemptCount()) { try { // attempt to reconnect for a certain number of times. // servers may take some time to recover. ++retryCount; try { //passing the retry count will prevent a runaway retry where // we can acquire connections but are unable to execute any queries if (retryCount > 1) { // We are retrying more than once lets wait to give connection time to restart. //Give the failover time to recover. Thread.currentThread().sleep(getLogin().getDelayBetweenConnectionAttempts()); } return executionSession.executeQuery(query, row, retryCount); } catch (DatabaseException newException){ //replace original exception with last exception thrown //this exception could be a data based exception as opposed //to a connection exception that needs to go back to the customer. exception = newException; } } catch (InterruptedException ex) { //Ignore interrupted exception. } } return exception; } /** * PUBLIC: * Execute the call on the database and return the result. * The call must return a value, if no value is return executeNonSelectCall must be used. * The call can be a stored procedure call, SQL call or other type of call. * A vector of database rows is returned, database row implements Java 2 Map which should be used to access the data. * <p>Example: * <p>session.executeSelectingCall(new SQLCall("Select * from Employee"); * * @see #executeNonSelectingCall(Call) */ @Override public Vector executeSelectingCall(Call call) throws DatabaseException { DataReadQuery query = new DataReadQuery(); query.setCall(call); query.setIsExecutionClone(true); return (Vector)executeQuery(query); } /** * PUBLIC: * Execute the sql on the database and return the result. * It must return a value, if no value is return executeNonSelectingSQL must be used. * A vector of database rows is returned, database row implements Java 2 Map which should be used to access the data. * Warning: Allowing an unverified SQL string to be passed into this * method makes your application vulnerable to SQL injection attacks. * <p>Example: * <p>session.executeSelectingCall("Select * from Employee"); * * @see #executeSelectingCall(Call) */ @Override public Vector executeSQL(String sqlString) throws DatabaseException { return executeSelectingCall(new SQLCall(sqlString)); } /** * INTERNAL: * This should normally not be used, most sessions do not have a single accessor. * ServerSession has a set of connection pools. * ClientSession only has an accessor during a transaction. * SessionBroker has multiple accessors. * getAccessors() should be used to support partitioning. * To maintain backward compatibility, and to support certain cases that required a default accessor, * this returns the first accessor. */ public Accessor getAccessor() { Collection<Accessor> accessors = getAccessors(); if ((accessors == null) || accessors.isEmpty()) { return null; } if (accessors instanceof List) { return ((List<Accessor>)accessors).get(0); } return accessors.iterator().next(); } /** * INTERNAL: * This should normally not be used, most sessions do not have specific accessors. * ServerSession has a set of connection pools. * ClientSession only has an accessor during a transaction. * SessionBroker has multiple accessors. * getAccessors() is used to support partitioning. * If the accessor is null, this lazy initializes one for backwardcompatibility with DatabaseSession. */ public Collection<Accessor> getAccessors() { if ((this.accessors == null) && (this.project != null) && (this.project.getDatasourceLogin() != null)) { synchronized (this) { if ((this.accessors == null) && (this.project != null) && (this.project.getDatasourceLogin() != null)) { // PERF: lazy init, not always required. List<Accessor> newAccessors = new ArrayList(1); newAccessors.add(this.project.getDatasourceLogin().buildAccessor()); this.accessors = newAccessors; } } } return this.accessors; } /** * INTERNAL: * Return the connections to use for the query execution. */ public Collection<Accessor> getAccessors(Call call, AbstractRecord translationRow, DatabaseQuery query) { // Check for partitioning. Collection<Accessor> accessors = null; if (query.getPartitioningPolicy() != null) { accessors = query.getPartitioningPolicy().getConnectionsForQuery(this, query, translationRow); if (accessors != null) { return accessors; } } ClassDescriptor descriptor = query.getDescriptor(); if ((descriptor != null) && (descriptor.getPartitioningPolicy() != null)) { accessors = descriptor.getPartitioningPolicy().getConnectionsForQuery(this, query, translationRow); if (accessors != null) { return accessors; } } if (this.partitioningPolicy != null) { accessors = this.partitioningPolicy.getConnectionsForQuery(this, query, translationRow); if (accessors != null) { return accessors; } } return accessors; } /** * INTERNAL: * Execute the call on each accessors and merge the results. */ public Object basicExecuteCall(Call call, AbstractRecord translationRow, DatabaseQuery query) throws DatabaseException { Object result = null; if (query.getAccessors().size() == 1) { result = query.getAccessor().executeCall(call, translationRow, this); } else { RuntimeException exception = null; // Replication or partitioning may require execution on multiple connections. for (Accessor accessor : query.getAccessors()) { Object object = null; try { object = accessor.executeCall(call, translationRow, this); } catch (RuntimeException failed) { // Catch any exceptions to allow execution on each connections. // This is used to have DDL run on every database even if one db fails because table already exists. if (exception == null) { exception = failed; } } if (call.isOneRowReturned()) { // If one row is desired, then break on first hit. if (object != null) { result = object; break; } } else if (call.isNothingReturned()) { // If no return ensure row count is consistent, 0 if any 0, otherwise first number. if (result == null) { result = object; } else { if (object instanceof Integer) { if (((Integer)result).intValue() != 0) { if (((Integer)object).intValue() != 0) { result = object; } } } } } else { // Either a set of rows (union), or cursor (return). if (result == null) { result = object; } else { if (object instanceof List) { ((List)result).addAll((List)object); } else { break; // Not sure what to do, so break (if a cursor, don't only want to open one cursor. } } } } if (exception != null) { throw exception; } } return result; } /** * INTERNAL: */ public ExposedNodeLinkedList getActiveCommandThreads() { if (activeCommandThreads == null) { activeCommandThreads = new ExposedNodeLinkedList(); } return activeCommandThreads; } /** * PUBLIC: * Return the active session for the current active external (JTS) transaction. * This should only be used with JTS and will return the session if no external transaction exists. */ @Override public org.eclipse.persistence.sessions.Session getActiveSession() { org.eclipse.persistence.sessions.Session activeSession = getActiveUnitOfWork(); if (activeSession == null) { activeSession = this; } return activeSession; } /** * PUBLIC: * Return the active unit of work for the current active external (JTS) transaction. * This should only be used with JTS and will return null if no external transaction exists. */ @Override public org.eclipse.persistence.sessions.UnitOfWork getActiveUnitOfWork() { if (hasExternalTransactionController()) { return getExternalTransactionController().getActiveUnitOfWork(); } /* Steven Vo: CR# 2517 Get from the server session since the external transaction controller could be null out from the client session by TL WebLogic 5.1 to provide non-jts transaction operations */ if (isClientSession()) { return ((org.eclipse.persistence.sessions.server.ClientSession)this).getParent().getActiveUnitOfWork(); } return null; } /** * INTERNAL: * Returns the alias descriptors Map. */ public Map getAliasDescriptors() { return project.getAliasDescriptors(); } /** * ADVANCED: * Answers the past time this session is as of. Indicates whether or not this * is a special historical session where all objects are read relative to a * particular point in time. * @return An immutable object representation of the past time. * <code>null</code> if no clause set, or this a regular session. * @see #acquireHistoricalSession(org.eclipse.persistence.history.AsOfClause) */ @Override public AsOfClause getAsOfClause() { return null; } /** * INTERNAL: * Allow the session to be used from a session broker. */ public AbstractSession getBroker() { return broker; } /** * INTERNAL: * The session that this query is executed against when not in transaction. * The session containing the shared identity map. * <p> * In most cases this is the root ServerSession or DatabaseSession. * <p> * In cases where objects are not to be cached in the global identity map * an alternate session may be returned: * <ul> * <li>A ClientSession if in transaction * <li>An isolated ClientSession or HistoricalSession * <li>A registered session of a root SessionBroker * </ul> */ public AbstractSession getRootSession(DatabaseQuery query) { ClassDescriptor descriptor = null; if (query != null){ descriptor = query.getDescriptor(); } return getParentIdentityMapSession(descriptor, true, true); } /** * INTERNAL: * Gets the parent session. */ public AbstractSession getParent() { return null; } /** * INTERNAL: * Gets the next link in the chain of sessions followed by a query's check * early return, the chain of sessions with identity maps all the way up to * the root session. */ public AbstractSession getParentIdentityMapSession(DatabaseQuery query) { ClassDescriptor descriptor = null; if (query != null){ descriptor = query.getDescriptor(); } return getParentIdentityMapSession(descriptor, false, false); } /** * INTERNAL: * Gets the next link in the chain of sessions followed by a query's check * early return, the chain of sessions with identity maps all the way up to * the root session. * <p> * Used for session broker which delegates to registered sessions, or UnitOfWork * which checks parent identity map also. * @param canReturnSelf true when method calls itself. If the path * starting at <code>this</code> is acceptable. Sometimes true if want to * move to the first valid session, i.e. executing on ClientSession when really * should be on ServerSession. * @param terminalOnly return the session we will execute the call on, not * the next step towards it. * @return this if there is no next link in the chain */ public AbstractSession getParentIdentityMapSession(DatabaseQuery query, boolean canReturnSelf, boolean terminalOnly) { ClassDescriptor descriptor = null; if (query != null){ descriptor = query.getDescriptor(); } return getParentIdentityMapSession(descriptor, canReturnSelf, terminalOnly); } /** * INTERNAL: * Returns the appropriate IdentityMap session for this descriptor. Sessions can be * chained and each session can have its own Cache/IdentityMap. Entities can be stored * at different levels based on Cache Isolation. This method will return the correct Session * for a particular Entity class based on the Isolation Level and the attributes provided. * <p> * @param canReturnSelf true when method calls itself. If the path * starting at <code>this</code> is acceptable. Sometimes true if want to * move to the first valid session, i.e. executing on ClientSession when really * should be on ServerSession. * @param terminalOnly return the last session in the chain where the Enitity is stored. * @return Session with the required IdentityMap */ public AbstractSession getParentIdentityMapSession(ClassDescriptor descriptor, boolean canReturnSelf, boolean terminalOnly) { return this; } /** * INTERNAL: * Returns the default pessimistic lock timeout value. */ public Integer getPessimisticLockTimeoutDefault() { return pessimisticLockTimeoutDefault; } /** * PUBLIC: * Return the default query timeout for this session. * This timeout will apply to any queries that do not have a timeout set, * and that do not have a default timeout defined in their descriptor. */ public int getQueryTimeoutDefault() { return queryTimeoutDefault; } public TimeUnit getQueryTimeoutUnitDefault() { return queryTimeoutUnitDefault; } @SuppressWarnings("unchecked") public <T> InjectionManager<T> getInjectionManager() { if (injectionManager == null){ injectionManager = createInjectionManager(this.getProperty(PersistenceUnitProperties.CDI_BEANMANAGER)); } return injectionManager; } /** * INTERNAL: * Gets the session which this query will be executed on. * Generally will be called immediately before the call is translated, * which is immediately before session.executeCall. * <p> * Since the execution session also knows the correct datasource platform * to execute on, it is often used in the mappings where the platform is * needed for type conversion, or where calls are translated. * <p> * Is also the session with the accessor. Will return a ClientSession if * it is in transaction and has a write connection. * @return a session with a live accessor * @param query may store session name or reference class for brokers case */ public AbstractSession getExecutionSession(DatabaseQuery query) { return this; } /** * INTERNAL: * Return if the commit manager has been set. */ public boolean hasCommitManager() { return commitManager != null; } /** * INTERNAL: * The commit manager is used to resolve referential integrity on commits of multiple objects. * All brokered sessions share the same commit manager. */ public CommitManager getCommitManager() { if (hasBroker()) { return getBroker().getCommitManager(); } // PERF: lazy init, not always required, not required for client sessions if (commitManager == null) { commitManager = new CommitManager(this); } return commitManager; } /** * INTERNAL: * Returns the set of read-only classes that gets assigned to each newly created UnitOfWork. * * @see org.eclipse.persistence.sessions.Project#setDefaultReadOnlyClasses(Collection) */ public Vector getDefaultReadOnlyClasses() { //Bug#3911318 All brokered sessions share the same DefaultReadOnlyClasses. if (hasBroker()) { return getBroker().getDefaultReadOnlyClasses(); } return getProject().getDefaultReadOnlyClasses(); } /** * ADVANCED: * Return the descriptor specified for the class. * If the class does not have a descriptor but implements an interface that is also implemented * by one of the classes stored in the map, that descriptor will be stored under the * new class. If a descriptor does not exist for the Class parameter, null is returned. * If the passed Class parameter is null, then null will be returned. */ @Override public ClassDescriptor getClassDescriptor(Class theClass) { if (theClass == null) { return null; } return getDescriptor(theClass); } /** * ADVANCED: * Return the descriptor specified for the object's class. * If a descriptor does not exist for the Object parameter, null is returned. * If the passed Object parameter is null, then null will be returned. */ @Override public ClassDescriptor getClassDescriptor(Object domainObject) { if (domainObject == null) { return null; } return getDescriptor(domainObject); } /** * PUBLIC: * Return the descriptor for the alias. * UnitOfWork delegates this to the parent */ @Override public ClassDescriptor getClassDescriptorForAlias(String alias) { return project.getDescriptorForAlias(alias); } /** * ADVANCED: * Return the descriptor specified for the class. * If the class does not have a descriptor but implements an interface that is also implemented * by one of the classes stored in the map, that descriptor will be stored under the * new class. If the passed Class is null, null will be returned. */ @Override public ClassDescriptor getDescriptor(Class theClass) { if (theClass == null) { return null; } // Optimize descriptor lookup through caching the last one accessed. ClassDescriptor descriptor = this.lastDescriptorAccessed; if ((descriptor != null) && (descriptor.getJavaClass() == theClass)) { return descriptor; } if (this.descriptors != null) { descriptor = this.descriptors.get(theClass); } else { descriptor = this.project.getDescriptors().get(theClass); } if (descriptor == null) { if (hasBroker()) { // Also check the broker descriptor = getBroker().getDescriptor(theClass); } if (descriptor == null) { // Allow for an event listener to lazy register the descriptor for a class. if (this.eventManager != null) { this.eventManager.missingDescriptor(theClass); } descriptor = getDescriptors().get(theClass); if (descriptor == null) { // This allows for the correct descriptor to be found if the class implements an interface, // or extends a class that a descriptor is registered for. // This is used by EJB to find the descriptor for a stub and remote to unwrap it, // and by inheritance to allow for subclasses that have no additional state to not require a descriptor. if (!theClass.isInterface()) { Class[] interfaces = theClass.getInterfaces(); for (int index = 0; index < interfaces.length; ++index) { Class interfaceClass = interfaces[index]; descriptor = getDescriptor(interfaceClass); if (descriptor != null) { getDescriptors().put(interfaceClass, descriptor); break; } } if (descriptor == null ) { descriptor = checkHierarchyForDescriptor(theClass); } } } } } // Cache for optimization. this.lastDescriptorAccessed = descriptor; return descriptor; } /** * ADVANCED: * Return the descriptor specified for the object's class. * If the passed Object is null, null will be returned. */ @Override public ClassDescriptor getDescriptor(Object domainObject) { if (domainObject == null) { return null; } //Bug#3947714 Check and trigger the proxy here if (this.project.hasProxyIndirection()) { return getDescriptor(ProxyIndirectionPolicy.getValueFromProxy(domainObject).getClass()); } return getDescriptor(domainObject.getClass()); } /** * PUBLIC: * Return the descriptor for the alias. * @param alias The descriptor alias. * @return The descriptor for the alias or {@code null} if no descriptor was found. */ @Override public ClassDescriptor getDescriptorForAlias(final String alias) { // If we have a descriptors list return our sessions descriptor and // not that of the project since we may be dealing with a multitenant // descriptor which will have been initialized locally on the session. // The project descriptor will be not initialized. final ClassDescriptor desc = project.getDescriptorForAlias(alias); if (desc != null && desc.hasMultitenantPolicy() && this.descriptors != null) { return this.descriptors.get(desc.getJavaClass()); } else { return desc; } } /** * ADVANCED: * Return all registered descriptors. */ @Override public Map<Class, ClassDescriptor> getDescriptors() { return this.project.getDescriptors(); } /** * INTERNAL: * Return if an event manager has been set. */ public boolean hasEventManager() { return eventManager != null; } /** * PUBLIC: * Return the event manager. * The event manager can be used to register for various session events. */ @Override public SessionEventManager getEventManager() { if (eventManager == null) { synchronized (this) { if (eventManager == null) { // PERF: lazy init. eventManager = new SessionEventManager(this); } } } return eventManager; } /** * INTERNAL: * Return a string which represents my ExceptionHandler's class * Added for F2104: Properties.xml * - gn */ public String getExceptionHandlerClass() { String className = null; try { className = getExceptionHandler().getClass().getName(); } catch (Exception exception) { return null; } return className; } /** * PUBLIC: * Return the ExceptionHandler.Exception handler can catch errors that occur on queries or during database access. */ @Override public ExceptionHandler getExceptionHandler() { return exceptionHandler; } /** * PUBLIC: * Used for JTS integration. If your application requires to have JTS control transactions instead of EclipseLink an * external transaction controller must be specified. * EclipseLink provides JTS controllers for several JTS implementations including JTS 1.0, Weblogic 5.1 and WebSphere 3.0. * * @see org.eclipse.persistence.transaction.JTATransactionController */ @Override public ExternalTransactionController getExternalTransactionController() { return externalTransactionController; } /** * PUBLIC: * The IdentityMapAccessor is the preferred way of accessing IdentityMap funcitons * This will return an object which implements an interface which exposes all public * IdentityMap functions. */ @Override public org.eclipse.persistence.sessions.IdentityMapAccessor getIdentityMapAccessor() { return identityMapAccessor; } /** * INTERNAL: * Return the internally available IdentityMapAccessor instance. */ public org.eclipse.persistence.internal.sessions.IdentityMapAccessor getIdentityMapAccessorInstance() { return identityMapAccessor; } /** * PUBLIC: * Returns the integrityChecker.IntegrityChecker holds all the ClassDescriptor Exceptions. */ @Override public IntegrityChecker getIntegrityChecker() { // BUG# 2700595 - Lazily create an IntegrityChecker if one has not already been created. if (integrityChecker == null) { integrityChecker = new IntegrityChecker(); } return integrityChecker; } /** * ADVANCED: * Return all pre-defined not yet parsed JPQL queries. */ @Override public List<DatabaseQuery> getJPAQueries() { return getProject().getJPAQueries(); } /** * ADVANCED: * Return all pre-defined not yet parsed JPQL queries. */ public List<DatabaseQuery> getJPATablePerTenantQueries() { return getProject().getJPATablePerTenantQueries(); } /** * PUBLIC: * Return the writer to which an accessor writes logged messages and SQL. * If not set, this reference defaults to a writer on System.out. * To enable logging, logMessages must be turned on. * * @see #setLoggingOff(boolean) */ @Override public Writer getLog() { return getSessionLog().getWriter(); } /** * INTERNAL: * Return the name of the session: class name + system hashcode. * <p> * This should be the implementation of toString(), and also the * value should be calculated in the constructor for it is used all the * time. However everything is lazily initialized now and the value is * transient for the system hashcode could vary? */ public String getLogSessionString() { if (logSessionString == null) { StringWriter writer = new StringWriter(); writer.write(getSessionTypeString()); writer.write("("); writer.write(String.valueOf(System.identityHashCode(this))); writer.write(")"); logSessionString = writer.toString(); } return logSessionString; } /** * INTERNAL: * Returns the type of session, its class. * <p> * Override to hide from the user when they are using an internal subclass * of a known class. * <p> * A user does not need to know that their UnitOfWork is a * non-deferred UnitOfWork, or that their ClientSession is an * IsolatedClientSession. */ public String getSessionTypeString() { return Helper.getShortClassName(getClass()); } /** * INTERNAL: * Return the static metamodel class associated with the given model class * if available. Callers must handle null. */ public String getStaticMetamodelClass(String modelClassName) { if (staticMetamodelClasses != null) { return staticMetamodelClasses.get(modelClassName); } return null; } /** * OBSOLETE: * Return the login, the login holds any database connection information given. * This has been replaced by getDatasourceLogin to make use of the Login interface * to support non-relational datasources, * if DatabaseLogin API is required it will need to be cast. */ @Override public DatabaseLogin getLogin() { try { return (DatabaseLogin)getDatasourceLogin(); } catch (ClassCastException wrongType) { throw ValidationException.notSupportedForDatasource(); } } /** * PUBLIC: * Return the login, the login holds any database connection information given. * This return the Login interface and may need to be cast to the datasource specific implementation. */ @Override public Login getDatasourceLogin() { if (this.project == null) { return null; } return this.project.getDatasourceLogin(); } /** * INTERNAL: * Return the mapped superclass descriptor if one exists for the given * class name. Must check any broker as well. */ public ClassDescriptor getMappedSuperclass(String className) { ClassDescriptor desc = getProject().getMappedSuperclass(className); if (desc == null && hasBroker()) { getBroker().getMappedSuperclass(className); } return desc; } /** * PUBLIC: * Return a set of multitenant context properties this session */ public Set<String> getMultitenantContextProperties() { if (this.multitenantContextProperties == null) { this.multitenantContextProperties = new HashSet<String>(); } return this.multitenantContextProperties; } /** * PUBLIC: * Return the name of the session. * This is used with the session broker, or to give the session a more meaningful name. */ @Override public String getName() { return name; } /** * ADVANCED: * Return the sequnce number from the database */ @Override public Number getNextSequenceNumberValue(Class domainClass) { return (Number)getSequencing().getNextValue(domainClass); } /** * INTERNAL: * Return the number of units of work connected. */ public int getNumberOfActiveUnitsOfWork() { return numberOfActiveUnitsOfWork; } /** * INTERNAL: * For use within the merge process this method will get an object from the shared * cache using a readlock. If a readlock is unavailable then the merge manager will be * transitioned to deferred locks and a deferred lock will be used. */ protected CacheKey getCacheKeyFromTargetSessionForMerge(Object implementation, ObjectBuilder builder, ClassDescriptor descriptor, MergeManager mergeManager){ Object original = null; Object primaryKey = builder.extractPrimaryKeyFromObject(implementation, this, true); if (primaryKey == null) { return null; } CacheKey cacheKey = null; if (mergeManager == null){ cacheKey = getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey, implementation.getClass(), descriptor, true); if (cacheKey != null){ cacheKey.checkReadLock(); } return cacheKey; } cacheKey = getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey, implementation.getClass(), descriptor, true); if (cacheKey != null) { if (cacheKey.acquireReadLockNoWait()) { original = cacheKey.getObject(); cacheKey.releaseReadLock(); } else { if (!mergeManager.isTransitionedToDeferredLocks()) { getIdentityMapAccessorInstance().getWriteLockManager().transitionToDeferredLocks(mergeManager); } cacheKey.acquireDeferredLock(); original = cacheKey.getObject(); if (original == null) { synchronized (cacheKey) { if (cacheKey.isAcquired()) { try { cacheKey.wait(); } catch (InterruptedException e) { //ignore and return } } original = cacheKey.getObject(); } } cacheKey.releaseDeferredLock(); } } return cacheKey; } /** * INTERNAL: * Return the database platform currently connected to. * The platform is used for database specific behavior. * NOTE: this must only be used for relational specific usage, * it will fail for non-relational datasources. */ @Override public DatabasePlatform getPlatform() { // PERF: Cache the platform. if (platform == null) { platform = getDatasourceLogin().getPlatform(); } return (DatabasePlatform)platform; } /** * INTERNAL: * Return the class loader for the session's application. * This loader should be able to load any application or EclipseLink class. */ public ClassLoader getLoader() { return getDatasourcePlatform().getConversionManager().getLoader(); } /** * INTERNAL: * Return the database platform currently connected to. * The platform is used for database specific behavior. */ @Override public Platform getDatasourcePlatform() { // PERF: Cache the platform. if (platform == null) { platform = getDatasourceLogin().getDatasourcePlatform(); } return platform; } /** * INTERNAL: * Marked internal as this is not customer API but helper methods for * accessing the server platform from within EclipseLink's other sessions types * (ie not DatabaseSession) */ @Override public ServerPlatform getServerPlatform() { return null; } /** * INTERNAL: * Return the database platform currently connected to * for specified class. * The platform is used for database specific behavior. */ @Override public Platform getPlatform(Class domainClass) { // PERF: Cache the platform. if (platform == null) { platform = getDatasourcePlatform(); } return platform; } /** * PUBLIC: * Return the profiler. * The profiler is a tool that can be used to determine performance bottlenecks. * The profiler can be queries to print summaries and configure for logging purposes. */ @Override public SessionProfiler getProfiler() { return profiler; } /** * PUBLIC: * Return the project, the project holds configuartion information including the descriptors. */ @Override public org.eclipse.persistence.sessions.Project getProject() { return project; } /** * ADVANCED: * Allow for user defined properties. */ @Override public Map<String, Object> getProperties() { if (properties == null) { properties = new HashMap(5); } return properties; } /** * INTERNAL: * Allow to check for user defined properties. */ public boolean hasProperties() { return ((properties != null) && !properties.isEmpty()); } /** * INTERNAL: * Return list of table per tenant multitenant descriptors. */ public boolean hasTablePerTenantDescriptors() { return (tablePerTenantDescriptors != null && ! tablePerTenantDescriptors.isEmpty()); } /** * INTERNAL: * Return a list of table per tenant multitenant queries. */ public boolean hasTablePerTenantQueries() { return (tablePerTenantQueries != null && ! tablePerTenantQueries.isEmpty()); } /** * ADVANCED: * Returns the user defined property. */ @Override public Object getProperty(String name) { if(this.properties==null){ return null; } return getProperties().get(name); } /** * ADVANCED: * Return all pre-defined queries. */ @Override public Map<String, List<DatabaseQuery>> getQueries() { // PERF: lazy init, not normally required. if (queries == null) { queries = new HashMap(5); } return queries; } /** * ADVANCED: * Return an attribute group of a particular name. */ /** * ADVANCED * Return all predefined attribute groups */ public Map<String, AttributeGroup> getAttributeGroups(){ if (this.attributeGroups == null){ this.attributeGroups = new HashMap<String, AttributeGroup>(5); } return this.attributeGroups; } /** * INTERNAL: * Return the pre-defined queries in this session. * A single vector containing all the queries is returned. * * @see #getQueries() */ public List<DatabaseQuery> getAllQueries() { Vector allQueries = new Vector(); for (Iterator vectors = getQueries().values().iterator(); vectors.hasNext();) { allQueries.addAll((Vector)vectors.next()); } return allQueries; } /** * PUBLIC: * Return the query from the session pre-defined queries with the given name. * This allows for common queries to be pre-defined, reused and executed by name. */ @Override public DatabaseQuery getQuery(String name) { return getQuery(name, null); } /** * PUBLIC: * Return the query from the session pre-defined queries with the given name and argument types. * This allows for common queries to be pre-defined, reused and executed by name. * This method should be used if the Session has multiple queries with the same name but * different arguments. * * @see #getQuery(String) */ @Override public DatabaseQuery getQuery(String name, List arguments) { if (arguments instanceof Vector) { return getQuery(name, (Vector)arguments); } else { return getQuery(name, new Vector(arguments)); } } /** * PUBLIC: * Return the query from the session pre-defined queries with the given name and argument types. * This allows for common queries to be pre-defined, reused and executed by name. * This method should be used if the Session has multiple queries with the same name but * different arguments. * * @see #getQuery(String) */ public DatabaseQuery getQuery(String name, Vector arguments) { return getQuery(name, arguments, true); } /** * INTERNAL: * Return the query from the session pre-defined queries with the given name and argument types. * This allows for common queries to be pre-defined, reused and executed by name. * This method should be used if the Session has multiple queries with the same name but * different arguments. * * @param shouldSearchParent indicates whether parent should be searched if query not found. * @see #getQuery(String, List) */ public DatabaseQuery getQuery(String name, Vector arguments, boolean shouldSearchParent) { Vector queries = (Vector)getQueries().get(name); if ((queries != null) && !queries.isEmpty()) { // Short circuit the simple, most common case of only one query. if (queries.size() == 1) { return (DatabaseQuery)queries.firstElement(); } // CR#3754; Predrag; mar 19/2002; // We allow multiple named queries with the same name but // different argument set; we can have only one query with // no arguments; Vector queries is not sorted; // When asked for the query with no parameters the // old version did return the first query - wrong: // return (DatabaseQuery) queries.firstElement(); int argumentTypesSize = 0; if (arguments != null) { argumentTypesSize = arguments.size(); } Vector argumentTypes = new Vector(argumentTypesSize); for (int i = 0; i < argumentTypesSize; i++) { argumentTypes.addElement(arguments.elementAt(i).getClass()); } for (Enumeration queriesEnum = queries.elements(); queriesEnum.hasMoreElements();) { DatabaseQuery query = (DatabaseQuery)queriesEnum.nextElement(); if (Helper.areTypesAssignable(argumentTypes, query.getArgumentTypes())) { return query; } } } if(shouldSearchParent) { AbstractSession parent = getParent(); if(parent != null) { return parent.getQuery(name, arguments, true); } } return null; } /** * Returns an AttributeGroup by name */ /** * INTERNAL: * Return the Sequencing object used by the session. */ public Sequencing getSequencing() { return null; } /** * INTERNAL: * Return the session to be used for the class. * Used for compatibility with the session broker. */ public AbstractSession getSessionForClass(Class domainClass) { if (hasBroker()) { return getBroker().getSessionForClass(domainClass); } return this; } /** * INTERNAL: * Return the session by name. * Used for compatibility with the session broker. */ public AbstractSession getSessionForName(String name) throws ValidationException { if (hasBroker()) { return getBroker().getSessionForName(name); } return this; } /** * PUBLIC: * Return the session log to which an accessor logs messages and SQL. * If not set, this will default to a session log on a writer on System.out. * To enable logging, logMessages must be turned on. * * @see #setLoggingOff(boolean) */ @Override public SessionLog getSessionLog() { if (sessionLog == null) { setSessionLog(new DefaultSessionLog()); } return sessionLog; } /** * INTERNAL: * Return list of table per tenant multitenant descriptors. */ public List<ClassDescriptor> getTablePerTenantDescriptors() { if (tablePerTenantDescriptors == null) { tablePerTenantDescriptors = new ArrayList<ClassDescriptor>(); } return tablePerTenantDescriptors; } /** * INTERNAL: * Return list of table per tenant multitenant descriptors. */ public List<DatabaseQuery> getTablePerTenantQueries() { if (tablePerTenantQueries == null) { tablePerTenantQueries = new ArrayList<DatabaseQuery>(); } return tablePerTenantQueries; } /** * INTERNAL: * The transaction mutex ensure mutual exclusion on transaction across multiple threads. */ public ConcurrencyManager getTransactionMutex() { // PERF: not always required, defer. if (transactionMutex == null) { synchronized (this) { if (transactionMutex == null) { transactionMutex = new ConcurrencyManager(); } } } return transactionMutex; } /** * PUBLIC: * Allow any WARNING level exceptions that occur within EclipseLink to be logged and handled by the exception handler. */ @Override public Object handleException(RuntimeException exception) throws RuntimeException { if ((exception instanceof EclipseLinkException)) { EclipseLinkException eclipseLinkException = (EclipseLinkException)exception; if (eclipseLinkException.getSession() == null) { eclipseLinkException.setSession(this); } //Bug#3559280 Avoid logging an exception twice if (!eclipseLinkException.hasBeenLogged()) { logThrowable(SessionLog.WARNING, null, exception); eclipseLinkException.setHasBeenLogged(true); } } else { logThrowable(SessionLog.WARNING, null, exception); } if (hasExceptionHandler()) { if (this.broker != null && this.broker.hasExceptionHandler()) { try { return getExceptionHandler().handleException(exception); } catch (RuntimeException ex) { // handle the original exception return this.broker.getExceptionHandler().handleException(exception); } } else { return getExceptionHandler().handleException(exception); } } else { if (this.broker != null && this.broker.hasExceptionHandler()) { return this.broker.getExceptionHandler().handleException(exception); } else { throw exception; } } } /** * INTERNAL: * Allow the session to be used from a session broker. */ public boolean hasBroker() { return broker != null; } /** * ADVANCED: * Return true if a descriptor exists for the given class. */ @Override public boolean hasDescriptor(Class theClass) { if (theClass == null) { return false; } return getDescriptors().get(theClass) != null; } /** * PUBLIC: * Return if an exception handler is present. */ @Override public boolean hasExceptionHandler() { if (exceptionHandler == null) { return false; } return true; } /** * PUBLIC: * Used for JTA integration. If your application requires to have JTA control transactions instead of EclipseLink an * external transaction controler must be specified. EclipseLink provides JTA controlers for JTA 1.0 and application * servers. * @see org.eclipse.persistence.transaction.JTATransactionController */ @Override public boolean hasExternalTransactionController() { return externalTransactionController != null; } /** * INTERNAL: * Set up the IdentityMapManager. This method allows subclasses of Session to override * the default IdentityMapManager functionality. */ public void initializeIdentityMapAccessor() { this.identityMapAccessor = new org.eclipse.persistence.internal.sessions.IdentityMapAccessor(this, new IdentityMapManager(this)); } /** * PUBLIC: * Insert the object and all of its privately owned parts into the database. * Insert should only be used if the application knows that the object is new, * otherwise writeObject should be used. * The insert operation can be customized through using an insert query. * * @exception DatabaseException if an error occurs on the database, * these include constraint violations, security violations and general database errors. * * @see InsertObjectQuery * @see #writeObject(Object) */ public Object insertObject(Object domainObject) throws DatabaseException { InsertObjectQuery query = new InsertObjectQuery(); query.setObject(domainObject); query.setIsExecutionClone(true); return executeQuery(query); } /** * INTERNAL: * Return the results from exeucting the database query. * The arguments should be a database row with raw data values. * This method is provided to allow subclasses to change the default querying behavior. * All querying goes through this method. */ public Object internalExecuteQuery(DatabaseQuery query, AbstractRecord databaseRow) throws DatabaseException { return query.execute(this, databaseRow); } /** * INTERNAL: * Returns true if the session is a session Broker. */ public boolean isBroker() { return false; } /** * INTERNAL: * Returns true if the session is in a session Broker. */ public boolean isInBroker() { return isInBroker; } /** * PUBLIC: * Return if the class is defined as read-only. */ public boolean isClassReadOnly(Class theClass) { ClassDescriptor descriptor = getDescriptor(theClass); return isClassReadOnly(theClass, descriptor); } /** * INTERNAL: * Return if the class is defined as read-only. * PERF: Pass descriptor to avoid re-lookup. */ public boolean isClassReadOnly(Class theClass, ClassDescriptor descriptor) { if ((descriptor != null) && descriptor.shouldBeReadOnly()) { return true; } if (theClass != null) { return getDefaultReadOnlyClasses().contains(theClass); } return false; } /** * PUBLIC: * Return if this session is a client session. */ @Override public boolean isClientSession() { return false; } /** * PUBLIC: * Return if this session is an isolated client session. */ public boolean isIsolatedClientSession() { return false; } /** * PUBLIC: * Return if this session is an exclusive isolated client session. */ public boolean isExclusiveIsolatedClientSession() { return false; } /** * PUBLIC: * Return if this session is connected to the database. */ @Override public boolean isConnected() { if (getAccessor() == null) { return false; } return getAccessor().isConnected(); } /** * PUBLIC: * Return if this session is a database session. */ @Override public boolean isDatabaseSession() { return false; } /** * PUBLIC: * Return if this session is a distributed session. */ @Override public boolean isDistributedSession() { return false; } /** * PUBLIC: * Return if a profiler is being used. */ @Override public boolean isInProfile() { return isInProfile; } /** * PUBLIC: * Allow for user deactive a profiler */ public void setIsInProfile(boolean inProfile) { this.isInProfile = inProfile; } /** * INTERNAL: * Set if this session is contained in a SessionBroker. */ public void setIsInBroker(boolean isInBroker) { this.isInBroker = isInBroker; } /** * PUBLIC: * Return if this session's decendants should use finalizers. * The allows certain finalizers such as in ClientSession to be enabled. * These are disable by default for performance reasons. */ @Override public boolean isFinalizersEnabled() { return isFinalizersEnabled; } /** * INTERNAL: * Register a finalizer to release this session. */ public void registerFinalizer() { // Ensure the finalizer is referenced until the session is gc'd. setProperty("finalizer", new SessionFinalizer(this)); } /** * INTERNAL: * Return if this session is a historical session. */ public boolean isHistoricalSession() { return false; } /** * PUBLIC: * Set if this session's decendants should use finalizers. * The allows certain finalizers such as in ClientSession to be enabled. * These are disable by default for performance reasons. */ @Override public void setIsFinalizersEnabled(boolean isFinalizersEnabled) { this.isFinalizersEnabled = isFinalizersEnabled; } /** * PUBLIC: * Return if the session is currently in the progress of a database transaction. * Because nested transactions are allowed check if the transaction mutex has been aquired. */ public boolean isInTransaction() { return this.transactionMutex != null && this.transactionMutex.isAcquired(); } /** * INTERNAL: * used to see if JPA Queries have been processed during initialization */ public boolean isJPAQueriesProcessed(){ return this.jpaQueriesProcessed; } /** * PUBLIC: * Returns true if Protected Entities should be built within this session */ public boolean isProtectedSession(){ return true; } /** * PUBLIC: * Return if this session is remote. */ @Override public boolean isRemoteSession() { return false; } /** * PUBLIC: * Return if this session is a unit of work. */ @Override public boolean isRemoteUnitOfWork() { return false; } /** * PUBLIC: * Return if this session is a server session. */ @Override public boolean isServerSession() { return false; } /** * PUBLIC: * Return if this session is a session broker. */ @Override public boolean isSessionBroker() { return false; } /** * INTERNAL: * Return if this session is synchronized. */ public boolean isSynchronized() { return isSynchronized; } /** * PUBLIC: * Return if this session is a unit of work. */ @Override public boolean isUnitOfWork() { return false; } /** * ADVANCED: * Extract and return the primary key from the object. */ @Override public Object getId(Object domainObject) throws ValidationException { ClassDescriptor descriptor = getDescriptor(domainObject); return keyFromObject(domainObject, descriptor); } /** * ADVANCED: * Extract and return the primary key from the object. * @deprecated since EclipseLink 2.1, replaced by getId(Object) * @see #getId(Object) */ @Override @Deprecated public Vector keyFromObject(Object domainObject) throws ValidationException { ClassDescriptor descriptor = getDescriptor(domainObject); return (Vector)keyFromObject(domainObject, descriptor); } /** * ADVANCED: * Extract and return the primary key from the object. */ public Object keyFromObject(Object domainObject, ClassDescriptor descriptor) throws ValidationException { if (descriptor == null) { throw ValidationException.missingDescriptor(domainObject.getClass().getName()); } Object implemention = descriptor.getObjectBuilder().unwrapObject(domainObject, this); if (implemention == null) { return null; } return descriptor.getObjectBuilder().extractPrimaryKeyFromObject(implemention, this); } /** * PUBLIC: * Log the log entry. */ @Override public void log(SessionLogEntry entry) { if (this.isLoggingOff) { return; } if (shouldLog(entry.getLevel(), entry.getNameSpace())) { if (entry.getSession() == null) {// Used for proxy session. entry.setSession(this); } getSessionLog().log(entry); } } /** * Log a untranslated message to the EclipseLink log at FINER level. */ @Override public void logMessage(String message) { if (this.isLoggingOff) { return; } log(SessionLog.FINER, SessionLog.MISC, message, (Object[])null, null, false); } /** * INTERNAL: * A call back to do session specific preparation of a query. * <p> * The call back occurs soon before we clone the query for execution, * meaning that if this method needs to clone the query then the caller will * determine that it doesn't need to clone the query itself. */ public DatabaseQuery prepareDatabaseQuery(DatabaseQuery query) { if (!isUnitOfWork() && query.isObjectLevelReadQuery()) { return ((ObjectLevelReadQuery)query).prepareOutsideUnitOfWork(this); } else { return query; } } /** * PUBLIC: * Read all of the instances of the class from the database. * This operation can be customized through using a ReadAllQuery, * or through also passing in a selection criteria. * * @see ReadAllQuery * @see #readAllObjects(Class, Expression) */ @Override public Vector readAllObjects(Class domainClass) throws DatabaseException { ReadAllQuery query = new ReadAllQuery(); query.setIsExecutionClone(true); query.setReferenceClass(domainClass); return (Vector)executeQuery(query); } /** * PUBLIC: * Read all of the instances of the class from the database return through execution the SQL string. * The SQL string must be a valid SQL select statement or selecting stored procedure call. * This operation can be customized through using a ReadAllQuery. * Warning: Allowing an unverified SQL string to be passed into this * method makes your application vulnerable to SQL injection attacks. * * @see ReadAllQuery */ public Vector readAllObjects(Class domainClass, String sqlString) throws DatabaseException { ReadAllQuery query = new ReadAllQuery(); query.setReferenceClass(domainClass); query.setSQLString(sqlString); query.setIsExecutionClone(true); return (Vector)executeQuery(query); } /** * PUBLIC: * Read all the instances of the class from the database returned through execution the Call string. * The Call can be an SQLCall or JPQLCall. * * example: session.readAllObjects(Employee.class, new SQLCall("SELECT * FROM EMPLOYEE")); * @see Call */ @Override public Vector readAllObjects(Class referenceClass, Call aCall) throws DatabaseException { ReadAllQuery raq = new ReadAllQuery(); raq.setReferenceClass(referenceClass); raq.setCall(aCall); raq.setIsExecutionClone(true); return (Vector)executeQuery(raq); } /** * PUBLIC: * Read all of the instances of the class from the database matching the given expression. * This operation can be customized through using a ReadAllQuery. * * @see ReadAllQuery */ @Override public Vector readAllObjects(Class domainClass, Expression expression) throws DatabaseException { ReadAllQuery query = new ReadAllQuery(); query.setReferenceClass(domainClass); query.setSelectionCriteria(expression); query.setIsExecutionClone(true); return (Vector)executeQuery(query); } /** * PUBLIC: * Read the first instance of the class from the database. * This operation can be customized through using a ReadObjectQuery, * or through also passing in a selection criteria. * * @see ReadObjectQuery * @see #readAllObjects(Class, Expression) */ @Override public Object readObject(Class domainClass) throws DatabaseException { ReadObjectQuery query = new ReadObjectQuery(); query.setReferenceClass(domainClass); query.setIsExecutionClone(true); return executeQuery(query); } /** * PUBLIC: * Read the first instance of the class from the database return through execution the SQL string. * The SQL string must be a valid SQL select statement or selecting stored procedure call. * This operation can be customized through using a ReadObjectQuery. * Warning: Allowing an unverified SQL string to be passed into this * method makes your application vulnerable to SQL injection attacks. * * @see ReadObjectQuery */ public Object readObject(Class domainClass, String sqlString) throws DatabaseException { ReadObjectQuery query = new ReadObjectQuery(); query.setReferenceClass(domainClass); query.setSQLString(sqlString); query.setIsExecutionClone(true); return executeQuery(query); } /** * PUBLIC: * Read the first instance of the class from the database returned through execution the Call string. * The Call can be an SQLCall or JPQLCall. * * example: session.readObject(Employee.class, new SQLCall("SELECT * FROM EMPLOYEE")); * @see SQLCall * @see JPQLCall */ @Override public Object readObject(Class domainClass, Call aCall) throws DatabaseException { ReadObjectQuery query = new ReadObjectQuery(); query.setReferenceClass(domainClass); query.setCall(aCall); query.setIsExecutionClone(true); return executeQuery(query); } /** * PUBLIC: * Read the first instance of the class from the database matching the given expression. * This operation can be customized through using a ReadObjectQuery. * * @see ReadObjectQuery */ @Override public Object readObject(Class domainClass, Expression expression) throws DatabaseException { ReadObjectQuery query = new ReadObjectQuery(); query.setReferenceClass(domainClass); query.setSelectionCriteria(expression); query.setIsExecutionClone(true); return executeQuery(query); } /** * PUBLIC: * Use the example object to consruct a read object query by the objects primary key. * This will read the object from the database with the same primary key as the object * or null if no object is found. */ @Override public Object readObject(Object object) throws DatabaseException { ReadObjectQuery query = new ReadObjectQuery(); query.setSelectionObject(object); query.setIsExecutionClone(true); return executeQuery(query); } /** * PUBLIC: * Refresh the attributes of the object and of all of its private parts from the database. * The object will be pessimisticly locked on the database for the duration of the transaction. * If the object is already locked this method will wait until the lock is released. * A no wait option is available through setting the lock mode. * @see #refreshAndLockObject(Object, short) */ public Object refreshAndLockObject(Object object) throws DatabaseException { return refreshAndLockObject(object, ObjectBuildingQuery.LOCK); } /** * PUBLIC: * Refresh the attributes of the object and of all of its private parts from the database. * The object will be pessimisticly locked on the database for the duration of the transaction. * <p>Lock Modes: ObjectBuildingQuery.NO_LOCK, LOCK, LOCK_NOWAIT */ public Object refreshAndLockObject(Object object, short lockMode) throws DatabaseException { ReadObjectQuery query = new ReadObjectQuery(); query.setSelectionObject(object); query.refreshIdentityMapResult(); query.cascadePrivateParts(); query.setLockMode(lockMode); query.setIsExecutionClone(true); return executeQuery(query); } /** * PUBLIC: * Refresh the attributes of the object and of all of its private parts from the database. * This can be used to ensure the object is up to date with the database. * Caution should be used when using this to make sure the application has no un commited * changes to the object. */ @Override public Object refreshObject(Object object) throws DatabaseException { return refreshAndLockObject(object, ObjectBuildingQuery.NO_LOCK); } /** * PUBLIC: * Release the session. * This does nothing by default, but allows for other sessions such as the ClientSession to do something. */ @Override public void release() { } /** * INTERNAL: * Release the unit of work, if lazy release the connection. */ public void releaseUnitOfWork(UnitOfWorkImpl unitOfWork) { // Nothing is required by default, allow subclasses to do cleanup. setNumberOfActiveUnitsOfWork(getNumberOfActiveUnitsOfWork() - 1); } /** * PUBLIC: * Remove the user defined property. */ @Override public void removeProperty(String property) { getProperties().remove(property); } /** * PUBLIC: * Remove all queries with the given queryName regardless of the argument types. * * @see #removeQuery(String, Vector) */ @Override public void removeQuery(String queryName) { getQueries().remove(queryName); } /** * PUBLIC: * Remove the specific query with the given queryName and argumentTypes. */ public void removeQuery(String queryName, Vector argumentTypes) { Vector queries = (Vector)getQueries().get(queryName); if (queries == null) { return; } else { DatabaseQuery query = null; for (Enumeration enumtr = queries.elements(); enumtr.hasMoreElements();) { query = (DatabaseQuery)enumtr.nextElement(); if (Helper.areTypesAssignable(argumentTypes, query.getArgumentTypes())) { break; } } if (query != null) { queries.remove(query); } } } /** * PROTECTED: * Attempts to rollback the running internally started external transaction. * Returns true only in one case - * extenal transaction has been internally rolled back during this method call: * wasJTSTransactionInternallyStarted()==true in the beginning of this method and * wasJTSTransactionInternallyStarted()==false in the end of this method. */ protected boolean rollbackExternalTransaction() { boolean externalTransactionHasRolledBack = false; if (hasExternalTransactionController() && wasJTSTransactionInternallyStarted()) { try { getExternalTransactionController().rollbackTransaction(this); } catch (RuntimeException exception) { handleException(exception); } if (!wasJTSTransactionInternallyStarted()) { externalTransactionHasRolledBack = true; log(SessionLog.FINER, SessionLog.TRANSACTION, "external_transaction_has_rolled_back_internally"); } } return externalTransactionHasRolledBack; } /** * PUBLIC: * Rollback the active database transaction. * This allows a group of database modification to be commited or rolledback as a unit. * All writes/deletes will be sent to the database be will not be visible to other users until commit. * Although databases do not allow nested transaction, * EclipseLink supports nesting through only committing to the database on the outer commit. * * @exception DatabaseException if the database connection is lost or the rollback fails. * @exception ConcurrencyException if this session is not within a transaction. */ public void rollbackTransaction() throws DatabaseException, ConcurrencyException { ConcurrencyManager mutex = getTransactionMutex(); // Ensure release of mutex and call subclass specific release. try { if (!mutex.isNested()) { if (this.eventManager != null) { this.eventManager.preRollbackTransaction(); } basicRollbackTransaction(); if (this.eventManager != null) { this.eventManager.postRollbackTransaction(); } } } finally { mutex.release(); // If there is no db transaction in progress // if there is an active external transaction // which was started internally - it should be rolled back internally, too. if (!mutex.isAcquired()) { rollbackExternalTransaction(); } } } /** * INTERNAL: * Set the accessor. */ public void setAccessor(Accessor accessor) { this.accessors = new ArrayList(1); this.accessors.add(accessor); } /** * INTERNAL: * Allow the session to be used from a session broker. */ public void setBroker(AbstractSession broker) { this.broker = broker; } /** * INTERNAL: * The commit manager is used to resolve referncial integrity on commits of multiple objects. */ public void setCommitManager(CommitManager commitManager) { this.commitManager = commitManager; } public void setInjectionManager( InjectionManager injectionManager) { this.injectionManager = injectionManager; } /** * INTERNAL: * Set the event manager. * The event manager can be used to register for various session events. */ public void setEventManager(SessionEventManager eventManager) { this.eventManager = eventManager; if (eventManager != null) { this.eventManager.setSession(this); } } /** * PUBLIC: * Set the exceptionHandler. * Exception handler can catch errors that occur on queries or during database access. */ @Override public void setExceptionHandler(ExceptionHandler exceptionHandler) { this.exceptionHandler = exceptionHandler; } /** * Used for JTS integration internally by ServerPlatform. */ @Override public void setExternalTransactionController(ExternalTransactionController externalTransactionController) { this.externalTransactionController = externalTransactionController; if (externalTransactionController == null) { return; } if (!hasBroker()) { externalTransactionController.setSession(this); } } /** * PUBLIC: * set the integrityChecker. IntegrityChecker holds all the ClassDescriptor Exceptions. */ @Override public void setIntegrityChecker(IntegrityChecker integrityChecker) { this.integrityChecker = integrityChecker; } /** * INTERNAL: * used to set if JPA Queries have been processed during initialization */ public void setJPAQueriesProcessed(boolean jpaQueriesProcessed){ this.jpaQueriesProcessed = jpaQueriesProcessed; } /** * PUBLIC: * Set the writer to which an accessor writes logged messages and SQL. * If not set, this reference defaults to a writer on System.out. * To enable logging logMessages() is used. * * @see #setLoggingOff(boolean) */ @Override public void setLog(Writer log) { getSessionLog().setWriter(log); } /** * PUBLIC: * Set the login. */ public void setLogin(DatabaseLogin login) { setDatasourceLogin(login); } /** * PUBLIC: * Set the login. */ public void setLogin(Login login) { setDatasourceLogin(login); } /** * PUBLIC: * Set the login. */ public void setDatasourceLogin(Login login) { getProject().setDatasourceLogin(login); this.platform = null; } /** * PUBLIC: * Set the name of the session. * This is used with the session broker. */ @Override public void setName(String name) { this.name = name; } protected void setNumberOfActiveUnitsOfWork(int numberOfActiveUnitsOfWork) { this.numberOfActiveUnitsOfWork = numberOfActiveUnitsOfWork; } /** * PUBLIC: * Set the default pessimistic lock timeout value. This value will be used * to set the WAIT clause of a SQL SELECT FOR UPDATE statement. It defines * how long EcliseLink should wait for a lock on the database row before * aborting. */ public void setPessimisticLockTimeoutDefault(Integer pessimisticLockTimeoutDefault) { this.pessimisticLockTimeoutDefault = pessimisticLockTimeoutDefault; } /** * PUBLIC: * Set the default query timeout for this session. * This timeout will apply to any queries that do not have a timeout set, * and that do not have a default timeout defined in their descriptor. */ @Override public void setQueryTimeoutDefault(int queryTimeoutDefault) { this.queryTimeoutDefault = queryTimeoutDefault; } @Override public void setQueryTimeoutUnitDefault(TimeUnit queryTimeoutUnitDefault) { this.queryTimeoutUnitDefault = queryTimeoutUnitDefault; } /** * PUBLIC: * Set the profiler for the session. * This allows for performance operations to be profiled. */ @Override public void setProfiler(SessionProfiler profiler) { this.profiler = profiler; if (profiler != null) { profiler.setSession(this); setIsInProfile(getProfiler().getProfileWeight() != SessionProfiler.NONE); // Clear cached flag that bypasses the profiler check. getIdentityMapAccessorInstance().getIdentityMapManager().checkIsCacheAccessPreCheckRequired(); } else { setIsInProfile(false); } } /** * INTERNAL: * Set the project, the project holds configuration information including the descriptors. */ public void setProject(org.eclipse.persistence.sessions.Project project) { this.project = project; } /** * INTERNAL: * Set the user defined properties by shallow copying the propertiesMap. * @param propertiesMap */ public void setProperties(Map<String, Object> propertiesMap) { if (null == propertiesMap) { // Keep current behavior and set properties map to null properties = null; } else { /* * Bug# 219097 Clone as (HashMap) possible immutable maps to avoid * an UnsupportedOperationException on a later put() We do not know * the key:value type values at design time. putAll() is not * synchronized. We clone all maps whether immutable or not. */ properties = new HashMap(); // Shallow copy all internal key:value pairs - a null propertiesMap will throw a NPE properties.putAll(propertiesMap); } } /** * PUBLIC: Allow for user defined properties. */ @Override public void setProperty(String propertyName, Object propertyValue) { getProperties().put(propertyName, propertyValue); } /** * INTERNAL: * Set the named queries. */ public void setQueries(Map<String, List<DatabaseQuery>> queries) { this.queries = queries; } /** * PUBLIC: * Set the session log to which an accessor logs messages and SQL. * If not set, this will default to a session log on a writer on System.out. * To enable logging, log level can not be OFF. * Also set a backpointer to this session in SessionLog. * * @see #logMessage(String) */ @Override public void setSessionLog(SessionLog sessionLog) { this.isLoggingOff = false; this.sessionLog = sessionLog; if ((sessionLog != null) && (sessionLog.getSession() == null)) { sessionLog.setSession(this); } } /** * INTERNAL: * Set isSynchronized flag to indicate that this session is synchronized. * This method should only be called by setSynchronized methods of derived classes. */ public void setSynchronized(boolean synched) { isSynchronized = synched; } protected void setTransactionMutex(ConcurrencyManager transactionMutex) { this.transactionMutex = transactionMutex; } /** * INTERNAL: * Return if a JTS transaction was started by the session. * The session will start a JTS transaction if a unit of work or transaction is begun without a JTS transaction present. */ public void setWasJTSTransactionInternallyStarted(boolean wasJTSTransactionInternallyStarted) { this.wasJTSTransactionInternallyStarted = wasJTSTransactionInternallyStarted; } /** * PUBLIC: * Return if logging is enabled (false if log level is OFF) */ @Override public boolean shouldLogMessages() { if (this.isLoggingOff) { return false; } if (getLogLevel(null) == SessionLog.OFF) { return false; } else { return true; } } /** * INTERNAL: * Start the operation timing. */ @Override public void startOperationProfile(String operationName) { if (this.isInProfile) { getProfiler().startOperationProfile(operationName); } } /** * INTERNAL: * Start the operation timing. */ public void startOperationProfile(String operationName, DatabaseQuery query, int weight) { if (this.isInProfile) { getProfiler().startOperationProfile(operationName, query, weight); } } /** * Print the connection status with the session. */ @Override public String toString() { StringWriter writer = new StringWriter(); writer.write(getSessionTypeString() + "(" + Helper.cr() + "\t" + getAccessor() + Helper.cr() + "\t" + getDatasourcePlatform() + ")"); return writer.toString(); } /** * INTERNAL: * Unwrap the object if required. * This is used for the wrapper policy support and EJB. */ public Object unwrapObject(Object proxy) { return getDescriptor(proxy).getObjectBuilder().unwrapObject(proxy, this); } /** * PUBLIC: * Update the object and all of its privately owned parts in the database. * Update should only be used if the application knows that the object is new, * otherwise writeObject should be used. * The update operation can be customized through using an update query. * * @exception DatabaseException if an error occurs on the database, * these include constraint violations, security violations and general database errors. * @exception OptimisticLockException if the object's descriptor is using optimistic locking and * the object has been updated or deleted by another user since it was last read. * * @see UpdateObjectQuery * @see #writeObject(Object) */ public Object updateObject(Object domainObject) throws DatabaseException, OptimisticLockException { UpdateObjectQuery query = new UpdateObjectQuery(); query.setObject(domainObject); query.setIsExecutionClone(true); return executeQuery(query); } /** * ADVANCED: * This can be used to help debugging an object identity problem. * An object identity problem is when an object in the cache references an object not in the cache. * This method will validate that all cached objects are in a correct state. */ @Override public void validateCache() { getIdentityMapAccessorInstance().validateCache(); } /** * INTERNAL: * This method will be used to update the query with any settings required * For this session. It can also be used to validate execution. */ public void validateQuery(DatabaseQuery query) { // a no-op for this class } /** * TESTING: * This is used by testing code to ensure that a deletion was successful. */ public boolean verifyDelete(Object domainObject) { ObjectBuilder builder = getDescriptor(domainObject).getObjectBuilder(); Object implementation = builder.unwrapObject(domainObject, this); return builder.verifyDelete(implementation, this); } /** * INTERNAL: * Return if a JTS transaction was started by the session. * The session will start a JTS transaction if a unit of work or transaction is begun without a JTS transaction present. */ public boolean wasJTSTransactionInternallyStarted() { return wasJTSTransactionInternallyStarted; } /** * INTERNAL: * Wrap the object if required. * This is used for the wrapper policy support and EJB. */ public Object wrapObject(Object implementation) { return getDescriptor(implementation).getObjectBuilder().wrapObject(implementation, this); } /** * INTERNAL: * Write all of the objects and all of their privately owned parts in the database. * The allows for a group of new objects to be commited as a unit. * The objects will be commited through a single transactions and any * foreign keys/circular references between the objects will be resolved. */ protected void writeAllObjectsWithChangeSet(UnitOfWorkChangeSet uowChangeSet) throws DatabaseException, OptimisticLockException { getCommitManager().commitAllObjectsWithChangeSet(uowChangeSet); } /** * PUBLIC: * Write the object and all of its privately owned parts in the database. * Write will determine if an insert or an update should be done, * it may go to the database to determine this (by default will check the identity map). * The write operation can be customized through using an write query. * * @exception DatabaseException if an error occurs on the database, * these include constraint violations, security violations and general database errors. * @exception OptimisticLockException if the object's descriptor is using optimistic locking and * the object has been updated or deleted by another user since it was last read. * * @see WriteObjectQuery * @see #insertObject(Object) * @see #updateObject(Object) */ public Object writeObject(Object domainObject) throws DatabaseException, OptimisticLockException { WriteObjectQuery query = new WriteObjectQuery(); query.setObject(domainObject); query.setIsExecutionClone(true); return executeQuery(query); } /** * INTERNAL: * This method notifies the accessor that a particular sets of writes has * completed. This notification can be used for such thing as flushing the * batch mechanism */ public void writesCompleted() { if (getAccessors() == null) { return; } for (Accessor accessor : getAccessors()) { accessor.writesCompleted(this); } } /** * INTERNAL: * RemoteCommandManager method. This is a required method in order * to implement the CommandProcessor interface. * Process the remote command from the RCM. The command may have come from * any remote session or application. Since this is a EclipseLink session we can * always assume that the object that we receive here will be a Command object. */ @Override public void processCommand(Object command) { ((Command)command).executeWithSession(this); } /** * INTERNAL: * Process the JPA named queries into EclipseLink Session queries. This * method is called after descriptor initialization. * Temporarily made public for ODI. Should not be used elsewhere. */ public void processJPAQueries() { if (! jpaQueriesProcessed) { // Process the JPA queries that do not query table per tenant entities. for (DatabaseQuery jpaQuery : getJPAQueries()) { processJPAQuery(jpaQuery); } // Process the JPA queries that query table per tenant entities. At // the EMF level, these queries will be initialized and added right // away. At the EM level we must defer their initialization to each // individual client session. for (DatabaseQuery jpaQuery : getJPATablePerTenantQueries()) { boolean processQuery = true; for (ClassDescriptor descriptor : jpaQuery.getDescriptors()) { // If the descriptor is not fully initialized then we can // not initialize the query and must isolate it to be // initialized and stored per client session (EM). if (! descriptor.isFullyInitialized()) { processQuery = false; break; } } if (processQuery) { processJPAQuery(jpaQuery); } else { addTablePerTenantQuery(jpaQuery); } } jpaQueriesProcessed = true; } } /** * INTERNAL: * Process the JPA named query into an EclipseLink Session query. This * method is called after descriptor initialization. */ protected void processJPAQuery(DatabaseQuery jpaQuery) { // This is a hack, to allow the core Session to initialize JPA queries, without have a dependency on JPA. // They need to be initialized after login, as the database platform must be known. try { jpaQuery.prepareInternal(this); } catch (RuntimeException re) { // If jpql-tolerate-error==true, any problems will be ignored at query prep time and the runtime will // continue chugging along. The invalid query will be left in place so that an exception // will be thrown at runtime if a user attempts to use it. if (!tolerateInvalidJPQL) { throw re; } } DatabaseQuery databaseQuery = (DatabaseQuery) jpaQuery.getProperty("databasequery"); databaseQuery = (databaseQuery == null) ? jpaQuery : databaseQuery; addQuery(databaseQuery, false); // this should be true but for backward compatibility it // is set to false. } /** * PUBLIC: * Return the CommandManager that allows this session to act as a * CommandProcessor and receive or propagate commands from/to the * EclipseLink cluster. * * @see CommandManager * @return The CommandManager instance that controls the remote command * service for this session */ @Override public CommandManager getCommandManager() { return commandManager; } /** * ADVANCED: * Set the CommandManager that allows this session to act as a * CommandProcessor and receive or propagate commands from/to the * EclipseLink cluster. * * @see CommandManager * @param mgr The CommandManager instance to control the remote command * service for this session */ @Override public void setCommandManager(CommandManager mgr) { commandManager = mgr; } /** * PUBLIC: * Return whether changes should be propagated to other sessions or applications * in a EclipseLink cluster through the Remote Command Manager mechanism. In order for * this to occur the CommandManager must be set. * * @see #setCommandManager(CommandManager) * @return True if propagation is set to occur, false if not */ public boolean shouldPropagateChanges() { return shouldPropagateChanges; } /** * ADVANCED: * Set whether changes should be propagated to other sessions or applications * in a EclipseLink cluster through the Remote Command Manager mechanism. In order for * this to occur the CommandManager must be set. * * @see #setCommandManager(CommandManager) * @param choice If true (and the CommandManager is set) then propagation will occur */ public void setShouldPropagateChanges(boolean choice) { shouldPropagateChanges = choice; } /** * INTERNAL: * RemoteCommandManager method. This is a required method in order * to implement the CommandProcessor interface. * Return true if a message at the specified log level would be logged given the * log level setting of this session. This can be used by the CommandManager to * know whether it should even bother to create the localized strings and call * the logMessage method, or if it would only find that the message would not be * logged because the session level does not permit logging. The log level passed * in will be one of the constants LOG_ERROR, LOG_WARNING, LOG_INFO, and LOG_DEBUG * defined in the CommandProcessor interface. */ @Override public boolean shouldLogMessages(int logLevel) { if (this.isLoggingOff) { return false; } if (LOG_ERROR == logLevel) { return getLogLevel(SessionLog.PROPAGATION) <= SessionLog.SEVERE; } if (LOG_WARNING == logLevel) { return getLogLevel(SessionLog.PROPAGATION) <= SessionLog.WARNING; } if (LOG_INFO == logLevel) { return getLogLevel(SessionLog.PROPAGATION) <= SessionLog.FINER; } if (LOG_DEBUG == logLevel) { return getLogLevel(SessionLog.PROPAGATION) <= SessionLog.FINEST; } return false; } /** * INTERNAL: * RemoteCommandManager method. This is a required method in order * to implement the CommandProcessor interface. * Log the specified message string at the specified level if it should be logged * given the log level setting in this session. The log level passed in will be one * of the constants LOG_ERROR, LOG_WARNING, LOG_INFO, and LOG_DEBUG defined in the * CommandProcessor interface. */ @Override public void logMessage(int logLevel, String message) { if (this.isLoggingOff) { return; } if (shouldLogMessages(logLevel)) { int level; switch (logLevel) { case CommandProcessor.LOG_ERROR: level = SessionLog.SEVERE; break; case CommandProcessor.LOG_WARNING: level = SessionLog.WARNING; break; case CommandProcessor.LOG_INFO: level = SessionLog.FINER; break; case CommandProcessor.LOG_DEBUG: level = SessionLog.FINEST; break; default: level = SessionLog.ALL; ; } log(level, SessionLog.PROPAGATION, message, null, null, false); } } /** * PUBLIC: * <p> * Return the log level * </p><p> * * @return the log level * </p><p> * @param category the string representation of a EclipseLink category, e.g. "sql", "transaction" ... * </p> */ @Override public int getLogLevel(String category) { return getSessionLog().getLevel(category); } /** * PUBLIC: * <p> * Return the log level * </p><p> * @return the log level * </p> */ @Override public int getLogLevel() { return getSessionLog().getLevel(); } /** * PUBLIC: * <p> * Set the log level * </p><p> * * @param level the new log level * </p> */ @Override public void setLogLevel(int level) { this.isLoggingOff = false; getSessionLog().setLevel(level); } /** * PUBLIC: * Return true if SQL logging should log visible bind parameters. If the * shouldDisplayData is not set, check the session log level and return * true for a level greater than CONFIG. */ public boolean shouldDisplayData() { return getSessionLog().shouldDisplayData(); } /** * PUBLIC: * <p> * Check if a message of the given level would actually be logged. * </p><p> * * @return true if the given message level will be logged * </p><p> * @param level the log request level * @param category the string representation of a EclipseLink category * </p> */ @Override public boolean shouldLog(int Level, String category) { if (this.isLoggingOff) { return false; } return getSessionLog().shouldLog(Level, category); } /** * PUBLIC: * <p> * Log a message with level and category that needs to be translated. * </p><p> * * @param level the log request level value * </p><p> * @param message the string message * </p><p> * @param category the string representation of a EclipseLink category. * </p> */ public void log(int level, String category, String message) { if (this.isLoggingOff) { return; } if (!shouldLog(level, category)) { return; } log(level, category, message, (Object[])null); } /** * PUBLIC: * <p> * Log a message with level, category and a parameter that needs to be translated. * </p><p> * * @param level the log request level value * </p><p> * @param message the string message * </p><p> * @param category the string representation of a EclipseLink category. * </p><p> * @param param a parameter of the message * </p> */ public void log(int level, String category, String message, Object param) { if (this.isLoggingOff) { return; } if (!shouldLog(level, category)) { return; } log(level, category, message, new Object[] { param }); } /** * PUBLIC: * <p> * Log a message with level, category and two parameters that needs to be translated. * </p><p> * * @param level the log request level value * </p><p> * @param message the string message * </p><p> * @param category the string representation of a EclipseLink category. * </p><p> * @param param1 a parameter of the message * </p><p> * @param param2 second parameter of the message * </p> */ public void log(int level, String category, String message, Object param1, Object param2) { if (this.isLoggingOff) { return; } if (!shouldLog(level, category)) { return; } log(level, category, message, new Object[] { param1, param2 }); } /** * PUBLIC: * <p> * Log a message with level, category and three parameters that needs to be translated. * </p><p> * * @param level the log request level value * </p><p> * @param message the string message * </p><p> * @param category the string representation of a EclipseLink category. * </p><p> * @param param1 a parameter of the message * </p><p> * @param param2 second parameter of the message * </p><p> * @param param3 third parameter of the message * </p> */ public void log(int level, String category, String message, Object param1, Object param2, Object param3) { if (this.isLoggingOff) { return; } if (!shouldLog(level, category)) { return; } log(level, category, message, new Object[] { param1, param2, param3 }); } /** * PUBLIC: * <p> * Log a message with level, category and an array of parameters that needs to be translated. * </p><p> * * @param level the log request level value * </p><p> * @param message the string message * </p><p> * @param category the string representation of a EclipseLink category. * </p><p> * @param params array of parameters to the message * </p> */ public void log(int level, String category, String message, Object[] params) { if (this.isLoggingOff) { return; } log(level, category, message, params, null); } /** * PUBLIC: * <p> * Log a message with level, category, parameters and accessor that needs to be translated. * </p><p> * * @param level the log request level value * </p><p> * @param message the string message * </p><p> * @param params array of parameters to the message * </p><p> * @param accessor the connection that generated the log entry * </p><p> * @param category the string representation of a EclipseLink category. * </p> */ public void log(int level, String category, String message, Object[] params, Accessor accessor) { if (this.isLoggingOff) { return; } log(level, category, message, params, accessor, true); } /** * PUBLIC: * <p> * Log a message with level, category, parameters and accessor. shouldTranslate determines if the message needs to be translated. * </p><p> * * @param level the log request level value * </p><p> * @param message the string message * </p><p> * @param params array of parameters to the message * </p><p> * @param accessor the connection that generated the log entry * </p><p> * @param category the string representation of a EclipseLink category. * </p><p> * @param shouldTranslate true if the message needs to be translated. * </p> */ public void log(int level, String category, String message, Object[] params, Accessor accessor, boolean shouldTranslate) { if (this.isLoggingOff) { return; } if (shouldLog(level, category)) { startOperationProfile(SessionProfiler.Logging); log(new SessionLogEntry(level, category, this, message, params, accessor, shouldTranslate)); endOperationProfile(SessionProfiler.Logging); } } /** * PUBLIC: * <p> * Log a message with level, parameters and accessor that needs to be translated. * </p><p> * * @param level the log request level value * </p><p> * @param message the string message * </p><p> * @param params array of parameters to the message * </p><p> * @param accessor the connection that generated the log entry * </p> * @deprecated since 2.6 replaced by log with category, log(int, String, String, Object[], Accessor) */ @Deprecated public void log(int level, String message, Object[] params, Accessor accessor) { if (this.isLoggingOff) { return; } log(level, message, params, accessor, true); } /** * PUBLIC: * <p> * Log a message with level, parameters and accessor. shouldTranslate determines if the message needs to be translated. * </p><p> * * @param level the log request level value * </p><p> * @param message the string message * </p><p> * @param params array of parameters to the message * </p><p> * @param accessor the connection that generated the log entry * </p><p> * @param shouldTranslate true if the message needs to be translated. * </p> * @deprecated since 2.6 replaced by log with category, log(int, String, String, Object[], Accessor, boolean) */ @Deprecated public void log(int level, String message, Object[] params, Accessor accessor, boolean shouldTranslate) { if (this.isLoggingOff) { return; } if (shouldLog(level, null)) { startOperationProfile(SessionProfiler.Logging); log(new SessionLogEntry(level, this, message, params, accessor, shouldTranslate)); endOperationProfile(SessionProfiler.Logging); } } /** * PUBLIC: * <p> * Log a throwable with level and category. * </p><p> * * @param level the log request level value * </p><p> * @param category the string representation of a EclipseLink category. * </p><p> * @param throwable a Throwable * </p> */ public void logThrowable(int level, String category, Throwable throwable) { if (this.isLoggingOff) { return; } // Must not create the log if not logging as is a performance issue. if (shouldLog(level, category)) { startOperationProfile(SessionProfiler.Logging); log(new SessionLogEntry(this, level, category, throwable)); endOperationProfile(SessionProfiler.Logging); } } /** * PUBLIC: * <p> * This method is called when a severe level message needs to be logged. * The message will be translated * </p><p> * * @param message the message key * </p> */ public void severe(String message, String category) { if (this.isLoggingOff) { return; } log(SessionLog.SEVERE, category, message); } /** * PUBLIC: * <p> * This method is called when a warning level message needs to be logged. * The message will be translated * </p><p> * * @param message the message key * </p> */ public void warning(String message, String category) { if (this.isLoggingOff) { return; } log(SessionLog.WARNING, category, message); } /** * PUBLIC: * <p> * This method is called when a info level message needs to be logged. * The message will be translated * </p><p> * * @param message the message key * </p> */ public void info(String message, String category) { if (this.isLoggingOff) { return; } log(SessionLog.INFO, category, message); } /** * PUBLIC: * <p> * This method is called when a config level message needs to be logged. * The message will be translated * </p><p> * * @param message the message key * </p> */ public void config(String message, String category) { if (this.isLoggingOff) { return; } log(SessionLog.CONFIG, category, message); } /** * PUBLIC: * <p> * This method is called when a fine level message needs to be logged. * The message will be translated * </p><p> * * @param message the message key * </p> */ public void fine(String message, String category) { if (this.isLoggingOff) { return; } log(SessionLog.FINE, category, message); } /** * PUBLIC: * <p> * This method is called when a finer level message needs to be logged. * The message will be translated * </p><p> * * @param message the message key * </p> */ public void finer(String message, String category) { if (this.isLoggingOff) { return; } log(SessionLog.FINER, category, message); } /** * PUBLIC: * <p> * This method is called when a finest level message needs to be logged. * The message will be translated * </p><p> * * @param message the message key * </p> */ public void finest(String message, String category) { if (this.isLoggingOff) { return; } log(SessionLog.FINEST, category, message); } /** * PUBLIC: * Allow any SEVERE level exceptions that occur within EclipseLink to be logged and handled by the exception handler. */ @Override public Object handleSevere(RuntimeException exception) throws RuntimeException { logThrowable(SessionLog.SEVERE, null, exception); if (hasExceptionHandler()) { return getExceptionHandler().handleException(exception); } else { throw exception; } } /** * INTERNAL: */ public void releaseReadConnection(Accessor connection) { //bug 4668234 -- used to only release connections on server sessions but should always release //do nothing -- overidden in UnitOfWork,ClientSession and ServerSession } /** * INTERNAL: * Copies descriptors cached on the Project. * Used after Project.descriptors has been reset by addDescriptor(s) when the session is connected. */ public void copyDescriptorsFromProject() { this.descriptors = getDescriptors(); } /** * INTERNAL: * This method will be used to copy all EclipseLink named queries defined in descriptors into the session. * @param allowSameQueryNameDiffArgsCopyToSession if the value is true, it allow * multiple queries of the same name but different arguments to be copied to the session. */ public void copyDescriptorNamedQueries(boolean allowSameQueryNameDiffArgsCopyToSession) { for (ClassDescriptor descriptor : getProject().getOrderedDescriptors()) { Map queries = descriptor.getQueryManager().getQueries(); if ((queries != null) && (queries.size() > 0)) { for (Iterator keyValueItr = queries.entrySet().iterator(); keyValueItr.hasNext();){ Map.Entry entry = (Map.Entry) keyValueItr.next(); Vector thisQueries = (Vector)entry.getValue(); if ((thisQueries != null) && (thisQueries.size() > 0)){ for( Iterator thisQueriesItr=thisQueries.iterator();thisQueriesItr.hasNext();){ DatabaseQuery queryToBeAdded = (DatabaseQuery)thisQueriesItr.next(); if (allowSameQueryNameDiffArgsCopyToSession){ addQuery(queryToBeAdded, false); } else { if (getQuery(queryToBeAdded.getName()) == null){ addQuery(queryToBeAdded, false); } else { log(SessionLog.WARNING, SessionLog.PROPERTIES, "descriptor_named_query_cannot_be_added", new Object[]{queryToBeAdded,queryToBeAdded.getName(),queryToBeAdded.getArgumentTypes()}); } } } } } } } } /** * INTERNAL: * This method rises appropriate for the session event(s) * right after connection is acquired. */ public void postAcquireConnection(Accessor accessor) { if (getProject().hasVPDIdentifier(this)) { if (getPlatform().supportsVPD()) { DatabaseQuery query = getPlatform().getVPDSetIdentifierQuery(getProject().getVPDIdentifier()); List argValues = new ArrayList(); query.addArgument(getProject().getVPDIdentifier()); argValues.add(getProperty(getProject().getVPDIdentifier())); executeQuery(query, argValues); } else { throw ValidationException.vpdNotSupported(getPlatform().getClass().getName()); } } if (this.eventManager != null) { this.eventManager.postAcquireConnection(accessor); } } /** * INTERNAL: * This method rises appropriate for the session event(s) * right before the connection is released. */ public void preReleaseConnection(Accessor accessor) { if (getProject().hasVPDIdentifier(this)) { if (getPlatform().supportsVPD()) { DatabaseQuery query = getPlatform().getVPDClearIdentifierQuery(getProject().getVPDIdentifier()); List argValues = new ArrayList(); query.addArgument(getProject().getVPDIdentifier()); argValues.add(getProperty(getProject().getVPDIdentifier())); executeQuery(query, argValues); } else { throw ValidationException.vpdNotSupported(getPlatform().getClass().getName()); } } if (this.eventManager != null) { this.eventManager.preReleaseConnection(accessor); } } /** * INTERNAL: * Execute the call on the database. Calling this method will bypass a * global setting to disallow native SQL queries. (set by default when * one Entity is marked as multitenant) * * The row count is returned. * * The call can be a stored procedure call, SQL call or other type of call. * * <p>Example: * <p>session.executeNonSelectingCall(new SQLCall("Delete from Employee"), true); * * @see #priviledgedExecuteSelectingCall(Call) */ public int priviledgedExecuteNonSelectingCall(Call call) throws DatabaseException { DataModifyQuery query = new DataModifyQuery(); query.setAllowNativeSQLQuery(true); query.setIsExecutionClone(true); query.setCall(call); Integer value = (Integer)executeQuery(query); if (value == null) { return 0; } else { return value.intValue(); } } /** * INTERNAL: * Execute the call on the database and return the result. Calling this * method will bypass a global setting to disallow native SQL queries. (set * by default when one Entity is marked as multitenant) * * The call must return a value, if no value is return executeNonSelectCall * must be used. * * The call can be a stored procedure call, SQL call or other type of call. * * A vector of database rows is returned, database row implements Java 2 Map * which should be used to access the data. * * <p>Example: * <p>session.executeSelectingCall(new SQLCall("Select * from Employee"); * * @see #priviledgedExecuteNonSelectingCall(Call) */ public Vector priviledgedExecuteSelectingCall(Call call) throws DatabaseException { DataReadQuery query = new DataReadQuery(); query.setAllowNativeSQLQuery(true); query.setCall(call); query.setIsExecutionClone(true); return (Vector)executeQuery(query); } /** * INTERNAL: * This method is called in case externalConnectionPooling is used. * If returns true, accessor used by the session keeps its * connection open until released by the session. */ public boolean isExclusiveConnectionRequired() { return false; } /** * Stores the default Session wide reference mode that a UnitOfWork will use when referencing * managed objects. * @see org.eclipse.persistence.config.ReferenceMode */ @Override public ReferenceMode getDefaultReferenceMode() { return defaultReferenceMode; } /** * Stores the default Session wide reference mode that a UnitOfWork will use when referencing * managed objects. * @see org.eclipse.persistence.config.ReferenceMode */ @Override public void setDefaultReferenceMode(ReferenceMode defaultReferenceMode) { this.defaultReferenceMode = defaultReferenceMode; } /** * This method will load the passed object or collection of objects using the passed AttributeGroup. * In case of collection all members should be either objects of the same mapped type * or have a common inheritance hierarchy mapped root class. * The AttributeGroup should correspond to the object type. * * @param objectOrCollection */ public void load(Object objectOrCollection, AttributeGroup group) { if (objectOrCollection == null || group == null) { return; } if (objectOrCollection instanceof Collection) { Iterator iterator = ((Collection)objectOrCollection).iterator(); while (iterator.hasNext()) { Object object = iterator.next(); load(object, group, getClassDescriptor(object.getClass()), false); } } else { ClassDescriptor concreteDescriptor = getClassDescriptor(objectOrCollection.getClass()); load(objectOrCollection, group, concreteDescriptor, false); } } /** * This method will load the passed object or collection of objects using the passed AttributeGroup. * In case of collection all members should be either objects of the same mapped type * or have a common inheritance hierarchy mapped root class. * The AttributeGroup should correspond to the object type. * * @param objectOrCollection */ public void load(Object objectOrCollection, AttributeGroup group, ClassDescriptor referenceDescriptor, boolean fromFetchGroup) { if (objectOrCollection == null || group == null) { return; } if (objectOrCollection instanceof Collection) { Iterator iterator = ((Collection)objectOrCollection).iterator(); while (iterator.hasNext()) { load(iterator.next(), group, referenceDescriptor, fromFetchGroup); } } else { ClassDescriptor concreteDescriptor = referenceDescriptor; if (concreteDescriptor.hasInheritance() && !objectOrCollection.getClass().equals(concreteDescriptor.getJavaClass())){ concreteDescriptor = concreteDescriptor.getInheritancePolicy().getDescriptor(objectOrCollection.getClass()); } AttributeGroup concreteGroup = group.findGroup(concreteDescriptor); concreteDescriptor.getObjectBuilder().load(objectOrCollection, concreteGroup, this, fromFetchGroup); } } public CacheKey retrieveCacheKey(Object primaryKey, ClassDescriptor concreteDescriptor, JoinedAttributeManager joinManager, ObjectBuildingQuery query){ CacheKey cacheKey; //lock the object in the IM // PERF: Only use deferred locking if required. // CR#3876308 If joining is used, deferred locks are still required. if (query.requiresDeferredLocks()) { cacheKey = this.getIdentityMapAccessorInstance().acquireDeferredLock(primaryKey, concreteDescriptor.getJavaClass(), concreteDescriptor, query.isCacheCheckComplete() || query.shouldRetrieveBypassCache()); if (cacheKey.getActiveThread() != Thread.currentThread()) { int counter = 0; while ((cacheKey.getObject() == null) && (counter < 1000)) { //must release lock here to prevent acquiring multiple deferred locks but only //releasing one at the end of the build object call. //bug 5156075 cacheKey.releaseDeferredLock(); //sleep and try again if we are not the owner of the lock for CR 2317 // prevents us from modifying a cache key that another thread has locked. try { Thread.sleep(10); } catch (InterruptedException exception) { } cacheKey = this.getIdentityMapAccessorInstance().acquireDeferredLock(primaryKey, concreteDescriptor.getJavaClass(), concreteDescriptor, query.isCacheCheckComplete() || query.shouldRetrieveBypassCache()); if (cacheKey.getActiveThread() == Thread.currentThread()) { break; } counter++; } if (counter == 1000) { throw ConcurrencyException.maxTriesLockOnBuildObjectExceded(cacheKey.getActiveThread(), Thread.currentThread()); } } } else { cacheKey = this.getIdentityMapAccessorInstance().acquireLock(primaryKey, concreteDescriptor.getJavaClass(), concreteDescriptor, query.isCacheCheckComplete() || query.shouldRetrieveBypassCache()); } return cacheKey; } /** * PUBLIC: * Return the session's partitioning policy. */ @Override public PartitioningPolicy getPartitioningPolicy() { return partitioningPolicy; } /** * PUBLIC: * Set the session's partitioning policy. * A PartitioningPolicy is used to partition, load-balance or replicate data across multiple difference databases * or across a database cluster such as Oracle RAC. * Partitioning can provide improved scalability by allowing multiple database machines to service requests. */ @Override public void setPartitioningPolicy(PartitioningPolicy partitioningPolicy) { this.partitioningPolicy = partitioningPolicy; } /** * INTERNAL: * This currently only used by JPA with RCM to force a refresh of the metadata used within EntityManagerFactoryWrappers */ public MetadataRefreshListener getRefreshMetadataListener() { return metadatalistener; } public void setRefreshMetadataListener(MetadataRefreshListener metadatalistener) { this.metadatalistener = metadatalistener; } /** * ADVANCED: * Return if the session enables concurrent processing. * Concurrent processing allow certain processing to be done on seperate threads. * This can result in improved performance. * This will use the session's server platform's thread pool. */ public boolean isConcurrent() { return this.isConcurrent; } /** * ADVANCED: * Set if the session enables concurrent processing. * Concurrent processing allow certain processing to be done on seperate threads. * This can result in improved performance. * This will use the session's server platform's thread pool. */ public void setIsConcurrent(boolean isConcurrent) { this.isConcurrent = isConcurrent; } /** * ADVANCED: * Set to indicate whether ObjectLevelReadQuery should by default use ResultSet Access optimization. * If not set then parent's flag is used, is none set then ObjectLevelReadQuery.isResultSetAccessOptimizedQueryDefault is used. * If the optimization specified by the session is ignored if incompatible with other query settings. */ public void setShouldOptimizeResultSetAccess(boolean shouldOptimizeResultSetAccess) { this.shouldOptimizeResultSetAccess = shouldOptimizeResultSetAccess; } /** * ADVANCED: * Indicates whether ObjectLevelReadQuery should by default use ResultSet Access optimization. * Optimization specified by the session is ignored if incompatible with other query settings. */ public boolean shouldOptimizeResultSetAccess() { return this.shouldOptimizeResultSetAccess; } /** * ADVANCED: Indicates whether an invalid NamedQuery will be tolerated at init time. * * Default is false. */ public void setTolerateInvalidJPQL(boolean b) { this.tolerateInvalidJPQL = b; } /** * ADVANCED: Indicates whether an invalid NamedQuery will be tolerated at init time. * * Default is false. */ public boolean shouldTolerateInvalidJPQL() { return this.tolerateInvalidJPQL; } }