Fix CS warnings in blueprint and enable enforcement
[controller.git] / opendaylight / blueprint / src / main / java / org / opendaylight / controller / blueprint / BlueprintBundleTracker.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;
9
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.Dictionary;
15 import java.util.Enumeration;
16 import java.util.HashSet;
17 import java.util.Hashtable;
18 import java.util.List;
19 import org.apache.aries.blueprint.NamespaceHandler;
20 import org.apache.aries.blueprint.services.BlueprintExtenderService;
21 import org.apache.aries.util.AriesFrameworkUtil;
22 import org.opendaylight.controller.blueprint.ext.OpendaylightNamespaceHandler;
23 import org.opendaylight.controller.config.api.ConfigSystemService;
24 import org.osgi.framework.Bundle;
25 import org.osgi.framework.BundleActivator;
26 import org.osgi.framework.BundleContext;
27 import org.osgi.framework.BundleEvent;
28 import org.osgi.framework.ServiceReference;
29 import org.osgi.framework.ServiceRegistration;
30 import org.osgi.framework.SynchronousBundleListener;
31 import org.osgi.service.blueprint.container.BlueprintContainer;
32 import org.osgi.service.blueprint.container.EventConstants;
33 import org.osgi.service.event.Event;
34 import org.osgi.service.event.EventHandler;
35 import org.osgi.util.tracker.BundleTracker;
36 import org.osgi.util.tracker.BundleTrackerCustomizer;
37 import org.osgi.util.tracker.ServiceTracker;
38 import org.osgi.util.tracker.ServiceTrackerCustomizer;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * This class is created in bundle activation and scans ACTIVE bundles for blueprint XML files located under
44  * the well-known org/opendaylight/blueprint/ path and deploys the XML files via the Aries
45  * BlueprintExtenderService. This path differs from the standard OSGI-INF/blueprint path to allow for
46  * controlled deployment of blueprint containers in an orderly manner.
47  *
48  * @author Thomas Pantelis
49  */
50 public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCustomizer<Bundle>, EventHandler,
51         SynchronousBundleListener {
52     private static final Logger LOG = LoggerFactory.getLogger(BlueprintBundleTracker.class);
53     private static final String BLUEPRINT_FILE_PATH = "org/opendaylight/blueprint/";
54     private static final String BLUEPRINT_FLE_PATTERN = "*.xml";
55     private static final long SYSTEM_BUNDLE_ID = 0;
56
57     private ServiceTracker<BlueprintExtenderService, BlueprintExtenderService> serviceTracker;
58     private BundleTracker<Bundle> bundleTracker;
59     private BundleContext bundleContext;
60     private volatile BlueprintExtenderService blueprintExtenderService;
61     private volatile ServiceRegistration<?> blueprintContainerRestartReg;
62     private volatile BlueprintContainerRestartServiceImpl restartService;
63     private volatile boolean shuttingDown;
64     private ServiceRegistration<?> eventHandlerReg;
65     private ServiceRegistration<?> namespaceReg;
66
67     /**
68      * Implemented from BundleActivator.
69      */
70     @Override
71     public void start(BundleContext context) {
72         LOG.info("Starting {}", getClass().getSimpleName());
73
74         bundleContext = context;
75
76         registerBlueprintEventHandler(context);
77
78         registerNamespaceHandler(context);
79
80         bundleTracker = new BundleTracker<>(context, Bundle.ACTIVE, this);
81
82         serviceTracker = new ServiceTracker<>(context, BlueprintExtenderService.class.getName(),
83                 new ServiceTrackerCustomizer<BlueprintExtenderService, BlueprintExtenderService>() {
84                     @Override
85                     public BlueprintExtenderService addingService(
86                             ServiceReference<BlueprintExtenderService> reference) {
87                         blueprintExtenderService = reference.getBundle().getBundleContext().getService(reference);
88                         bundleTracker.open();
89
90                         context.addBundleListener(BlueprintBundleTracker.this);
91
92                         LOG.debug("Got BlueprintExtenderService");
93
94                         restartService = new BlueprintContainerRestartServiceImpl(blueprintExtenderService);
95                         blueprintContainerRestartReg = context.registerService(
96                                 BlueprintContainerRestartService.class.getName(), restartService, new Hashtable<>());
97
98                         return blueprintExtenderService;
99                     }
100
101                     @Override
102                     public void modifiedService(ServiceReference<BlueprintExtenderService> reference,
103                             BlueprintExtenderService service) {
104                     }
105
106                     @Override
107                     public void removedService(ServiceReference<BlueprintExtenderService> reference,
108                             BlueprintExtenderService service) {
109                     }
110                 });
111         serviceTracker.open();
112     }
113
114     private void registerNamespaceHandler(BundleContext context) {
115         Dictionary<String, Object> props = new Hashtable<>();
116         props.put("osgi.service.blueprint.namespace", OpendaylightNamespaceHandler.NAMESPACE_1_0_0);
117         namespaceReg = context.registerService(NamespaceHandler.class.getName(),
118                 new OpendaylightNamespaceHandler(), props);
119     }
120
121     private void registerBlueprintEventHandler(BundleContext context) {
122         Dictionary<String, Object> props = new Hashtable<>();
123         props.put(org.osgi.service.event.EventConstants.EVENT_TOPIC,
124                 new String[]{EventConstants.TOPIC_CREATED, EventConstants.TOPIC_FAILURE});
125         eventHandlerReg = context.registerService(EventHandler.class.getName(), this, props);
126     }
127
128     /**
129      * Implemented from BundleActivator.
130      */
131     @Override
132     public void stop(BundleContext context) {
133         bundleTracker.close();
134         serviceTracker.close();
135
136         AriesFrameworkUtil.safeUnregisterService(eventHandlerReg);
137         AriesFrameworkUtil.safeUnregisterService(namespaceReg);
138         AriesFrameworkUtil.safeUnregisterService(blueprintContainerRestartReg);
139     }
140
141     /**
142      * Implemented from SynchronousBundleListener.
143      */
144     @Override
145     public void bundleChanged(BundleEvent event) {
146         // If the system bundle (id 0) is stopping, do an orderly shutdown of all blueprint containers. On
147         // shutdown the system bundle is stopped first.
148         if (event.getBundle().getBundleId() == SYSTEM_BUNDLE_ID && event.getType() == BundleEvent.STOPPING) {
149             shutdownAllContainers();
150         }
151     }
152
153     /**
154      * Implemented from BundleActivator.
155      */
156     @Override
157     public Bundle addingBundle(Bundle bundle, BundleEvent event) {
158         modifiedBundle(bundle, event, bundle);
159         return bundle;
160     }
161
162     /**
163      * Implemented from BundleActivator.
164      */
165     @Override
166     public void modifiedBundle(Bundle bundle, BundleEvent event, Bundle object) {
167         if (shuttingDown) {
168             return;
169         }
170
171         if (bundle.getState() == Bundle.ACTIVE) {
172             List<Object> paths = findBlueprintPaths(bundle);
173
174             if (!paths.isEmpty()) {
175                 LOG.info("Creating blueprint container for bundle {} with paths {}", bundle, paths);
176
177                 blueprintExtenderService.createContainer(bundle, paths);
178             }
179         }
180     }
181
182     /**
183      * Implemented from BundleActivator.
184      */
185     @Override
186     public void removedBundle(Bundle bundle, BundleEvent event, Bundle object) {
187         // BlueprintExtenderService will handle this.
188     }
189
190     /**
191      * Implemented from EventHandler to listen for blueprint events.
192      *
193      * @param event the event to handle
194      */
195     @Override
196     public void handleEvent(Event event) {
197         if (EventConstants.TOPIC_CREATED.equals(event.getTopic())) {
198             LOG.info("Blueprint container for bundle {} was successfully created",
199                     event.getProperty(EventConstants.BUNDLE));
200         } else if (EventConstants.TOPIC_FAILURE.equals(event.getTopic())) {
201             // If the container timed out waiting for dependencies, we'll destroy it and start it again. This
202             // is indicated via a non-null DEPENDENCIES property containing the missing dependencies. The
203             // default timeout is 5 min and ideally we would set this to infinite but the timeout can only
204             // be set at the bundle level in the manifest - there's no way to set it globally.
205             if (event.getProperty(EventConstants.DEPENDENCIES) != null) {
206                 Bundle bundle = (Bundle) event.getProperty(EventConstants.BUNDLE);
207
208                 List<Object> paths = findBlueprintPaths(bundle);
209                 if (!paths.isEmpty()) {
210                     LOG.warn("Blueprint container for bundle {} timed out waiting for dependencies - restarting it",
211                             event.getProperty(EventConstants.BUNDLE));
212
213                     restartService.restartContainer(bundle, paths);
214                 }
215             }
216         }
217     }
218
219     @SuppressWarnings({ "rawtypes", "unchecked" })
220     static List<Object> findBlueprintPaths(Bundle bundle) {
221         Enumeration<?> rntries = bundle.findEntries(BLUEPRINT_FILE_PATH, BLUEPRINT_FLE_PATTERN, false);
222         if (rntries == null) {
223             return Collections.emptyList();
224         } else {
225             return Collections.list((Enumeration)rntries);
226         }
227     }
228
229     private void shutdownAllContainers() {
230         shuttingDown = true;
231
232         restartService.close();
233
234         // Close all CSS modules first.
235         ConfigSystemService configSystem = getOSGiService(ConfigSystemService.class);
236         if (configSystem != null) {
237             configSystem.closeAllConfigModules();
238         }
239
240         LOG.info("Shutting down all blueprint containers...");
241
242         Collection<Bundle> containerBundles = new HashSet<>(Arrays.asList(bundleContext.getBundles()));
243         while (!containerBundles.isEmpty()) {
244             // For each iteration of getBundlesToDestroy, as containers are destroyed, other containers become
245             // eligible to be destroyed. We loop until we've destroyed them all.
246             for (Bundle bundle : getBundlesToDestroy(containerBundles)) {
247                 containerBundles.remove(bundle);
248                 BlueprintContainer container = blueprintExtenderService.getContainer(bundle);
249                 if (container != null) {
250                     blueprintExtenderService.destroyContainer(bundle, container);
251                 }
252             }
253         }
254
255         LOG.info("Shutdown of blueprint containers complete");
256     }
257
258     private List<Bundle> getBundlesToDestroy(Collection<Bundle> containerBundles) {
259         List<Bundle> bundlesToDestroy = new ArrayList<>();
260
261         // Find all container bundles that either have no registered services or whose services are no
262         // longer in use.
263         for (Bundle bundle : containerBundles) {
264             ServiceReference<?>[] references = bundle.getRegisteredServices();
265             int usage = 0;
266             if (references != null) {
267                 for (ServiceReference<?> reference : references) {
268                     usage += getServiceUsage(reference);
269                 }
270             }
271
272             LOG.debug("Usage for bundle {} is {}", bundle, usage);
273             if (usage == 0) {
274                 bundlesToDestroy.add(bundle);
275             }
276         }
277
278         if (!bundlesToDestroy.isEmpty()) {
279             Collections.sort(bundlesToDestroy, (b1, b2) -> (int) (b2.getLastModified() - b1.getLastModified()));
280
281             LOG.debug("Selected bundles {} for destroy (no services in use)", bundlesToDestroy);
282         } else {
283             // There's either no more container bundles or they all have services being used. For
284             // the latter it means there's either circular service usage or a service is being used
285             // by a non-container bundle. But we need to make progress so we pick the bundle with a
286             // used service with the highest service ID. Each service is assigned a monotonically
287             // increasing ID as they are registered. By picking the bundle with the highest service
288             // ID, we're picking the bundle that was (likely) started after all the others and thus
289             // is likely the safest to destroy at this point.
290
291             ServiceReference<?> ref = null;
292             for (Bundle bundle : containerBundles) {
293                 ServiceReference<?>[] references = bundle.getRegisteredServices();
294                 if (references == null) {
295                     continue;
296                 }
297
298                 for (ServiceReference<?> reference : references) {
299                     // We did check the service usage above but it's possible the usage has changed since
300                     // then,
301                     if (getServiceUsage(reference) == 0) {
302                         continue;
303                     }
304
305                     // Choose 'reference' if it has a lower service ranking or, if the rankings are equal
306                     // which is usually the case, if it has a higher service ID. For the latter the < 0
307                     // check looks backwards but that's how ServiceReference#compareTo is documented to work.
308                     if (ref == null || reference.compareTo(ref) < 0) {
309                         LOG.debug("Currently selecting bundle {} for destroy (with reference {})", bundle, reference);
310                         ref = reference;
311                     }
312                 }
313             }
314
315             if (ref != null) {
316                 bundlesToDestroy.add(ref.getBundle());
317             }
318
319             LOG.debug("Selected bundle {} for destroy (lowest ranking service or highest service ID)",
320                     bundlesToDestroy);
321         }
322
323         return bundlesToDestroy;
324     }
325
326     private static int getServiceUsage(ServiceReference<?> ref) {
327         Bundle[] usingBundles = ref.getUsingBundles();
328         return usingBundles != null ? usingBundles.length : 0;
329     }
330
331     private <T> T getOSGiService(Class<T> serviceInterface) {
332         try {
333             ServiceReference<T> serviceReference = bundleContext.getServiceReference(serviceInterface);
334             if (serviceReference == null) {
335                 LOG.warn("{} service reference not found", serviceInterface.getSimpleName());
336                 return null;
337             }
338
339             T service = bundleContext.getService(serviceReference);
340             if (service == null) {
341                 // This could happen on shutdown if the service was already unregistered so we log as debug.
342                 LOG.debug("{} service instance was not found", serviceInterface.getSimpleName());
343             }
344
345             return service;
346         } catch (IllegalStateException e) {
347             // This is thrown if the BundleContext is no longer valid which is possible on shutdown so we
348             // log as debug.
349             LOG.debug("Error obtaining OSGi service {}", serviceInterface.getSimpleName(), e);
350         }
351
352         return null;
353     }
354 }