blob: 51696f5f92fa2782f143ed3f233244969dc5094c [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2018 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 - refactor for OSGi
// zhao jianyong - Bug 324627 - JarList stream is not explicitly closed after use in JavaSECMPInitializer
package org.eclipse.persistence.internal.jpa.deployment;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.ProtectionDomain;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import jakarta.persistence.PersistenceException;
import jakarta.persistence.spi.ClassTransformer;
import jakarta.persistence.spi.PersistenceUnitInfo;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.exceptions.EntityManagerSetupException;
import org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedGetConstructorFor;
import org.eclipse.persistence.internal.security.PrivilegedInvokeConstructor;
import org.eclipse.persistence.logging.AbstractSessionLog;
import org.eclipse.persistence.logging.SessionLog;
/**
* INTERNAL:
*
* JavaSECMPInitializer is used to bootstrap the deployment of EntityBeans in EJB 3.0
* when deployed in a non-managed setting
*
* It is called internally by our Provider
*
* @see org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider
*/
public class JavaSECMPInitializer extends JPAInitializer {
// Used when byte code enhancing
public static Instrumentation globalInstrumentation;
// Adding this flag because globalInstrumentation could be set to null after weaving is done.
protected static boolean usesAgent;
// Adding this flag to know that within a JEE container so weaving should be enabled without an agent for non managed persistence units.
protected static boolean isInContainer;
// Indicates whether has been initialized - that could be done only once.
protected static boolean isInitialized;
// Singleton corresponding to the main class loader. Created only if agent is used.
protected static JavaSECMPInitializer initializer;
// Used as a lock in getJavaSECMPInitializer.
private static final Object initializationLock = new Object();
public static boolean isInContainer() {
return isInContainer;
}
public static void setIsInContainer(boolean isInContainer) {
JavaSECMPInitializer.isInContainer = isInContainer;
}
/**
* Get the singleton entityContainer.
*/
public static JavaSECMPInitializer getJavaSECMPInitializer() {
return getJavaSECMPInitializer(Thread.currentThread().getContextClassLoader(), null, false);
}
public static JavaSECMPInitializer getJavaSECMPInitializer(ClassLoader classLoader) {
return getJavaSECMPInitializer(classLoader, null, false);
}
public static JavaSECMPInitializer getJavaSECMPInitializerFromAgent() {
return getJavaSECMPInitializer(Thread.currentThread().getContextClassLoader(), null, true);
}
public static JavaSECMPInitializer getJavaSECMPInitializerFromMain(Map m) {
return getJavaSECMPInitializer(Thread.currentThread().getContextClassLoader(), m, false);
}
public static JavaSECMPInitializer getJavaSECMPInitializer(ClassLoader classLoader, Map m, boolean fromAgent) {
if(!isInitialized) {
if(globalInstrumentation != null) {
synchronized(initializationLock) {
if(!isInitialized) {
initializeTopLinkLoggingFile();
if(fromAgent) {
AbstractSessionLog.getLog().log(SessionLog.FINER, SessionLog.WEAVER, "cmp_init_initialize_from_agent", null);
}
usesAgent = true;
initializer = new JavaSECMPInitializer(classLoader);
initializer.initialize(m != null ? m : new HashMap<>(0));
// all the transformers have been added to instrumentation, don't need it any more.
globalInstrumentation = null;
}
}
}
isInitialized = true;
}
if(initializer != null && initializer.getInitializationClassLoader() == classLoader) {
return initializer;
} else {
// when agent is not used initializer does not need to be initialized.
return new JavaSECMPInitializer(classLoader);
}
}
/**
* User should not instantiate JavaSECMPInitializer.
*/
protected JavaSECMPInitializer() {
super();
}
protected JavaSECMPInitializer(ClassLoader loader) {
super();
this.initializationClassloader = loader;
}
/**
* Check whether weaving is possible and update the properties and variable as appropriate
* @param properties The list of properties to check for weaving and update if weaving is not needed
*/
@Override
public void checkWeaving(Map properties){
String weaving = EntityManagerFactoryProvider.getConfigPropertyAsString(PersistenceUnitProperties.WEAVING, properties, null);
// Check usesAgent instead of globalInstrumentation!=null because globalInstrumentation is set to null after initialization,
// but we still have to keep weaving so that the resulting projects correspond to the woven (during initialization) classes.
if (!usesAgent && !isInContainer) {
if (weaving == null) {
properties.put(PersistenceUnitProperties.WEAVING, "false");
weaving = "false";
} else if (weaving.equalsIgnoreCase("true")) {
throw new PersistenceException(EntityManagerSetupException.wrongWeavingPropertyValue());
}
}
if ((weaving != null) && ((weaving.equalsIgnoreCase("false")) || (weaving.equalsIgnoreCase("static")))){
shouldCreateInternalLoader = false;
}
}
/**
* Create a temporary class loader that can be used to inspect classes and then
* thrown away. This allows classes to be introspected prior to loading them
* with application's main class loader enabling weaving.
*/
@Override
protected ClassLoader createTempLoader(Collection col) {
return createTempLoader(col, true);
}
@Override
protected ClassLoader createTempLoader(Collection col, boolean shouldOverrideLoadClassForCollectionMembers) {
if (!shouldCreateInternalLoader) {
return Thread.currentThread().getContextClassLoader();
}
ClassLoader currentLoader = Thread.currentThread().getContextClassLoader();
if (!(currentLoader instanceof URLClassLoader)) {
//we can't create a TempEntityLoader so just use the current one
//shouldn't be a problem (and should only occur) in JavaSE
return currentLoader;
}
URL[] urlPath = ((URLClassLoader)currentLoader).getURLs();
ClassLoader tempLoader = null;
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
try {
Class[] argsClasses = new Class[] { URL[].class, ClassLoader.class, Collection.class, boolean.class };
Object[] args = new Object[] { urlPath, currentLoader, col, shouldOverrideLoadClassForCollectionMembers };
Constructor<TempEntityLoader> classLoaderConstructor = AccessController.doPrivileged(new PrivilegedGetConstructorFor<>(TempEntityLoader.class, argsClasses, true));
tempLoader = (ClassLoader) AccessController.doPrivileged(new PrivilegedInvokeConstructor(classLoaderConstructor, args));
} catch (PrivilegedActionException privilegedException) {
throw new PersistenceException(EntityManagerSetupException.failedToInstantiateTemporaryClassLoader(privilegedException));
}
} else {
tempLoader = new TempEntityLoader(urlPath, currentLoader, col, shouldOverrideLoadClassForCollectionMembers);
}
AbstractSessionLog.getLog().log(SessionLog.FINER, SessionLog.WEAVER, "cmp_init_tempLoader_created", tempLoader);
AbstractSessionLog.getLog().log(SessionLog.FINER, SessionLog.WEAVER, "cmp_init_shouldOverrideLoadClassForCollectionMembers", shouldOverrideLoadClassForCollectionMembers);
return tempLoader;
}
/**
* INTERNAL:
* Should be called only by the agent. (when weaving classes)
* If succeeded return true, false otherwise.
*/
protected static void initializeFromAgent(Instrumentation instrumentation) throws Exception {
// Squirrel away the instrumentation for later
globalInstrumentation = instrumentation;
getJavaSECMPInitializerFromAgent();
}
/**
* Usually JavaSECMPInitializer is initialized from agent during premain
* to ensure that the classes to be weaved haven't been loaded before initialization.
* However, in this case initialization can't be debugged.
* In order to be able to debug initialization specify
* in java options -javaagent with parameter "main": (note: a separate eclipselink-agent.jar is no longer required)
* -javaagent:c:\trunk\eclipselink.jar=main
* that causes instrumentation to be cached during premain and postpones initialization until main.
* With initialization done in main (during the first createEntityManagerFactory call)
* there's a danger of the classes to be weaved being already loaded.
* In that situation initializeFromMain should be called before any classes are loaded.
* The sure-to-work method would be to create a new runnable class with a main method
* consisting of just two lines: calling initializeFromMain
* followed by reflective call to the main method of the original runnable class.
* The same could be achieved by calling PersistenceProvider.createEntityManagerFactory method instead
* of JavaSECMPInitializer.initializeFromMain method,
* however initializeFromMain might be more convenient because it
* doesn't require a persistence unit name.
* The method doesn't do anything if JavaSECMPInitializer has been already initialized.
* @param m - a map containing the set of properties to instantiate with.
*/
public static void initializeFromMain(Map m) {
getJavaSECMPInitializerFromMain(m);
}
/**
* The version of initializeFromMain that passes an empty map.
*/
public static void initializeFromMain() {
initializeFromMain(new HashMap<>());
}
/**
* Register a transformer. In this case, we use the instrumentation to add a transformer for the
* JavaSE environment
*/
@Override
public void registerTransformer(final ClassTransformer transformer, PersistenceUnitInfo persistenceUnitInfo, Map properties){
if ((transformer != null) && (globalInstrumentation != null)) {
AbstractSessionLog.getLog().log(SessionLog.FINER, SessionLog.WEAVER, "cmp_init_register_transformer", persistenceUnitInfo.getPersistenceUnitName());
globalInstrumentation.addTransformer(new ClassFileTransformer() {
// adapt ClassTransformer to ClassFileTransformer interface
@Override
public byte[] transform(
ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
return transformer.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
}
});
} else if (transformer == null) {
AbstractSessionLog.getLog().log(SessionLog.FINER, SessionLog.WEAVER, "cmp_init_transformer_is_null", null, true);
} else if (globalInstrumentation == null) {
AbstractSessionLog.getLog().log(SessionLog.FINER, SessionLog.WEAVER, "cmp_init_globalInstrumentation_is_null", null, true);
}
}
/**
* Indicates whether puName uniquely defines the persistence unit.
* usesAgent means that it is a stand alone SE case.
* Otherwise it could be an application server case where different persistence units
* may have the same name: that could happen if they are loaded by different classloaders;
* the common case is the same persistence unit jar deployed in several applications.
*/
@Override
public boolean isPersistenceUnitUniquelyDefinedByName() {
return usesAgent;
}
/**
* Indicates whether initialization has already occurred.
*/
public static boolean isInitialized() {
return isInitialized;
}
/**
* Indicates whether Java agent and globalInstrumentation was used.
*/
public static boolean usesAgent() {
return usesAgent;
}
/**
* Indicates whether initialPuInfos and initialEmSetupImpls are used.
*/
@Override
protected boolean keepAllPredeployedPersistenceUnits() {
return usesAgent;
}
/*********************************/
/***** Temporary Classloader *****/
/*********************************/
/**
* This class loader is provided at initialization time to allow us to temporarily load
* domain classes so we can examine them for annotations. After they are loaded we will throw this
* class loader away. Transformers can then be registered on the real class loader to allow
* weaving to occur.
*
* It selectively loads classes based on the list of classnames it is instantiated with. Classes
* not on that list are allowed to be loaded by the parent.
*/
public static class TempEntityLoader extends URLClassLoader {
Collection classNames;
boolean shouldOverrideLoadClassForCollectionMembers;
//added to resolved gf #589 - without this, the orm.xml url would be returned twice
@Override
public Enumeration<URL> getResources(String name) throws java.io.IOException {
return this.getParent().getResources(name);
}
public TempEntityLoader(URL[] urls, ClassLoader parent, Collection classNames, boolean shouldOverrideLoadClassForCollectionMembers) {
super(urls, parent);
this.classNames = classNames;
this.shouldOverrideLoadClassForCollectionMembers = shouldOverrideLoadClassForCollectionMembers;
}
public TempEntityLoader(URL[] urls, ClassLoader parent, Collection classNames) {
this(urls, parent, classNames, true);
}
// Indicates if the classLoad should be overridden for the passed className.
// Returns true in case the class should NOT be loaded by parent classLoader.
protected boolean shouldOverrideLoadClass(String name) {
if (shouldOverrideLoadClassForCollectionMembers) {
// Override classLoad if the name is in collection
return (classNames != null) && classNames.contains(name);
} else {
// Directly opposite: Override classLoad if the name is NOT in collection.
// Forced to check for java. and javax. packages here, because even if the class
// has been loaded by parent loader we would load it again
// (see comment in loadClass)
return !name.startsWith("java.") && !name.startsWith("javax.") && !name.startsWith("jakarta.") && ((classNames == null) || !classNames.contains(name));
}
}
@Override
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (shouldOverrideLoadClass(name)) {
// First, check if the class has already been loaded.
// Note that the check only for classes loaded by this loader,
// it doesn't return true if the class has been loaded by parent loader
// (forced to live with that because findLoadedClass method defined as final protected:
// neither can override it nor call it on the parent loader)
Class<?> c = findLoadedClass(name);
if (c == null) {
c = findClass(name);
}
if (resolve) {
resolveClass(c);
}
return c;
} else {
return super.loadClass(name, resolve);
}
}
}
}