Initial opendaylight infrastructure commit!!
[controller.git] / opendaylight / sal / api / src / main / java / org / opendaylight / controller / sal / core / ComponentActivatorAbstractBase.java
diff --git a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/core/ComponentActivatorAbstractBase.java b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/core/ComponentActivatorAbstractBase.java
new file mode 100644 (file)
index 0000000..d04e67b
--- /dev/null
@@ -0,0 +1,464 @@
+
+/*
+ * Copyright (c) 2013 Cisco 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.sal.core;
+
+/**
+ * @file   ComponentActivatorAbstractBase.java
+ *
+ * @brief  Abstract class which need to be subclassed in order to
+ * track and register dependencies per-container
+ *
+ * Abstract class which need to be subclassed in order to
+ * track and register dependencies per-container
+ *
+ */
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentStateListener;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ServiceDependency;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract class which need to be subclassed in order to track and
+ * register dependencies per-container
+ *
+ */
+abstract public class ComponentActivatorAbstractBase implements
+        BundleActivator, IContainerAware {
+    Logger logger = LoggerFactory
+            .getLogger(ComponentActivatorAbstractBase.class);
+    private ServiceRegistration containerAwareRegistration;
+    private DependencyManager dm;
+    private ConcurrentMap<ImmutablePair<String, Object>, Component> dbInstances = (ConcurrentMap<ImmutablePair<String, Object>, Component>) new ConcurrentHashMap<ImmutablePair<String, Object>, Component>();
+    private ConcurrentMap<Object, Component> dbGlobalInstances = (ConcurrentMap<Object, Component>) new ConcurrentHashMap<Object, Component>();
+
+    /**
+     * Abstract method that MUST be implemented by the derived class
+     * that wants to activate the Component bundle in a container. Here
+     * customization for the component are expected
+     */
+    abstract protected void init();
+
+    /**
+     * Abstract method that MUST be implemented by the derived class
+     * that wants to DE-activate the Component bundle in a container. Here
+     * customization for the component are expected
+     */
+    abstract protected void destroy();
+
+    /**
+     * Method which tells how many implementations are supported by
+     * the bundle. This way we can tune the number of components
+     * created.
+     *
+     *
+     * @return The list of implementations the bundle will support,
+     * this will be used to decide how many components need to be
+     * created per-container
+     */
+    protected Object[] getImplementations() {
+        return null;
+    }
+
+    /**
+     * Method which tells how many Global implementations are
+     * supported by the bundle. This way we can tune the number of
+     * components created. This components will be created ONLY at the
+     * time of bundle startup and will be destroyed only at time of
+     * bundle destruction, this is the major difference with the
+     * implementation retrieved via getImplementations where all of
+     * them are assumed to be in a container!
+     *
+     *
+     * @return The list of implementations the bundle will support,
+     * in Global version
+     */
+    protected Object[] getGlobalImplementations() {
+        return null;
+    }
+
+    /**
+     * Configure the dependency for a given instance inside a container
+     *
+     * @param c Component assigned for this instance, this will be
+     * what will be used for configuration
+     * @param imp implementation to be configured
+     * @param containerName container on which the configuration happens
+     */
+    protected void configureInstance(Component c, Object imp,
+            String containerName) {
+        // do nothing by default
+    }
+
+    /**
+     * Configure the dependency for a given instance Global
+     *
+     * @param c Component assigned for this instance, this will be
+     * what will be used for configuration
+     * @param imp implementation to be configured
+     * @param containerName container on which the configuration happens
+     */
+    protected void configureGlobalInstance(Component c, Object imp) {
+        // Do nothing by default
+    }
+
+    // Private class used to listen to state transition so we can
+    // implement the necessary logic to call "started" and "stopping"
+    // methods on the component. Right now the framework natively
+    // support only the call of:
+    // - "init": Called after dependency are satisfied
+    // - "start": Called after init but before services are registered
+    // - "stop": Called after services are unregistered but before the
+    // component is going to be destroyed
+    // - "destroy": Called to destroy the component.
+    // There is still a need for two notifications:
+    // - "started" method to be called after "start" and after the
+    // services has been registered in the OSGi service registry
+    // - "stopping" method to be called before "stop" method and
+    // before the services of the component are removed from OSGi
+    // service registry
+    class ListenerComponentStates implements ComponentStateListener {
+        @Override
+        public void starting(Component component) {
+            // do nothing
+        }
+
+        @Override
+        public void started(Component component) {
+            if (component == null) {
+                return;
+            }
+            component.invokeCallbackMethod(new Object[] { component
+                    .getService() }, "started", new Class[][] {
+                    { Component.class }, {} }, new Object[][] { { component },
+                    {} });
+        }
+
+        @Override
+        public void stopped(Component component) {
+            if (component == null) {
+                return;
+            }
+            component.invokeCallbackMethod(new Object[] { component
+                    .getService() }, "stopping", new Class[][] {
+                    { Component.class }, {} }, new Object[][] { { component },
+                    {} });
+        }
+
+        @Override
+        public void stopping(Component component) {
+            // do nothing
+        }
+    }
+
+    /**
+     * Method of IContainerAware called when a new container is available
+     *
+     * @param containerName Container being created
+     */
+    @Override
+    public void containerCreate(String containerName) {
+        try {
+            Object[] imps = getImplementations();
+            logger.trace("Creating instance " + containerName);
+            if (imps != null) {
+                for (int i = 0; i < imps.length; i++) {
+                    ImmutablePair<String, Object> key = new ImmutablePair<String, Object>(
+                            containerName, imps[i]);
+                    Component c = this.dbInstances.get(key);
+                    if (c == null) {
+                        c = this.dm.createComponent();
+                        c.addStateListener(new ListenerComponentStates());
+                        // Now let the derived class to configure the
+                        // dependencies it wants
+                        configureInstance(c, imps[i], containerName);
+                        // Set the implementation so the component can manage
+                        // its lifecycle
+                        if (c.getService() == null) {
+                            logger
+                                    .trace("Setting implementation to:"
+                                            + imps[i]);
+                            c.setImplementation(imps[i]);
+                        }
+
+                        //Set the service properties to include the containerName
+                        //in the service, that is fundamental for supporting
+                        //multiple services just distinguished via a container
+                        Dictionary<String, String> serviceProps = c
+                                .getServiceProperties();
+                        if (serviceProps != null) {
+                            logger.trace("Adding new property for container");
+                            serviceProps.put("containerName", containerName);
+                        } else {
+                            logger
+                                    .trace("Create a new properties for the service");
+                            serviceProps = new Hashtable<String, String>();
+                            serviceProps.put("containerName", containerName);
+                        }
+                        c.setServiceProperties(serviceProps);
+
+                        // Now add the component to the dependency Manager
+                        // which will immediately start tracking the dependencies
+                        this.dm.add(c);
+
+                        //Now lets keep track in our shadow database of the
+                        //association
+                        this.dbInstances.put(key, c);
+                    } else {
+                        logger
+                                .error("I have been asked again to create an instance "
+                                        + "on: "
+                                        + containerName
+                                        + "for object: "
+                                        + imps[i]
+                                        + "when i already have it!!");
+                    }
+                }
+            }
+        } catch (Exception ex) {
+            logger
+                    .error("During containerDestroy invocation caught exception: "
+                            + ex
+                            + "\nStacktrace:"
+                            + stackToString(ex.getStackTrace()));
+        }
+    }
+
+    @Override
+    public void containerDestroy(String containerName) {
+        try {
+            Object[] imps = getImplementations();
+            logger.trace("Destroying instance " + containerName);
+            if (imps != null) {
+                for (int i = 0; i < imps.length; i++) {
+                    ImmutablePair<String, Object> key = new ImmutablePair<String, Object>(
+                            containerName, imps[i]);
+                    Component c = this.dbInstances.get(key);
+                    if (c != null) {
+                        // Now remove the component from dependency manager,
+                        // which will implicitely stop it first
+                        this.dm.remove(c);
+                    } else {
+                        logger
+                                .error("I have been asked again to remove an instance "
+                                        + "on: "
+                                        + containerName
+                                        + "for object: "
+                                        + imps[i]
+                                        + "when i already have cleared it!!");
+                    }
+
+                    //Now lets remove the association from our shadow
+                    //database so the component can be recycled, this is done
+                    //unconditionally in case of spurious conditions
+                    this.dbInstances.remove(key);
+                }
+            }
+        } catch (Exception ex) {
+            logger
+                    .error("During containerDestroy invocation caught exception: "
+                            + ex
+                            + "\nStacktrace:"
+                            + stackToString(ex.getStackTrace()));
+        }
+    }
+
+    private String stackToString(StackTraceElement[] stack) {
+        if (stack == null) {
+            return "<EmptyStack>";
+        }
+        StringBuffer buffer = new StringBuffer();
+
+        for (int i = 0; i < stack.length; i++) {
+            buffer.append("\n\t" + stack[i].toString());
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Method called by the OSGi framework when the OSGi bundle
+     * starts. The functionality we want to perform here are:
+     *
+     * 1) Register with the OSGi framework, that we are a provider of
+     * IContainerAware service and so in case of startup of a container we
+     * want to be called
+     *
+     * 2) Create data structures that allow to keep track of all the
+     * instances created per-container given the derived class of
+     * ComponentActivatorAbstractBase will act as a Factory manager
+     *
+     * @param context OSGi bundle context to interact with OSGi framework
+     */
+    @Override
+    public void start(BundleContext context) {
+        try {
+            this.dm = new DependencyManager(context);
+
+            logger.trace("Activating");
+
+            // Now create Global components
+            Object[] imps = getGlobalImplementations();
+            if (imps != null) {
+                for (int i = 0; i < imps.length; i++) {
+                    Object key = imps[i];
+                    Component c = this.dbGlobalInstances.get(key);
+                    if (c == null) {
+                        try {
+                            c = this.dm.createComponent();
+                            c.addStateListener(new ListenerComponentStates());
+                            // Now let the derived class to configure the
+                            // dependencies it wants
+                            configureGlobalInstance(c, imps[i]);
+                            // Set the implementation so the component
+                            // can manage its lifesycle
+                            if (c.getService() == null) {
+                                logger.trace("Setting implementation to:"
+                                        + imps[i]);
+                                c.setImplementation(imps[i]);
+                            }
+
+                            // Now add the component to the dependency
+                            // Manager which will immediately start
+                            // tracking the dependencies
+                            this.dm.add(c);
+                        } catch (Exception nex) {
+                            logger.error("During creation of a Global "
+                                    + "instance caught exception: " + nex
+                                    + "\nStacktrace:"
+                                    + stackToString(nex.getStackTrace()));
+                        }
+
+                        //Now lets keep track in our shadow database of the
+                        //association
+                        if (c != null)
+                            this.dbGlobalInstances.put(key, c);
+                    } else {
+                        logger.error("I have been asked again to create an "
+                                + "instance " + " Global for object: "
+                                + imps[i] + "when i already have it!!");
+                    }
+                }
+            }
+
+            // Register with OSGi the provider for the service IContainerAware
+            this.containerAwareRegistration = context.registerService(
+                    IContainerAware.class.getName(), this, null);
+
+            // Now call the derived class init function
+            this.init();
+
+            logger.trace("Activation DONE!");
+        } catch (Exception ex) {
+            logger.error("During Activator start caught exception: " + ex
+                    + "\nStacktrace:" + stackToString(ex.getStackTrace()));
+        }
+    }
+
+    /**
+     * Method called by the OSGi framework when the OSGi bundle
+     * stops. The functionality we want to perform here are:
+     *
+     * 1) Force all the instances to stop and do cleanup and
+     * unreference them so garbage collection can clean them up
+     *
+     * NOTE: UN-Register with the OSGi framework,is not needed because
+     * the framework will automatically do it
+     *
+     * @param context OSGi bundle context to interact with OSGi framework
+     */
+    @Override
+    public void stop(BundleContext context) {
+        try {
+            logger.trace("DE-Activating");
+
+            // Now call the derived class destroy function
+            this.destroy();
+
+            // Now remove all the components tracked for container components
+            for (ImmutablePair<String, Object> key : this.dbInstances.keySet()) {
+                try {
+                    Component c = this.dbInstances.get(key);
+                    if (c != null) {
+                        logger.trace("Remove component on container:"
+                                + key.getLeft() + " Object:" + key.getRight());
+                        this.dm.remove(c);
+                    }
+                } catch (Exception nex) {
+                    logger.error("During removal of a container component "
+                            + "instance caught exception: " + nex
+                            + "\nStacktrace:"
+                            + stackToString(nex.getStackTrace()));
+                }
+                this.dbInstances.remove(key);
+            }
+
+            // Now remove all the components tracked for Global Components
+            for (Object key : this.dbGlobalInstances.keySet()) {
+                try {
+                    Component c = this.dbGlobalInstances.get(key);
+                    if (c != null) {
+                        logger.trace("Remove component for Object:" + key);
+                        this.dm.remove(c);
+                    }
+                } catch (Exception nex) {
+                    logger.error("During removal of a Global "
+                            + "instance caught exception: " + nex
+                            + "\nStacktrace:"
+                            + stackToString(nex.getStackTrace()));
+                }
+
+                this.dbGlobalInstances.remove(key);
+            }
+
+            // Detach Dependency Manager
+            this.dm = null;
+
+            logger.trace("Deactivation DONE!");
+        } catch (Exception ex) {
+            logger.error("During Activator stop caught exception: " + ex
+                    + "\nStacktrace:" + stackToString(ex.getStackTrace()));
+        }
+    }
+
+    /**
+     * Return a ServiceDependency customized ad hoc for slicing, this
+     * essentially the same org.apache.felix.dm.ServiceDependency just
+     * with some filters pre-set
+     *
+     * @param containerName containerName for which we want to create the dependency
+     *
+     * @return a ServiceDependency
+     */
+    protected ServiceDependency createContainerServiceDependency(
+            String containerName) {
+        return (new ContainerServiceDependency(this.dm, containerName));
+    }
+
+    /**
+     * Return a ServiceDependency as provided by Dependency Manager as it's
+     *
+     *
+     * @return a ServiceDependency
+     */
+    protected ServiceDependency createServiceDependency() {
+        return this.dm.createServiceDependency();
+    }
+}