This is an automated email from the ASF dual-hosted git repository. jlmonteiro pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tomee-jakarta.git
commit 28fc39cb36d75edf7306925ada93551aba0da7ee Author: Jean-Louis Monteiro <[email protected]> AuthorDate: Tue Apr 13 12:24:45 2021 +0200 Import OpenJPA Persistence Provider so we can patch it Signed-off-by: Jean-Louis Monteiro <[email protected]> --- .../persistence/PersistenceProviderImpl.java | 448 +++++++++++++++++++++ 1 file changed, 448 insertions(+) diff --git a/transform/src/patch/java/org/apache/openjpa/persistence/PersistenceProviderImpl.java b/transform/src/patch/java/org/apache/openjpa/persistence/PersistenceProviderImpl.java new file mode 100644 index 0000000..8df3d5d --- /dev/null +++ b/transform/src/patch/java/org/apache/openjpa/persistence/PersistenceProviderImpl.java @@ -0,0 +1,448 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.persistence; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import java.util.HashMap; +import java.util.Map; + +import javax.persistence.EntityManager; +import javax.persistence.spi.ClassTransformer; +import javax.persistence.spi.LoadState; +import javax.persistence.spi.PersistenceProvider; +import javax.persistence.spi.PersistenceUnitInfo; +import javax.persistence.spi.ProviderUtil; + +import org.apache.openjpa.conf.BrokerValue; +import org.apache.openjpa.conf.OpenJPAConfiguration; +import org.apache.openjpa.conf.OpenJPAConfigurationImpl; +import org.apache.openjpa.enhance.PCClassFileTransformer; +import org.apache.openjpa.enhance.PCEnhancerAgent; +import org.apache.openjpa.kernel.AbstractBrokerFactory; +import org.apache.openjpa.kernel.Bootstrap; +import org.apache.openjpa.kernel.BrokerFactory; +import org.apache.openjpa.kernel.ConnectionRetainModes; +import org.apache.openjpa.lib.conf.Configuration; +import org.apache.openjpa.lib.conf.ConfigurationProvider; +import org.apache.openjpa.lib.conf.Configurations; +import org.apache.openjpa.lib.log.Log; +import org.apache.openjpa.lib.util.Localizer; +import org.apache.openjpa.lib.util.MultiClassLoader; +import org.apache.openjpa.meta.AbstractCFMetaDataFactory; +import org.apache.openjpa.meta.MetaDataModes; +import org.apache.openjpa.meta.MetaDataRepository; +import org.apache.openjpa.persistence.osgi.BundleUtils; +import org.apache.openjpa.persistence.validation.ValidationUtils; +import org.apache.openjpa.util.ClassResolver; + + +/** + * Bootstrapping class that allows the creation of a stand-alone + * {@link EntityManager}. + * + * @see javax.persistence.Persistence#createEntityManagerFactory(String,Map) + * @published + */ +public class PersistenceProviderImpl + implements PersistenceProvider, ProviderUtil { + + static final String CLASS_TRANSFORMER_OPTIONS = "ClassTransformerOptions"; + private static final String EMF_POOL = "EntityManagerFactoryPool"; + + private static final Localizer _loc = Localizer.forPackage(PersistenceProviderImpl.class); + + private Log _log; + /** + * Loads the entity manager specified by <code>name</code>, applying + * the properties in <code>m</code> as overrides to the properties defined + * in the XML configuration file for <code>name</code>. If <code>name</code> + * is <code>null</code>, this method loads the XML in the resource + * identified by <code>resource</code>, and uses the first resource found + * when doing this lookup, regardless of the name specified in the XML + * resource or the name of the jar that the resource is contained in. + * This does no pooling of EntityManagersFactories. + * @return EntityManagerFactory or null + */ + public OpenJPAEntityManagerFactory createEntityManagerFactory(String name, String resource, Map m) { + PersistenceProductDerivation pd = new PersistenceProductDerivation(); + try { + Object poolValue = Configurations.removeProperty(EMF_POOL, m); + ConfigurationProvider cp = pd.load(resource, name, m); + if (cp == null) { + return null; + } + + BrokerFactory factory = getBrokerFactory(cp, poolValue, BundleUtils.getBundleClassLoader()); + OpenJPAConfiguration conf = factory.getConfiguration(); + conf.setUserClassLoader(BundleUtils.getBundleClassLoader()); + _log = conf.getLog(OpenJPAConfiguration.LOG_RUNTIME); + pd.checkPuNameCollisions(_log,name); + + // add enhancer + loadAgent(factory); + + // Create appropriate LifecycleEventManager + loadValidator(factory); + + if (conf.getConnectionRetainModeConstant() == ConnectionRetainModes.CONN_RETAIN_ALWAYS) { + // warn about EMs holding on to connections. + _log.warn(_loc.get("retain-always", conf.getId())); + } + + OpenJPAEntityManagerFactory emf = JPAFacadeHelper.toEntityManagerFactory(factory); + if (_log.isTraceEnabled()) { + _log.trace(this + " creating " + emf + " for PU " + name + "."); + } + return emf; + } catch (Exception e) { + if (_log != null) { + _log.error(_loc.get("create-emf-error", name), e); + } + + /* + * + * Maintain 1.x behavior of throwing exceptions, even though + * JPA2 9.2 - createEMF "must" return null for PU it can't handle. + * + * JPA 2.0 Specification Section 9.2 states: + * "If a provider does not qualify as the provider for the named persistence unit, + * it must return null when createEntityManagerFactory is invoked on it." + * That specification compliance behavior has happened few lines above on null return. + * Throwing runtime exception in the following code is valid (and useful) behavior + * because the qualified provider has encountered an unexpected situation. + */ + throw PersistenceExceptions.toPersistenceException(e); + } + } + + private BrokerFactory getBrokerFactory(ConfigurationProvider cp, + Object poolValue, ClassLoader loader) { + // handle "true" and "false" + if (poolValue instanceof String + && ("true".equalsIgnoreCase((String) poolValue) + || "false".equalsIgnoreCase((String) poolValue))) + poolValue = Boolean.valueOf((String) poolValue); + + if (poolValue != null && !(poolValue instanceof Boolean)) { + // we only support boolean settings for this option currently. + throw new IllegalArgumentException(poolValue.toString()); + } + + if (poolValue == null || !((Boolean) poolValue).booleanValue()) + return Bootstrap.newBrokerFactory(cp, loader); + else + return Bootstrap.getBrokerFactory(cp, loader); + } + + @Override + public OpenJPAEntityManagerFactory createEntityManagerFactory(String name, Map m) { + return createEntityManagerFactory(name, null, m); + } + + @Override + public OpenJPAEntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo pui, Map m) { + PersistenceProductDerivation pd = new PersistenceProductDerivation(); + try { + Object poolValue = Configurations.removeProperty(EMF_POOL, m); + ConfigurationProvider cp = pd.load(pui, m); + if (cp == null) + return null; + + // add enhancer + Exception transformerException = null; + String ctOpts = (String) Configurations.getProperty(CLASS_TRANSFORMER_OPTIONS, pui.getProperties()); + try { + pui.addTransformer(new ClassTransformerImpl(cp, ctOpts, + pui.getNewTempClassLoader(), newConfigurationImpl())); + } catch (Exception e) { + // fail gracefully + transformerException = e; + } + + // if the BrokerImpl hasn't been specified, switch to the + // non-finalizing one, since anything claiming to be a container + // should be doing proper resource management. + if (!Configurations.containsProperty(BrokerValue.KEY, cp.getProperties())) { + cp.addProperty("openjpa." + BrokerValue.KEY, getDefaultBrokerAlias()); + } + + + ClassLoader loader = pui.getClassLoader(); + if (BundleUtils.runningUnderOSGi()) { + // OPENJPA-1491 : If running under OSGi, use the Bundle's ClassLoader instead of the application one + // OPENJPA-2542 : Also try to load from app loader in the case of a user implemented interface/config + loader = new MultiClassLoader(BundleUtils.getBundleClassLoader(), loader); + } + BrokerFactory factory = getBrokerFactory(cp, poolValue, loader); + OpenJPAConfiguration conf = factory.getConfiguration(); + setPersistenceEnvironmentInfo(conf, pui); + _log = conf.getLog(OpenJPAConfiguration.LOG_RUNTIME); + // now we can log any transformer exceptions from above + if (transformerException != null) { + if (_log.isTraceEnabled()) { + _log.warn(_loc.get("transformer-registration-error-ex", pui), transformerException); + } else { + _log.warn(_loc.get("transformer-registration-error", pui)); + } + } + + if (conf.getConnectionRetainModeConstant() == ConnectionRetainModes.CONN_RETAIN_ALWAYS) { + // warn about container managed EMs holding on to connections. + _log.warn(_loc.get("cm-retain-always",conf.getId())); + } + + // Create appropriate LifecycleEventManager + loadValidator(factory); + + OpenJPAEntityManagerFactory emf = JPAFacadeHelper.toEntityManagerFactory(factory); + if (_log.isTraceEnabled()) { + _log.trace(this + " creating container " + emf + " for PU " + pui.getPersistenceUnitName() + "."); + } + + return emf; + } catch (Exception e) { + throw PersistenceExceptions.toPersistenceException(e); + } + } + + @Override + public void generateSchema(final PersistenceUnitInfo info, final Map map) { + final Map runMap = map == null ? new HashMap<>() : new HashMap<>(map); + runMap.put("javax.persistence.schema-generation.database.action", "create"); + final OpenJPAEntityManagerFactory factory = createContainerEntityManagerFactory(info, runMap); + try { + synchronizeMappings(factory); + } finally { + factory.close(); + } + } + + @Override + public boolean generateSchema(final String persistenceUnitName, final Map map) { + final Map runMap = map == null ? new HashMap<>() : new HashMap<>(map); + runMap.put("javax.persistence.schema-generation.database.action", "create"); + final OpenJPAEntityManagerFactory factory = createEntityManagerFactory(persistenceUnitName, runMap); + try { + final Object obj = synchronizeMappings(factory); + return Boolean.class.cast(obj) ? Boolean.class.cast(obj) : true; + } finally { + factory.close(); + } + } + + private Object synchronizeMappings(final OpenJPAEntityManagerFactory factory) { + if (EntityManagerFactoryImpl.class.isInstance(factory)) { + final EntityManagerFactoryImpl entityManagerFactory = EntityManagerFactoryImpl.class.cast(factory); + final BrokerFactory brokerFactory = entityManagerFactory.getBrokerFactory(); + if (!AbstractBrokerFactory.class.isInstance(brokerFactory)) { + throw new IllegalArgumentException("expected AbstractBrokerFactory but got " + brokerFactory); + } + try { + final Method synchronizeMappings = brokerFactory.getClass() + .getDeclaredMethod("synchronizeMappings", ClassLoader.class); + if (!synchronizeMappings.isAccessible()) { + synchronizeMappings.setAccessible(true); + } + return synchronizeMappings.invoke(brokerFactory, Thread.currentThread().getContextClassLoader()); + } catch (final NoSuchMethodException | IllegalAccessException e) { + throw new IllegalStateException(e); + } catch (final InvocationTargetException e) { + final Throwable targetException = e.getTargetException(); + if (RuntimeException.class.isInstance(targetException)) { + throw RuntimeException.class.cast(targetException); + } + throw new IllegalStateException(targetException); + } + } else { + throw new IllegalArgumentException("expected EntityManagerFactoryImpl but got " + factory); + } + } + + public void setPersistenceEnvironmentInfo(OpenJPAConfiguration conf, PersistenceUnitInfo pui) { + // OPENJPA-1460 Fix scope visibility of orm.xml when it is packaged in both ear file and war file + if (conf instanceof OpenJPAConfigurationImpl) { + Map<String, Object> peMap =((OpenJPAConfigurationImpl)conf).getPersistenceEnvironment(); + if (peMap == null) { + peMap = new HashMap<>(); + ((OpenJPAConfigurationImpl)conf).setPersistenceEnvironment(peMap); + } + peMap.put(AbstractCFMetaDataFactory.PERSISTENCE_UNIT_ROOT_URL, pui.getPersistenceUnitRootUrl()); + peMap.put(AbstractCFMetaDataFactory.MAPPING_FILE_NAMES, pui.getMappingFileNames()); + peMap.put(AbstractCFMetaDataFactory.JAR_FILE_URLS, pui.getJarFileUrls()); + } + } + + /* + * Returns a ProviderUtil for use with entities managed by this + * persistence provider. + */ + @Override + public ProviderUtil getProviderUtil() { + return this; + } + + /* + * Returns a default Broker alias to be used when no openjpa.BrokerImpl + * is specified. This method allows PersistenceProvider subclass to + * override the default broker alias. + */ + protected String getDefaultBrokerAlias() { + return BrokerValue.NON_FINALIZING_ALIAS; + } + + /* + * Return a new instance of Configuration subclass used by entity + * enhancement in ClassTransformerImpl. If OpenJPAConfigurationImpl + * instance is used, configuration options declared in configuration + * sub-class will not be recognized and a warning is posted in the log. + */ + protected OpenJPAConfiguration newConfigurationImpl() { + return new OpenJPAConfigurationImpl(); + } + + /** + * Java EE 5 class transformer. + */ + private static class ClassTransformerImpl + implements ClassTransformer { + + private final ClassFileTransformer _trans; + + private ClassTransformerImpl(ConfigurationProvider cp, String props, + final ClassLoader tmpLoader, OpenJPAConfiguration conf) { + cp.setInto(conf); + // use the temporary loader for everything + conf.setClassResolver(new ClassResolver() { + @Override + public ClassLoader getClassLoader(Class<?> context, ClassLoader env) { + return tmpLoader; + } + }); + conf.setReadOnly(Configuration.INIT_STATE_FREEZING); + + MetaDataRepository repos = conf.getMetaDataRepositoryInstance(); + repos.setResolve(MetaDataModes.MODE_MAPPING, false); + _trans = new PCClassFileTransformer(repos, + Configurations.parseProperties(props), tmpLoader); + } + + @Override + public byte[] transform(ClassLoader cl, String name, + Class<?> previousVersion, ProtectionDomain pd, byte[] bytes) + throws IllegalClassFormatException { + return _trans.transform(cl, name, previousVersion, pd, bytes); + } + } + + /** + * This private worker method will attempt load the PCEnhancerAgent. + */ + private void loadAgent(BrokerFactory factory) { + OpenJPAConfiguration conf = factory.getConfiguration(); + Log log = conf.getLog(OpenJPAConfiguration.LOG_RUNTIME); + + if (conf.getDynamicEnhancementAgent() == true) { + boolean res = PCEnhancerAgent.loadDynamicAgent(log); + if (log.isInfoEnabled() && res == true ){ + log.info(_loc.get("dynamic-agent")); + } + } + } + + /** + * This private worker method will attempt to setup the proper + * LifecycleEventManager type based on if the javax.validation APIs are + * available and a ValidatorImpl is required by the configuration. + * @param log + * @param conf + * @throws if validation setup failed and was required by the config + */ + private void loadValidator(BrokerFactory factory) { + OpenJPAConfiguration conf = factory.getConfiguration(); + Log log = conf.getLog(OpenJPAConfiguration.LOG_RUNTIME); + + if ((ValidationUtils.setupValidation(conf) == true) && + log.isInfoEnabled()) { + log.info(_loc.get("vlem-creation-info")); + } + } + + /** + * Determines whether the specified object is loaded. + * + * @return LoadState.LOADED - if all implicit or explicit EAGER fetch + * attributes are loaded + * LoadState.NOT_LOADED - if any implicit or explicit EAGER fetch + * attribute is not loaded + * LoadState.UNKNOWN - if the entity is not managed by this + * provider. + */ + @Override + public LoadState isLoaded(Object obj) { + return isLoadedWithoutReference(obj, null); + } + + /** + * Determines whether the attribute on the specified object is loaded. This + * method may access the value of the attribute to determine load state (but + * currently does not). + * + * @return LoadState.LOADED - if the attribute is loaded. + * LoadState.NOT_LOADED - if the attribute is not loaded or any + * EAGER fetch attributes of the entity are not loaded. + * LoadState.UNKNOWN - if the entity is not managed by this + * provider or if it does not contain the persistent + * attribute. + */ + @Override + public LoadState isLoadedWithReference(Object obj, String attr) { + // TODO: Are there be any cases where OpenJPA will need to examine + // the contents of a field to determine load state? If so, per JPA + // contract, this method permits that sort of access. In the extremely + // unlikely case that the the entity is managed by multiple providers, + // even if it doesn't trigger loading in OpenJPA, accessing field data + // could trigger loading by an alternate provider. + return isLoadedWithoutReference(obj, attr); + } + + /** + * Determines whether the attribute on the specified object is loaded. This + * method does not access the value of the attribute to determine load + * state. + * + * @return LoadState.LOADED - if the attribute is loaded. + * LoadState.NOT_LOADED - if the attribute is not loaded or any + * EAGER fetch attributes of the entity are not loaded. + * LoadState.UNKNOWN - if the entity is not managed by this + * provider or if it does not contain the persistent + * attribute. + */ + @Override + public LoadState isLoadedWithoutReference(Object obj, String attr) { + if (obj == null) { + return LoadState.UNKNOWN; + } + + return OpenJPAPersistenceUtil.isLoaded(obj, attr); + } +} \ No newline at end of file
