blob: f4c18d69a7409f9dc35091e9793efa7af85f0acf [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 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
// tware - 1.0RC1 - OSGI refactor
// 12/23/2008-1.1M5 Michael O'Brien
// - 253701: set persistenceInitializationHelper so EntityManagerSetupImpl.undeploy() can clear the JavaSECMPInitializer
// 12/24/2012-2.5 Guy Pelletier
// - 389090: JPA 2.1 DDL Generation Support
// 01/08/2013-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
// 02/04/2013-2.5 Guy Pelletier
// - 389090: JPA 2.1 DDL Generation Support
// 02/02/2015-2.6 Dalia Abo Sheasha
// - 458462: generateSchema throws a ClassCastException within a container
// 02/17/2015-2.6 Rick Curtis
// - 460138: Change method visibility.
package org.eclipse.persistence.jpa;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.PersistenceException;
import jakarta.persistence.spi.ClassTransformer;
import jakarta.persistence.spi.LoadState;
import jakarta.persistence.spi.PersistenceUnitInfo;
import jakarta.persistence.spi.ProviderUtil;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.config.SystemProperties;
import org.eclipse.persistence.exceptions.PersistenceUnitLoadingException;
import org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl;
import org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider;
import org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl;
import org.eclipse.persistence.internal.jpa.deployment.JPAInitializer;
import org.eclipse.persistence.internal.jpa.deployment.JavaSECMPInitializer;
import org.eclipse.persistence.internal.jpa.deployment.PersistenceUnitProcessor;
import org.eclipse.persistence.internal.jpa.deployment.SEPersistenceUnitInfo;
import org.eclipse.persistence.internal.weaving.PersistenceWeaved;
/**
* This is the EclipseLink EJB 3.0 provider
*
* This provider should be used by JavaEE and JavaSE users.
*/
public class PersistenceProvider implements jakarta.persistence.spi.PersistenceProvider, ProviderUtil {
public PersistenceProvider(){
}
/**
* Internal method to return the entity manager factory.
*/
protected EntityManagerFactoryImpl createEntityManagerFactoryImpl(PersistenceUnitInfo puInfo, Map properties, boolean requiresConnection){
if (puInfo != null) {
boolean isNew = false;
String uniqueName = null; // the name the uniquely defines the pu
String sessionName = null;
EntityManagerSetupImpl emSetupImpl = null;
String puName = puInfo.getPersistenceUnitName();
JPAInitializer initializer = getInitializer(puInfo.getPersistenceUnitName(), properties);
try {
if (EntityManagerSetupImpl.mustBeCompositeMember(puInfo)) {
// Persistence unit cannot be used standalone (only as a composite member).
// Still the factory will be created but attempt to createEntityManager would cause an exception.
emSetupImpl = new EntityManagerSetupImpl(puName, puName);
// Predeploy assigns puInfo and does not do anything else.
// The session is not created, no need to add emSetupImpl to the global map.
emSetupImpl.predeploy(puInfo, properties);
isNew = true;
} else {
if (initializer.isPersistenceUnitUniquelyDefinedByName()) {
uniqueName = puName;
} else {
uniqueName = initializer.createUniquePersistenceUnitName(puInfo);
}
sessionName = EntityManagerSetupImpl.getOrBuildSessionName(properties, puInfo, uniqueName);
synchronized (EntityManagerFactoryProvider.emSetupImpls) {
emSetupImpl = EntityManagerFactoryProvider.getEntityManagerSetupImpl(sessionName);
if (emSetupImpl == null) {
// there may be initial emSetupImpl cached in Initializer - remove it and use.
emSetupImpl = initializer.extractInitialEmSetupImpl(puName);
if (emSetupImpl != null) {
// change the name
emSetupImpl.changeSessionName(sessionName);
} else {
// create and predeploy a new emSetupImpl
emSetupImpl = initializer.callPredeploy((SEPersistenceUnitInfo) puInfo, properties, uniqueName, sessionName);
}
// emSetupImpl has been already predeployed, predeploy will just increment factoryCount.
emSetupImpl.predeploy(emSetupImpl.getPersistenceUnitInfo(), properties);
EntityManagerFactoryProvider.addEntityManagerSetupImpl(sessionName, emSetupImpl);
isNew = true;
}
}
}
} catch (Exception e) {
throw PersistenceUnitLoadingException.exceptionSearchingForPersistenceResources(initializer.getInitializationClassLoader(), e);
}
if (! isNew) {
if (! uniqueName.equals(emSetupImpl.getPersistenceUnitUniqueName())) {
throw PersistenceUnitLoadingException.sessionNameAlreadyInUse(sessionName, uniqueName, emSetupImpl.getPersistenceUnitUniqueName());
}
// synchronized to prevent undeploying by other threads.
boolean undeployed = false;
synchronized(emSetupImpl) {
if (emSetupImpl.isUndeployed()) {
undeployed = true;
} else {
// emSetupImpl has been already predeployed, predeploy will just increment factoryCount.
emSetupImpl.predeploy(emSetupImpl.getPersistenceUnitInfo(), properties);
}
}
if (undeployed) {
// after the emSetupImpl has been obtained from emSetupImpls
// it has been undeployed by factory.close() in another thread - start all over again.
return (EntityManagerFactoryImpl) createEntityManagerFactory(puName, properties);
}
}
EntityManagerFactoryImpl factory = null;
try {
factory = new EntityManagerFactoryImpl(emSetupImpl, properties);
emSetupImpl.setRequiresConnection(requiresConnection);
emSetupImpl.preInitializeCanonicalMetamodel(factory);
// This code has been added to allow validation to occur without actually calling createEntityManager
if (emSetupImpl.shouldGetSessionOnCreateFactory(properties)) {
factory.getDatabaseSession();
}
return factory;
} catch (RuntimeException ex) {
if (factory != null) {
factory.close();
} else {
emSetupImpl.undeploy();
}
throw ex;
}
}
return null;
}
/**
* Called by Persistence class when an EntityManagerFactory
* is to be created.
*
* @param emName The name of the persistence unit
* @param properties A Map of properties for use by the
* persistence provider. These properties may be used to
* override the values of the corresponding elements in
* the persistence.xml file or specify values for
* properties not specified in the persistence.xml.
* @return EntityManagerFactory for the persistence unit,
* or null if the provider is not the right provider
*/
@Override
public EntityManagerFactory createEntityManagerFactory(String emName, Map properties){
Map nonNullProperties = (properties == null) ? new HashMap() : properties;
if (checkForProviderProperty(nonNullProperties)){
String name = (emName == null) ? "" : emName;
JPAInitializer initializer = getInitializer(name, nonNullProperties);
return createEntityManagerFactoryImpl(initializer.findPersistenceUnitInfo(name, nonNullProperties), nonNullProperties, true);
}
// Not EclipseLink so return null;
return null;
}
/**
* Create database schemas and/or tables and/or create DDL
* scripts as determined by the supplied properties.
* <p>
* Called by the Persistence class when schema generation is to occur as a
* separate phase from creation of the entity manager factory.
*
* @param info the name of the persistence unit
* @param properties properties for schema generation; these may also
* contain provider-specific properties. The value of these
* properties override any values that may have been configured
* elsewhere.
* @throws PersistenceException if insufficient or inconsistent
* configuration information is provided of if schema generation
* otherwise fails
*
* @since Java Persistence 2.1
*/
@Override
public void generateSchema(PersistenceUnitInfo info, Map properties) {
if (checkForProviderProperty(properties)) {
// Bug 458462 - Generate the DDL and then close. This method is
// called when running within a container.
createContainerEntityManagerFactoryImpl(info, properties, false).close();
}
}
/**
* Create database schemas and/or tables and/or create DDL scripts as
* determined by the supplied properties.
* <p>
* Called by the Persistence class when schema generation is to occur as a
* separate phase from creation of the entity manager factory.
*
* @param persistenceUnitName the name of the persistence unit
* @param properties properties for schema generation; these may also
* contain provider-specific properties. The value of these
* properties override any values that may have been configured
* elsewhere.
* @throws PersistenceException if insufficient or inconsistent
* configuration information is provided of if schema generation
* otherwise fails
*
* @since Java Persistence 2.1
*/
@Override
public boolean generateSchema(String persistenceUnitName, Map properties) {
String puName = (persistenceUnitName == null) ? "" : persistenceUnitName;
Map nonNullProperties = (properties == null) ? new HashMap() : properties;
// If not EclipseLink, do nothing.
if (checkForProviderProperty(nonNullProperties)) {
JPAInitializer initializer = getInitializer(puName, nonNullProperties);
SEPersistenceUnitInfo puInfo = initializer.findPersistenceUnitInfo(puName, nonNullProperties);
if (puInfo != null && checkForProviderProperty(properties)) {
// Will cause a login if necessary, generate the DDL and then close.
// The false indicates that we do not require a connection if
// generating only to script. Since the user may have connected with
// specific database credentials for DDL generation or even provided
// a specific connection, close the emf once we're done.
createEntityManagerFactoryImpl(puInfo, properties, false).close();
return true;
}
}
return false;
}
/**
* Return JPAInitializer corresponding to the passed classLoader.
* Note: This is written as an instance method rather than a static to allow
* it to be overridden
* @param emName
* @param m
* @return
*/
public JPAInitializer getInitializer(String emName, Map m){
ClassLoader classLoader = getClassLoader(emName, m);
return JavaSECMPInitializer.getJavaSECMPInitializer(classLoader);
}
/**
* Need to check that the provider property is null or set for EclipseLink
*/
public boolean checkForProviderProperty(Map properties){
Object provider = properties.get("jakarta.persistence.provider");
if (provider != null){
//user has specified a provider make sure it is us or abort.
if (provider instanceof Class){
provider = ((Class)provider).getName();
}
try{
if (!(EntityManagerFactoryProvider.class.getName().equals(provider) || PersistenceProvider.class.getName().equals(provider))){
return false;
//user has requested another provider so lets ignore this request.
}
}catch(ClassCastException e){
return false;
// not a recognized provider property value so must be another provider.
}
}
return true;
}
/**
* Called by the container when an EntityManagerFactory
* is to be created.
*
* @param info Metadata for use by the persistence provider
* @return EntityManagerFactory for the persistence unit
* specified by the metadata
* @param properties A Map of integration-level properties for use
* by the persistence provider.
*/
@Override
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) {
return createContainerEntityManagerFactoryImpl(info, properties, true);
}
protected EntityManagerFactory createContainerEntityManagerFactoryImpl(PersistenceUnitInfo info, Map properties, boolean requiresConnection) {
// Record that we are inside a JEE container to allow weaving for non managed persistence units.
JavaSECMPInitializer.setIsInContainer(true);
Map nonNullProperties = (properties == null) ? new HashMap() : properties;
String forceTargetServer = EntityManagerFactoryProvider.getConfigPropertyAsString(SystemProperties.ENFORCE_TARGET_SERVER, null);
if ("true".equalsIgnoreCase(forceTargetServer)) {
nonNullProperties.remove(PersistenceUnitProperties.TARGET_SERVER);
}
EntityManagerSetupImpl emSetupImpl = null;
if (EntityManagerSetupImpl.mustBeCompositeMember(info)) {
// persistence unit cannot be used standalone (only as a composite member).
// still the factory will be created but attempt to createEntityManager would cause an exception.
emSetupImpl = new EntityManagerSetupImpl(info.getPersistenceUnitName(), info.getPersistenceUnitName());
// predeploy assigns puInfo and does not do anything else.
// the session is not created, no need to add emSetupImpl to the global map.
emSetupImpl.predeploy(info, nonNullProperties);
} else {
boolean isNew = false;
ClassTransformer transformer = null;
String uniqueName = PersistenceUnitProcessor.buildPersistenceUnitName(info.getPersistenceUnitRootUrl(), info.getPersistenceUnitName());
String sessionName = EntityManagerSetupImpl.getOrBuildSessionName(nonNullProperties, info, uniqueName);
synchronized (EntityManagerFactoryProvider.emSetupImpls) {
emSetupImpl = EntityManagerFactoryProvider.getEntityManagerSetupImpl(sessionName);
if (emSetupImpl == null){
emSetupImpl = new EntityManagerSetupImpl(uniqueName, sessionName);
isNew = true;
emSetupImpl.setIsInContainerMode(true);
// if predeploy fails then emSetupImpl shouldn't be added to FactoryProvider
transformer = emSetupImpl.predeploy(info, nonNullProperties);
EntityManagerFactoryProvider.addEntityManagerSetupImpl(sessionName, emSetupImpl);
}
}
if(!isNew) {
if(!uniqueName.equals(emSetupImpl.getPersistenceUnitUniqueName())) {
throw PersistenceUnitLoadingException.sessionNameAlreadyInUse(sessionName, uniqueName, emSetupImpl.getPersistenceUnitUniqueName());
}
// synchronized to prevent undeploying by other threads.
boolean undeployed = false;
synchronized(emSetupImpl) {
if(emSetupImpl.isUndeployed()) {
undeployed = true;
} else {
// emSetupImpl has been already predeployed, predeploy will just increment factoryCount.
transformer = emSetupImpl.predeploy(emSetupImpl.getPersistenceUnitInfo(), nonNullProperties);
}
}
if(undeployed) {
// after the emSetupImpl has been obtained from emSetupImpls
// it has been undeployed by factory.close() in another thread - start all over again.
return createContainerEntityManagerFactory(info, properties);
}
}
if (transformer != null){
info.addTransformer(transformer);
}
}
EntityManagerFactoryImpl factory = null;
try {
factory = new EntityManagerFactoryImpl(emSetupImpl, nonNullProperties);
emSetupImpl.setRequiresConnection(requiresConnection);
emSetupImpl.preInitializeCanonicalMetamodel(factory);
// This code has been added to allow validation to occur without actually calling createEntityManager
if (emSetupImpl.shouldGetSessionOnCreateFactory(nonNullProperties)) {
factory.getDatabaseSession();
}
return factory;
} catch (RuntimeException ex) {
if(factory != null) {
factory.close();
} else {
emSetupImpl.undeploy();
}
throw ex;
}
}
/**
* Return the utility interface implemented by the persistence
* provider.
* @return ProviderUtil interface
*
* @since Java Persistence 2.0
*/
@Override
public ProviderUtil getProviderUtil(){
return this;
}
/**
* If the provider determines that the entity has been provided
* by itself and that the state of the specified attribute has
* been loaded, this method returns <code>LoadState.LOADED</code>.
* If the provider determines that the entity has been provided
* by itself and that either entity attributes with <code>FetchType.EAGER</code>
* have not been loaded or that the state of the specified
* attribute has not been loaded, this methods returns
* <code>LoadState.NOT_LOADED</code>.
* If a provider cannot determine the load state, this method
* returns <code>LoadState.UNKNOWN</code>.
* The provider's implementation of this method must not obtain
* a reference to an attribute value, as this could trigger the
* loading of entity state if the entity has been provided by a
* different provider.
* @param entity
* @param attributeName name of attribute whose load status is
* to be determined
* @return load status of the attribute
*/
@Override
public LoadState isLoadedWithoutReference(Object entity, String attributeName){
if (entity instanceof PersistenceWeaved){
return isLoadedWithReference(entity, attributeName);
}
return LoadState.UNKNOWN;
}
/**
* If the provider determines that the entity has been provided
* by itself and that the state of the specified attribute has
* been loaded, this method returns <code>LoadState.LOADED</code>.
* If a provider determines that the entity has been provided
* by itself and that either the entity attributes with <code>FetchType.EAGER</code>
* have not been loaded or that the state of the specified
* attribute has not been loaded, this method returns
* return <code>LoadState.NOT_LOADED</code>.
* If the provider cannot determine the load state, this method
* returns <code>LoadState.UNKNOWN</code>.
* The provider's implementation of this method is permitted to
* obtain a reference to the attribute value. (This access is
* safe because providers which might trigger the loading of the
* attribute state will have already been determined by
* <code>isLoadedWithoutReference</code>. )
*
* @param entity
* @param attributeName name of attribute whose load status is
* to be determined
* @return load status of the attribute
*/
@Override
public LoadState isLoadedWithReference(Object entity, String attributeName) {
synchronized (EntityManagerFactoryProvider.emSetupImpls) {
Iterator<EntityManagerSetupImpl> setups = EntityManagerFactoryProvider.emSetupImpls.values().iterator();
while (setups.hasNext()){
EntityManagerSetupImpl setup = setups.next();
if (setup.isDeployed()){
Boolean isLoaded = EntityManagerFactoryImpl.isLoaded(entity, setup.getSession());
if (isLoaded != null){
if (isLoaded && attributeName != null){
isLoaded = EntityManagerFactoryImpl.isLoaded(entity, attributeName, setup.getSession());
}
if (isLoaded != null){
return isLoaded ? LoadState.LOADED : LoadState.NOT_LOADED;
}
}
}
}
return LoadState.UNKNOWN;
}
}
/**
* If the provider determines that the entity has been provided
* by itself and that the state of all attributes for which
* <code>FetchType.EAGER</code> has been specified have been loaded, this
* method returns <code>LoadState.LOADED</code>.
* If the provider determines that the entity has been provided
* by itself and that not all attributes with <code>FetchType.EAGER</code>
* have been loaded, this method returns <code>LoadState.NOT_LOADED</code>.
* If the provider cannot determine if the entity has been
* provided by itself, this method returns <code>LoadState.UNKNOWN</code>.
* The provider's implementation of this method must not obtain
* a reference to any attribute value, as this could trigger the
* loading of entity state if the entity has been provided by a
* different provider.
* @param entity whose loaded status is to be determined
* @return load status of the entity
*/
@Override
public LoadState isLoaded(Object entity){
if (entity instanceof PersistenceWeaved){
return isLoadedWithReference(entity, null);
}
return LoadState.UNKNOWN;
}
/**
* Answer the classloader to use to create an EntityManager.
* If a classloader is not found in the properties map then
* use the current thread classloader.
*
* @param properties
* @return ClassLoader
*/
public ClassLoader getClassLoader(String emName, Map properties) {
ClassLoader classloader = null;
if (properties != null) {
classloader = (ClassLoader)properties.get(PersistenceUnitProperties.CLASSLOADER);
}
if (classloader == null) {
classloader = Thread.currentThread().getContextClassLoader();
}
return classloader;
}
}