e823523e8e75aea7cdcc69da90fa634e995296d0
[controller.git] / opendaylight / blueprint / src / main / java / org / opendaylight / controller / blueprint / ext / AbstractDependentComponentFactoryMetadata.java
1 /*
2  * Copyright (c) 2016 Brocade Communications Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.blueprint.ext;
9
10 import com.google.common.base.Preconditions;
11 import java.util.ArrayList;
12 import java.util.Collections;
13 import java.util.List;
14 import java.util.concurrent.atomic.AtomicBoolean;
15 import java.util.function.Consumer;
16 import javax.annotation.Nullable;
17 import javax.annotation.concurrent.GuardedBy;
18 import org.apache.aries.blueprint.di.AbstractRecipe;
19 import org.apache.aries.blueprint.di.ExecutionContext;
20 import org.apache.aries.blueprint.di.Recipe;
21 import org.apache.aries.blueprint.ext.DependentComponentFactoryMetadata;
22 import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
23 import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
24 import org.osgi.framework.ServiceReference;
25 import org.osgi.service.blueprint.container.ComponentDefinitionException;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 /**
30  * Abstract base class for a DependentComponentFactoryMetadata implementation.
31  *
32  * @author Thomas Pantelis
33  */
34 abstract class AbstractDependentComponentFactoryMetadata implements DependentComponentFactoryMetadata {
35     final Logger log = LoggerFactory.getLogger(getClass());
36     private final String id;
37     private final AtomicBoolean started = new AtomicBoolean();
38     private final AtomicBoolean satisfied = new AtomicBoolean();
39     private final AtomicBoolean restarting = new AtomicBoolean();
40     @GuardedBy("serviceRecipes")
41     private final List<StaticServiceReferenceRecipe> serviceRecipes = new ArrayList<>();
42     private volatile ExtendedBlueprintContainer container;
43     private volatile SatisfactionCallback satisfactionCallback;
44     private volatile String failureMessage;
45     private volatile Throwable failureCause;
46     private volatile String dependencyDesc;
47     @GuardedBy("serviceRecipes")
48     private boolean stoppedServiceRecipes;
49
50     protected AbstractDependentComponentFactoryMetadata(final String id) {
51         this.id = Preconditions.checkNotNull(id);
52     }
53
54     @Override
55     public String getId() {
56         return id;
57     }
58
59     @Override
60     public int getActivation() {
61         return ACTIVATION_EAGER;
62     }
63
64     @Override
65     public List<String> getDependsOn() {
66         return Collections.emptyList();
67     }
68
69     @Override
70     public String getDependencyDescriptor() {
71         return dependencyDesc;
72     }
73
74     @Override
75     public boolean isSatisfied() {
76         return satisfied.get();
77     }
78
79     protected void setFailureMessage(final String failureMessage) {
80         setFailure(failureMessage, null);
81     }
82
83     @SuppressWarnings("checkstyle:hiddenField")
84     protected void setFailure(final String failureMessage, final Throwable failureCause) {
85         this.failureMessage = failureMessage;
86         this.failureCause = failureCause;
87     }
88
89     protected void setDependencyDesc(final String dependencyDesc) {
90         this.dependencyDesc = dependencyDesc;
91     }
92
93     protected final ExtendedBlueprintContainer container() {
94         return container;
95     }
96
97     protected void setSatisfied() {
98         if (satisfied.compareAndSet(false, true)) {
99             satisfactionCallback.notifyChanged();
100         }
101     }
102
103     protected void retrieveService(final String name, final Class<?> interfaceClass,
104             final Consumer<Object> onServiceRetrieved) {
105         retrieveService(name, interfaceClass.getName(), onServiceRetrieved);
106     }
107
108     protected void retrieveService(final String name, final String interfaceName,
109             final Consumer<Object> onServiceRetrieved) {
110         synchronized (serviceRecipes) {
111             if (stoppedServiceRecipes) {
112                 return;
113             }
114
115             StaticServiceReferenceRecipe recipe = new StaticServiceReferenceRecipe(getId() + "-" + name,
116                     container, interfaceName);
117             setDependencyDesc(recipe.getOsgiFilter());
118             serviceRecipes.add(recipe);
119
120             recipe.startTracking(onServiceRetrieved);
121         }
122     }
123
124     protected final String logName() {
125         return (container != null ? container.getBundleContext().getBundle().getSymbolicName() : "") + " (" + id + ")";
126     }
127
128     @Override
129     public void init(final ExtendedBlueprintContainer newContainer) {
130         this.container = newContainer;
131
132         log.debug("{}: In init", logName());
133     }
134
135     protected void onCreate() throws ComponentDefinitionException {
136         if (failureMessage != null) {
137             throw new ComponentDefinitionException(failureMessage, failureCause);
138         }
139
140         // The following code is a bit odd so requires some explanation. A little background... If a bean
141         // is a prototype then the corresponding Recipe create method does not register the bean as created
142         // with the BlueprintRepository and thus the destroy method isn't called on container destroy. We
143         // rely on destroy being called to close our DTCL registration. Unfortunately the default setting
144         // for the prototype flag in AbstractRecipe is true and the DependentComponentFactoryRecipe, which
145         // is created for DependentComponentFactoryMetadata types of which we are one, doesn't have a way for
146         // us to indicate the prototype state via our metadata.
147         //
148         // The ExecutionContext is actually backed by the BlueprintRepository so we access it here to call
149         // the removePartialObject method which removes any partially created instance, which does not apply
150         // in our case, and also has the side effect of registering our bean as created as if it wasn't a
151         // prototype. We also obtain our corresponding Recipe instance and clear the prototype flag. This
152         // doesn't look to be necessary but is done so for completeness. Better late than never. Note we have
153         // to do this here rather than in startTracking b/c the ExecutionContext is not available yet at that
154         // point.
155         //
156         // Now the stopTracking method is called on container destroy but startTracking/stopTracking can also
157         // be called multiple times during the container creation process for Satisfiable recipes as bean
158         // processors may modify the metadata which could affect how dependencies are satisfied. An example of
159         // this is with service references where the OSGi filter metadata can be modified by bean processors
160         // after the initial service dependency is satisfied. However we don't have any metadata that could
161         // be modified by a bean processor and we don't want to register/unregister our DTCL multiple times
162         // so we only process startTracking once and close the DTCL registration once on container destroy.
163         ExecutionContext executionContext = ExecutionContext.Holder.getContext();
164         executionContext.removePartialObject(id);
165
166         Recipe myRecipe = executionContext.getRecipe(id);
167         if (myRecipe instanceof AbstractRecipe) {
168             log.debug("{}: setPrototype to false", logName());
169             ((AbstractRecipe)myRecipe).setPrototype(false);
170         } else {
171             log.warn("{}: Recipe is null or not an AbstractRecipe", logName());
172         }
173     }
174
175     protected abstract void startTracking();
176
177     @Override
178     public final void startTracking(final SatisfactionCallback newSatisfactionCallback) {
179         if (!started.compareAndSet(false, true)) {
180             return;
181         }
182
183         log.debug("{}: In startTracking", logName());
184
185         this.satisfactionCallback = newSatisfactionCallback;
186
187         startTracking();
188     }
189
190     @Override
191     public void stopTracking() {
192         log.debug("{}: In stopTracking", logName());
193
194         stopServiceRecipes();
195     }
196
197     @Override
198     public void destroy(final Object instance) {
199         log.debug("{}: In destroy", logName());
200
201         stopServiceRecipes();
202     }
203
204     private void stopServiceRecipes() {
205         synchronized (serviceRecipes) {
206             stoppedServiceRecipes = true;
207             for (StaticServiceReferenceRecipe recipe: serviceRecipes) {
208                 recipe.stop();
209             }
210
211             serviceRecipes.clear();
212         }
213     }
214
215     protected void restartContainer() {
216         if (restarting.compareAndSet(false, true)) {
217             BlueprintContainerRestartService restartService = getOSGiService(BlueprintContainerRestartService.class);
218             if (restartService != null) {
219                 log.debug("{}: Restarting container", logName());
220                 restartService.restartContainerAndDependents(container().getBundleContext().getBundle());
221             }
222         }
223     }
224
225     @SuppressWarnings("unchecked")
226     @Nullable
227     protected <T> T getOSGiService(final Class<T> serviceInterface) {
228         try {
229             ServiceReference<T> serviceReference =
230                     container().getBundleContext().getServiceReference(serviceInterface);
231             if (serviceReference == null) {
232                 log.warn("{}: {} reference not found", logName(), serviceInterface.getSimpleName());
233                 return null;
234             }
235
236             T service = (T)container().getService(serviceReference);
237             if (service == null) {
238                 // This could happen on shutdown if the service was already unregistered so we log as debug.
239                 log.debug("{}: {} was not found", logName(), serviceInterface.getSimpleName());
240             }
241
242             return service;
243         } catch (final IllegalStateException e) {
244             // This is thrown if the BundleContext is no longer valid which is possible on shutdown so we
245             // log as debug.
246             log.debug("{}: Error obtaining {}", logName(), serviceInterface.getSimpleName(), e);
247         }
248
249         return null;
250     }
251 }