| /* |
| * 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.servicemix.jbi.deployer.impl; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Dictionary; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import javax.jbi.JBIException; |
| import javax.jbi.management.LifeCycleMBean; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.servicemix.jbi.deployer.Component; |
| import org.apache.servicemix.jbi.deployer.ServiceAssembly; |
| import org.apache.servicemix.jbi.deployer.ServiceUnit; |
| import org.apache.servicemix.jbi.deployer.SharedLibrary; |
| import org.apache.servicemix.jbi.deployer.descriptor.ComponentDesc; |
| import org.apache.servicemix.jbi.deployer.descriptor.Descriptor; |
| import org.apache.servicemix.jbi.deployer.descriptor.DescriptorFactory; |
| import org.apache.servicemix.jbi.deployer.descriptor.ServiceAssemblyDesc; |
| import org.apache.servicemix.jbi.deployer.descriptor.ServiceUnitDesc; |
| import org.apache.servicemix.jbi.deployer.descriptor.SharedLibraryDesc; |
| import org.apache.servicemix.jbi.deployer.descriptor.SharedLibraryList; |
| import org.apache.xbean.classloader.MultiParentClassLoader; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.ServiceRegistration; |
| import org.osgi.service.prefs.Preferences; |
| import org.osgi.service.prefs.PreferencesService; |
| import org.springframework.osgi.util.BundleDelegatingClassLoader; |
| import org.springframework.osgi.util.OsgiStringUtils; |
| |
| /** |
| * Deployer for JBI artifacts |
| * |
| */ |
| public class Deployer extends AbstractBundleWatcher { |
| |
| public static final String NAME = "NAME"; |
| public static final String TYPE = "TYPE"; |
| |
| private static final Log LOGGER = LogFactory.getLog(Deployer.class); |
| |
| private static final String JBI_DESCRIPTOR = "META-INF/jbi.xml"; |
| |
| private Map<String, SharedLibraryImpl> sharedLibraries; |
| |
| private Map<String, ServiceAssemblyImpl> serviceAssemblies; |
| |
| private Map<String, ComponentImpl> components; |
| |
| private Map<Bundle, List<ServiceRegistration>> services; |
| |
| private List<Bundle> pendingBundles; |
| |
| private File jbiRootDir; |
| |
| private PreferencesService preferencesService; |
| |
| private boolean autoStart = true; |
| |
| public Deployer() throws JBIException{ |
| sharedLibraries = new ConcurrentHashMap<String, SharedLibraryImpl>(); |
| serviceAssemblies = new ConcurrentHashMap<String, ServiceAssemblyImpl>(); |
| components = new ConcurrentHashMap<String, ComponentImpl>(); |
| services = new ConcurrentHashMap<Bundle, List<ServiceRegistration>>(); |
| pendingBundles = new ArrayList<Bundle>(); |
| // TODO: control that using properties |
| jbiRootDir = new File(System.getProperty("servicemix.base"), "data/jbi"); |
| jbiRootDir.mkdirs(); |
| } |
| |
| public PreferencesService getPreferencesService() { |
| return preferencesService; |
| } |
| |
| public void setPreferencesService(PreferencesService preferencesService) { |
| this.preferencesService = preferencesService; |
| } |
| |
| public boolean isAutoStart() { |
| return autoStart; |
| } |
| |
| public void setAutoStart(boolean autoStart) { |
| this.autoStart = autoStart; |
| } |
| |
| @Override |
| protected boolean match(Bundle bundle) { |
| LOGGER.debug("Checking bundle: '" + OsgiStringUtils.nullSafeNameAndSymName(bundle) + "'"); |
| URL url = bundle.getResource(JBI_DESCRIPTOR); |
| if (url == null) { |
| LOGGER.debug("Bundle '" + OsgiStringUtils.nullSafeNameAndSymName(bundle) + "' does not contain any JBI descriptor."); |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| protected void register(Bundle bundle) { |
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); |
| try { |
| Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); |
| URL url = bundle.getResource(JBI_DESCRIPTOR); |
| Descriptor descriptor = DescriptorFactory.buildDescriptor(url); |
| DescriptorFactory.checkDescriptor(descriptor); |
| if (descriptor.getComponent() != null) { |
| installComponent(descriptor.getComponent(), bundle); |
| } else if (descriptor.getServiceAssembly() != null) { |
| deployServiceAssembly(descriptor.getServiceAssembly(), bundle); |
| } else if (descriptor.getSharedLibrary() != null) { |
| installSharedLibrary(descriptor.getSharedLibrary(), bundle); |
| } else { |
| throw new IllegalStateException("Unrecognized JBI descriptor: " + url); |
| } |
| } catch (PendingException e) { |
| pendingBundles.add(e.getBundle()); |
| LOGGER.warn("Requirements not met for JBI artifact in bundle " + OsgiStringUtils.nullSafeNameAndSymName(bundle) + ". Installation pending."); |
| } catch (Exception e) { |
| LOGGER.error("Error handling bundle start event", e); |
| } finally { |
| Thread.currentThread().setContextClassLoader(cl); |
| } |
| } |
| |
| @Override |
| protected void unregister(Bundle bundle) { |
| pendingBundles.remove(bundle); |
| List<ServiceRegistration> registrations = services.remove(bundle); |
| if (registrations != null) { |
| for (ServiceRegistration reg : registrations) { |
| try { |
| reg.unregister(); |
| } catch (IllegalStateException e) { |
| // Ignore |
| } |
| } |
| } |
| try { |
| URL url = bundle.getResource(JBI_DESCRIPTOR); |
| Descriptor descriptor = DescriptorFactory.buildDescriptor(url); |
| if (descriptor.getComponent() != null) { |
| uninstallComponent(descriptor.getComponent(), bundle); |
| } else if (descriptor.getServiceAssembly() != null) { |
| undeployServiceAssembly(descriptor.getServiceAssembly(), bundle); |
| } else if (descriptor.getSharedLibrary() != null) { |
| uninstallSharedLibrary(descriptor.getSharedLibrary(), bundle); |
| } |
| } catch (Exception e) { |
| LOGGER.error("Error handling bundle stop event", e); |
| } |
| } |
| |
| protected void installComponent(ComponentDesc componentDesc, Bundle bundle) throws Exception { |
| LOGGER.info("Deploying bundle '" + OsgiStringUtils.nullSafeNameAndSymName(bundle) + "' as a JBI component"); |
| // Check requirements |
| if (componentDesc.getSharedLibraries() != null) { |
| for (SharedLibraryList sl : componentDesc.getSharedLibraries()) { |
| if (sharedLibraries.get(sl.getName()) == null) { |
| throw new PendingException(bundle, "SharedLibrary not installed: " + sl.getName()); |
| } |
| } |
| } |
| String name = componentDesc.getIdentification().getName(); |
| // Create component class loader |
| ClassLoader classLoader = createComponentClassLoader(componentDesc, bundle); |
| Thread.currentThread().setContextClassLoader(classLoader); |
| // Extract component (needed to feed the installRoot) |
| // Few components actually use this, but Ode is one of them |
| File installRoot = new File(System.getProperty("servicemix.base"), "data/jbi/" + name + "/install"); |
| installRoot.mkdirs(); |
| extractBundle(installRoot, bundle, "/"); |
| // Instanciate component |
| Preferences prefs = preferencesService.getUserPreferences(name); |
| Class clazz = classLoader.loadClass(componentDesc.getComponentClassName()); |
| javax.jbi.component.Component innerComponent = (javax.jbi.component.Component) clazz.newInstance(); |
| ComponentImpl component = new ComponentImpl(componentDesc, innerComponent, prefs, autoStart, this); |
| components.put(name, component); |
| // populate props from the component meta-data |
| Dictionary<String, String> props = new Hashtable<String, String>(); |
| props.put(NAME, name); |
| props.put(TYPE, componentDesc.getType()); |
| // register the component in the OSGi registry |
| LOGGER.debug("Registering JBI component"); |
| registerService(bundle, Component.class.getName(), component, props); |
| registerService(bundle, javax.jbi.component.Component.class.getName(), component.getComponent(), props); |
| } |
| |
| private void extractBundle(File installRoot, Bundle bundle, String path) throws IOException { |
| Enumeration e = bundle.getEntryPaths(path); |
| while (e != null && e.hasMoreElements()) { |
| String entry = (String) e.nextElement(); |
| File fout = new File(installRoot, entry); |
| if (entry.endsWith("/")) { |
| fout.mkdirs(); |
| extractBundle(installRoot, bundle, entry); |
| } else { |
| InputStream in = bundle.getEntry(entry).openStream(); |
| OutputStream out = new FileOutputStream(fout); |
| try { |
| FileUtil.copyInputStream(in, out); |
| } finally { |
| in.close(); |
| out.close(); |
| } |
| } |
| } |
| } |
| |
| protected void deployServiceAssembly(ServiceAssemblyDesc serviceAssembyDesc, Bundle bundle) throws Exception { |
| LOGGER.info("Deploying bundle '" + OsgiStringUtils.nullSafeNameAndSymName(bundle) + "' as a JBI service assembly"); |
| // Check requirements |
| for (ServiceUnitDesc sud : serviceAssembyDesc.getServiceUnits()) { |
| String componentName = sud.getTarget().getComponentName(); |
| ComponentImpl component = components.get(componentName); |
| if (component == null) { |
| throw new PendingException(bundle, "Component not installed: " + componentName); |
| } |
| if (LifeCycleMBean.UNKNOWN.equals(component.getCurrentState())) { |
| throw new PendingException(bundle, "Component is in an unknown state: " + componentName); |
| } |
| } |
| // Create the SA directory |
| File saDir = new File(jbiRootDir, Long.toString(bundle.getBundleId())); |
| FileUtil.deleteFile(saDir); |
| FileUtil.buildDirectory(saDir); |
| // Iterate each SU and deploy it |
| List<ServiceUnitImpl> sus = new ArrayList<ServiceUnitImpl>(); |
| boolean failure = false; |
| for (ServiceUnitDesc sud : serviceAssembyDesc.getServiceUnits()) { |
| // Create directory for this SU |
| File suRootDir = new File(saDir, sud.getIdentification().getName()); |
| suRootDir.mkdirs(); |
| // Unpack it |
| String zip = sud.getTarget().getArtifactsZip(); |
| URL zipUrl = bundle.getResource(zip); |
| FileUtil.unpackArchive(zipUrl, suRootDir); |
| // Find component |
| String componentName = sud.getTarget().getComponentName(); |
| ComponentImpl component = components.get(componentName); |
| // Create service unit object |
| ServiceUnitImpl su = new ServiceUnitImpl(sud, suRootDir, component); |
| try { |
| LOGGER.debug("Deploying SU " + su.getName()); |
| su.deploy(); |
| // Add it to the list |
| sus.add(su); |
| } catch (Exception e) { |
| LOGGER.error("Error deploying SU " + su.getName(), e); |
| failure = true; |
| break; |
| } |
| } |
| // If failure, undeploy SU and exit |
| if (failure) { |
| for (ServiceUnitImpl su : sus) { |
| try { |
| LOGGER.debug("Undeploying SU " + su.getName()); |
| su.undeploy(); |
| } catch (Exception e) { |
| LOGGER.warn("Error undeploying SU " + su.getName(), e); |
| } |
| } |
| return; |
| } |
| // Now create the SA and initialize it |
| Preferences prefs = preferencesService.getUserPreferences(serviceAssembyDesc.getIdentification().getName()); |
| ServiceAssemblyImpl sa = new ServiceAssemblyImpl(serviceAssembyDesc, sus, prefs, autoStart); |
| sa.init(); |
| serviceAssemblies.put(sa.getName(), sa); |
| // populate props from the component meta-data |
| Dictionary<String, String> props = new Hashtable<String, String>(); |
| props.put(NAME, serviceAssembyDesc.getIdentification().getName()); |
| // register the service assembly in the OSGi registry |
| LOGGER.debug("Registering JBI service assembly"); |
| registerService(bundle, ServiceAssembly.class.getName(), sa, props); |
| } |
| |
| protected void installSharedLibrary(SharedLibraryDesc sharedLibraryDesc, Bundle bundle) { |
| LOGGER.info("Deploying bundle '" + OsgiStringUtils.nullSafeNameAndSymName(bundle) + "' as a JBI shared library"); |
| SharedLibraryImpl sl = new SharedLibraryImpl(sharedLibraryDesc, bundle); |
| sharedLibraries.put(sl.getName(), sl); |
| Dictionary<String, String> props = new Hashtable<String, String>(); |
| // populate props from the library meta-data |
| props.put(NAME, sharedLibraryDesc.getIdentification().getName()); |
| LOGGER.debug("Registering JBI Shared Library"); |
| registerService(bundle, SharedLibrary.class.getName(), sl, props); |
| // Check pending bundles |
| checkPendingBundles(); |
| } |
| |
| protected void uninstallComponent(ComponentDesc componentDesc, Bundle bundle) throws Exception { |
| String name = componentDesc.getIdentification().getName(); |
| ComponentImpl component = components.remove(name); |
| if (component != null) { |
| if (component.getState() == ComponentImpl.State.Started) { |
| component.stop(false); |
| } |
| if (component.getState() == ComponentImpl.State.Stopped) { |
| component.shutDown(false); |
| } |
| File file = new File(System.getProperty("servicemix.base"), "data/jbi/" + name); |
| FileUtil.deleteFile(file); |
| } |
| } |
| protected void undeployServiceAssembly(ServiceAssemblyDesc serviceAssembyDesc, Bundle bundle) throws Exception { |
| String name = serviceAssembyDesc.getIdentification().getName(); |
| ServiceAssemblyImpl sa = serviceAssemblies.remove(name); |
| if (sa != null) { |
| if (sa.getState() == ServiceAssemblyImpl.State.Started) { |
| sa.stop(false); |
| } |
| if (sa.getState() == ServiceAssemblyImpl.State.Stopped) { |
| sa.shutDown(false); |
| } |
| for (ServiceUnit su : sa.getServiceUnits()) { |
| ((ServiceUnitImpl) su).undeploy(); |
| } |
| } |
| } |
| protected void uninstallSharedLibrary(SharedLibraryDesc sharedLibraryDesc, Bundle bundle) throws JBIException { |
| } |
| |
| protected void checkPendingBundles() { |
| if (!pendingBundles.isEmpty()) { |
| final List<Bundle> pending = pendingBundles; |
| pendingBundles = new ArrayList<Bundle>(); |
| // Synchronous call because if using a separate thread |
| // we run into deadlocks |
| for (Bundle bundle : pending) { |
| register(bundle); |
| } |
| } |
| } |
| |
| protected void registerService(Bundle bundle, String clazz, Object service, Dictionary props) { |
| BundleContext context = bundle.getBundleContext() != null ? bundle.getBundleContext() : getBundleContext(); |
| ServiceRegistration reg = context.registerService(clazz, service, props); |
| List<ServiceRegistration> registrations = services.get(bundle); |
| if (registrations == null) { |
| registrations = new ArrayList<ServiceRegistration>(); |
| services.put(bundle, registrations); |
| } |
| registrations.add(reg); |
| } |
| |
| protected ClassLoader createComponentClassLoader(ComponentDesc component, Bundle bundle) { |
| // Create parents classloaders |
| ClassLoader[] parents; |
| if (component.getSharedLibraries() != null) { |
| parents = new ClassLoader[component.getSharedLibraries().length + 1]; |
| for (int i = 0; i < component.getSharedLibraries().length; i++) { |
| parents[i + 1] = getSharedLibraryClassLoader(component.getSharedLibraries()[i]); |
| } |
| } else { |
| parents = new ClassLoader[1]; |
| } |
| parents[0] = BundleDelegatingClassLoader.createBundleClassLoaderFor(bundle, getClass().getClassLoader()); |
| |
| // Create urls |
| String[] classPathNames = component.getComponentClassPath().getPathElements(); |
| URL[] urls = new URL[classPathNames.length]; |
| for (int i = 0; i < classPathNames.length; i++) { |
| urls[i] = bundle.getResource(classPathNames[i]); |
| if (urls[i] == null) { |
| throw new IllegalArgumentException("SharedLibrary classpath entry not found: '" + classPathNames[i] + "'"); |
| } |
| } |
| |
| // Create classloader |
| return new MultiParentClassLoader( |
| component.getIdentification().getName(), |
| urls, |
| parents, |
| component.isComponentClassLoaderDelegationSelfFirst(), |
| new String[0], |
| new String[] {"java.", "javax." }); |
| } |
| |
| protected ClassLoader getSharedLibraryClassLoader(SharedLibraryList sharedLibraryList) { |
| SharedLibraryImpl sl = sharedLibraries.get(sharedLibraryList.getName()); |
| if (sl != null) { |
| return sl.getClassLoader(); |
| } else { |
| throw new IllegalStateException("SharedLibrary not installed: " + sharedLibraryList.getName()); |
| } |
| } |
| |
| } |