blob: bbe358d824e169bff6780ab771caf835e0aeea7b [file] [log] [blame]
/*
* 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)
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 = PCClassFileTransformer.newInstance(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()) {
boolean res = PCEnhancerAgent.loadDynamicAgent(log);
if (log.isInfoEnabled() && res){
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.
* @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)) &&
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);
}
}