| /* |
| * 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 |
| * @param transformer |
| * @param persistenceUnitInfo |
| */ |
| @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); |
| } |
| } |
| } |
| } |