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 java.io.IOException;
14 import java.nio.charset.StandardCharsets;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.HashSet;
20 import java.util.concurrent.ConcurrentSkipListSet;
21 import org.osgi.framework.Bundle;
22 import org.osgi.framework.BundleEvent;
23 import org.osgi.framework.ServiceReference;
24 import org.osgi.service.blueprint.container.ComponentDefinitionException;
25 import org.osgi.util.tracker.BundleTracker;
26 import org.osgi.util.tracker.BundleTrackerCustomizer;
27 import org.osgi.util.tracker.ServiceTracker;
28 import org.osgi.util.tracker.ServiceTrackerCustomizer;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
33 * Factory metadata corresponding to the "specific-reference-list" element that obtains a specific list
34 * of service instances from the OSGi registry for a given interface. The specific list is learned by first
35 * extracting the list of expected service types by inspecting RESOLVED bundles for a resource file under
36 * META-INF/services with the same name as the given interface. The type(s) listed in the resource file
37 * must match the "type" property of the advertised service(s). In this manner, an app bundle announces the
38 * service type(s) that it will advertise so that this class knows which services to expect up front. Once
39 * all the expected services are obtained, the container is notified that all dependencies of this component
40 * factory are satisfied.
42 * @author Thomas Pantelis
44 class SpecificReferenceListMetadata extends AbstractDependentComponentFactoryMetadata {
45 private static final Logger LOG = LoggerFactory.getLogger(SpecificReferenceListMetadata.class);
47 private final String interfaceName;
48 private final String serviceResourcePath;
49 private final Collection<String> expectedServiceTypes = new ConcurrentSkipListSet<>();
50 private final Collection<String> retrievedServiceTypes = new ConcurrentSkipListSet<>();
51 private final Collection<Object> retrievedServices = Collections.synchronizedList(new ArrayList<>());
52 private volatile BundleTracker<Bundle> bundleTracker;
53 private volatile ServiceTracker<Object, Object> serviceTracker;
55 SpecificReferenceListMetadata(String id, String interfaceName) {
57 this.interfaceName = interfaceName;
58 serviceResourcePath = "META-INF/services/" + interfaceName;
62 protected void startTracking() {
63 BundleTrackerCustomizer<Bundle> bundleListener = new BundleTrackerCustomizer<Bundle>() {
65 public Bundle addingBundle(Bundle bundle, BundleEvent event) {
71 public void modifiedBundle(Bundle bundle, BundleEvent event, Bundle object) {
75 public void removedBundle(Bundle bundle, BundleEvent event, Bundle object) {
79 bundleTracker = new BundleTracker<>(container().getBundleContext(), Bundle.RESOLVED | Bundle.STARTING
80 | Bundle.STOPPING | Bundle.ACTIVE, bundleListener);
82 // This will get the list of all current RESOLVED+ bundles.
85 if (expectedServiceTypes.isEmpty()) {
90 ServiceTrackerCustomizer<Object, Object> serviceListener = new ServiceTrackerCustomizer<Object, Object>() {
92 public Object addingService(ServiceReference<Object> reference) {
93 return serviceAdded(reference);
97 public void modifiedService(ServiceReference<Object> reference, Object service) {
101 public void removedService(ServiceReference<Object> reference, Object service) {
102 container().getBundleContext().ungetService(reference);
106 setDependencyDesc(interfaceName + " services with types " + expectedServiceTypes);
108 serviceTracker = new ServiceTracker<>(container().getBundleContext(), interfaceName, serviceListener);
109 serviceTracker.open();
112 private void bundleAdded(Bundle bundle) {
113 URL resource = bundle.getEntry(serviceResourcePath);
114 if (resource == null) {
118 LOG.debug("{}: Found {} resource in bundle {}", logName(), resource, bundle.getSymbolicName());
121 for (String line : Resources.readLines(resource, StandardCharsets.UTF_8)) {
122 int ci = line.indexOf('#');
124 line = line.substring(0, ci);
128 if (line.isEmpty()) {
132 String serviceType = line;
133 LOG.debug("{}: Retrieved service type {}", logName(), serviceType);
134 expectedServiceTypes.add(serviceType);
136 } catch (IOException e) {
137 setFailure(String.format("%s: Error reading resource %s from bundle %s", logName(), resource,
138 bundle.getSymbolicName()), e);
142 private Object serviceAdded(ServiceReference<Object> reference) {
143 Object service = container().getBundleContext().getService(reference);
144 String serviceType = (String) reference.getProperty(OpendaylightNamespaceHandler.TYPE_ATTR);
146 LOG.debug("{}: Service type {} added from bundle {}", logName(), serviceType,
147 reference.getBundle().getSymbolicName());
149 if (serviceType == null) {
150 LOG.error("{}: Missing OSGi service property '{}' for service interface {} in bundle {}", logName(),
151 OpendaylightNamespaceHandler.TYPE_ATTR, interfaceName, reference.getBundle().getSymbolicName());
155 if (!expectedServiceTypes.contains(serviceType)) {
156 LOG.error("{}: OSGi service property '{}' for service interface {} in bundle {} was not found in the "
157 + "expected service types {} obtained via {} bundle resources. Is the bundle resource missing or "
158 + "the service type misspelled?", logName(), OpendaylightNamespaceHandler.TYPE_ATTR, interfaceName,
159 reference.getBundle().getSymbolicName(), expectedServiceTypes, serviceResourcePath);
163 // If already satisfied, meaning we got all initial services, then a new bundle must've been
164 // dynamically installed or a prior service's blueprint container was restarted, in which case we
165 // restart our container.
169 retrievedServiceTypes.add(serviceType);
170 retrievedServices.add(service);
172 if (retrievedServiceTypes.equals(expectedServiceTypes)) {
173 LOG.debug("{}: Got all expected service types", logName());
176 Set<String> remaining = new HashSet<>(expectedServiceTypes);
177 remaining.removeAll(retrievedServiceTypes);
178 setDependencyDesc(interfaceName + " services with types " + remaining);
186 public Object create() throws ComponentDefinitionException {
187 LOG.debug("{}: In create: interfaceName: {}", logName(), interfaceName);
191 LOG.debug("{}: create returning service list {}", logName(), retrievedServices);
193 synchronized (retrievedServices) {
194 return ImmutableList.copyOf(retrievedServices);
199 public void destroy(Object instance) {
200 super.destroy(instance);
202 if (bundleTracker != null) {
203 bundleTracker.close();
204 bundleTracker = null;
207 if (serviceTracker != null) {
208 serviceTracker.close();
209 serviceTracker = null;
214 public String toString() {
215 StringBuilder builder = new StringBuilder();
216 builder.append("SpecificReferenceListMetadata [interfaceName=").append(interfaceName)
217 .append(", serviceResourcePath=").append(serviceResourcePath).append("]");
218 return builder.toString();