/*
* 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.security.AccessController;
import java.security.ProtectionDomain;
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.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.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.Options;
import org.apache.openjpa.meta.MetaDataModes;
import org.apache.openjpa.meta.MetaDataRepository;
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, null);
OpenJPAConfiguration conf = factory.getConfiguration();
_log = conf.getLog(OpenJPAConfiguration.LOG_RUNTIME);
pd.checkPuNameCollisions(_log,name);
// add enhancer
loadAgent(factory);
// Create appropriate LifecycleEventManager
loadValidator(factory);
// We need to wait to preload until after we get back a fully configured/instantiated
// BrokerFactory. This is because it is possible that someone has extended OpenJPA
// functions and they need to be allowed time to configure themselves before we go off and
// start instanting configurable objects (ie:openjpa.MetaDataRepository). Don't catch
// any exceptions here because we want to fail-fast.
preloadMetaDataRepository(factory);
return JPAFacadeHelper.toEntityManagerFactory(factory);
} 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);
}
public OpenJPAEntityManagerFactory createEntityManagerFactory(String name, Map m) {
return createEntityManagerFactory(name, null, m);
}
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());
}
BrokerFactory factory = getBrokerFactory(cp, poolValue, pui.getClassLoader());
if (transformerException != null) {
Log log = factory.getConfiguration().getLog(OpenJPAConfiguration.LOG_RUNTIME);
if (log.isTraceEnabled()) {
log.warn(_loc.get("transformer-registration-error-ex", pui), transformerException);
} else {
log.warn(_loc.get("transformer-registration-error", pui));
}
}
// Create appropriate LifecycleEventManager
loadValidator(factory);
// We need to wait to preload until after we get back a fully configured/instantiated
// BrokerFactory. This is because it is possible that someone has extended OpenJPA
// functions and they need to be allowed time to configure themselves before we go off and
// start instanting configurable objects (ie:openjpa.MetaDataRepository). Don't catch
// any exceptions here because we want to fail-fast.
preloadMetaDataRepository(factory);
return JPAFacadeHelper.toEntityManagerFactory(factory);
} catch (Exception e) {
throw PersistenceExceptions.toPersistenceException(e);
}
}
/*
* Returns a ProviderUtil for use with entities managed by this
* persistence provider.
*/
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();
}
/**
* Private worker method that will call to the MetaDataRepository to preload if the provided
* BrokerFactory is configured to do so.
*/
private void preloadMetaDataRepository(BrokerFactory factory){
// We need to wait to preload until after we get back a fully configured/instantiated
// BrokerFactory. This is because it is possible that someone has extended OpenJPA
// functions and they need to be allowed time to configure themselves before we go off and
// start instantiating configurable objects (ie:openjpa.MetaDataRepository). Don't catch
// any exceptions here because we want to fail-fast.
OpenJPAConfiguration conf = factory.getConfiguration();
Options o = Configurations.parseProperties(Configurations.getProperties(conf.getMetaDataRepository()));
if (MetaDataRepository.needsPreload(o) == true) {
MetaDataRepository mdr = conf.getMetaDataRepositoryInstance();
mdr.setValidate(MetaDataRepository.VALIDATE_RUNTIME, true);
mdr.setResolve(MetaDataRepository.MODE_MAPPING_INIT, true);
// Load persistent classes and hook in subclasser
((AbstractBrokerFactory) factory).loadPersistentTypes((ClassLoader) AccessController
.doPrivileged(J2DoPrivHelper.getContextClassLoaderAction()));
mdr.preload();
}
}
/**
* 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() {
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);
}
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.
*/
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.
*/
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.
*/
public LoadState isLoadedWithoutReference(Object obj, String attr) {
if (obj == null) {
return LoadState.UNKNOWN;
}
return OpenJPAPersistenceUtil.isLoaded(obj, attr);
}
}