Add "static-reference" 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         retrieveService(name, interfaceClass.getName(), onServiceRetrieved);
100     }
101
102     protected void retrieveService(String name, String interfaceName, Consumer<Object> onServiceRetrieved) {
103         StaticServiceReferenceRecipe recipe = new StaticServiceReferenceRecipe(getId() + "-" + name,
104                 container, interfaceName);
105         setDependendencyDesc(recipe.getOsgiFilter());
106         serviceRecipes.add(recipe);
107
108         recipe.startTracking(onServiceRetrieved);
109     }
110
111     protected final String logName() {
112         return (container != null ? container.getBundleContext().getBundle().getSymbolicName() : "") +
113                 " (" + id + ")";
114     }
115
116     @Override
117     public void init(ExtendedBlueprintContainer container) {
118         this.container = container;
119
120         log.debug("{}: In init", logName());
121     }
122
123     protected void onCreate() throws ComponentDefinitionException {
124         if(failureMessage != null) {
125             throw new ComponentDefinitionException(failureMessage, failureCause);
126         }
127
128         // The following code is a bit odd so requires some explanation. A little background... If a bean
129         // is a prototype then the corresponding Recipe create method does not register the bean as created
130         // with the BlueprintRepository and thus the destroy method isn't called on container destroy. We
131         // rely on destroy being called to close our DTCL registration. Unfortunately the default setting
132         // for the prototype flag in AbstractRecipe is true and the DependentComponentFactoryRecipe, which
133         // is created for DependentComponentFactoryMetadata types of which we are one, doesn't have a way for
134         // us to indicate the prototype state via our metadata.
135         //
136         // The ExecutionContext is actually backed by the BlueprintRepository so we access it here to call
137         // the removePartialObject method which removes any partially created instance, which does not apply
138         // in our case, and also has the side effect of registering our bean as created as if it wasn't a
139         // prototype. We also obtain our corresponding Recipe instance and clear the prototype flag. This
140         // doesn't look to be necessary but is done so for completeness. Better late than never. Note we have
141         // to do this here rather than in startTracking b/c the ExecutionContext is not available yet at that
142         // point.
143         //
144         // Now the stopTracking method is called on container destroy but startTracking/stopTracking can also
145         // be called multiple times during the container creation process for Satisfiable recipes as bean
146         // processors may modify the metadata which could affect how dependencies are satisfied. An example of
147         // this is with service references where the OSGi filter metadata can be modified by bean processors
148         // after the initial service dependency is satisfied. However we don't have any metadata that could
149         // be modified by a bean processor and we don't want to register/unregister our DTCL multiple times
150         // so we only process startTracking once and close the DTCL registration once on container destroy.
151         ExecutionContext executionContext = ExecutionContext.Holder.getContext();
152         executionContext.removePartialObject(id);
153
154         Recipe myRecipe = executionContext.getRecipe(id);
155         if(myRecipe instanceof AbstractRecipe) {
156             log.debug("{}: setPrototype to false", logName());
157             ((AbstractRecipe)myRecipe).setPrototype(false);
158         } else {
159             log.warn("{}: Recipe is null or not an AbstractRecipe", logName());
160         }
161     }
162
163     @Override
164     public final void startTracking(final SatisfactionCallback satisfactionCallback) {
165         if(!started.compareAndSet(false, true)) {
166             return;
167         }
168
169         log.debug("{}: In startTracking", logName());
170
171         this.satisfactionCallback = satisfactionCallback;
172
173         startTracking();
174     }
175
176     @Override
177     public void stopTracking() {
178         log.debug("{}: In stopTracking", logName());
179
180         stopServiceRecipes();
181     }
182
183     @Override
184     public void destroy(Object instance) {
185         log.debug("{}: In destroy", logName());
186
187         stopServiceRecipes();
188     }
189
190     private void stopServiceRecipes() {
191         for(StaticServiceReferenceRecipe recipe: serviceRecipes) {
192             recipe.stop();
193         }
194
195         serviceRecipes.clear();
196     }
197
198     protected void restartContainer() {
199         if(restarting.compareAndSet(false, true)) {
200             BlueprintContainerRestartService restartService = getOSGiService(BlueprintContainerRestartService.class);
201             if(restartService != null) {
202                 log.debug("{}: Restarting container", logName());
203                 restartService.restartContainerAndDependents(container().getBundleContext().getBundle());
204             }
205         }
206     }
207
208     @SuppressWarnings("unchecked")
209     @Nullable
210     protected <T> T getOSGiService(Class<T> serviceInterface) {
211         try {
212             ServiceReference<T> serviceReference =
213                     container().getBundleContext().getServiceReference(serviceInterface);
214             if(serviceReference == null) {
215                 log.warn("{}: {} reference not found", logName(), serviceInterface.getSimpleName());
216                 return null;
217             }
218
219             T service = (T)container().getService(serviceReference);
220             if(service == null) {
221                 // This could happen on shutdown if the service was already unregistered so we log as debug.
222                 log.debug("{}: {} was not found", logName(), serviceInterface.getSimpleName());
223             }
224
225             return service;
226         } catch(IllegalStateException e) {
227             // This is thrown if the BundleContext is no longer valid which is possible on shutdown so we
228             // log as debug.
229             log.debug("{}: Error obtaining {}", logName(), serviceInterface.getSimpleName(), e);
230         }
231
232         return null;
233     }
234 }