0412f00a7afb94132183271f6afdadeb640a4c7a
[controller.git] / opendaylight / blueprint / src / main / java / org / opendaylight / controller / blueprint / ext / SpecificReferenceListMetadata.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.collect.ImmutableList;
11 import com.google.common.io.Resources;
12 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
13 import java.io.IOException;
14 import java.net.URL;
15 import java.nio.charset.StandardCharsets;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.HashSet;
20 import java.util.Set;
21 import java.util.concurrent.ConcurrentSkipListSet;
22 import org.osgi.framework.Bundle;
23 import org.osgi.framework.BundleEvent;
24 import org.osgi.framework.ServiceReference;
25 import org.osgi.service.blueprint.container.ComponentDefinitionException;
26 import org.osgi.util.tracker.BundleTracker;
27 import org.osgi.util.tracker.BundleTrackerCustomizer;
28 import org.osgi.util.tracker.ServiceTracker;
29 import org.osgi.util.tracker.ServiceTrackerCustomizer;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 /**
34  * Factory metadata corresponding to the "specific-reference-list" element that obtains a specific list
35  * of service instances from the OSGi registry for a given interface. The specific list is learned by first
36  * extracting the list of expected service types by inspecting RESOLVED bundles for a resource file under
37  * META-INF/services with the same name as the given interface. The type(s) listed in the resource file
38  * must match the "type" property of the advertised service(s). In this manner, an app bundle announces the
39  * service type(s) that it will advertise so that this class knows which services to expect up front. Once
40  * all the expected services are obtained, the container is notified that all dependencies of this component
41  * factory are satisfied.
42  *
43  * @author Thomas Pantelis
44  */
45 class SpecificReferenceListMetadata extends AbstractDependentComponentFactoryMetadata {
46     private static final Logger LOG = LoggerFactory.getLogger(SpecificReferenceListMetadata.class);
47
48     private final String interfaceName;
49     private final String serviceResourcePath;
50     private final Collection<String> expectedServiceTypes = new ConcurrentSkipListSet<>();
51     private final Collection<String> retrievedServiceTypes = new ConcurrentSkipListSet<>();
52     private final Collection<Object> retrievedServices = Collections.synchronizedList(new ArrayList<>());
53     private volatile BundleTracker<Bundle> bundleTracker;
54     private volatile ServiceTracker<Object, Object> serviceTracker;
55
56     SpecificReferenceListMetadata(final String id, final String interfaceName) {
57         super(id);
58         this.interfaceName = interfaceName;
59         serviceResourcePath = "META-INF/services/" + interfaceName;
60     }
61
62     @Override
63     protected void startTracking() {
64         BundleTrackerCustomizer<Bundle> bundleListener = new BundleTrackerCustomizer<>() {
65             @Override
66             public Bundle addingBundle(final Bundle bundle, final BundleEvent event) {
67                 bundleAdded(bundle);
68                 return bundle;
69             }
70
71             @Override
72             public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
73             }
74
75             @Override
76             public void removedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
77             }
78         };
79
80         bundleTracker = new BundleTracker<>(container().getBundleContext(), Bundle.RESOLVED | Bundle.STARTING
81                 | Bundle.STOPPING | Bundle.ACTIVE, bundleListener);
82
83         // This will get the list of all current RESOLVED+ bundles.
84         bundleTracker.open();
85
86         if (expectedServiceTypes.isEmpty()) {
87             setSatisfied();
88             return;
89         }
90
91         ServiceTrackerCustomizer<Object, Object> serviceListener = new ServiceTrackerCustomizer<>() {
92             @Override
93             public Object addingService(final ServiceReference<Object> reference) {
94                 return serviceAdded(reference);
95             }
96
97             @Override
98             public void modifiedService(final ServiceReference<Object> reference, final Object service) {
99             }
100
101             @Override
102             public void removedService(final ServiceReference<Object> reference, final Object service) {
103                 container().getBundleContext().ungetService(reference);
104             }
105         };
106
107         setDependencyDesc(interfaceName + " services with types " + expectedServiceTypes);
108
109         serviceTracker = new ServiceTracker<>(container().getBundleContext(), interfaceName, serviceListener);
110         serviceTracker.open();
111     }
112
113     @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
114             justification = "https://github.com/spotbugs/spotbugs/issues/811")
115     private void bundleAdded(final Bundle bundle) {
116         URL resource = bundle.getEntry(serviceResourcePath);
117         if (resource == null) {
118             return;
119         }
120
121         LOG.debug("{}: Found {} resource in bundle {}", logName(), resource, bundle.getSymbolicName());
122
123         try {
124             for (String line : Resources.readLines(resource, StandardCharsets.UTF_8)) {
125                 int ci = line.indexOf('#');
126                 if (ci >= 0) {
127                     line = line.substring(0, ci);
128                 }
129
130                 line = line.trim();
131                 if (line.isEmpty()) {
132                     continue;
133                 }
134
135                 String serviceType = line;
136                 LOG.debug("{}: Retrieved service type {}", logName(), serviceType);
137                 expectedServiceTypes.add(serviceType);
138             }
139         } catch (final IOException e) {
140             setFailure(String.format("%s: Error reading resource %s from bundle %s", logName(), resource,
141                     bundle.getSymbolicName()), e);
142         }
143     }
144
145     @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
146             justification = "https://github.com/spotbugs/spotbugs/issues/811")
147     private Object serviceAdded(final ServiceReference<Object> reference) {
148         Object service = container().getBundleContext().getService(reference);
149         String serviceType = (String) reference.getProperty(OpendaylightNamespaceHandler.TYPE_ATTR);
150
151         LOG.debug("{}: Service type {} added from bundle {}", logName(), serviceType,
152                 reference.getBundle().getSymbolicName());
153
154         if (serviceType == null) {
155             LOG.error("{}: Missing OSGi service property '{}' for service interface {} in bundle {}", logName(),
156                     OpendaylightNamespaceHandler.TYPE_ATTR, interfaceName,  reference.getBundle().getSymbolicName());
157             return service;
158         }
159
160         if (!expectedServiceTypes.contains(serviceType)) {
161             LOG.error("{}: OSGi service property '{}' for service interface {} in bundle {} was not found in the "
162                     + "expected service types {} obtained via {} bundle resources. Is the bundle resource missing or "
163                     + "the service type misspelled?", logName(), OpendaylightNamespaceHandler.TYPE_ATTR, interfaceName,
164                     reference.getBundle().getSymbolicName(), expectedServiceTypes, serviceResourcePath);
165             return service;
166         }
167
168         // If already satisfied, meaning we got all initial services, then a new bundle must've been
169         // dynamically installed or a prior service's blueprint container was restarted, in which case we
170         // restart our container.
171         if (isSatisfied()) {
172             restartContainer();
173         } else {
174             retrievedServiceTypes.add(serviceType);
175             retrievedServices.add(service);
176
177             if (retrievedServiceTypes.equals(expectedServiceTypes)) {
178                 LOG.debug("{}: Got all expected service types", logName());
179                 setSatisfied();
180             } else {
181                 Set<String> remaining = new HashSet<>(expectedServiceTypes);
182                 remaining.removeAll(retrievedServiceTypes);
183                 setDependencyDesc(interfaceName + " services with types " + remaining);
184             }
185         }
186
187         return service;
188     }
189
190     @Override
191     public Object create() throws ComponentDefinitionException {
192         LOG.debug("{}: In create: interfaceName: {}", logName(), interfaceName);
193
194         super.onCreate();
195
196         LOG.debug("{}: create returning service list {}", logName(), retrievedServices);
197
198         synchronized (retrievedServices) {
199             return ImmutableList.copyOf(retrievedServices);
200         }
201     }
202
203     @Override
204     public void destroy(final Object instance) {
205         super.destroy(instance);
206
207         if (bundleTracker != null) {
208             bundleTracker.close();
209             bundleTracker = null;
210         }
211
212         if (serviceTracker != null) {
213             serviceTracker.close();
214             serviceTracker = null;
215         }
216     }
217
218     @Override
219     public String toString() {
220         StringBuilder builder = new StringBuilder();
221         builder.append("SpecificReferenceListMetadata [interfaceName=").append(interfaceName)
222                 .append(", serviceResourcePath=").append(serviceResourcePath).append("]");
223         return builder.toString();
224     }
225 }