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 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;
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;
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;
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.
43 * @author Thomas Pantelis
45 class SpecificReferenceListMetadata extends AbstractDependentComponentFactoryMetadata {
46 private static final Logger LOG = LoggerFactory.getLogger(SpecificReferenceListMetadata.class);
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;
56 SpecificReferenceListMetadata(final String id, final String interfaceName) {
58 this.interfaceName = interfaceName;
59 serviceResourcePath = "META-INF/services/" + interfaceName;
63 protected void startTracking() {
64 BundleTrackerCustomizer<Bundle> bundleListener = new BundleTrackerCustomizer<>() {
66 public Bundle addingBundle(final Bundle bundle, final BundleEvent event) {
72 public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
76 public void removedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
80 bundleTracker = new BundleTracker<>(container().getBundleContext(), Bundle.RESOLVED | Bundle.STARTING
81 | Bundle.STOPPING | Bundle.ACTIVE, bundleListener);
83 // This will get the list of all current RESOLVED+ bundles.
86 if (expectedServiceTypes.isEmpty()) {
91 ServiceTrackerCustomizer<Object, Object> serviceListener = new ServiceTrackerCustomizer<>() {
93 public Object addingService(final ServiceReference<Object> reference) {
94 return serviceAdded(reference);
98 public void modifiedService(final ServiceReference<Object> reference, final Object service) {
102 public void removedService(final ServiceReference<Object> reference, final Object service) {
103 container().getBundleContext().ungetService(reference);
107 setDependencyDesc(interfaceName + " services with types " + expectedServiceTypes);
109 serviceTracker = new ServiceTracker<>(container().getBundleContext(), interfaceName, serviceListener);
110 serviceTracker.open();
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) {
121 LOG.debug("{}: Found {} resource in bundle {}", logName(), resource, bundle.getSymbolicName());
124 for (String line : Resources.readLines(resource, StandardCharsets.UTF_8)) {
125 int ci = line.indexOf('#');
127 line = line.substring(0, ci);
131 if (line.isEmpty()) {
135 String serviceType = line;
136 LOG.debug("{}: Retrieved service type {}", logName(), serviceType);
137 expectedServiceTypes.add(serviceType);
139 } catch (final IOException e) {
140 setFailure(String.format("%s: Error reading resource %s from bundle %s", logName(), resource,
141 bundle.getSymbolicName()), e);
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);
151 LOG.debug("{}: Service type {} added from bundle {}", logName(), serviceType,
152 reference.getBundle().getSymbolicName());
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());
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);
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.
174 retrievedServiceTypes.add(serviceType);
175 retrievedServices.add(service);
177 if (retrievedServiceTypes.equals(expectedServiceTypes)) {
178 LOG.debug("{}: Got all expected service types", logName());
181 Set<String> remaining = new HashSet<>(expectedServiceTypes);
182 remaining.removeAll(retrievedServiceTypes);
183 setDependencyDesc(interfaceName + " services with types " + remaining);
191 public Object create() throws ComponentDefinitionException {
192 LOG.debug("{}: In create: interfaceName: {}", logName(), interfaceName);
196 LOG.debug("{}: create returning service list {}", logName(), retrievedServices);
198 synchronized (retrievedServices) {
199 return ImmutableList.copyOf(retrievedServices);
204 public void destroy(final Object instance) {
205 super.destroy(instance);
207 if (bundleTracker != null) {
208 bundleTracker.close();
209 bundleTracker = null;
212 if (serviceTracker != null) {
213 serviceTracker.close();
214 serviceTracker = null;
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();