import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
+import org.apache.aries.blueprint.NamespaceHandler;
import org.apache.aries.blueprint.services.BlueprintExtenderService;
+import org.apache.aries.util.AriesFrameworkUtil;
+import org.opendaylight.controller.blueprint.ext.OpendaylightNamespaceHandler;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
private ServiceTracker<BlueprintExtenderService, BlueprintExtenderService> serviceTracker;
private BundleTracker<Bundle> bundleTracker;
private volatile BlueprintExtenderService blueprintExtenderService;
+ private volatile ServiceRegistration<?> blueprintContainerRestartReg;
private ServiceRegistration<?> eventHandlerReg;
+ private ServiceRegistration<?> namespaceReg;
/**
* Implemented from BundleActivator.
public void start(BundleContext context) {
LOG.info("Starting {}", getClass().getSimpleName());
- // Register EventHandler for blueprint events
+ registerBlueprintEventHandler(context);
- Dictionary<String, Object> props = new Hashtable<>();
- props.put(org.osgi.service.event.EventConstants.EVENT_TOPIC, EventConstants.TOPIC_CREATED);
- eventHandlerReg = context.registerService(EventHandler.class.getName(), this, props);
+ registerNamespaceHandler(context);
bundleTracker = new BundleTracker<>(context, Bundle.ACTIVE, this);
LOG.debug("Got BlueprintExtenderService");
+ blueprintContainerRestartReg = context.registerService(
+ BlueprintContainerRestartService.class.getName(),
+ new BlueprintContainerRestartServiceImpl(blueprintExtenderService), new Hashtable<>());
+
return blueprintExtenderService;
}
serviceTracker.open();
}
+ private void registerNamespaceHandler(BundleContext context) {
+ Dictionary<String, Object> props = new Hashtable<>();
+ props.put("osgi.service.blueprint.namespace", OpendaylightNamespaceHandler.NAMESPACE_1_0_0);
+ namespaceReg = context.registerService(NamespaceHandler.class.getName(),
+ new OpendaylightNamespaceHandler(), props);
+ }
+
+ private void registerBlueprintEventHandler(BundleContext context) {
+ Dictionary<String, Object> props = new Hashtable<>();
+ props.put(org.osgi.service.event.EventConstants.EVENT_TOPIC, EventConstants.TOPIC_CREATED);
+ eventHandlerReg = context.registerService(EventHandler.class.getName(), this, props);
+ }
+
/**
* Implemented from BundleActivator.
*/
public void stop(BundleContext context) {
bundleTracker.close();
serviceTracker.close();
- eventHandlerReg.unregister();
+
+ AriesFrameworkUtil.safeUnregisterService(eventHandlerReg);
+ AriesFrameworkUtil.safeUnregisterService(namespaceReg);
+ AriesFrameworkUtil.safeUnregisterService(blueprintContainerRestartReg);
}
/**
--- /dev/null
+/*
+ * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.blueprint;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * Interface that restarts blueprint containers.
+ *
+ * @author Thomas Pantelis
+ */
+public interface BlueprintContainerRestartService {
+
+ /**
+ * Restarts the blueprint container for the given bundle and all its dependent containers in an atomic
+ * and orderly manner. The dependent containers are identified by walking the OSGi service dependency
+ * hierarchies for the service(s) provided by the given bundle.
+ *
+ * @param bundle the bundle to restart
+ */
+ void restartContainerAndDependents(Bundle bundle);
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.blueprint;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import org.apache.aries.blueprint.services.BlueprintExtenderService;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Implementation of the BlueprintContainerRestartService.
+ *
+ * @author Thomas Pantelis
+ */
+class BlueprintContainerRestartServiceImpl implements AutoCloseable, BlueprintContainerRestartService {
+ private static final Logger LOG = LoggerFactory.getLogger(BlueprintContainerRestartServiceImpl.class);
+
+ private final ExecutorService restartExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().
+ setDaemon(true).setNameFormat("BlueprintContainerRestartService").build());
+ private final BlueprintExtenderService blueprintExtenderService;
+
+ BlueprintContainerRestartServiceImpl(BlueprintExtenderService blueprintExtenderService) {
+ this.blueprintExtenderService = blueprintExtenderService;
+ }
+
+ @Override
+ public void restartContainerAndDependents(final Bundle bundle) {
+ LOG.debug("restartContainerAndDependents for bundle {}", bundle);
+
+ restartExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ restartContainerAndDependentsInternal(bundle);
+
+ }
+ });
+ }
+
+ private void restartContainerAndDependentsInternal(Bundle forBundle) {
+ Set<Bundle> containerBundlesSet = new LinkedHashSet<>();
+ findDependentContainersRecursively(forBundle, containerBundlesSet);
+
+ List<Bundle> containerBundles = new ArrayList<>(containerBundlesSet);
+
+ LOG.info("Restarting blueprint containers for bundle {} and its dependent bundles {}", forBundle,
+ containerBundles.subList(1, containerBundles.size()));
+
+ // Destroy the containers in reverse order with 'forBundle' last, ie bottom-up in the service tree.
+ for(int i = containerBundles.size() - 1; i >= 0; i--) {
+ Bundle bundle = containerBundles.get(i);
+ blueprintExtenderService.destroyContainer(bundle, blueprintExtenderService.getContainer(bundle));
+ }
+
+ // Restart the containers top-down starting with 'forBundle'.
+ for(Bundle bundle: containerBundles) {
+ List<Object> paths = BlueprintBundleTracker.findBlueprintPaths(bundle);
+
+ LOG.info("Restarting blueprint container for bundle {} with paths {}", bundle, paths);
+
+ blueprintExtenderService.createContainer(bundle, paths);
+ }
+ }
+
+ /**
+ * Recursively finds the services registered by the given bundle and the bundles using those services.
+ * User bundles that have an associated blueprint container are added to containerBundles.
+ *
+ * @param bundle the bundle to traverse
+ * @param containerBundles the current set of bundles containing blueprint containers
+ */
+ private void findDependentContainersRecursively(Bundle bundle, Set<Bundle> containerBundles) {
+ if(containerBundles.contains(bundle)) {
+ return;
+ }
+
+ containerBundles.add(bundle);
+ ServiceReference<?>[] references = bundle.getRegisteredServices();
+ if (references != null) {
+ for (ServiceReference<?> reference : references) {
+ Bundle[] usingBundles = reference.getUsingBundles();
+ if(usingBundles != null) {
+ for(Bundle usingBundle: usingBundles) {
+ if(blueprintExtenderService.getContainer(usingBundle) != null) {
+ findDependentContainersRecursively(usingBundle, containerBundles);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ restartExecutor.shutdownNow();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.blueprint.ext;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import org.apache.aries.blueprint.ComponentDefinitionRegistry;
+import org.apache.aries.blueprint.ComponentDefinitionRegistryProcessor;
+import org.apache.aries.blueprint.ext.AbstractPropertyPlaceholder;
+import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
+import org.apache.aries.util.AriesFrameworkUtil;
+import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.blueprint.reflect.BeanProperty;
+import org.osgi.service.blueprint.reflect.ComponentMetadata;
+import org.osgi.service.blueprint.reflect.ValueMetadata;
+import org.osgi.service.cm.ManagedService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The singleton component processor that is invoked by the blueprint container to perform operations on
+ * various component definitions prior to component creation.
+ *
+ * @author Thomas Pantelis
+ */
+public class ComponentProcessor implements ComponentDefinitionRegistryProcessor {
+ private static final Logger LOG = LoggerFactory.getLogger(ComponentProcessor.class);
+ private static final String CM_PERSISTENT_ID_PROPERTY = "persistentId";
+
+ private final List<ServiceRegistration<?>> managedServiceRegs = new ArrayList<>();
+ private Bundle bundle;
+ private BlueprintContainerRestartService blueprintContainerRestartService;
+ private boolean restartDependentsOnUpdates;
+
+ public void setBundle(Bundle bundle) {
+ this.bundle = bundle;
+ }
+
+ public void setBlueprintContainerRestartService(BlueprintContainerRestartService restartService) {
+ this.blueprintContainerRestartService = restartService;
+ }
+
+ public void setRestartDependentsOnUpdates(boolean restartDependentsOnUpdates) {
+ this.restartDependentsOnUpdates = restartDependentsOnUpdates;
+ }
+
+ public void destroy() {
+ for(ServiceRegistration<?> reg: managedServiceRegs) {
+ AriesFrameworkUtil.safeUnregisterService(reg);
+ }
+ }
+
+ @Override
+ public void process(ComponentDefinitionRegistry registry) {
+ LOG.debug("{}: In process", bundle.getSymbolicName());
+
+ for(String name : registry.getComponentDefinitionNames()) {
+ ComponentMetadata component = registry.getComponentDefinition(name);
+ if(component instanceof MutableBeanMetadata) {
+ processMutableBeanMetadata((MutableBeanMetadata)component);
+ }
+ }
+ }
+
+ private void processMutableBeanMetadata(MutableBeanMetadata bean) {
+ if(restartDependentsOnUpdates && bean.getRuntimeClass() != null &&
+ AbstractPropertyPlaceholder.class.isAssignableFrom(bean.getRuntimeClass())) {
+ LOG.debug("{}: Found PropertyPlaceholder bean: {}, runtime {}", bundle.getSymbolicName(), bean.getId(),
+ bean.getRuntimeClass());
+
+ for(BeanProperty prop: bean.getProperties()) {
+ if(CM_PERSISTENT_ID_PROPERTY.equals(prop.getName())) {
+ if(prop.getValue() instanceof ValueMetadata) {
+ ValueMetadata persistentId = (ValueMetadata)prop.getValue();
+
+ LOG.debug("{}: Found {} property, value : {}", bundle.getSymbolicName(),
+ CM_PERSISTENT_ID_PROPERTY, persistentId.getStringValue());
+
+ registerManagedService(persistentId.getStringValue());
+ } else {
+ LOG.debug("{}: {} property metadata {} is not instanceof ValueMetadata",
+ bundle.getSymbolicName(), CM_PERSISTENT_ID_PROPERTY, prop.getValue());
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ private void registerManagedService(final String persistentId) {
+ // Register a ManagedService so we git updates from the ConfigAdmin when the cfg file corresponding
+ // to the persistentId changes.
+ ManagedService managedService = new ManagedService() {
+ private volatile boolean initialUpdate = true;
+
+ @Override
+ public void updated(Dictionary<String, ?> properties) {
+ LOG.debug("{}: ManagedService updated for persistentId {}, properties: {}, initialUpdate: {}",
+ bundle.getSymbolicName(), persistentId, properties, initialUpdate);
+
+ // The first update occurs when the service is registered so ignore it as we want subsequent
+ // updates when it changes. The ConfigAdmin will send an update even if the cfg file doesn't
+ // yet exist.
+ if(initialUpdate) {
+ initialUpdate = false;
+ } else {
+ blueprintContainerRestartService.restartContainerAndDependents(bundle);
+ }
+ }
+ };
+
+ Dictionary<String, Object> props = new Hashtable<>();
+ props.put(Constants.SERVICE_PID, persistentId);
+ props.put(Constants.BUNDLE_SYMBOLICNAME, bundle.getSymbolicName());
+ props.put(Constants.BUNDLE_VERSION, bundle.getHeaders().get(Constants.BUNDLE_VERSION));
+ managedServiceRegs.add(bundle.getBundleContext().registerService(ManagedService.class.getName(),
+ managedService, props));
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.blueprint.ext;
+
+import java.net.URL;
+import java.util.Collections;
+import java.util.Set;
+import org.apache.aries.blueprint.ComponentDefinitionRegistry;
+import org.apache.aries.blueprint.NamespaceHandler;
+import org.apache.aries.blueprint.ParserContext;
+import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
+import org.apache.aries.blueprint.mutable.MutableRefMetadata;
+import org.apache.aries.blueprint.mutable.MutableReferenceMetadata;
+import org.apache.aries.blueprint.mutable.MutableValueMetadata;
+import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.osgi.service.blueprint.reflect.BeanMetadata;
+import org.osgi.service.blueprint.reflect.ComponentMetadata;
+import org.osgi.service.blueprint.reflect.Metadata;
+import org.osgi.service.blueprint.reflect.RefMetadata;
+import org.osgi.service.blueprint.reflect.ReferenceMetadata;
+import org.osgi.service.blueprint.reflect.ValueMetadata;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * The NamespaceHandler for Opendaylight blueprint extensions.
+ *
+ * @author Thomas Pantelis
+ */
+public class OpendaylightNamespaceHandler implements NamespaceHandler {
+ public static final String NAMESPACE_1_0_0 = "http://opendaylight.org/xmlns/blueprint/v1.0.0";
+
+ private static final Logger LOG = LoggerFactory.getLogger(OpendaylightNamespaceHandler.class);
+ private static final String COMPONENT_PROCESSOR_NAME = ComponentProcessor.class.getName();
+ private static final String RESTART_DEPENDENTS_ON_UPDATES = "restart-dependents-on-updates";
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Set<Class> getManagedClasses() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public URL getSchemaLocation(String namespace) {
+ if(NAMESPACE_1_0_0.equals(namespace)) {
+ URL url = getClass().getResource("/opendaylight-blueprint-ext-1.0.0.xsd");
+ LOG.debug("getSchemaLocation for {} returning URL {}", namespace, url);
+ return url;
+ }
+
+ return null;
+ }
+
+ @Override
+ public Metadata parse(Element element, ParserContext context) {
+ LOG.debug("In parse for {}", element);
+ throw new ComponentDefinitionException("Unsupported standalone element: " + element.getNodeName());
+ }
+
+ @Override
+ public ComponentMetadata decorate(Node node, ComponentMetadata component, ParserContext context) {
+ if(node instanceof Attr) {
+ if (nodeNameEquals(node, RESTART_DEPENDENTS_ON_UPDATES)) {
+ return decorateRestartDependentsOnUpdates((Attr)node, component, context);
+ }
+
+ throw new ComponentDefinitionException("Unsupported attribute: " + node.getNodeName());
+ } else {
+ throw new ComponentDefinitionException("Unsupported node type: " + node);
+ }
+ }
+
+ private static ComponentMetadata decorateRestartDependentsOnUpdates(Attr attr, ComponentMetadata component,
+ ParserContext context) {
+ if(component != null) {
+ throw new ComponentDefinitionException("Attribute " + attr.getNodeName() +
+ " can only be used on the root <blueprint> element");
+ }
+
+ LOG.debug("decorateRestartDependentsOnUpdates: {}", attr.getValue());
+
+ if(!Boolean.TRUE.equals(Boolean.valueOf(attr.getValue()))) {
+ return component;
+ }
+
+ MutableBeanMetadata metadata = registerComponentProcessor(context);
+ metadata.addProperty("restartDependentsOnUpdates", createValue(context, "true"));
+
+ return component;
+ }
+
+ private static MutableBeanMetadata registerComponentProcessor(ParserContext context) {
+ ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry();
+ MutableBeanMetadata metadata = (MutableBeanMetadata) registry.getComponentDefinition(COMPONENT_PROCESSOR_NAME);
+ if(metadata == null) {
+ metadata = context.createMetadata(MutableBeanMetadata.class);
+ metadata.setProcessor(true);
+ metadata.setId(COMPONENT_PROCESSOR_NAME);
+ metadata.setActivation(BeanMetadata.ACTIVATION_EAGER);
+ metadata.setScope(BeanMetadata.SCOPE_SINGLETON);
+ metadata.setRuntimeClass(ComponentProcessor.class);
+ metadata.setDestroyMethod("destroy");
+ metadata.addProperty("bundle", createRef(context, "blueprintBundle"));
+ metadata.addProperty("blueprintContainerRestartService", createServiceRef(context,
+ BlueprintContainerRestartService.class, null));
+
+ LOG.debug("Registering ComponentProcessor bean: {}", metadata);
+
+ registry.registerComponentDefinition(metadata);
+ }
+
+ return metadata;
+ }
+
+ private static ValueMetadata createValue(ParserContext context, String value) {
+ MutableValueMetadata m = context.createMetadata(MutableValueMetadata.class);
+ m.setStringValue(value);
+ return m;
+ }
+
+ private static MutableReferenceMetadata createServiceRef(ParserContext context, Class<?> cls, String filter) {
+ MutableReferenceMetadata m = context.createMetadata(MutableReferenceMetadata.class);
+ m.setRuntimeInterface(cls);
+ m.setInterface(cls.getName());
+ m.setActivation(ReferenceMetadata.ACTIVATION_EAGER);
+ m.setAvailability(ReferenceMetadata.AVAILABILITY_MANDATORY);
+
+ if(filter != null) {
+ m.setFilter(filter);
+ }
+
+ return m;
+ }
+
+ private static RefMetadata createRef(ParserContext context, String value) {
+ MutableRefMetadata metadata = context.createMetadata(MutableRefMetadata.class);
+ metadata.setComponentId(value);
+ return metadata;
+ }
+
+ private static boolean nodeNameEquals(Node node, String name) {
+ return name.equals(node.getNodeName()) || name.equals(node.getLocalName());
+ }
+}
--- /dev/null
+<xsd:schema xmlns="http://opendaylight.org/xmlns/blueprint/v1.0.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:bp="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+ targetNamespace="http://opendaylight.org/xmlns/blueprint/v1.0.0" elementFormDefault="qualified"
+ attributeFormDefault="unqualified" version="1.0.0">
+
+<!-- <xsd:import namespace="http://www.osgi.org/xmlns/blueprint/v1.0.0"/> -->
+
+ <xsd:attribute name="restart-dependents-on-updates" type="xsd:boolean"/>
+
+</xsd:schema>
\ No newline at end of file