blob: dca7270fa7fa53a7ade0e58688b82997005be349 [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 WARRANTIESOR 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.aries.jpa.container.impl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.persistence.spi.PersistenceProvider;
import javax.persistence.spi.PersistenceUnitInfo;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.sql.DataSource;
import org.apache.aries.jpa.container.parser.impl.PersistenceUnit;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.jdbc.DataSourceFactory;
import org.osgi.service.jpa.EntityManagerFactoryBuilder;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* FIXME We are currently not configuring a DataSource for the persistence unit.
* It still works in the tests as the DataSource is defined in the
* DataSourceTracker or DSFTracker. This not fully correct though.
*/
public class AriesEntityManagerFactoryBuilder implements EntityManagerFactoryBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(AriesEntityManagerFactoryBuilder.class);
private static final String JPA_CONFIGURATION_PREFIX = "org.apache.aries.jpa.";
private static final String JAVAX_PERSISTENCE_JDBC_DRIVER = "javax.persistence.jdbc.driver";
private static final String JAVAX_PERSISTENCE_JTA_DATASOURCE = "javax.persistence.jtaDataSource";
private static final String JAVAX_PERSISTENCE_DATASOURCE = "javax.persistence.dataSource";
private static final String JAVAX_PERSISTENCE_NON_JTA_DATASOURCE = "javax.persistence.nonJtaDataSource";
private static final String JAVAX_PERSISTENCE_TX_TYPE = "javax.persistence.transactionType";
private boolean closed;
private final PersistenceProvider provider;
private final Bundle providerBundle;
private final PersistenceUnit persistenceUnit;
private final BundleContext containerContext;
private final PersistenceUnitTransactionType originalTxType;
private final Bundle bundle;
private String driver;
private EntityManagerFactory emf;
private ServiceRegistration<EntityManagerFactory> reg;
private ServiceRegistration<?> configReg;
private Object activeConnectionProvider;
private Map<String, Object> activeProps;
private ServiceTracker<?,?> tracker;
private boolean complete;
public AriesEntityManagerFactoryBuilder(BundleContext containerContext, PersistenceProvider provider, Bundle providerBundle, PersistenceUnit persistenceUnit) {
this.provider = provider;
this.providerBundle = providerBundle;
this.persistenceUnit = persistenceUnit;
this.containerContext = containerContext;
this.originalTxType = persistenceUnit.getTransactionType();
this.bundle = persistenceUnit.getBundle();
this.driver = persistenceUnit.getProperties().getProperty(JAVAX_PERSISTENCE_JDBC_DRIVER);
this.tracker = createDataSourceTracker(provider);
// This must be done separately to avoid an immediate callback seeing the wrong state
if(this.tracker != null) {
this.tracker.open();
}
registerManagedService(containerContext, persistenceUnit);
}
private ServiceTracker<?, ?> createDataSourceTracker(PersistenceProvider provider) {
if (usesDataSource()) {
synchronized (this) {
driver = "Pre Configured DataSource";
}
if (!usesDataSourceService()) {
LOGGER.warn("Persistence unit " + persistenceUnit.getPersistenceUnitName() + " refers to a non OSGi service DataSource");
return null;
}
DataSourceTracker dsTracker = new DataSourceTracker(containerContext, this,
DataSourceTracker.getDsName(persistenceUnit));
return dsTracker;
} else if (usesDSF()) {
String jdbcClass = DSFTracker.getDriverName(persistenceUnit);
synchronized (this) {
driver = jdbcClass;
}
DSFTracker dsfTracker = new DSFTracker(containerContext, this,
jdbcClass);
return dsfTracker;
} else {
LOGGER.debug("Persistence unit " + getPUName() + " does not refer a DataSource. "
+"It can only be used with EntityManagerFactoryBuilder.");
return null;
}
}
private boolean usesDataSource() {
return persistenceUnit.getJtaDataSourceName() != null || persistenceUnit.getNonJtaDataSourceName() != null;
}
private boolean usesDSF() {
return DSFTracker.getDriverName(persistenceUnit) != null;
}
private boolean usesDataSourceService() {
return persistenceUnit.getJtaDataSourceName() != null && persistenceUnit.getJtaDataSourceName().startsWith(DataSourceTracker.DS_PREFIX)
|| persistenceUnit.getNonJtaDataSourceName() != null && persistenceUnit.getNonJtaDataSourceName().startsWith(DataSourceTracker.DS_PREFIX);
}
@Override
public String getPersistenceProviderName() {
String name = persistenceUnit.getPersistenceProviderClassName();
return name == null ? provider.getClass().getName() : name;
}
@Override
public Bundle getPersistenceProviderBundle() {
return providerBundle;
}
@Override
public EntityManagerFactory createEntityManagerFactory(Map<String, Object> props) {
synchronized (this) {
if (closed) {
throw new IllegalStateException("The EntityManagerFactoryBuilder for " +
getPUName() + " is no longer valid");
}
}
if (bundle.getState() == Bundle.UNINSTALLED || bundle.getState() == Bundle.INSTALLED
|| bundle.getState() == Bundle.STOPPING) {
// Not sure why but during the TCK tests updated sometimes was
// called for uninstalled bundles
throw new IllegalStateException("The EntityManagerFactoryBuilder for " +
getPUName() + " is no longer valid");
}
Map<String, Object> processedProperties = processProperties(props);
synchronized (this) {
if(processedProperties.equals(activeProps) && emf != null) {
return emf;
}
}
closeEMF();
final EntityManagerFactory toUse = createAndPublishEMF(processedProperties);
return (EntityManagerFactory) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[] {EntityManagerFactory.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("close".equals(method.getName())) {
// Close the registration as per the spec
closeEMF();
// Do not delegate as the closeEMF call already closes
return null;
}
return method.invoke(toUse, args);
}
});
}
public String getPUName() {
return persistenceUnit.getPersistenceUnitName();
}
private Map<String, Object> processProperties(Map<String, Object> props) {
Map<String, Object> processed = new HashMap<String, Object>();
Properties punitProps = persistenceUnit.getProperties();
for(String key : punitProps.stringPropertyNames()) {
processed.put(key, punitProps.get(key));
}
if(props != null) {
processed.putAll(props);
}
String newDriver = (String) processed.get(JAVAX_PERSISTENCE_JDBC_DRIVER);
synchronized (this) {
if(newDriver != null) {
if(driver == null) {
driver = newDriver;
} else if (!newDriver.equals(driver)) {
throw new IllegalArgumentException("Cannot rebind to a different database driver, as per the JPA service specification");
}
}
}
boolean dataSourceProvided = false;
// Handle overridden datasources in a provider agnostic way
// This isn't necessary for EclipseLink, but Hibernate and
// OpenJPA both need some extra help.
Object o = processed.get(JAVAX_PERSISTENCE_JTA_DATASOURCE);
if (o instanceof DataSource) {
persistenceUnit.setJtaDataSource((DataSource) o);
processed.remove(JAVAX_PERSISTENCE_JTA_DATASOURCE);
dataSourceProvided = true;
}
o = processed.get(JAVAX_PERSISTENCE_NON_JTA_DATASOURCE);
if (o instanceof DataSource) {
persistenceUnit.setNonJtaDataSource((DataSource) o);
processed.remove(JAVAX_PERSISTENCE_NON_JTA_DATASOURCE);
dataSourceProvided = true;
} else {
o = processed.get(JAVAX_PERSISTENCE_DATASOURCE);
if (o != null && o instanceof DataSource) {
persistenceUnit.setNonJtaDataSource((DataSource) o);
processed.remove(JAVAX_PERSISTENCE_DATASOURCE);
dataSourceProvided = true;
}
}
o = processed.get(JAVAX_PERSISTENCE_TX_TYPE);
if (o instanceof PersistenceUnitTransactionType) {
persistenceUnit.setTransactionType((PersistenceUnitTransactionType) o);
} else if (o instanceof String) {
persistenceUnit.setTransactionType(PersistenceUnitTransactionType.valueOf((String) o));
} else {
LOGGER.debug("No transaction type set in config, restoring the original value {}", originalTxType);
persistenceUnit.setTransactionType(originalTxType);
}
// This Aries extension is used to communicate the actual transaction type to clients
processed.put(PersistenceUnitTransactionType.class.getName(), persistenceUnit.getTransactionType());
synchronized (this) {
// Either they provide a datasource, or we're already provided and using a tracker
complete = dataSourceProvided || (complete && tracker != null);
}
return processed;
}
private void registerManagedService(BundleContext containerContext, PersistenceUnitInfo persistenceUnit) {
Dictionary<String, Object> configuration = new Hashtable<String, Object>(); // NOSONAR
configuration.put(Constants.SERVICE_PID, JPA_CONFIGURATION_PREFIX + persistenceUnit.getPersistenceUnitName());
configReg = containerContext.registerService(ManagedService.class,
new ManagedEMF(this, persistenceUnit.getPersistenceUnitName()), configuration);
}
public void closeEMF() {
EntityManagerFactory emf;
ServiceRegistration<EntityManagerFactory> emfReg;
synchronized (this) {
emf = this.emf;
this.emf = null;
emfReg = this.reg;
this.reg = null;
}
if (emfReg != null) {
try {
emfReg.unregister();
} catch (Exception e) {
LOGGER.debug("Exception on unregister", e);
}
}
if (emf != null && emf.isOpen()) {
try {
emf.close();
} catch (Exception e) {
LOGGER.warn("Error closing EntityManagerFactory for " + getPUName(), e);
}
}
}
public void close() {
boolean unregister = false;
ServiceTracker<?, ?> toClose;
synchronized (this) {
closed = true;
unregister = true;
toClose = tracker;
}
if(unregister) {
try {
configReg.unregister();
} catch (Exception e) {
LOGGER.debug("Exception on unregister", e);
}
}
if (toClose != null) {
toClose.close();
}
closeEMF();
}
private EntityManagerFactory createAndPublishEMF(Map<String, Object> overrides) {
boolean makeTracker;
String dbDriver;
synchronized (this) {
makeTracker = driver != null && tracker == null;
dbDriver = driver;
}
if(makeTracker) {
ServiceTracker<?, ?> dsfTracker = new DSFTracker(containerContext,
this, dbDriver);
synchronized (this) {
tracker = dsfTracker;
activeProps = overrides;
}
dsfTracker.open();
synchronized (this) {
if(emf == null) {
throw new IllegalStateException("No database driver is currently available for class " + dbDriver);
} else {
return emf;
}
}
} else {
synchronized (this) {
if(!complete) {
throw new IllegalArgumentException("The persistence unit " + getPUName() +
" has incomplete configuration and cannot be created. The configuration is" + overrides);
}
}
}
final EntityManagerFactory tmp = provider.createContainerEntityManagerFactory(persistenceUnit, overrides);
boolean register = false;
synchronized (this) {
if(emf == null) {
emf = tmp;
activeProps = overrides;
register = true;
}
}
if(register) {
Dictionary<String, Object> props = createBuilderProperties(overrides);
BundleContext uctx = bundle.getBundleContext();
ServiceRegistration<EntityManagerFactory> tmpReg =
uctx.registerService(EntityManagerFactory.class,
(EntityManagerFactory) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[] {EntityManagerFactory.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("close".equals(method.getName())) {
// Ignore close as per the spec
return null;
}
return method.invoke(tmp, args);
}
}), props);
synchronized (this) {
if(emf == tmp) {
reg = tmpReg;
} else {
register = false;
}
}
if(!register) {
tmpReg.unregister();
}
} else {
tmp.close();
synchronized (this) {
return emf;
}
}
return tmp;
}
private Dictionary<String, Object> createBuilderProperties(Map<String, Object> config) {
Dictionary<String, Object> props = new Hashtable<String, Object>(); // NOSONAR
for(Entry<String,Object> e : config.entrySet()) {
String key = e.getKey();
// Don't display the password
if(DSFTracker.JDBC_PASSWORD.equals(key)) {
continue;
}
Object value = e.getValue();
if(key != null && value != null) {
props.put(key, e.getValue());
}
}
if (persistenceUnit.getPersistenceProviderClassName() != null) {
props.put(JPA_UNIT_PROVIDER, persistenceUnit.getPersistenceProviderClassName());
}
props.put(JPA_UNIT_VERSION, bundle.getVersion().toString());
props.put(JPA_UNIT_NAME, persistenceUnit.getPersistenceUnitName());
return props;
}
public static Dictionary<String, String> createBuilderProperties(PersistenceUnitInfo persistenceUnit, Bundle puBundle) {
Dictionary<String, String> props = new Hashtable<String, String>(); // NOSONAR
props.put(JPA_UNIT_NAME, persistenceUnit.getPersistenceUnitName());
if (persistenceUnit.getPersistenceProviderClassName() != null) {
props.put(JPA_UNIT_PROVIDER, persistenceUnit.getPersistenceProviderClassName());
}
props.put(JPA_UNIT_VERSION, puBundle.getVersion().toString());
return props;
}
public void foundDSF(DataSourceFactory dsf) {
boolean build = false;
Map<String,Object> props = null;
synchronized (this) {
if(activeConnectionProvider == null) {
activeConnectionProvider = dsf;
build = true;
props = activeProps == null ? new HashMap<String, Object>() :
new HashMap<String, Object>(activeProps);
}
}
if(build) {
Properties punitProps = persistenceUnit.getProperties();
for(String key : punitProps.stringPropertyNames()) {
if(!props.containsKey(key)) {
props.put(key, punitProps.get(key));
}
}
DataSource ds = DSFTracker.createDataSource(dsf, props, persistenceUnit.getName());
dataSourceReady(ds, props);
}
}
public void lostDSF(DataSourceFactory dsf, DataSourceFactory replacement) {
boolean destroy = false;
synchronized (this) {
if(activeConnectionProvider == dsf) {
activeConnectionProvider = null;
destroy = true;
}
}
if(destroy) {
closeEMF();
}
if(replacement != null) {
foundDSF(replacement);
}
}
public void foundDS(DataSource ds) {
boolean build = false;
Map<String,Object> props = null;
synchronized (this) {
if(activeConnectionProvider == null) {
activeConnectionProvider = ds;
build = true;
props = activeProps == null ? new HashMap<String, Object>() :
new HashMap<String, Object>(activeProps);
}
}
if(build) {
dataSourceReady(ds, props);
}
}
public void lostDS(DataSource ds, DataSource replacement) {
boolean destroy = false;
synchronized (this) {
if(activeConnectionProvider == ds) {
activeConnectionProvider = null;
destroy = true;
}
}
if(destroy) {
closeEMF();
}
if(replacement != null) {
foundDS(replacement);
}
}
private void dataSourceReady(DataSource ds, Map<String, Object> props) {
if (persistenceUnit.getTransactionType() == PersistenceUnitTransactionType.JTA) {
props.put(JAVAX_PERSISTENCE_JTA_DATASOURCE, ds);
} else {
props.put(JAVAX_PERSISTENCE_NON_JTA_DATASOURCE, ds);
}
createEntityManagerFactory(props);
}
}