/* * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors. * * 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 org.switchyard.deploy.osgi.internal; import java.io.InputStream; import java.lang.reflect.Method; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Dictionary; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.xml.XMLConstants; import javax.xml.validation.Schema; import org.osgi.framework.Bundle; import org.osgi.framework.Constants; import org.osgi.framework.Filter; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.framework.wiring.BundleWire; import org.osgi.framework.wiring.BundleWiring; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.switchyard.ServiceDomain; import org.switchyard.common.io.pull.ElementPuller; import org.switchyard.common.property.CompoundPropertyResolver; import org.switchyard.common.property.PropertyResolver; import org.switchyard.config.Configuration; import org.switchyard.config.model.Descriptor; import org.switchyard.config.model.Marshaller; import org.switchyard.config.model.ModelPuller; import org.switchyard.config.model.composite.BindingModel; import org.switchyard.config.model.composite.ComponentImplementationModel; import org.switchyard.config.model.composite.ComponentModel; import org.switchyard.config.model.composite.CompositeReferenceModel; import org.switchyard.config.model.composite.CompositeServiceModel; import org.switchyard.config.model.switchyard.SwitchYardModel; import org.switchyard.config.model.transform.TransformsModel; import org.switchyard.deploy.Activator; import org.switchyard.deploy.Component; import org.switchyard.deploy.internal.Deployment; import org.switchyard.deploy.osgi.ComponentRegistry; import org.switchyard.deploy.osgi.NamespaceHandler; import org.switchyard.deploy.osgi.NamespaceHandlerSet; import org.switchyard.deploy.osgi.SwitchYardContainer; import org.switchyard.deploy.osgi.SwitchYardEvent; import org.switchyard.deploy.osgi.base.SimpleExtension; import org.switchyard.transform.internal.DuplicateTransformerException; import org.switchyard.transform.internal.TransformerRegistryLoader; import org.switchyard.transform.osgi.internal.TransformSource; import org.w3c.dom.Attr; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * SwitchYardContainerImpl. */ public class SwitchYardContainerImpl extends SimpleExtension implements NamespaceHandlerSet.Listener, ComponentRegistry.Listener, Runnable, SwitchYardContainer { public static final String SWITCHYARD_DEPLOYMENT_BUNDLE = "switchyard.deployment.bundle"; public static final String CONTAINER_SYMBOLIC_NAME_PROPERTY = "switchyard.container.symbolicname"; public static final String CONTAINER_VERSION_PROPERTY = "switchyard.container.version"; private static final Logger logger = LoggerFactory.getLogger(SwitchYardExtender.class); public enum State { Unknown, WaitForCdi, WaitForNamespaceHandlers, WaitForComponents, Created, Failed, } private final SwitchYardExtender _extender; private final Bundle _extenderBundle; private State _state = State.Unknown; private Element _xml; private SwitchYardModel _model; private Set<URI> _namespaces; private NamespaceHandlerSet _nhs; private Set<String> _types; private ServiceDomain _domain; private Deployment _deployment; private final AtomicBoolean _scheduled = new AtomicBoolean(); private final AtomicBoolean _destroyed = new AtomicBoolean(false); private final ExecutorService _executors; private ServiceRegistration<SwitchYardContainer> _registration; private ServiceTracker _cdiContainerTracker; private Object _cdiContainer; public SwitchYardContainerImpl(SwitchYardExtender extender, Bundle bundle, ExecutorService executor) { super(bundle); _extender = extender; _extenderBundle = extender.getBundleContext().getBundle(); _executors = executor != null ? new ExecutorServiceWrapper(executor) : null; } @Override protected Object getLock() { return _scheduled; } public void schedule() { if (_scheduled.compareAndSet(false, true) && !_destroyed.get()) { _executors.submit(this); } } public void run() { _scheduled.set(false); synchronized (_scheduled) { doStart(); } } @Override protected void doStart() { ClassLoader tccl = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(_bundle.adapt(BundleWiring.class).getClassLoader()); for (;;) { if (_destroyed.get()) { return; } if (_bundle.getState() != Bundle.ACTIVE && _bundle.getState() != Bundle.STARTING) { return; } if (_bundle.getBundleContext() != _bundleContext) { return; } logger.debug("Running switchyard container for bundle {} in state {}", _bundle.getSymbolicName(), _state); switch (_state) { case Unknown: { dispatch(SwitchYardEvent.CREATING); boolean needsCdi = false; List<BundleWire> wires = _bundle.adapt(BundleWiring.class).getRequiredWires("osgi.extender"); for (BundleWire wire : wires) { String filterStr = wire.getRequirement().getDirectives().get("filter"); Filter filter = FrameworkUtil.createFilter(filterStr); Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put("osgi.extender", "pax.cdi"); needsCdi = filter.match(props); } if (needsCdi) { String filter = "(&(objectClass=org.ops4j.pax.cdi.spi.CdiContainer)(bundleId=" + _bundle.getBundleId() + "))"; _cdiContainerTracker = new ServiceTracker(_bundleContext, FrameworkUtil.createFilter(filter), new ServiceTrackerCustomizer() { @Override public Object addingService(ServiceReference reference) { synchronized (_scheduled) { _cdiContainer = _bundleContext.getService(reference); } schedule(); return _cdiContainer; } @Override public void modifiedService(ServiceReference reference, Object service) { } @Override public void removedService(ServiceReference reference, Object service) { _bundleContext.ungetService(reference); enterGracePeriod(); } }); _cdiContainerTracker.open(); } _state = State.WaitForCdi; break; } case WaitForCdi: { if (_cdiContainerTracker != null) { if (_cdiContainer == null) { String filter = "(&(objectClass=org.ops4j.pax.cdi.spi.CdiContainer)(bundleId=" + _bundle.getBundleId() + "))"; dispatch(SwitchYardEvent.GRACE_PERIOD, Collections.singleton(filter)); return; } } if (_nhs == null) { URL configUrl = getBundle().getEntry(SwitchYardExtender.SWITCHYARD_XML); if (configUrl == null) { configUrl = getBundle().getEntry(SwitchYardExtender.WEBINF_SWITCHYARD_XML); } InputStream configStream = configUrl.openStream(); try { _xml = new ElementPuller().pull(configStream); } finally { configStream.close(); } _namespaces = findNamespaces(new HashSet<URI>(), _xml); _nhs = _extender.getNamespaceHandlerRegistry().getNamespaceHandlers(_namespaces, getBundle()); _nhs.addListener(this); } _state = State.WaitForNamespaceHandlers; break; } case WaitForNamespaceHandlers: { List<String> missing = new ArrayList<String>(); List<URI> missingURIs = new ArrayList<URI>(); for (URI ns : _namespaces) { if (_nhs.getNamespaceHandler(ns) == null) { missing.add("(&(" + Constants.OBJECTCLASS + "=" + NamespaceHandler.class.getName() + ")(" + NamespaceHandler.NAMESPACES + "=" + ns + "))"); missingURIs.add(ns); dispatch(SwitchYardEvent.GRACE_PERIOD, missing); } } if (missing.size() > 0) { logger.info("Bundle {} is waiting for namespace handlers {}", getBundle().getSymbolicName(), missingURIs); return; } _model = new ModelPuller<SwitchYardModel>(new OsgiDescriptor(_nhs)).pull(_xml); OsgiPropertyResolver.set(_model, _bundle); _types = new HashSet<String>(); if (_model.getComposite() != null) { for (CompositeReferenceModel reference : _model.getComposite().getReferences()) { for (BindingModel binding : reference.getBindings()) { _types.add(binding.getType()); } } for (ComponentModel component : _model.getComposite().getComponents()) { ComponentImplementationModel impl = component.getImplementation(); if (impl == null) { throw new IllegalStateException("Component implementation should not be null"); } _types.add(impl.getType()); } for (CompositeServiceModel service : _model.getComposite().getServices()) { for (BindingModel binding : service.getBindings()) { _types.add(binding.getType()); } } } else { logger.info("A composite element is missing from the switchyard.xml"); } _extender.getComponentRegistry().addListener(this); _state = State.WaitForComponents; break; } case WaitForComponents: { List<Component> components = new ArrayList<Component>(); List<String> missingTypes = new ArrayList<String>(); for (String type : _types) { Component component = _extender.getComponentRegistry().getComponent(type); if (component == null) { missingTypes.add(type); } else { components.add(component); } } if (!missingTypes.isEmpty()) { logger.info("Bundle {} is waiting for components {}", getBundle().getSymbolicName(), missingTypes); dispatch(SwitchYardEvent.GRACE_PERIOD, missingTypes); return; } ClassLoader oldTccl = Thread.currentThread().getContextClassLoader(); ClassLoader newTccl = oldTccl; if (_cdiContainer != null) { try { Method method = _cdiContainer.getClass().getMethod("getContextClassLoader"); newTccl = (ClassLoader) method.invoke(_cdiContainer); } catch (Throwable t) { // Ignore } } try { Thread.currentThread().setContextClassLoader(newTccl); _domain = _extender.getDomainManager().createDomain(getBundleContext(), _model.getQName(), _model); _domain.setProperty(SWITCHYARD_DEPLOYMENT_BUNDLE, getBundle()); registerOOTBTransformers(); List<Activator> activators = new ArrayList<Activator>(); for (Component component : components) { activators.add(component.createActivator(_domain)); } _deployment = new Deployment(_model); _deployment.init(_domain, activators); _deployment.start(); } finally { Thread.currentThread().setContextClassLoader(oldTccl); } // Register the BlueprintContainer in the OSGi registry int bs = _bundle.getState(); if (_registration == null && (bs == Bundle.ACTIVE || bs == Bundle.STARTING)) { Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(CONTAINER_SYMBOLIC_NAME_PROPERTY, _bundle.getSymbolicName()); props.put(CONTAINER_VERSION_PROPERTY, _bundle.getVersion()); _registration = _bundleContext.registerService(SwitchYardContainer.class, this, props); } dispatch(SwitchYardEvent.CREATED); _state = State.Created; break; } case Created: case Failed: return; } } } catch (Throwable t) { try { _state = State.Failed; logger.error("Unable to start switchyard for bundle " + getBundle().getSymbolicName(), t); dispatch(SwitchYardEvent.FAILURE, t); destroyDeployment(); } catch (RuntimeException re) { logger.debug("Tidying up components failed. ", re); throw re; } } finally { Thread.currentThread().setContextClassLoader(tccl); } } @Override protected void doDestroy() throws Exception { dispatch(SwitchYardEvent.DESTROYING); _executors.shutdownNow(); try { if (_registration != null) { _registration.unregister(); _registration = null; } } catch (Throwable t) { logger.debug("Error unregistering Switchyard container", t); } if (_cdiContainerTracker != null) { _cdiContainerTracker.close(); _cdiContainerTracker = null; } if (_nhs != null) { _nhs.removeListener(this); _nhs.destroy(); _nhs = null; } try { _executors.awaitTermination(5 * 60, TimeUnit.SECONDS); } catch (InterruptedException e) { logger.debug("Interrupted waiting for executor to shut down"); } destroyDeployment(); dispatch(SwitchYardEvent.DESTROYED); logger.debug("Switchyard container destroyed: {}", _bundleContext); } private void dispatch(int type) { dispatch(new SwitchYardEvent(type, getBundle(), _extenderBundle)); } private void dispatch(int type, Collection<String> deps) { String[] depsArray = deps.toArray(new String[deps.size()]); dispatch(new SwitchYardEvent(type, getBundle(), _extenderBundle, depsArray)); } private void dispatch(int type, Throwable t) { dispatch(new SwitchYardEvent(type, getBundle(), _extenderBundle, t)); } private void dispatch(SwitchYardEvent event) { _extender.getEventDispatcher().switchyardEvent(event); } @Override public void namespaceHandlerRegistered(URI uri) { if (_nhs.isComplete()) { schedule(); } } @Override public void namespaceHandlerUnregistered(URI uri) { if (!_nhs.isComplete()) { enterGracePeriod(); } } @Override public void componentRegistered(String type) { if (_types != null && _types.contains(type)) { schedule(); } } @Override public void componentUnregistered(String type) { if (_types != null && _types.contains(type)) { enterGracePeriod(); } } private void destroyDeployment() { _extender.getComponentRegistry().removeListener(this); if (_deployment != null) { _deployment.stop(); _deployment.destroy(); _deployment = null; } if (_domain != null) { _domain.destroy(); _domain = null; } } private void enterGracePeriod() { synchronized (_scheduled) { if (_destroyed.get()) { return; } try { destroyDeployment(); } catch (Exception e) { logger.error("Error while stopping switchyard", e); } _cdiContainer = null; _state = State.WaitForCdi; schedule(); } } private Set<URI> findNamespaces(Set<URI> namespaces, Node node) { if (node instanceof Element || node instanceof Attr) { String ns = node.getNamespaceURI(); if (ns != null && !isIgnorableAttributeNamespace(ns)) { namespaces.add(URI.create(ns)); } } NamedNodeMap nnm = node.getAttributes(); if (nnm != null) { for (int i = 0; i< nnm.getLength(); i++) { findNamespaces(namespaces, nnm.item(i)); } } NodeList nl = node.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { findNamespaces(namespaces, nl.item(i)); } return namespaces; } private boolean isIgnorableAttributeNamespace(String ns) { return XMLConstants.RELAXNG_NS_URI.equals(ns) || XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI.equals(ns) || XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(ns) || XMLConstants.W3C_XPATH_DATATYPE_NS_URI.equals(ns) || XMLConstants.W3C_XPATH_DATATYPE_NS_URI.equals(ns) || XMLConstants.XML_DTD_NS_URI.equals(ns) || XMLConstants.XML_NS_URI.equals(ns) || XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(ns); } /** * OSGi container code needs to register these outside of * TransformerRegistryLoader.registerOOTBTransformers() due to the fact that * descriptors cannot be parsed directly from the class path. */ private void registerOOTBTransformers() throws Exception { Collection<ServiceReference<TransformSource>> refs = _bundleContext.getServiceReferences(TransformSource.class, null); // Find all SY bundles which contain transformer definitions for (final ServiceReference<TransformSource> ref : refs) { // Create a customized transformer loader which loads classes via the bundle TransformerRegistryLoader loader = new TransformerRegistryLoader(_domain.getTransformerRegistry()) { @Override protected Class<?> getClass(String className) { Class<?> clazz = null; try { clazz = ref.getBundle().loadClass(className); } catch (ClassNotFoundException ex) { logger.warn("Failed to load transformer class " + className + " from bundle " + ref.getBundle().getSymbolicName()); } return clazz; } }; TransformSource trs = _bundleContext.getService(ref); InputStream tStream = null; // parse and register the transformer definitions try { tStream = trs.getTransformsURL().openStream(); Element tConfig = new ElementPuller().pull(tStream); Set<URI> tNamespaces = findNamespaces(new HashSet<URI>(), tConfig); NamespaceHandlerSet tHandlers = _extender.getNamespaceHandlerRegistry() .getNamespaceHandlers(tNamespaces, getBundle()); TransformsModel tm = new ModelPuller<TransformsModel>( new OsgiDescriptor(tHandlers)).pull(tConfig); loader.registerTransformers(tm, false); } catch (final DuplicateTransformerException e) { // duplicate OOTB transformers are not an error - log for visibility logger.debug(e.getMessage()); } catch (Exception ex) { logger.warn("Failed to load transformers from bundle: " + ref.getBundle().getSymbolicName(), ex); } finally { if (tStream != null) { tStream.close(); } _bundleContext.ungetService(ref); } } } /** * OSGi wrapper for Descriptor which loads marshallers and schema based on * registered namespace handlers. */ private class OsgiDescriptor extends Descriptor { private NamespaceHandlerSet _namespaceHandlers; /** * Create a new OSGi wrapper Descriptor. * @param namespaceHandlers namespace handlers to use in descriptor */ OsgiDescriptor(NamespaceHandlerSet namespaceHandlers) { super(); _namespaceHandlers = namespaceHandlers; } @Override public synchronized Schema getSchema(Set<String> namespaces) { try { return _namespaceHandlers.getSchema(); } catch (Exception e) { throw new RuntimeException(e); } } @Override public synchronized Marshaller getMarshaller(String namespace) { return _namespaceHandlers.getNamespaceHandler(URI.create(namespace)).createMarshaller(namespace, this); } @Override public boolean equals(Object obj) { return super.equals(obj); } @Override public int hashCode() { return super.hashCode(); } }; private static final class OsgiPropertyResolver implements PropertyResolver { private org.osgi.service.cm.Configuration _wrapped; private OsgiPropertyResolver(org.osgi.service.cm.Configuration wrapped) { _wrapped = wrapped; } /** * {@inheritDoc} */ @Override public Object resolveProperty(String key) { return _wrapped.getProperties() != null ? _wrapped.getProperties().get(key) : null; } private static void set(SwitchYardModel switchyardModel, Bundle bundle) throws Exception { ServiceReference configurationAdminReference = bundle.getBundleContext().getServiceReference(ConfigurationAdmin.class.getName()); ConfigurationAdmin configAdmin = (ConfigurationAdmin) bundle.getBundleContext().getService(configurationAdminReference); org.osgi.service.cm.Configuration osgiConfig = configAdmin.getConfiguration(bundle.getSymbolicName()); if(osgiConfig != null) { Configuration config = switchyardModel.getModelConfiguration(); config.setPropertyResolver(CompoundPropertyResolver.compact(config.getPropertyResolver(), new OsgiPropertyResolver(osgiConfig))); } } } }