/** * 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.aries.ejb.openejb.extender; import java.io.IOException; import java.lang.reflect.Field; import java.net.URL; import java.util.Collections; import java.util.Dictionary; import java.util.Enumeration; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.naming.NamingException; import org.apache.aries.util.AriesFrameworkUtil; import org.apache.aries.util.tracker.RecursiveBundleTracker; import org.apache.openejb.OpenEJBException; import org.apache.openejb.assembler.classic.Assembler; import org.apache.openejb.assembler.classic.EjbJarInfo; import org.apache.openejb.assembler.classic.EnterpriseBeanInfo; import org.apache.openejb.assembler.classic.PersistenceContextReferenceInfo; import org.apache.openejb.assembler.classic.PersistenceUnitReferenceInfo; import org.apache.openejb.assembler.classic.ProxyFactoryInfo; import org.apache.openejb.assembler.classic.ReferenceLocationInfo; import org.apache.openejb.assembler.classic.SecurityServiceInfo; import org.apache.openejb.assembler.classic.TransactionServiceInfo; import org.apache.openejb.assembler.dynamic.PassthroughFactory; import org.apache.openejb.config.ConfigurationFactory; import org.apache.openejb.config.EjbModule; import org.apache.openejb.config.ValidationContext; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.persistence.JtaEntityManagerRegistry; import org.apache.openejb.ri.sp.PseudoSecurityService; import org.apache.openejb.util.OpenEjbVersion; import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.util.tracker.BundleTrackerCustomizer; public class EJBExtender implements BundleActivator, BundleTrackerCustomizer { private static final int STARTABLE = Bundle.STARTING | Bundle.ACTIVE; private static final Object PROCESSING_OBJECT = new Object(); private static final Object REMOVING_OBJECT = new Object(); private RecursiveBundleTracker tracker; private final ConcurrentMap<Bundle, RunningApplication> runningApps = new ConcurrentHashMap<Bundle, RunningApplication>(); private final ConcurrentMap<Bundle, Object> processingMap = new ConcurrentHashMap<Bundle, Object>(); public void start(BundleContext context) throws Exception { //Internal setup OSGiTransactionManager.init(context); AriesProxyService.init(context); try { AriesPersistenceContextIntegration.init(context); } catch (NoClassDefFoundError ncdfe) { //TODO log that no JPA Context integration is available } //Setup OpenEJB with our own extensions setupOpenEJB(); tracker = new RecursiveBundleTracker(context, Bundle.INSTALLED | Bundle.RESOLVED | Bundle.STARTING | Bundle.ACTIVE | Bundle.STOPPING, this); tracker.open(); } private void setupOpenEJB() throws OpenEJBException { //Avoid a ClassLoader problem ClassLoader cl = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(OpenEjbVersion.class.getClassLoader()); OpenEjbVersion.get(); } finally { Thread.currentThread().setContextClassLoader(cl); } Assembler a = new Assembler(); TransactionServiceInfo tsi = new TransactionServiceInfo(); tsi.service = "TransactionManager"; tsi.id = "OSGi Transaction Manager"; PassthroughFactory.add(tsi, OSGiTransactionManager.get()); //Avoid another ClassLoader problem try { Thread.currentThread().setContextClassLoader(PassthroughFactory.class.getClassLoader()); a.createTransactionManager(tsi); } finally { Thread.currentThread().setContextClassLoader(cl); } try { //Overwrite existing, default JPA integration with an Aries JPA integrated one Assembler.getContext().put(JtaEntityManagerRegistry.class.getName(), AriesPersistenceContextIntegration.get()); SystemInstance.get().setComponent(JtaEntityManagerRegistry.class, AriesPersistenceContextIntegration.get()); } catch (NoClassDefFoundError ncdfe) { //TODO log that no JPA Context integration is available } SecurityServiceInfo ssi = new SecurityServiceInfo(); ssi.service = "SecurityService"; ssi.id = "Pseudo Security Service"; PassthroughFactory.add(ssi, new PseudoSecurityService()); //Avoid another ClassLoader problem try { Thread.currentThread().setContextClassLoader(PassthroughFactory.class.getClassLoader()); a.createSecurityService(ssi); } finally { Thread.currentThread().setContextClassLoader(cl); } ProxyFactoryInfo proxyFactoryInfo = new ProxyFactoryInfo(); proxyFactoryInfo.id = "Aries ProxyFactory"; proxyFactoryInfo.service = "ProxyFactory"; proxyFactoryInfo.properties = new Properties(); PassthroughFactory.add(proxyFactoryInfo, AriesProxyService.get()); try { Thread.currentThread().setContextClassLoader(PassthroughFactory.class.getClassLoader()); a.createProxyFactory(proxyFactoryInfo); } finally { Thread.currentThread().setContextClassLoader(cl); } } public void stop(BundleContext context) throws Exception { tracker.close(); AriesProxyService.get().destroy(); OSGiTransactionManager.get().destroy(); try { AriesPersistenceContextIntegration.get().destroy(); } catch (NoClassDefFoundError ncdfe) { //TODO log that no JPA Context integration is available } } public Object addingBundle(Bundle bundle, BundleEvent event) { if(mightContainEJBs(bundle)) { if((bundle.getState() & STARTABLE) != 0) { startEJBs(bundle); } return bundle; } return null; } private boolean mightContainEJBs(Bundle bundle) { Dictionary<String, String> headers = bundle.getHeaders(); return (headers.get("Export-EJB") != null) || (headers.get("Web-ContextPath") != null); } public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) { if((bundle.getState() & STARTABLE) != 0) { startEJBs(bundle); } else if (bundle.getState() == Bundle.STOPPING) { stopEJBs(bundle); } } private void startEJBs(final Bundle bundle) { try { //If there is another thread adding or removing then stop here Object o = processingMap.put(bundle, PROCESSING_OBJECT); if(o == REMOVING_OBJECT || o == PROCESSING_OBJECT) { return; } //If already running then avoid if(runningApps.get(bundle) != null) return; //Broken validation for persistence :( EjbModule ejbModule = new EjbModule(AriesFrameworkUtil.getClassLoaderForced(bundle), null, null, null); try { Field f = EjbModule.class.getDeclaredField("validation"); f.setAccessible(true); f.set(ejbModule, new ValidationProofValidationContext(ejbModule)); } catch (Exception e) { // Hmmm } addAltDDs(ejbModule, bundle); //We build our own because we can't trust anyone to get the classpath right otherwise! ejbModule.setFinder(new OSGiFinder(bundle)); ConfigurationFactory configurationFactory = new ConfigurationFactory(); EjbJarInfo ejbInfo = null; //Avoid yet another ClassLoading problem ClassLoader cl = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(new ClassLoader(OpenEjbVersion.class.getClassLoader()) { protected Class<?> findClass(String name) throws ClassNotFoundException { for(Bundle b : bundle.getBundleContext().getBundles()) { if(b.getSymbolicName().contains("jaxb-impl")) return b.loadClass(name); } throw new ClassNotFoundException(name); } }); ejbInfo = configurationFactory.configureApplication(ejbModule); //Another oddity here ejbInfo.validationInfo = null; } finally { Thread.currentThread().setContextClassLoader(cl); } processJPAMappings(ejbInfo); Assembler assembler = (Assembler) SystemInstance.get().getComponent(Assembler.class); RunningApplication app = null; try { SystemInstance.get().setProperty("openejb.geronimo", "true"); cl = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(OpenEjbVersion.class.getClassLoader()); app = new RunningApplication(assembler.createApplication(ejbInfo, new AppClassLoader(ejbModule.getClassLoader())), bundle, ejbInfo.enterpriseBeans); } finally { Thread.currentThread().setContextClassLoader(cl); } } finally { SystemInstance.get().getProperties().remove("openejb.geronimo"); } runningApps.put(bundle, app); app.init(); } catch (OpenEJBException oee) { // TODO Auto-generated catch block oee.printStackTrace(); } catch (NamingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if(processingMap.remove(bundle) == REMOVING_OBJECT) { stopEJBs(bundle); } } } private void processJPAMappings(EjbJarInfo ejbInfo) { for(EnterpriseBeanInfo ebi : ejbInfo.enterpriseBeans){ for(PersistenceUnitReferenceInfo pui : ebi.jndiEnc.persistenceUnitRefs) { pui.location = new ReferenceLocationInfo(); pui.location.jndiName = "aries/integration/unit/" + pui.persistenceUnitName; } for(PersistenceContextReferenceInfo pci : ebi.jndiEnc.persistenceContextRefs) { pci.location = new ReferenceLocationInfo(); pci.location.jndiName = "aries/integration/context/" + pci.persistenceUnitName; } } } private void addAltDDs(EjbModule ejbModule, Bundle bundle) { Map<String, Object> altDDs = ejbModule.getAltDDs(); String folder = (bundle.getHeaders().get("Web-ContextPath") == null) ? "META-INF" : "WEB-INF"; Enumeration<URL> e = bundle.findEntries(folder, "*.xml", false); if(e == null) return; for(URL u : Collections.list(e)) { String urlString = u.toExternalForm(); urlString = urlString.substring(urlString.lastIndexOf('/') + 1); altDDs.put(urlString, u); } //Persistence descriptors are handled by Aries JPA, but OpenEJB fails validation //if we hide them. As a result we switch it off. //altDDs.remove("persistence.xml"); } private void stopEJBs(Bundle bundle) { if(processingMap.put(bundle, REMOVING_OBJECT) == PROCESSING_OBJECT) return; else { try { RunningApplication app = runningApps.remove(bundle); if(app != null) { app.destroy(); Assembler assembler = (Assembler) SystemInstance.get().getComponent(Assembler.class); assembler.destroyApplication(app.getCtx()); } } catch (OpenEJBException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if(processingMap.remove(bundle) == PROCESSING_OBJECT) startEJBs(bundle); } } } public void removedBundle(Bundle bundle, BundleEvent event, Object object) { if (bundle.getState() == Bundle.STOPPING) { stopEJBs(bundle); } } private static final class ValidationProofValidationContext extends ValidationContext { private ValidationProofValidationContext(EjbModule mod) { super(mod); } @Override public boolean hasErrors() { return false; } @Override public boolean hasFailures() { return false; } @Override public boolean hasWarnings() { return false; } } private static final class AppClassLoader extends ClassLoader { private AppClassLoader(ClassLoader parentLoader) { super(parentLoader); } @Override protected Class<?> findClass(String className) throws ClassNotFoundException { return Class.forName(className, false, OpenEjbVersion.class.getClassLoader()); } } }