2 * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.blueprint.ext;
10 import static java.util.Objects.requireNonNull;
12 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.List;
16 import java.util.concurrent.atomic.AtomicBoolean;
17 import java.util.function.Consumer;
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.checkerframework.checker.lock.qual.GuardedBy;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
26 import org.osgi.framework.ServiceReference;
27 import org.osgi.service.blueprint.container.ComponentDefinitionException;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
32 * Abstract base class for a DependentComponentFactoryMetadata implementation.
34 * @author Thomas Pantelis
36 abstract class AbstractDependentComponentFactoryMetadata implements DependentComponentFactoryMetadata {
37 @SuppressFBWarnings("SLF4J_LOGGER_SHOULD_BE_PRIVATE")
38 final Logger log = LoggerFactory.getLogger(getClass());
39 private final String id;
40 private final AtomicBoolean started = new AtomicBoolean();
41 private final AtomicBoolean satisfied = new AtomicBoolean();
42 private final AtomicBoolean restarting = new AtomicBoolean();
43 @GuardedBy("serviceRecipes")
44 private final List<StaticServiceReferenceRecipe> serviceRecipes = new ArrayList<>();
45 private volatile ExtendedBlueprintContainer container;
46 private volatile SatisfactionCallback satisfactionCallback;
47 private volatile String failureMessage;
48 private volatile Throwable failureCause;
49 private volatile String dependencyDesc;
50 @GuardedBy("serviceRecipes")
51 private boolean stoppedServiceRecipes;
53 protected AbstractDependentComponentFactoryMetadata(final String id) {
54 this.id = requireNonNull(id);
58 public String getId() {
63 public int getActivation() {
64 return ACTIVATION_EAGER;
68 public List<String> getDependsOn() {
69 return Collections.emptyList();
73 public String getDependencyDescriptor() {
74 return dependencyDesc;
78 public boolean isSatisfied() {
79 return satisfied.get();
82 protected void setFailureMessage(final String failureMessage) {
83 setFailure(failureMessage, null);
86 @SuppressWarnings("checkstyle:hiddenField")
87 protected void setFailure(final String failureMessage, final Throwable failureCause) {
88 this.failureMessage = failureMessage;
89 this.failureCause = failureCause;
92 protected void setDependencyDesc(final String dependencyDesc) {
93 this.dependencyDesc = dependencyDesc;
96 protected final ExtendedBlueprintContainer container() {
100 protected void setSatisfied() {
101 if (satisfied.compareAndSet(false, true)) {
102 satisfactionCallback.notifyChanged();
106 protected void retrieveService(final String name, final Class<?> interfaceClass,
107 final Consumer<Object> onServiceRetrieved) {
108 retrieveService(name, interfaceClass.getName(), onServiceRetrieved);
111 protected void retrieveService(final String name, final String interfaceName,
112 final Consumer<Object> onServiceRetrieved) {
113 synchronized (serviceRecipes) {
114 if (stoppedServiceRecipes) {
118 StaticServiceReferenceRecipe recipe = new StaticServiceReferenceRecipe(getId() + "-" + name,
119 container, interfaceName);
120 setDependencyDesc(recipe.getOsgiFilter());
121 serviceRecipes.add(recipe);
123 recipe.startTracking(onServiceRetrieved);
127 protected final String logName() {
128 return (container != null ? container.getBundleContext().getBundle().getSymbolicName() : "") + " (" + id + ")";
132 public void init(final ExtendedBlueprintContainer newContainer) {
133 this.container = newContainer;
135 log.debug("{}: In init", logName());
138 protected void onCreate() throws ComponentDefinitionException {
139 if (failureMessage != null) {
140 throw new ComponentDefinitionException(failureMessage, failureCause);
143 // The following code is a bit odd so requires some explanation. A little background... If a bean
144 // is a prototype then the corresponding Recipe create method does not register the bean as created
145 // with the BlueprintRepository and thus the destroy method isn't called on container destroy. We
146 // rely on destroy being called to close our DTCL registration. Unfortunately the default setting
147 // for the prototype flag in AbstractRecipe is true and the DependentComponentFactoryRecipe, which
148 // is created for DependentComponentFactoryMetadata types of which we are one, doesn't have a way for
149 // us to indicate the prototype state via our metadata.
151 // The ExecutionContext is actually backed by the BlueprintRepository so we access it here to call
152 // the removePartialObject method which removes any partially created instance, which does not apply
153 // in our case, and also has the side effect of registering our bean as created as if it wasn't a
154 // prototype. We also obtain our corresponding Recipe instance and clear the prototype flag. This
155 // doesn't look to be necessary but is done so for completeness. Better late than never. Note we have
156 // to do this here rather than in startTracking b/c the ExecutionContext is not available yet at that
159 // Now the stopTracking method is called on container destroy but startTracking/stopTracking can also
160 // be called multiple times during the container creation process for Satisfiable recipes as bean
161 // processors may modify the metadata which could affect how dependencies are satisfied. An example of
162 // this is with service references where the OSGi filter metadata can be modified by bean processors
163 // after the initial service dependency is satisfied. However we don't have any metadata that could
164 // be modified by a bean processor and we don't want to register/unregister our DTCL multiple times
165 // so we only process startTracking once and close the DTCL registration once on container destroy.
166 ExecutionContext executionContext = ExecutionContext.Holder.getContext();
167 executionContext.removePartialObject(id);
169 Recipe myRecipe = executionContext.getRecipe(id);
170 if (myRecipe instanceof AbstractRecipe) {
171 log.debug("{}: setPrototype to false", logName());
172 ((AbstractRecipe)myRecipe).setPrototype(false);
174 log.warn("{}: Recipe is null or not an AbstractRecipe", logName());
178 protected abstract void startTracking();
181 public final void startTracking(final SatisfactionCallback newSatisfactionCallback) {
182 if (!started.compareAndSet(false, true)) {
186 log.debug("{}: In startTracking", logName());
188 this.satisfactionCallback = newSatisfactionCallback;
194 public void stopTracking() {
195 log.debug("{}: In stopTracking", logName());
197 stopServiceRecipes();
201 public void destroy(final Object instance) {
202 log.debug("{}: In destroy", logName());
204 stopServiceRecipes();
207 private void stopServiceRecipes() {
208 synchronized (serviceRecipes) {
209 stoppedServiceRecipes = true;
210 for (StaticServiceReferenceRecipe recipe: serviceRecipes) {
214 serviceRecipes.clear();
218 protected void restartContainer() {
219 if (restarting.compareAndSet(false, true)) {
220 BlueprintContainerRestartService restartService = getOSGiService(BlueprintContainerRestartService.class);
221 if (restartService != null) {
222 log.debug("{}: Restarting container", logName());
223 restartService.restartContainerAndDependents(container().getBundleContext().getBundle());
228 @SuppressWarnings("unchecked")
229 protected <T> @Nullable T getOSGiService(final Class<T> serviceInterface) {
231 ServiceReference<T> serviceReference =
232 container().getBundleContext().getServiceReference(serviceInterface);
233 if (serviceReference == null) {
234 log.warn("{}: {} reference not found", logName(), serviceInterface.getSimpleName());
238 T service = (T)container().getService(serviceReference);
239 if (service == null) {
240 // This could happen on shutdown if the service was already unregistered so we log as debug.
241 log.debug("{}: {} was not found", logName(), serviceInterface.getSimpleName());
245 } catch (final IllegalStateException e) {
246 // This is thrown if the BundleContext is no longer valid which is possible on shutdown so we
248 log.debug("{}: Error obtaining {}", logName(), serviceInterface.getSimpleName(), e);