/*
* Copyright (c) OSGi Alliance (2013). All Rights Reserved.
*
* Licensed 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 osgi.jpa.managed.support;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URL;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;
import javax.persistence.spi.PersistenceProvider;
import javax.sql.XADataSource;
import javax.transaction.TransactionManager;
import javax.xml.bind.JAXB;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleReference;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.hooks.weaving.WeavingHook;
import org.osgi.util.tracker.BundleTracker;
import osgi.jpa.managed.api.JPABridgePersistenceProvider;
import v2_0.Persistence;
import v2_0.Persistence.PersistenceUnit;
import v2_0.Persistence.PersistenceUnit.Properties.Property;
import aQute.bnd.annotation.component.Activate;
import aQute.bnd.annotation.component.Component;
import aQute.bnd.annotation.component.Deactivate;
import aQute.bnd.annotation.component.Reference;
import aQute.lib.converter.Converter;
import aQute.libg.clauses.Clauses;
import aQute.service.logger.Log;
/**
* This component bridges JPA, TransactionManager, and DataSourceFactory
* services into an Entity Manager for each matching Persistence Unit. An Entity
* Manager is registered that will automatically create and enlist an Entity
* Manager per transaction. Connections for the JTA Data Source in JPA are
* automatically enlisted in the current transaction.
*/
@Component(designateFactory = JPAManager.Config.class, immediate = true)
public class JPAManager {
static Pattern WORD = Pattern.compile("[a-zA-Z0-9]+");
static final String META_PERSISTENCE = "Meta-Persistence";
BundleContext context;
TransactionManager transactionManager;
XADataSource xaDataSource;
PersistenceProvider persistenceProvider;
BundleTracker<PersistentBundle> bundles;
JPABridgeLogMessages log;
Map<String, Object> bridgeProperties = new HashMap<String, Object>();
TransformersHook transformersHook;
ServiceRegistration<WeavingHook> transformersHookRegistration;
interface Config {
String name();
}
Config config;
@Activate
void activate(BundleContext context, Map<String, Object> props) throws Exception {
this.context = context;
config = Converter.cnv(Config.class, props);
//
// The Transformers hook enables weaving in OSGi
// Every persistence unit will register itself with
// the transformers hook. Order is important, once
// the bundle tracker is called, the transformers
// will be registered so do not move this method lower.
//
transformersHook = new TransformersHook(getImports(persistenceProvider));
context.registerService(WeavingHook.class, transformersHook, null);
//
// Track bundles with persistence units.
//
bundles = new BundleTracker<PersistentBundle>(context, Bundle.ACTIVE + Bundle.STARTING, null) {
@Override
public PersistentBundle addingBundle(Bundle bundle, BundleEvent event) {
try {
//
// Parse any persistence units, returns null (not tracked)
// when there is no PU
//
return parse(bundle);
}
catch (Exception e) {
e.printStackTrace();
log.bundleParseException(bundle, e);
return null;
}
}
@Override
public void removedBundle(Bundle bundle, BundleEvent event, PersistentBundle put) {
put.close();
}
};
bundles.open();
}
@Deactivate
void deactivate() {
bundles.close();
}
/**
* Calculate the imports of the persistence provider. Since this guy is
* running, it must have satisfied all its imports. So we use those exact
* imports and add them to our transformed classes, this will ensure that
* any classes that the Persistence Provider needs from that class will be
* satisfied.
*
* @param pp Persistence Provider for this unit
* @return A list of import clauses from the provider
*/
private List<String> getImports(PersistenceProvider pp) throws IOException {
//
// Check if this pp is a bridge that is aware of
// what we're doing
//
if (pp instanceof JPABridgePersistenceProvider) {
List<String> wovenImports = ((JPABridgePersistenceProvider) pp).getWovenImports();
if (wovenImports != null)
return wovenImports;
}
//
// Get the pp's class's bundle's context
//
Bundle b;
if (pp instanceof BundleReference)
b = ((BundleReference) pp).getBundle();
else
b = FrameworkUtil.getBundle(pp.getClass());
if (b != null) {
//
// Get the import clauses
//
Clauses clauses = Clauses.parse(b.getHeaders().get("Export-Package"), null);
if (!clauses.isEmpty()) {
List<String> list = new ArrayList<String>();
for (Entry<String, Map<String, String>> e : clauses.entrySet()) {
//
// Create a new clause
//
StringBuilder sb = new StringBuilder();
sb.append(e.getKey());
for (Entry<String, String> ee : e.getValue().entrySet()) {
if (ee.getKey().endsWith(":"))
continue;
sb.append(";").append(ee.getKey()).append("=");
String v = ee.getValue();
if (WORD.matcher(v).matches())
sb.append(ee.getValue());
else
sb.append("\"").append(ee.getValue()).append("\"");
}
list.add(sb.toString());
}
return list;
}
}
return Collections.emptyList();
}
/**
* Check a bundle for persistence units following the rules in the OSGi
* spec.
* <p>
* A Persistence Bundle is a bundle that specifies the Meta-Persistence
* header, see Meta Persistence Header on page 439. This header refers to
* one or more Persistence Descriptors in the Persistence Bundle. Commonly,
* this is the META-INF/persistence.xml resource. This location is the
* standard for non- OSGi environments, however an OSGi bundle can also use
* other locations as well as multiple resources. Any entity classes must
* originate in the bundle's JAR, it cannot come from a fragment. This
* requirement is necessary to simplify enhancing entity classes.
*
* @param bundle the bundle to be searched
* @return a Persistent Bundle or null if it has no matching persistence
* units
*/
PersistentBundle parse(Bundle bundle) throws Exception {
String metapersistence = bundle.getHeaders().get(META_PERSISTENCE);
if (metapersistence == null || metapersistence.trim().isEmpty())
return null;
//
// We can have multiple persistence units.
//
Set<Persistence.PersistenceUnit> set = new HashSet<Persistence.PersistenceUnit>();
for (String location : metapersistence.split("\\s*,\\s*")) {
Property p = new Property();
//
// Lets remember where we came from
//
p.setName("location");
p.setValue(location);
//
// Try to find the resource for the persistence unit
// on the classpath: getResource
//
URL url = bundle.getResource(location);
if (url == null) {
log.locationWithoutPersistenceUnit(bundle, location);
} else {
Persistence persistence = JAXB.unmarshal(url, Persistence.class);
for (Persistence.PersistenceUnit pu : persistence.getPersistenceUnit()) {
if (config.name() == null || config.name().trim().isEmpty() || config.name().equals("*")
|| config.name().equals(pu.getName())) {
if (pu.getProperties() == null)
pu.setProperties(new Persistence.PersistenceUnit.Properties());
pu.getProperties().getProperty().add(p);
String reason = isValid(pu);
if (reason == null) {
set.add(pu);
} else {
StringWriter sb = new StringWriter();
JAXB.marshal(pu, sb);
log.invalidPersistenceUnit(bundle, location, reason, sb.toString());
}
}
}
}
}
// Ignore this bundle if no valid PUs
if (set.isEmpty())
return null;
return new PersistentBundle(this, bundle, set);
}
/**
* Validate against the name, provider, etc. TODO validate the PU
*
* @param pu The persistence unit to check
* @return A failure reason when it is not good or null if it is ok
*/
private String isValid(PersistenceUnit pu) {
return null;
}
@Reference
void setDataSource(XADataSource dsf) throws SQLException {
this.xaDataSource = dsf;
}
@Reference
void setLog(Log log) {
this.log = log.logger(JPABridgeLogMessages.class);
}
@Reference
void setPersistenceProvider(PersistenceProvider pp) {
this.persistenceProvider = pp;
}
@Reference
void setTransactionManager(TransactionManager tm) {
this.transactionManager = tm;
}
}