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