blob: 2645403a8b87b09cc24c698049194c1d50d1ab27 [file] [log] [blame]
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation.
* Copyright (c) 2008, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.persistence.jpa;
import static java.util.Collections.unmodifiableMap;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.logging.Logger;
import org.glassfish.deployment.common.RootDeploymentDescriptor;
import org.glassfish.persistence.jpa.schemageneration.SchemaGenerationProcessor;
import org.glassfish.persistence.jpa.schemageneration.SchemaGenerationProcessorFactory;
import com.sun.enterprise.deployment.PersistenceUnitDescriptor;
import com.sun.enterprise.deployment.PersistenceUnitsDescriptor;
import com.sun.enterprise.util.i18n.StringManager;
import com.sun.logging.LogDomains;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.ValidationMode;
import jakarta.persistence.spi.PersistenceProvider;
import jakarta.persistence.spi.PersistenceUnitInfo;
import jakarta.persistence.spi.PersistenceUnitTransactionType;
/**
* Loads emf corresponding to a PersistenceUnit. Executes java2db if required.
*
* @author Mitesh Meswani
* @author Sanjeeb.Sahoo@Sun.COM
*/
public class PersistenceUnitLoader {
private static Logger logger = LogDomains.getLogger(PersistenceUnitLoader.class, LogDomains.PERSISTENCE_LOGGER);
private static final StringManager localStrings = StringManager.getManager(PersistenceUnitLoader.class);
private static Map<String, String> integrationProperties;
/** EclipseLink property name to enable/disable weaving **/
private static final String ECLIPSELINK_WEAVING_PROPERTY = "eclipselink.weaving";
/** Name of property used to specify validation mode */
private static final String VALIDATION_MODE_PROPERTY = "jakarta.persistence.validation.mode";
/** Name of property used to specify validator factory */
private static final String VALIDATOR_FACTORY = "jakarta.persistence.validation.factory";
private static final String DISABLE_UPGRADE_FROM_TOPLINK_ESSENTIALS = "org.glassfish.persistence.jpa.disable.upgrade.from.toplink.essentials";
static {
/*
* We set all the provider specific integration level properties here. It knows
* about all the integration level properties that are needed to integrate a
* provider with our container. When we add support for other containers, we
* should modify this code so that user does not have to specify such properties
* in their persistence.xml file. These properties can be overriden by
* persistence.xml as per the spec. Before applying default values for
* properties, this method first checks if the properties have been set in the
* system (typically done using -D option in domain.xml).
*
*/
// ------------------- The Base -------------------------
Map<String, String> props = new HashMap<>();
final String ECLIPSELINK_SERVER_PLATFORM_CLASS_NAME_PROPERTY = "eclipselink.target-server";
props.put(
ECLIPSELINK_SERVER_PLATFORM_CLASS_NAME_PROPERTY,
System.getProperty(ECLIPSELINK_SERVER_PLATFORM_CLASS_NAME_PROPERTY, "Glassfish"));
// Hibernate specific properties:
final String HIBERNATE_TRANSACTION_MANAGER_LOOKUP_CLASS_PROPERTY = "hibernate.transaction.manager_lookup_class";
props.put(HIBERNATE_TRANSACTION_MANAGER_LOOKUP_CLASS_PROPERTY,
System.getProperty(HIBERNATE_TRANSACTION_MANAGER_LOOKUP_CLASS_PROPERTY,
"org.hibernate.transaction.SunONETransactionManagerLookup"));
integrationProperties = unmodifiableMap(props);
}
/**
* Conduit to talk with container
*/
private ProviderContainerContractInfo providerContainerContractInfo;
private EntityManagerFactory entityManagerFactory;
/**
* The schemaGenerationProcessor instance for the Java2DB work.
*/
private SchemaGenerationProcessor schemaGenerationProcessor;
public PersistenceUnitLoader(PersistenceUnitDescriptor persistenceUnitToInstantiate, ProviderContainerContractInfo providerContainerContractInfo) {
this.providerContainerContractInfo = providerContainerContractInfo;
// A hack to work around EclipseLink issue
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=248328 for prelude
// This should be removed once version of EclipseLink which fixes the issue is
// integrated.
// set the system property required by EclipseLink before we load it.
setSystemPropertyToEnableDoPrivilegedInEclipseLink();
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(providerContainerContractInfo.getClassLoader());
try {
entityManagerFactory = loadPU(persistenceUnitToInstantiate);
} finally {
Thread.currentThread().setContextClassLoader(oldClassLoader);
}
}
/**
* @return The emf loaded.
*/
public EntityManagerFactory getEMF() {
return entityManagerFactory;
}
private void setSystemPropertyToEnableDoPrivilegedInEclipseLink() {
final String PROPERTY_NAME = "eclipselink.security.usedoprivileged";
// Need not invoke in doPrivileged block as the whole call stack consist of
// trusted code when this code
// is invoked
if (System.getProperty(PROPERTY_NAME) == null) {
// property not set. Set it to true
System.setProperty(PROPERTY_NAME, String.valueOf(Boolean.TRUE));
}
}
/**
* Loads an individual PersistenceUnitDescriptor and registers the
* EntityManagerFactory in appropriate DOL structure.
*
* @param persistenceUnitDescriptor PersistenceUnitDescriptor to be loaded.
*/
private EntityManagerFactory loadPU(PersistenceUnitDescriptor persistenceUnitDescriptor) {
checkForUpgradeFromTopLinkEssentials(persistenceUnitDescriptor);
checkForDataSourceOverride(persistenceUnitDescriptor);
calculateDefaultDataSource(persistenceUnitDescriptor);
PersistenceUnitInfo persistenceUnitInfo =
new PersistenceUnitInfoImpl(persistenceUnitDescriptor, providerContainerContractInfo);
String applicationLocation = providerContainerContractInfo.getApplicationLocation();
final boolean fineMsgLoggable = logger.isLoggable(FINE);
if (fineMsgLoggable) {
logger.fine("Loading persistence unit for application: \"" + applicationLocation + "\"pu Root is: " + persistenceUnitDescriptor.getPuRoot());
logger.fine("PersistenceInfo for this pud is :\n" + persistenceUnitInfo);
}
PersistenceProvider provider;
try {
// We use application CL as opposed to system CL to loadPU
// provider. This allows user to get hold of provider specific
// implementation classes in their code.
// But this also means
// provider must not use appserver implementation classes directly
// because once we implement isolation in our class loader hierarchy
// the only classes available to application class loader would be
// our appserver interface classes. By Sahoo
provider = PersistenceProvider.class
.cast(
providerContainerContractInfo.getClassLoader()
.loadClass(persistenceUnitInfo.getPersistenceProviderClassName())
.getDeclaredConstructor()
.newInstance());
} catch (ReflectiveOperationException | IllegalArgumentException | SecurityException e) {
throw new RuntimeException(e);
}
Map<String, Object> schemaGenerationOverrides;
schemaGenerationProcessor = SchemaGenerationProcessorFactory.createSchemaGenerationProcessor(persistenceUnitDescriptor);
if (providerContainerContractInfo.isJava2DBRequired()) {
schemaGenerationProcessor.init(persistenceUnitDescriptor, providerContainerContractInfo.getDeploymentContext());
schemaGenerationOverrides = schemaGenerationProcessor.getOverridesForSchemaGeneration();
} else {
// schema generation is not required if this EMF is being created for
// -appserver restarting or,
// -on an instance or,
// -appclient
// Suppress schema generation in this case
schemaGenerationOverrides = schemaGenerationProcessor.getOverridesForSuppressingSchemaGeneration();
}
Map<String, Object> overRides = new HashMap<String, Object>(integrationProperties);
if (schemaGenerationOverrides != null) {
overRides.putAll(schemaGenerationOverrides);
}
// Check if the persistence unit requires Bean Validation
ValidationMode validationMode = getValidationMode(persistenceUnitDescriptor);
if (validationMode == ValidationMode.AUTO || validationMode == ValidationMode.CALLBACK) {
overRides.put(VALIDATOR_FACTORY, providerContainerContractInfo.getValidatorFactory());
}
if (!providerContainerContractInfo.isWeavingEnabled()) {
overRides.put(ECLIPSELINK_WEAVING_PROPERTY, System.getProperty(ECLIPSELINK_WEAVING_PROPERTY, "false"));
}
EntityManagerFactory entityManagerFactory = provider.createContainerEntityManagerFactory(persistenceUnitInfo, overRides);
logger.logp(FINE, "PersistenceUnitLoader", "loadPU", "emf = {0}", entityManagerFactory);
PersistenceUnitsDescriptor parent = persistenceUnitDescriptor.getParent();
RootDeploymentDescriptor containingBundle = parent.getParent();
providerContainerContractInfo.registerEMF(
persistenceUnitInfo.getPersistenceUnitName(), persistenceUnitDescriptor.getPuRoot(), containingBundle, entityManagerFactory);
if (fineMsgLoggable) {
logger.fine("Finished loading persistence unit for application: " +
applicationLocation);
}
return entityManagerFactory;
}
/**
* If use provided data source is overridden, update PersistenceUnitDescriptor
* with it
*/
private void checkForDataSourceOverride(PersistenceUnitDescriptor persistenceUnitDescriptor) {
String jtaDataSourceOverride = providerContainerContractInfo.getJTADataSourceOverride();
if (jtaDataSourceOverride != null) {
persistenceUnitDescriptor.setJtaDataSource(jtaDataSourceOverride);
}
}
/** Calculate and set the default data source in given <code>pud</code> **/
private void calculateDefaultDataSource(PersistenceUnitDescriptor persistenceUnitDescriptor) {
String jtaDataSourceName =
calculateJtaDataSourceName(
persistenceUnitDescriptor.getTransactionType(),
persistenceUnitDescriptor.getJtaDataSource(),
persistenceUnitDescriptor.getNonJtaDataSource(),
persistenceUnitDescriptor.getName());
String nonJtaDataSourceName =
calculateNonJtaDataSourceName(
persistenceUnitDescriptor.getJtaDataSource(),
persistenceUnitDescriptor.getNonJtaDataSource());
persistenceUnitDescriptor.setJtaDataSource(jtaDataSourceName);
persistenceUnitDescriptor.setNonJtaDataSource(nonJtaDataSourceName);
}
/**
* @return DataSource Name to be used as JTA data source.
*/
private String calculateJtaDataSourceName(String transactionType, String userSuppliedJTADSName, String userSuppliedNonJTADSName,
String puName) {
/*
* Use DEFAULT_DS_NAME iff user has not specified both jta-ds-name and
* non-jta-ds-name; and user has specified transaction-type as JTA. See Gf issue
* #1204 as well.
*/
if (PersistenceUnitTransactionType.valueOf(transactionType) != PersistenceUnitTransactionType.JTA) {
logger.logp(FINE,
"PersistenceUnitInfoImpl",
"_getJtaDataSource",
"This PU is configured as non-jta, so jta-data-source is null");
return null; // this is a non-jta-data-source
}
String dataSourceName;
if (!isNullOrEmpty(userSuppliedJTADSName)) {
dataSourceName = userSuppliedJTADSName; // use user supplied jta-ds-name
} else if (isNullOrEmpty(userSuppliedNonJTADSName)) {
dataSourceName = providerContainerContractInfo.getDefaultDataSourceName();
} else {
String msg = localStrings.getString("puinfo.jta-ds-not-configured",
new Object[] { puName });
throw new RuntimeException(msg);
}
logger.logp(FINE, "PersistenceUnitLoaderImpl",
"_getJtaDataSource",
"JTADSName = {0}",
dataSourceName);
return dataSourceName;
}
private String calculateNonJtaDataSourceName(String userSuppliedJTADSName, String userSuppliedNonJTADSName) {
/*
* If non-JTA name is *not* provided - use the JTA DS name (if supplied) If
* non-JTA name is provided - use non-JTA DS name (this is done for ease of use,
* because user does not have to explicitly mark a connection pool as
* non-transactional. Calling lookupNonTxDataSource() with a resource which is
* already configured as non-transactional has no side effects.) If neither
* non-JTA nor JTA name is provided use DEFAULT_DS_NAME.
*/
String dataSourceName;
if (!isNullOrEmpty(userSuppliedNonJTADSName)) {
dataSourceName = userSuppliedNonJTADSName;
} else {
if (!isNullOrEmpty(userSuppliedJTADSName)) {
dataSourceName = userSuppliedJTADSName;
} else {
dataSourceName = providerContainerContractInfo.getDefaultDataSourceName();
}
}
logger.logp(FINE, "PersistenceUnitInfoImpl",
"_getNonJtaDataSource", "nonJTADSName = {0}",
dataSourceName);
return dataSourceName;
}
static boolean isNullOrEmpty(String s) {
return s == null || s.length() == 0;
}
/**
* If the app is using Toplink Essentials as the provider and TopLink Essentials
* is not available in classpath We try to upgrade the app to use EclipseLink.
* Change the provider to EclipseLink and translate "toplink.*" properties to
* "eclipselink.*" properties
*/
private void checkForUpgradeFromTopLinkEssentials(PersistenceUnitDescriptor persistenceUnitDescriptor) {
if (Boolean.getBoolean(DISABLE_UPGRADE_FROM_TOPLINK_ESSENTIALS)) {
// Return if instructed by System property
return;
}
boolean upgradeTopLinkEssentialsProperties = false;
String providerClassName = persistenceUnitDescriptor.getProvider();
if (providerClassName == null || providerClassName.isEmpty()) {
// This might be a JavaEE app running against V2 and relying in provider name
// being defaulted.
upgradeTopLinkEssentialsProperties = true;
} else if ("oracle.toplink.essentials.PersistenceProvider".equals(providerClassName)
|| "oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider".equals(providerClassName)) {
try {
providerContainerContractInfo.getClassLoader().loadClass(providerClassName);
} catch (ClassNotFoundException e) {
// Toplink Essentials classes are not available to an application using it as
// the provider
// Migrate the application to use EclipseLink
String defaultProvider = PersistenceUnitInfoImpl.getDefaultprovider();
if (logger.isLoggable(INFO)) {
logger.log(INFO, "puloader.defaulting.provider.on.upgrade", new Object[] { persistenceUnitDescriptor.getName(), defaultProvider });
}
// Change the provider name
persistenceUnitDescriptor.setProvider(defaultProvider);
upgradeTopLinkEssentialsProperties = true;
}
}
if (upgradeTopLinkEssentialsProperties) {
// For each "toplink*" property, add a "eclipselink* property
final String TOPLINK = "toplink";
final String ECLIPSELINK = "eclipselink";
Properties properties = persistenceUnitDescriptor.getProperties();
for (Entry<Object, Object> entry : properties.entrySet()) {
String key = (String) entry.getKey();
if (key.startsWith(TOPLINK)) {
String translatedKey = ECLIPSELINK + key.substring(TOPLINK.length());
persistenceUnitDescriptor.addProperty(translatedKey, entry.getValue());
}
}
}
}
/**
* Called during load when the correct classloader and transformer had been
* already set. For emf that require Java2DB, call createEntityManager() to
* populate the DDL files, then iterate over those files and execute each line
* in them.
*/
void doJava2DB() {
if (schemaGenerationProcessor.isContainerDDLExecutionRequired()) {
logger.fine("<--- To Create Tables");
schemaGenerationProcessor.executeCreateDDL();
logger.fine("---> Done Create Tables");
}
}
private ValidationMode getValidationMode(PersistenceUnitDescriptor persistenceUnitDescriptor) {
// Initialize with value element <validation-mode> in persitence.xml
ValidationMode validationMode = persistenceUnitDescriptor.getValidationMode();
// Check is overridden in properties
String validationModeFromProperty = (String) persistenceUnitDescriptor.getProperties().get(VALIDATION_MODE_PROPERTY);
if (validationModeFromProperty != null) {
// User would get IllegalArgumentException if he has specified invalid mode
validationMode = ValidationMode.valueOf(validationModeFromProperty);
}
return validationMode;
}
}