blob: ebf8e57de266cf7aac369e9f982607257ee47540 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2021 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
// 14/05/2012-2.4 Guy Pelletier
// - 376603: Provide for table per tenant support for multitenant applications
// 22/05/2012-2.4 Guy Pelletier
// - 380008: Multitenant persistence units with a dedicated emf should force tenant property specification up front.
// 31/05/2012-2.4 Guy Pelletier
// - 381196: Multitenant persistence units with a dedicated emf should allow for DDL generation.
// 12/24/2012-2.5 Guy Pelletier
// - 389090: JPA 2.1 DDL Generation Support
// 01/11/2013-2.5 Guy Pelletier
// - 389090: JPA 2.1 DDL Generation Support
// 03/19/2015 - Rick Curtis
// - 462586 : Add national character support for z/OS.
// 04/14/2015 - Will Dazey
// - 464641 : Fixed platform matching returning CNF.
// 09/03/2015 - Will Dazey
// - 456067 : Added support for defining query timeout units
// 06/26/2018 - Will Dazey
// - 532160 : Add support for non-extension OracleXPlatform classes
package org.eclipse.persistence.internal.sessions;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.partitioning.PartitioningPolicy;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.EclipseLinkException;
import org.eclipse.persistence.exceptions.IntegrityException;
import org.eclipse.persistence.exceptions.OptimisticLockException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.databaseaccess.Platform;
import org.eclipse.persistence.internal.helper.DBPlatformHelper;
import org.eclipse.persistence.internal.sequencing.Sequencing;
import org.eclipse.persistence.internal.sequencing.SequencingFactory;
import org.eclipse.persistence.internal.sequencing.SequencingHome;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.platform.database.DatabasePlatform;
import org.eclipse.persistence.platform.database.OraclePlatform;
import org.eclipse.persistence.platform.database.events.DatabaseEventListener;
import org.eclipse.persistence.platform.server.NoServerPlatform;
import org.eclipse.persistence.platform.server.ServerPlatform;
import org.eclipse.persistence.platform.server.ServerPlatformBase;
import org.eclipse.persistence.queries.AttributeGroup;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.QueryResultsCachePolicy;
import org.eclipse.persistence.queries.ReadQuery;
import org.eclipse.persistence.sequencing.Sequence;
import org.eclipse.persistence.sequencing.SequencingControl;
import org.eclipse.persistence.sessions.DatasourceLogin;
import org.eclipse.persistence.sessions.Login;
import org.eclipse.persistence.sessions.SessionProfiler;
import org.eclipse.persistence.tools.tuning.SessionTuner;
/**
* Implementation of org.eclipse.persistence.sessions.DatabaseSession
* The public interface should be used.
* @see org.eclipse.persistence.sessions.DatabaseSession
*
* <p>
* <b>Purpose</b>: Define the implementation for a single user/single connection EclipseLink 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. The database session is intended
* for usage in two-tier client-server applications. Although it could be used in a server
* situation, it is limited to only having a single database connection and only allows
* a single open database transaction.
* <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>
*/
public class DatabaseSessionImpl extends AbstractSession implements org.eclipse.persistence.sessions.DatabaseSession {
/**
* Database event listener, this allows database events to invalidate the cache.
*/
protected DatabaseEventListener databaseEventListener;
/**
* INTERNAL:
* sequencingHome for this session.
*/
protected SequencingHome sequencingHome;
/**
* Used to store the server platform that handles server-specific functionality for Oc4j, WLS, etc.
*/
protected ServerPlatform serverPlatform;
/**
* Stores the tuner used to tune the configuration of this session.
*/
protected SessionTuner tuner;
/**
* INTERNAL:
* connectedTime indicates the exact time this session was logged in.
*/
protected long connectedTime;
/**
* INTERNAL
* Indicate if this session is logged in.
*/
//Bug#3440544 Used to stop the attempt to login more than once.
protected volatile boolean isLoggedIn;
/**
* INTERNAL:
* Set the SequencingHome object used by the session.
*/
protected void setSequencingHome(SequencingHome sequencingHome) {
this.sequencingHome = sequencingHome;
}
/**
* INTERNAL:
* Return SequencingHome which used to obtain all sequence-related
* interfaces for DatabaseSession
*/
protected SequencingHome getSequencingHome() {
if (sequencingHome == null) {
setSequencingHome(SequencingFactory.createSequencingHome(this));
}
return sequencingHome;
}
/**
* INTERNAL:
* Return if the session was logged in.
* This may slight differ to isConnected which asks the JDBC Connection if it is connected.
*/
public boolean isLoggedIn() {
return isLoggedIn;
}
/**
* Return the database event listener, this allows database events to invalidate the cache.
*/
@Override
public DatabaseEventListener getDatabaseEventListener() {
return databaseEventListener;
}
/**
* PUBLIC:
* Set the database event listener, this allows database events to invalidate the cache.
*/
@Override
public void setDatabaseEventListener(DatabaseEventListener databaseEventListener) {
this.databaseEventListener = databaseEventListener;
}
/**
* INTERNAL:
* Issue any pre connect and post connect without an actual connection to
* the database. Descriptors are initialized in postConnectDatasource and
* are used in DDL generation. This will look to set the schema platform
* via the JPA 2.1 properties or through a detection on the connection
* before DDL generation.
*/
public void setDatasourceAndInitialize() throws DatabaseException {
preConnectDatasource();
setOrDetectDatasource(false);
postConnectDatasource();
}
/**
* INTERNAL:
* Will set the platform from specified schema generation properties or
* by detecting it through the connection (if one is available).
* Any connection that is open for detection is closed before this method
* returns.
*
* @param throwException - set to true if the caller cares to throw exceptions, false to swallow them.
*/
protected void setOrDetectDatasource(boolean throwException) {
String vendorName = null;
String minorVersion = null;
String majorVersion = null;
String driverName = null;
// Try to set the platform from JPA 2.1 schema properties first before
// attempting a detection.
if (getProperties().containsKey(PersistenceUnitProperties.SCHEMA_DATABASE_PRODUCT_NAME)) {
vendorName = (String) getProperties().get(PersistenceUnitProperties.SCHEMA_DATABASE_PRODUCT_NAME);
minorVersion = (String) getProperties().get(PersistenceUnitProperties.SCHEMA_DATABASE_MINOR_VERSION);
majorVersion = (String) getProperties().get(PersistenceUnitProperties.SCHEMA_DATABASE_MAJOR_VERSION);
} else {
Connection conn = null;
try {
conn = (Connection) getReadLogin().connectToDatasource(null, this);
DatabaseMetaData dmd = conn.getMetaData();
vendorName = dmd.getDatabaseProductName();
minorVersion = dmd.getDatabaseProductVersion();
majorVersion = Integer.toString(dmd.getDatabaseMajorVersion());
driverName = conn.getMetaData().getDriverName();
} catch (SQLException ex) {
if (throwException) {
DatabaseException dbEx = DatabaseException.errorRetrieveDbMetadataThroughJDBCConnection();
// Typically exception would occur if user did not provide
// correct connection
// parameters. The root cause of exception should be
// propagated up
dbEx.initCause(ex);
throw dbEx;
}
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException ex) {
if (throwException) {
DatabaseException dbEx = DatabaseException.errorRetrieveDbMetadataThroughJDBCConnection();
// Typically exception would occur if user did not
// provide correct connection
// parameters. The root cause of exception should be
// propagated up
dbEx.initCause(ex);
throw dbEx;
}
}
}
}
}
String platformName = null;
try {
// null out the cached platform because the platform on the login
// will be changed by the following line of code
this.platform = null;
platformName = DBPlatformHelper.getDBPlatform(vendorName, minorVersion, majorVersion, getSessionLog());
getLogin().setPlatformClassName(platformName);
} catch (EclipseLinkException classNotFound) {
if (platformName != null && platformName.indexOf("Oracle") != -1) {
try {
// If we are running against Oracle, it is possible that we are
// running in an environment where the extension OracleXPlatform classes can
// not be loaded. Try using the core OracleXPlatform classes
platformName = DBPlatformHelper.getDBPlatform("core."+ vendorName, minorVersion, majorVersion, getSessionLog());
getLogin().setPlatformClassName(platformName);
} catch (EclipseLinkException oracleClassNotFound) {
// If we still cannot classload a matching OracleXPlatform class,
// fallback on the base OraclePlatform class
getLogin().setPlatformClassName(OraclePlatform.class.getName());
}
} else {
throw classNotFound;
}
}
if (driverName != null) {
getLogin().getPlatform().setDriverName(driverName);
}
}
/**
* PUBLIC:
* Return SequencingControl which used for sequencing setup and
* customization including management of sequencing preallocation.
*/
@Override
public SequencingControl getSequencingControl() {
return getSequencingHome().getSequencingControl();
}
/**
* PUBLIC:
* Return the Sequencing object used by the session.
*/
@Override
public Sequencing getSequencing() {
return getSequencingHome().getSequencing();
}
/**
* INTERNAL:
* Indicates whether SequencingCallback is required.
* Always returns false if sequencing is not connected.
*/
public boolean isSequencingCallbackRequired() {
return getSequencingHome().isSequencingCallbackRequired();
}
/**
* INTERNAL:
* Creates sequencing object
*/
public void initializeSequencing() {
getSequencingHome().onDisconnect();
getSequencingHome().onConnect();
}
/**
* INTERNAL:
* If sequencing is connected then initializes sequences referenced by the passed descriptors,
* otherwise connects sequencing.
* @param descriptors class descriptors
*/
public void addDescriptorsToSequencing(Collection<ClassDescriptor> descriptors) {
getSequencingHome().onAddDescriptors(descriptors);
}
/**
* INTERNAL:
* Called in the end of beforeCompletion of external transaction synchronization listener.
* Close the managed sql connection corresponding to the external transaction.
*/
@Override
public void releaseJTSConnection() {
getAccessor().closeJTSConnection();
}
/**
* INTERNAL:
* Create and return a new default database session.
* Used for EJB SessionManager to instantiate a database session
*/
public DatabaseSessionImpl() {
super();
this.setServerPlatform(new NoServerPlatform(this));
this.shouldOptimizeResultSetAccess = ObjectLevelReadQuery.isResultSetAccessOptimizedQueryDefault;
}
/**
* 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 DatabaseSessionImpl(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 DatabaseSessionImpl(org.eclipse.persistence.sessions.Project project) {
super(project);
this.setServerPlatform(new NoServerPlatform(this));
this.shouldOptimizeResultSetAccess = ObjectLevelReadQuery.isResultSetAccessOptimizedQueryDefault;
}
/**
* PUBLIC:
* Add the descriptor to the session.
* All persistent classes must have a descriptor registered for them with the session.
* It is best to add the descriptors before login, if added after login the order in which
* descriptors are added is dependent on inheritance and references unless the addDescriptors
* method is used.
*
* @see #addDescriptors(Collection)
* @see #addDescriptors(org.eclipse.persistence.sessions.Project)
*/
@Override
public void addDescriptor(ClassDescriptor descriptor) {
// Reset cached data, as may be invalid later on.
this.lastDescriptorAccessed = null;
// Bug# 429760: Add descriptor to the session when session Map exists and is not the same as in the project.
if (this.descriptors != null && this.descriptors != getProject().getDescriptors()) {
this.descriptors.put(descriptor.getJavaClass(), descriptor);
}
getProject().addDescriptor(descriptor, this);
}
/**
* PUBLIC:
* Add the descriptors to the session.
* All persistent classes must have a descriptor registered for them with the session.
* This method allows for a batch of descriptors to be added at once so that EclipseLink
* can resolve the dependencies between the descriptors and perform initialization optimally.
* @param descriptors The descriptors to be added to the session and project.
*/
@Override
public void addDescriptors(final Collection descriptors) {
// Reset cached data, as may be invalid later on.
this.lastDescriptorAccessed = null;
// Bug# 429760: Add descriptors to the session when session Map exists and is not the same as in the project.
if (this.descriptors != null && this.descriptors != getProject().getDescriptors()) {
for (ClassDescriptor descriptor : (Collection<ClassDescriptor>) descriptors) {
this.descriptors.put(descriptor.getJavaClass(), descriptor);
}
}
getProject().addDescriptors(descriptors, this);
}
/**
* PUBLIC:
* Add the descriptors to the session from the Project.
* This can be used to combine the descriptors from multiple projects into a single session.
* This can be called after the session has been connected as long as there are no external dependencies.
*/
@Override
public void addDescriptors(org.eclipse.persistence.sessions.Project project) {
// Reset cached data, as may be invalid later on.
this.lastDescriptorAccessed = null;
getProject().addDescriptors(project, this);
}
/**
* PUBLIC:
* Add the sequence to the session.
* Allows to add a new sequence to the session even if the session is connected.
* If the session is connected then the sequence is added only
* if there is no sequence with the same name already in use.
* Call this method before addDescriptor(s) if need to add new descriptor
* with a new non-default sequence to connected session.
*/
@Override
public void addSequence(Sequence sequence) {
getProject().getLogin().getDatasourcePlatform().addSequence(sequence, this.getSequencingHome().isConnected());
}
/**
* INTERNAL:
* Connect the session only.
*/
public void connect() throws DatabaseException {
getAccessor().connect(getDatasourceLogin(), this);
}
/**
* INTERNAL:
* Disconnect the accessor only.
*/
public void disconnect() throws DatabaseException {
getSequencingHome().onDisconnect();
getAccessor().disconnect(this);
}
/**
* PUBLIC:
* Answer the server platform to handle server specific behavior for WLS, Oc4j, etc.
*
* If the user wants a different external transaction controller class or
* to provide some different behavior than the provided ServerPlatform(s), we recommend
* subclassing org.eclipse.persistence.platform.server.ServerPlatformBase (or a subclass),
* and overriding:
*
* ServerPlatformBase.getExternalTransactionControllerClass()
* ServerPlatformBase.registerMBean()
* ServerPlatformBase.unregisterMBean()
*
* for the desired behavior.
*
* @see org.eclipse.persistence.platform.server.ServerPlatformBase
*/
@Override
public ServerPlatform getServerPlatform() {
return serverPlatform;
}
/**
* PUBLIC:
* Set the server platform to handle server specific behavior for WLS, Oc4j, etc
*
* This is not permitted after the session is logged in.
*
* If the user wants a different external transaction controller class or
* to provide some different behavior than the provided ServerPlatform(s), we recommend
* subclassing org.eclipse.persistence.platform.server.ServerPlatformBase (or a subclass),
* and overriding:
*
* ServerPlatformBase.getExternalTransactionControllerClass()
* ServerPlatformBase.registerMBean()
* ServerPlatformBase.unregisterMBean()
*
* for the desired behavior.
*
* @see org.eclipse.persistence.platform.server.ServerPlatformBase
*/
@Override
public void setServerPlatform(ServerPlatform newServerPlatform) {
if (this.isLoggedIn) {
throw ValidationException.serverPlatformIsReadOnlyAfterLogin(newServerPlatform.getClass().getName());
}
this.serverPlatform = newServerPlatform;
}
/**
* INTERNAL:
* Logout in case still connected.
*/
@Override
protected void finalize() throws DatabaseException {
if (isConnected()) {
logout();
}
}
/**
* 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) {
if(isLoggedIn) {
platform = getDatasourceLogin().getPlatform();
} else {
return getDatasourceLogin().getPlatform();
}
}
return (DatabasePlatform)platform;
}
/**
* 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) {
if(isLoggedIn) {
platform = getDatasourceLogin().getDatasourcePlatform();
} else {
return getDatasourceLogin().getDatasourcePlatform();
}
}
return platform;
}
/**
* 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) {
if(isLoggedIn) {
platform = getDatasourceLogin().getDatasourcePlatform();
} else {
return getDatasourceLogin().getDatasourcePlatform();
}
}
return platform;
}
/**
* INTERNAL:
* A descriptor may have been added after the session is logged in.
* In this case the descriptor must be allowed to initialize any dependencies on this session.
* Normally the descriptors are added before login, then initialized on login.
*/
public void initializeDescriptorIfSessionAlive(ClassDescriptor descriptor) {
if (isConnected() && (descriptor.requiresInitialization(this))) {
try {
try {
Collection descriptorsToAdd = new ArrayList(1);
descriptorsToAdd.add(descriptor);
addDescriptorsToSequencing(descriptorsToAdd);
descriptor.preInitialize(this);
descriptor.initialize(this);
descriptor.postInitialize(this);
getCommitManager().initializeCommitOrder();
} catch (RuntimeException exception) {
getIntegrityChecker().handleError(exception);
}
if (getIntegrityChecker().hasErrors()) {
//CR#4011
handleException(new IntegrityException(getIntegrityChecker()));
}
} finally {
clearIntegrityChecker();
}
}
}
/**
* INTERNAL:
* Allow each descriptor to initialize any dependencies on this session.
* This is done in two passes to allow the inheritance to be resolved first.
* Normally the descriptors are added before login, then initialized on login.
*/
public void initializeDescriptors() {
// Must clone to avoid modification of the map while enumerating.
initializeDescriptors((Map)((HashMap)getDescriptors()).clone(), true);
// Initialize serializer
if (this.serializer != null) {
this.serializer.initialize(null, null, this);
}
// Initialize partitioning policies.
for (PartitioningPolicy policy : getProject().getPartitioningPolicies().values()) {
policy.initialize(this);
}
if (getProject().getMultitenantPolicy() != null) {
getProject().getMultitenantPolicy().initialize(this);
}
// Process JPA named queries and add as session queries,
// this must be done after descriptor init as requires to parse the JPQL.
processJPAQueries();
// Configure default query cache for all named queries.
QueryResultsCachePolicy defaultQueryCachePolicy = getProject().getDefaultQueryResultsCachePolicy();
if (defaultQueryCachePolicy != null) {
for (List<DatabaseQuery> queries : getQueries().values()) {
for (DatabaseQuery query : queries) {
if (query.isReadQuery() && (query.getDescriptor() != null) && !query.getDescriptor().getCachePolicy().isIsolated()) {
ReadQuery readQuery = (ReadQuery)query;
if (!readQuery.shouldCacheQueryResults()) {
readQuery.setQueryResultsCachePolicy(defaultQueryCachePolicy.clone());
}
}
}
}
}
for (AttributeGroup group : getProject().getAttributeGroups().values()){
getAttributeGroups().put(group.getName(), group);
this.getDescriptor(group.getType()).addAttributeGroup(group);
}
}
/**
* INTERNAL:
* Allow each descriptor to initialize any dependencies on this session.
* This is done in two passes to allow the inheritance to be resolved first.
* Normally the descriptors are added before login, then initialized on login.
* The descriptors session must be used, not the broker.
* Sequencing is (re)initialized: disconnected (if has been already connected), then connected.
*/
public void initializeDescriptors(Map descriptors) {
initializeDescriptors(descriptors.values(), false);
}
public void initializeDescriptors(Collection descriptors) {
initializeDescriptors(descriptors, false);
}
/**
* INTERNAL:
* Allow each descriptor to initialize any dependencies on this session.
* This is done in two passes to allow the inheritance to be resolved first.
* Normally the descriptors are added before login, then initialized on login.
* The descriptors session must be used, not the broker.
* If shouldInitializeSequencing parameter is true then sequencing is (re)initialized:
* disconnected (if has been connected), then connected.
* If shouldInitializeSequencing parameter is false then
* if sequencing has been already connected, then it stays connected:
* only the new sequences used by the passed descriptors are initialized;
* otherwise, if sequencing has NOT been connected then it is connected
* (just like in shouldInitializeSequencing==true case);
* disconnected (if has been connected), then connected.
*/
public void initializeDescriptors(Map descriptors, boolean shouldInitializeSequencing) {
initializeDescriptors(descriptors.values(), shouldInitializeSequencing);
}
public void initializeDescriptors(Collection descriptors, boolean shouldInitializeSequencing) {
if (shouldInitializeSequencing) {
initializeSequencing();
} else {
addDescriptorsToSequencing(descriptors);
}
try {
// First initialize basic properties (things that do not depend on anything else)
Iterator iterator = descriptors.iterator();
while (iterator.hasNext()) {
ClassDescriptor descriptor = (ClassDescriptor)iterator.next();
try {
AbstractSession session = getSessionForClass(descriptor.getJavaClass());
if (descriptor.requiresInitialization(session)) {
descriptor.preInitialize(session);
} else if (descriptor.hasTablePerMultitenantPolicy()) {
// If the descriptor doesn't require initialization and
// has a table per tenant policy then add to the list
// to be cloned and initialized per client session.
addTablePerTenantDescriptor(descriptor);
}
//check if inheritance is involved in aggregate relationship, and let the parent know the child descriptor
if (descriptor.isDescriptorTypeAggregate() && descriptor.isChildDescriptor()) {
descriptor.initializeAggregateInheritancePolicy(session);
}
} catch (RuntimeException exception) {
getIntegrityChecker().handleError(exception);
}
}
// Second initialize basic mappings
iterator = descriptors.iterator();
while (iterator.hasNext()) {
ClassDescriptor descriptor = (ClassDescriptor)iterator.next();
try {
AbstractSession session = getSessionForClass(descriptor.getJavaClass());
if (descriptor.requiresInitialization(session)) {
descriptor.initialize(session);
}
} catch (RuntimeException exception) {
getIntegrityChecker().handleError(exception);
}
}
// Third initialize child dependencies
iterator = descriptors.iterator();
while (iterator.hasNext()) {
ClassDescriptor descriptor = (ClassDescriptor)iterator.next();
try {
AbstractSession session = getSessionForClass(descriptor.getJavaClass());
if (descriptor.requiresInitialization(session)) {
descriptor.postInitialize(session);
}
} catch (RuntimeException exception) {
getIntegrityChecker().handleError(exception);
}
}
if (getIntegrityChecker().hasErrors()) {
//CR#4011
handleSevere(new IntegrityException(getIntegrityChecker()));
}
} finally {
clearIntegrityChecker();
}
getCommitManager().initializeCommitOrder();
}
/**
* INTERNAL:
* Return if this session is a database session.
*/
@Override
public boolean isDatabaseSession() {
return true;
}
/**
* PUBLIC:
* Returns true if Protected Entities should be built within this session
*/
@Override
public boolean isProtectedSession(){
return false;
}
/**
* INTERNAL:
* Return the login for the read connection. Used by the platform autodetect feature
*/
protected Login getReadLogin(){
return getDatasourceLogin();
}
/**
* PUBLIC:
* Connect to the database using the predefined login.
* During connection, attempt to auto detect the required database platform.
* This method can be used in systems where for ease of use developers have
* EclipseLink autodetect the platform.
* To be safe, however, the platform should be configured directly.
* The login must have been assigned when or after creating the session.
*
*/
public void loginAndDetectDatasource() throws DatabaseException {
preConnectDatasource();
setOrDetectDatasource(true);
connect();
postConnectDatasource();
}
/**
* PUBLIC:
* Connect to the database using the predefined login.
* The login must have been assigned when or after creating the session.
*
* @see #login(Login)
*/
@Override
public void login() throws DatabaseException {
preConnectDatasource();
connect();
postConnectDatasource();
}
/**
* INTERNAL:
* This method includes all of the code that is issued before the datasource
* is connected to.
*/
protected void preConnectDatasource(){
//Bug#3440544 Check if logged in already to stop the attempt to login more than once
if (isLoggedIn) {
throw ValidationException.alreadyLoggedIn(this.getName());
}
this.platform = null;
if (isInProfile()) {
getProfiler().initialize();
}
updateProfile(SessionProfiler.LoginTime, new Date(System.currentTimeMillis()));
updateProfile(SessionProfiler.SessionName, getName());
// Login and initialize
if (this.eventManager != null) {
this.eventManager.preLogin(this);
}
if (!hasBroker()) {
//setup the external transaction controller
getServerPlatform().initializeExternalTransactionController();
log(SessionLog.INFO, null, "topLink_version", DatasourceLogin.getVersion());
if (getServerPlatform().getServerNameAndVersion() != null &&
!getServerPlatform().getServerNameAndVersion().equals(ServerPlatformBase.DEFAULT_SERVER_NAME_AND_VERSION)) {
log(SessionLog.INFO, null, "application_server_name_and_version", getServerPlatform().getServerNameAndVersion());
}
}
this.isLoggingOff = (getLogLevel() == SessionLog.OFF);
}
/**
* INTERNAL:
* This method includes all of the code that is issued after the datasource
* is connected to.
*/
protected void postConnectDatasource(){
if (!hasBroker()) {
initializeDescriptors();
//added to process ejbQL query strings
if (getCommandManager() != null) {
getCommandManager().initialize();
}
}
// Once the descriptors are initialized we can check if there are
// multitenant entities and if this session (emf) is shared or not. If
// not shared, all multitenant properties must be available and set by
// the user at this point for us to validate (meaning they must be set
// in a persitence.xml or passed into the create EMF call).
if (getProperties().containsKey(PersistenceUnitProperties.MULTITENANT_SHARED_EMF)) {
String value = (String) getProperties().get(PersistenceUnitProperties.MULTITENANT_SHARED_EMF);
if (!Boolean.parseBoolean(value)) {
for (String property : getMultitenantContextProperties()) {
if (! getProperties().containsKey(property)) {
throw ValidationException.multitenantContextPropertyForNonSharedEMFNotSpecified(property);
}
}
// Once the properties are validated we can allow ddl generation to happen (if needed).
project.setAllowTablePerMultitenantDDLGeneration(true);
}
}
log(SessionLog.FINE, SessionLog.CONNECTION, "login_successful", this.getName());
// postLogin event should not be risen before descriptors have been initialized
if (!hasBroker()) {
postLogin();
}
initializeConnectedTime();
this.isLoggedIn = true;
this.platform = null;
if (!hasBroker()) {
//register the MBean
getServerPlatform().registerMBean();
}
this.descriptors = getDescriptors();
if (!isBroker()) {
// EclipseLink 23869 - Initialize plaformOperators eagerly to avoid concurrency issues.
getDatasourcePlatform().initialize();
getIdentityMapAccessorInstance().getIdentityMapManager().checkIsCacheAccessPreCheckRequired();
}
if (this.databaseEventListener != null) {
this.databaseEventListener.register(this);
}
if ((getDatasourcePlatform() instanceof DatabasePlatform) && getPlatform().getBatchWritingMechanism() != null) {
getPlatform().getBatchWritingMechanism().initialize(this);
}
}
/**
* INTERNAL:
* Rise postLogin event.
*/
public void postLogin() {
if (this.eventManager != null) {
this.eventManager.postLogin(this);
}
}
/**
* PUBLIC:
* Connect to the database using the given user name and password.
* The additional login information must have been preset in the session's login attribute.
* This is the login that should be used if each user has their own id,
* but all users share the same database configuration.
*/
@Override
public void login(String userName, String password) throws DatabaseException {
getDatasourceLogin().setUserName(userName);
getDatasourceLogin().setPassword(password);
login();
}
/**
* PUBLIC:
* Connect to the database using the given login.
* The login may also the preset and the login() protocol called.
* This is the login should only be used if each user has their own database configuration.
*/
@Override
public void login(Login login) throws DatabaseException {
setLogin(login);
login();
}
/**
* PUBLIC:
* Disconnect from the database.
*
* @exception EclipseLinkException if a transaction is active, you must rollback any active transaction before logout.
* @exception DatabaseException the database will also raise an error if their is an active transaction,
* or a general error occurs.
*/
@Override
public void logout() throws DatabaseException {
if (this.eventManager != null) {
this.eventManager.preLogout(this);
}
cleanUpInjectionManager();
// Reset cached data, as may be invalid later on.
this.lastDescriptorAccessed = null;
if (isInTransaction()) {
throw DatabaseException.logoutWhileTransactionInProgress();
}
if (getAccessor() == null) {
return;
}
if (this.databaseEventListener != null) {
this.databaseEventListener.remove(this);
}
// We're logging out so turn off change propagation.
setShouldPropagateChanges(false);
if (!hasBroker()) {
if (getCommandManager() != null) {
getCommandManager().shutdown();
}
// Unregister the JMX MBean before logout to avoid a javax.naming.NameNotFoundException
getServerPlatform().shutdown();
}
disconnect();
getIdentityMapAccessor().initializeIdentityMaps();
this.isLoggedIn = false;
if (this.eventManager != null) {
this.eventManager.postLogout(this);
}
log(SessionLog.FINE, SessionLog.CONNECTION, "logout_successful", this.getName());
}
/**
* PUBLIC:
* Initialize the time that this session got connected. This can help determine how long a session has been
* connected.
*/
public void initializeConnectedTime() {
connectedTime = System.currentTimeMillis();
}
/**
* PUBLIC:
* Answer the time that this session got connected. This can help determine how long a session has been
* connected.
*/
public long getConnectedTime() {
return connectedTime;
}
/**
* PUBLIC:
* Write all of the objects and all of their privately owned parts in the database.
* The objects will be committed through a single transaction.
*
* @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.
*/
@Override
public void writeAllObjects(Collection domainObjects) throws DatabaseException, OptimisticLockException {
for (Iterator objectsEnum = domainObjects.iterator(); objectsEnum.hasNext();) {
writeObject(objectsEnum.next());
}
}
/**
* PUBLIC:
* Write all of the objects and all of their privately owned parts in the database.
* The objects will be committed through a single transaction.
*
* @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 writeAllObjects(Vector domainObjects) throws DatabaseException, OptimisticLockException {
for (Enumeration objectsEnum = domainObjects.elements(); objectsEnum.hasMoreElements();) {
writeObject(objectsEnum.nextElement());
}
}
/**
* INTERNAL:
* A query execution failed due to an invalid query.
* Re-connect and retry the query.
*/
@Override
public Object retryQuery(DatabaseQuery query, AbstractRecord row, DatabaseException databaseException, int retryCount, AbstractSession executionSession) {
if (getClass() != DatabaseSessionImpl.class) {
return super.retryQuery(query, row, databaseException, retryCount, executionSession);
}
//attempt to reconnect connection:
final 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
++retryCount;
databaseException.getAccessor().reestablishConnection(this);
break;
} catch (DatabaseException ex) {
// failed to get connection because of
// database error.
try {
// Give the failover time to recover.
Thread.sleep(getLogin().getDelayBetweenConnectionAttempts());
Object[] args = new Object[1];
args[0] = ex;
log(SessionLog.INFO, SessionLog.QUERY, "communication_failure_attempting_query_retry", args, null);
} catch (InterruptedException intEx) {
break;
}
}
}
return executionSession.executeQuery(query, row, retryCount);
}
/**
* Return the tuner used to tune the configuration of this session.
*/
public SessionTuner getTuner() {
return tuner;
}
/**
* Set the tuner used to tune the configuration of this session.
*/
public void setTuner(SessionTuner tuner) {
this.tuner = tuner;
}
}