BUG-7608: Add ActionServiceMetadata and ActionProviderBean
[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 java.io.IOException;
13 import java.net.URL;
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;
19 import java.util.Set;
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;
31
32 /**
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.
41  *
42  * @author Thomas Pantelis
43  */
44 class SpecificReferenceListMetadata extends AbstractDependentComponentFactoryMetadata {
45     private static final Logger LOG = LoggerFactory.getLogger(SpecificReferenceListMetadata.class);
46
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;
54
55     SpecificReferenceListMetadata(String id, String interfaceName) {
56         super(id);
57         this.interfaceName = interfaceName;
58         serviceResourcePath = "META-INF/services/" + interfaceName;
59     }
60
61     @Override
62     protected void startTracking() {
63         BundleTrackerCustomizer<Bundle> bundleListener = new BundleTrackerCustomizer<Bundle>() {
64             @Override
65             public Bundle addingBundle(Bundle bundle, BundleEvent event) {
66                 bundleAdded(bundle);
67                 return bundle;
68             }
69
70             @Override
71             public void modifiedBundle(Bundle bundle, BundleEvent event, Bundle object) {
72             }
73
74             @Override
75             public void removedBundle(Bundle bundle, BundleEvent event, Bundle object) {
76             }
77         };
78
79         bundleTracker = new BundleTracker<>(container().getBundleContext(), Bundle.RESOLVED | Bundle.STARTING
80                 | Bundle.STOPPING | Bundle.ACTIVE, bundleListener);
81
82         // This will get the list of all current RESOLVED+ bundles.
83         bundleTracker.open();
84
85         if (expectedServiceTypes.isEmpty()) {
86             setSatisfied();
87             return;
88         }
89
90         ServiceTrackerCustomizer<Object, Object> serviceListener = new ServiceTrackerCustomizer<Object, Object>() {
91             @Override
92             public Object addingService(ServiceReference<Object> reference) {
93                 return serviceAdded(reference);
94             }
95
96             @Override
97             public void modifiedService(ServiceReference<Object> reference, Object service) {
98             }
99
100             @Override
101             public void removedService(ServiceReference<Object> reference, Object service) {
102                 container().getBundleContext().ungetService(reference);
103             }
104         };
105
106         setDependencyDesc(interfaceName + " services with types " + expectedServiceTypes);
107
108         serviceTracker = new ServiceTracker<>(container().getBundleContext(), interfaceName, serviceListener);
109         serviceTracker.open();
110     }
111
112     private void bundleAdded(Bundle bundle) {
113         URL resource = bundle.getEntry(serviceResourcePath);
114         if (resource == null) {
115             return;
116         }
117
118         LOG.debug("{}: Found {} resource in bundle {}", logName(), resource, bundle.getSymbolicName());
119
120         try {
121             for (String line : Resources.readLines(resource, StandardCharsets.UTF_8)) {
122                 int ci = line.indexOf('#');
123                 if (ci >= 0) {
124                     line = line.substring(0, ci);
125                 }
126
127                 line = line.trim();
128                 if (line.isEmpty()) {
129                     continue;
130                 }
131
132                 String serviceType = line;
133                 LOG.debug("{}: Retrieved service type {}", logName(), serviceType);
134                 expectedServiceTypes.add(serviceType);
135             }
136         } catch (IOException e) {
137             setFailure(String.format("%s: Error reading resource %s from bundle %s", logName(), resource,
138                     bundle.getSymbolicName()), e);
139         }
140     }
141
142     private Object serviceAdded(ServiceReference<Object> reference) {
143         Object service = container().getBundleContext().getService(reference);
144         Object serviceType = reference.getProperty(OpendaylightNamespaceHandler.TYPE_ATTR);
145
146         LOG.debug("{}: Service type {} added from bundle {}", logName(), serviceType,
147                 reference.getBundle().getSymbolicName());
148
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());
152             return service;
153         }
154
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);
160             return service;
161         }
162
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.
166         if (isSatisfied()) {
167             restartContainer();
168         } else {
169             retrievedServiceTypes.add(serviceType.toString());
170             retrievedServices.add(service);
171
172             if (retrievedServiceTypes.equals(expectedServiceTypes)) {
173                 LOG.debug("{}: Got all expected service types", logName());
174                 setSatisfied();
175             } else {
176                 Set<String> remaining = new HashSet<>(expectedServiceTypes);
177                 remaining.removeAll(retrievedServiceTypes);
178                 setDependencyDesc(interfaceName + " services with types " + remaining);
179             }
180         }
181
182         return service;
183     }
184
185     @Override
186     public Object create() throws ComponentDefinitionException {
187         LOG.debug("{}: In create: interfaceName: {}", logName(), interfaceName);
188
189         super.onCreate();
190
191         LOG.debug("{}: create returning service list {}", logName(), retrievedServices);
192
193         synchronized (retrievedServices) {
194             return ImmutableList.copyOf(retrievedServices);
195         }
196     }
197
198     @Override
199     public void destroy(Object instance) {
200         super.destroy(instance);
201
202         if (bundleTracker != null) {
203             bundleTracker.close();
204             bundleTracker = null;
205         }
206
207         if (serviceTracker != null) {
208             serviceTracker.close();
209             serviceTracker = null;
210         }
211     }
212
213     @Override
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();
219     }
220 }