Fix a wrong notification in ComponentActivatorAbstractBase
[controller.git] / opendaylight / sal / api / src / main / java / org / opendaylight / controller / sal / core / ComponentActivatorAbstractBase.java
1
2 /*
3  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
4  *
5  * This program and the accompanying materials are made available under the
6  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7  * and is available at http://www.eclipse.org/legal/epl-v10.html
8  */
9
10 package org.opendaylight.controller.sal.core;
11
12 /**
13  * @file   ComponentActivatorAbstractBase.java
14  *
15  * @brief  Abstract class which need to be subclassed in order to
16  * track and register dependencies per-container
17  *
18  * Abstract class which need to be subclassed in order to
19  * track and register dependencies per-container
20  *
21  */
22
23 import java.util.Dictionary;
24 import java.util.Hashtable;
25 import java.util.concurrent.ConcurrentHashMap;
26 import java.util.concurrent.ConcurrentMap;
27
28 import org.apache.commons.lang3.tuple.ImmutablePair;
29 import org.apache.felix.dm.Component;
30 import org.apache.felix.dm.ComponentStateListener;
31 import org.apache.felix.dm.DependencyManager;
32 import org.apache.felix.dm.ServiceDependency;
33 import org.osgi.framework.BundleActivator;
34 import org.osgi.framework.BundleContext;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 /**
39  * Abstract class which need to be subclassed in order to track and
40  * register dependencies per-container
41  *
42  */
43 abstract public class ComponentActivatorAbstractBase implements
44         BundleActivator, IContainerAware {
45     Logger logger = LoggerFactory
46             .getLogger(ComponentActivatorAbstractBase.class);
47     private DependencyManager dm;
48     private ConcurrentMap<ImmutablePair<String, Object>, Component> dbInstances = (ConcurrentMap<ImmutablePair<String, Object>, Component>) new ConcurrentHashMap<ImmutablePair<String, Object>, Component>();
49     private ConcurrentMap<Object, Component> dbGlobalInstances = (ConcurrentMap<Object, Component>) new ConcurrentHashMap<Object, Component>();
50
51     /**
52      * Abstract method that MUST be implemented by the derived class
53      * that wants to activate the Component bundle in a container. Here
54      * customization for the component are expected
55      */
56     abstract protected void init();
57
58     /**
59      * Abstract method that MUST be implemented by the derived class
60      * that wants to DE-activate the Component bundle in a container. Here
61      * customization for the component are expected
62      */
63     abstract protected void destroy();
64
65     /**
66      * Method which tells how many implementations are supported by
67      * the bundle. This way we can tune the number of components
68      * created.
69      *
70      *
71      * @return The list of implementations the bundle will support,
72      * this will be used to decide how many components need to be
73      * created per-container
74      */
75     protected Object[] getImplementations() {
76         return null;
77     }
78
79     /**
80      * Method which tells how many Global implementations are
81      * supported by the bundle. This way we can tune the number of
82      * components created. This components will be created ONLY at the
83      * time of bundle startup and will be destroyed only at time of
84      * bundle destruction, this is the major difference with the
85      * implementation retrieved via getImplementations where all of
86      * them are assumed to be in a container!
87      *
88      *
89      * @return The list of implementations the bundle will support,
90      * in Global version
91      */
92     protected Object[] getGlobalImplementations() {
93         return null;
94     }
95
96     /**
97      * Configure the dependency for a given instance inside a container
98      *
99      * @param c Component assigned for this instance, this will be
100      * what will be used for configuration
101      * @param imp implementation to be configured
102      * @param containerName container on which the configuration happens
103      */
104     protected void configureInstance(Component c, Object imp,
105             String containerName) {
106         // do nothing by default
107     }
108
109     /**
110      * Configure the dependency for a given instance Global
111      *
112      * @param c Component assigned for this instance, this will be
113      * what will be used for configuration
114      * @param imp implementation to be configured
115      * @param containerName container on which the configuration happens
116      */
117     protected void configureGlobalInstance(Component c, Object imp) {
118         // Do nothing by default
119     }
120
121     // Private class used to listen to state transition so we can
122     // implement the necessary logic to call "started" and "stopping"
123     // methods on the component. Right now the framework natively
124     // support only the call of:
125     // - "init": Called after dependency are satisfied
126     // - "start": Called after init but before services are registered
127     // - "stop": Called after services are unregistered but before the
128     // component is going to be destroyed
129     // - "destroy": Called to destroy the component.
130     // There is still a need for two notifications:
131     // - "started" method to be called after "start" and after the
132     // services has been registered in the OSGi service registry
133     // - "stopping" method to be called before "stop" method and
134     // before the services of the component are removed from OSGi
135     // service registry
136     class ListenerComponentStates implements ComponentStateListener {
137         @Override
138         public void starting(Component component) {
139             // do nothing
140         }
141
142         @Override
143         public void started(Component component) {
144             if (component == null) {
145                 return;
146             }
147             component.invokeCallbackMethod(new Object[] { component
148                     .getService() }, "started", new Class[][] {
149                     { Component.class }, {} }, new Object[][] { { component },
150                     {} });
151         }
152
153         @Override
154         public void stopped(Component component) {
155             // do nothing
156         }
157
158         @Override
159         public void stopping(Component component) {
160             if (component == null) {
161                 return;
162             }
163             component.invokeCallbackMethod(new Object[] { component
164                     .getService() }, "stopping", new Class[][] {
165                     { Component.class }, {} }, new Object[][] { { component },
166                     {} });
167         }
168     }
169
170     /**
171      * Method of IContainerAware called when a new container is available
172      *
173      * @param containerName Container being created
174      */
175     @Override
176     public void containerCreate(String containerName) {
177         try {
178             Object[] imps = getImplementations();
179             logger.trace("Creating instance {}", containerName);
180             if (imps != null) {
181                 for (int i = 0; i < imps.length; i++) {
182                     ImmutablePair<String, Object> key = new ImmutablePair<String, Object>(
183                             containerName, imps[i]);
184                     Component c = this.dbInstances.get(key);
185                     if (c == null) {
186                         c = this.dm.createComponent();
187                         c.addStateListener(new ListenerComponentStates());
188                         // Now let the derived class to configure the
189                         // dependencies it wants
190                         configureInstance(c, imps[i], containerName);
191                         // Set the implementation so the component can manage
192                         // its lifecycle
193                         if (c.getService() == null) {
194                             logger.trace("Setting implementation to: {}",
195                                           imps[i]);
196                             c.setImplementation(imps[i]);
197                         }
198
199                         //Set the service properties to include the containerName
200                         //in the service, that is fundamental for supporting
201                         //multiple services just distinguished via a container
202                         Dictionary<String, String> serviceProps = c
203                                 .getServiceProperties();
204                         if (serviceProps != null) {
205                             logger.trace("Adding new property for container");
206                             serviceProps.put("containerName", containerName);
207                         } else {
208                             logger
209                                     .trace("Create a new properties for the service");
210                             serviceProps = new Hashtable<String, String>();
211                             serviceProps.put("containerName", containerName);
212                         }
213                         c.setServiceProperties(serviceProps);
214
215                         // Now add the component to the dependency Manager
216                         // which will immediately start tracking the dependencies
217                         this.dm.add(c);
218
219                         //Now lets keep track in our shadow database of the
220                         //association
221                         this.dbInstances.put(key, c);
222                     } else {
223                         logger
224                                 .error("I have been asked again to create an instance "
225                                         + "on: "
226                                         + containerName
227                                         + "for object: "
228                                         + imps[i]
229                                         + "when i already have it!!");
230                     }
231                 }
232             }
233         } catch (Exception ex) {
234             logger
235                     .error("During containerDestroy invocation caught exception: "
236                             + ex
237                             + "\nStacktrace:"
238                             + stackToString(ex.getStackTrace()));
239         }
240     }
241
242     @Override
243     public void containerDestroy(String containerName) {
244         try {
245             Object[] imps = getImplementations();
246             logger.trace("Destroying instance {}", containerName);
247             if (imps != null) {
248                 for (int i = 0; i < imps.length; i++) {
249                     ImmutablePair<String, Object> key = new ImmutablePair<String, Object>(
250                             containerName, imps[i]);
251                     Component c = this.dbInstances.get(key);
252                     if (c != null) {
253                         // Now remove the component from dependency manager,
254                         // which will implicitely stop it first
255                         this.dm.remove(c);
256                     } else {
257                         logger
258                                 .error("I have been asked again to remove an instance "
259                                         + "on: "
260                                         + containerName
261                                         + "for object: "
262                                         + imps[i]
263                                         + "when i already have cleared it!!");
264                     }
265
266                     //Now lets remove the association from our shadow
267                     //database so the component can be recycled, this is done
268                     //unconditionally in case of spurious conditions
269                     this.dbInstances.remove(key);
270                 }
271             }
272         } catch (Exception ex) {
273             logger
274                     .error("During containerDestroy invocation caught exception: "
275                             + ex
276                             + "\nStacktrace:"
277                             + stackToString(ex.getStackTrace()));
278         }
279     }
280
281     private String stackToString(StackTraceElement[] stack) {
282         if (stack == null) {
283             return "<EmptyStack>";
284         }
285         StringBuffer buffer = new StringBuffer();
286
287         for (int i = 0; i < stack.length; i++) {
288             buffer.append("\n\t" + stack[i].toString());
289         }
290         return buffer.toString();
291     }
292
293     /**
294      * Method called by the OSGi framework when the OSGi bundle
295      * starts. The functionality we want to perform here are:
296      *
297      * 1) Register with the OSGi framework, that we are a provider of
298      * IContainerAware service and so in case of startup of a container we
299      * want to be called
300      *
301      * 2) Create data structures that allow to keep track of all the
302      * instances created per-container given the derived class of
303      * ComponentActivatorAbstractBase will act as a Factory manager
304      *
305      * @param context OSGi bundle context to interact with OSGi framework
306      */
307     @Override
308     public void start(BundleContext context) {
309         try {
310             this.dm = new DependencyManager(context);
311
312             logger.trace("Activating");
313
314             // Now create Global components
315             Object[] imps = getGlobalImplementations();
316             if (imps != null) {
317                 for (int i = 0; i < imps.length; i++) {
318                     Object key = imps[i];
319                     Component c = this.dbGlobalInstances.get(key);
320                     if (c == null) {
321                         try {
322                             c = this.dm.createComponent();
323                             c.addStateListener(new ListenerComponentStates());
324                             // Now let the derived class to configure the
325                             // dependencies it wants
326                             configureGlobalInstance(c, imps[i]);
327                             // Set the implementation so the component
328                             // can manage its lifesycle
329                             if (c.getService() == null) {
330                                 logger.trace("Setting implementation to: {}",
331                                         imps[i]);
332                                 c.setImplementation(imps[i]);
333                             }
334
335                             // Now add the component to the dependency
336                             // Manager which will immediately start
337                             // tracking the dependencies
338                             this.dm.add(c);
339                         } catch (Exception nex) {
340                             logger.error("During creation of a Global "
341                                     + "instance caught exception: " + nex
342                                     + "\nStacktrace:"
343                                     + stackToString(nex.getStackTrace()));
344                         }
345
346                         //Now lets keep track in our shadow database of the
347                         //association
348                         if (c != null)
349                             this.dbGlobalInstances.put(key, c);
350                     } else {
351                         logger.error("I have been asked again to create an "
352                                 + "instance " + " Global for object: "
353                                 + imps[i] + "when i already have it!!");
354                     }
355                 }
356             }
357
358             // Register with OSGi the provider for the service IContainerAware
359             context.registerService(
360                     IContainerAware.class.getName(), this, null);
361
362             // Now call the derived class init function
363             this.init();
364
365             logger.trace("Activation DONE!");
366         } catch (Exception ex) {
367             logger.error("During Activator start caught exception: " + ex
368                     + "\nStacktrace:" + stackToString(ex.getStackTrace()));
369         }
370     }
371
372     /**
373      * Method called by the OSGi framework when the OSGi bundle
374      * stops. The functionality we want to perform here are:
375      *
376      * 1) Force all the instances to stop and do cleanup and
377      * unreference them so garbage collection can clean them up
378      *
379      * NOTE: UN-Register with the OSGi framework,is not needed because
380      * the framework will automatically do it
381      *
382      * @param context OSGi bundle context to interact with OSGi framework
383      */
384     @Override
385     public void stop(BundleContext context) {
386         try {
387             logger.trace("DE-Activating");
388
389             // Now call the derived class destroy function
390             this.destroy();
391
392             // Now remove all the components tracked for container components
393             for (ImmutablePair<String, Object> key : this.dbInstances.keySet()) {
394                 try {
395                     Component c = this.dbInstances.get(key);
396                     if (c != null) {
397                         logger.trace("Remove component on container: {} Object: {}",
398                                 key.getLeft(), key.getRight());
399                         this.dm.remove(c);
400                     }
401                 } catch (Exception nex) {
402                     logger.error("During removal of a container component "
403                             + "instance caught exception: " + nex
404                             + "\nStacktrace:"
405                             + stackToString(nex.getStackTrace()));
406                 }
407                 this.dbInstances.remove(key);
408             }
409
410             // Now remove all the components tracked for Global Components
411             for (Object key : this.dbGlobalInstances.keySet()) {
412                 try {
413                     Component c = this.dbGlobalInstances.get(key);
414                     if (c != null) {
415                         logger.trace("Remove component for Object: {}" , key);
416                         this.dm.remove(c);
417                     }
418                 } catch (Exception nex) {
419                     logger.error("During removal of a Global "
420                             + "instance caught exception: " + nex
421                             + "\nStacktrace:"
422                             + stackToString(nex.getStackTrace()));
423                 }
424
425                 this.dbGlobalInstances.remove(key);
426             }
427
428             // Detach Dependency Manager
429             this.dm = null;
430
431             logger.trace("Deactivation DONE!");
432         } catch (Exception ex) {
433             logger.error("During Activator stop caught exception: " + ex
434                     + "\nStacktrace:" + stackToString(ex.getStackTrace()));
435         }
436     }
437
438     /**
439      * Return a ServiceDependency customized ad hoc for slicing, this
440      * essentially the same org.apache.felix.dm.ServiceDependency just
441      * with some filters pre-set
442      *
443      * @param containerName containerName for which we want to create the dependency
444      *
445      * @return a ServiceDependency
446      */
447     protected ServiceDependency createContainerServiceDependency(
448             String containerName) {
449         return (new ContainerServiceDependency(this.dm, containerName));
450     }
451
452     /**
453      * Return a ServiceDependency as provided by Dependency Manager as it's
454      *
455      *
456      * @return a ServiceDependency
457      */
458     protected ServiceDependency createServiceDependency() {
459         return this.dm.createServiceDependency();
460     }
461 }