Revert "Remove support for actions/rpc/notifications"
[controller.git] / opendaylight / blueprint / src / main / java / org / opendaylight / controller / blueprint / ext / SpecificReferenceListMetadata.java
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java
new file mode 100644 (file)
index 0000000..a98a062
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2016 Brocade Communications Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.blueprint.ext;
+
+import com.google.common.io.Resources;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.osgi.util.tracker.BundleTracker;
+import org.osgi.util.tracker.BundleTrackerCustomizer;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory metadata corresponding to the "specific-reference-list" element that obtains a specific list
+ * of service instances from the OSGi registry for a given interface. The specific list is learned by first
+ * extracting the list of expected service types by inspecting RESOLVED bundles for a resource file under
+ * META-INF/services with the same name as the given interface. The type(s) listed in the resource file
+ * must match the "type" property of the advertised service(s). In this manner, an app bundle announces the
+ * service type(s) that it will advertise so that this class knows which services to expect up front. Once
+ * all the expected services are obtained, the container is notified that all dependencies of this component
+ * factory are satisfied.
+ *
+ * @author Thomas Pantelis
+ */
+class SpecificReferenceListMetadata extends AbstractDependentComponentFactoryMetadata {
+    private static final Logger LOG = LoggerFactory.getLogger(SpecificReferenceListMetadata.class);
+
+    private final String interfaceName;
+    private final String serviceResourcePath;
+    private final Collection<String> expectedServiceTypes = new ConcurrentSkipListSet<>();
+    private final Collection<String> retrievedServiceTypes = new ConcurrentSkipListSet<>();
+    private final Collection<Object> retrievedServices = Collections.synchronizedList(new ArrayList<>());
+    private volatile BundleTracker<Bundle> bundleTracker;
+    private volatile ServiceTracker<Object, Object> serviceTracker;
+
+    SpecificReferenceListMetadata(final String id, final String interfaceName) {
+        super(id);
+        this.interfaceName = interfaceName;
+        serviceResourcePath = "META-INF/services/" + interfaceName;
+    }
+
+    @Override
+    protected void startTracking() {
+        BundleTrackerCustomizer<Bundle> bundleListener = new BundleTrackerCustomizer<>() {
+            @Override
+            public Bundle addingBundle(final Bundle bundle, final BundleEvent event) {
+                bundleAdded(bundle);
+                return bundle;
+            }
+
+            @Override
+            public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
+            }
+
+            @Override
+            public void removedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
+            }
+        };
+
+        bundleTracker = new BundleTracker<>(container().getBundleContext(), Bundle.RESOLVED | Bundle.STARTING
+                | Bundle.STOPPING | Bundle.ACTIVE, bundleListener);
+
+        // This will get the list of all current RESOLVED+ bundles.
+        bundleTracker.open();
+
+        if (expectedServiceTypes.isEmpty()) {
+            setSatisfied();
+            return;
+        }
+
+        ServiceTrackerCustomizer<Object, Object> serviceListener = new ServiceTrackerCustomizer<>() {
+            @Override
+            public Object addingService(final ServiceReference<Object> reference) {
+                return serviceAdded(reference);
+            }
+
+            @Override
+            public void modifiedService(final ServiceReference<Object> reference, final Object service) {
+            }
+
+            @Override
+            public void removedService(final ServiceReference<Object> reference, final Object service) {
+                container().getBundleContext().ungetService(reference);
+            }
+        };
+
+        setDependencyDesc(interfaceName + " services with types " + expectedServiceTypes);
+
+        serviceTracker = new ServiceTracker<>(container().getBundleContext(), interfaceName, serviceListener);
+        serviceTracker.open();
+    }
+
+    private void bundleAdded(final Bundle bundle) {
+        URL resource = bundle.getEntry(serviceResourcePath);
+        if (resource == null) {
+            return;
+        }
+
+        LOG.debug("{}: Found {} resource in bundle {}", logName(), resource, bundle.getSymbolicName());
+
+        try {
+            for (String line : Resources.readLines(resource, StandardCharsets.UTF_8)) {
+                int ci = line.indexOf('#');
+                if (ci >= 0) {
+                    line = line.substring(0, ci);
+                }
+
+                line = line.trim();
+                if (line.isEmpty()) {
+                    continue;
+                }
+
+                String serviceType = line;
+                LOG.debug("{}: Retrieved service type {}", logName(), serviceType);
+                expectedServiceTypes.add(serviceType);
+            }
+        } catch (final IOException e) {
+            setFailure(String.format("%s: Error reading resource %s from bundle %s", logName(), resource,
+                    bundle.getSymbolicName()), e);
+        }
+    }
+
+    private Object serviceAdded(final ServiceReference<Object> reference) {
+        Object service = container().getBundleContext().getService(reference);
+        String serviceType = (String) reference.getProperty(OpendaylightNamespaceHandler.TYPE_ATTR);
+
+        LOG.debug("{}: Service type {} added from bundle {}", logName(), serviceType,
+                reference.getBundle().getSymbolicName());
+
+        if (serviceType == null) {
+            LOG.error("{}: Missing OSGi service property '{}' for service interface {} in bundle {}", logName(),
+                    OpendaylightNamespaceHandler.TYPE_ATTR, interfaceName,  reference.getBundle().getSymbolicName());
+            return service;
+        }
+
+        if (!expectedServiceTypes.contains(serviceType)) {
+            LOG.error("{}: OSGi service property '{}' for service interface {} in bundle {} was not found in the "
+                    + "expected service types {} obtained via {} bundle resources. Is the bundle resource missing or "
+                    + "the service type misspelled?", logName(), OpendaylightNamespaceHandler.TYPE_ATTR, interfaceName,
+                    reference.getBundle().getSymbolicName(), expectedServiceTypes, serviceResourcePath);
+            return service;
+        }
+
+        // If already satisfied, meaning we got all initial services, then a new bundle must've been
+        // dynamically installed or a prior service's blueprint container was restarted, in which case we
+        // restart our container.
+        if (isSatisfied()) {
+            restartContainer();
+        } else {
+            retrievedServiceTypes.add(serviceType);
+            retrievedServices.add(service);
+
+            if (retrievedServiceTypes.equals(expectedServiceTypes)) {
+                LOG.debug("{}: Got all expected service types", logName());
+                setSatisfied();
+            } else {
+                Set<String> remaining = new HashSet<>(expectedServiceTypes);
+                remaining.removeAll(retrievedServiceTypes);
+                setDependencyDesc(interfaceName + " services with types " + remaining);
+            }
+        }
+
+        return service;
+    }
+
+    @Override
+    public Object create() throws ComponentDefinitionException {
+        LOG.debug("{}: In create: interfaceName: {}", logName(), interfaceName);
+
+        super.onCreate();
+
+        LOG.debug("{}: create returning service list {}", logName(), retrievedServices);
+
+        synchronized (retrievedServices) {
+            return List.copyOf(retrievedServices);
+        }
+    }
+
+    @Override
+    public void destroy(final Object instance) {
+        super.destroy(instance);
+
+        if (bundleTracker != null) {
+            bundleTracker.close();
+            bundleTracker = null;
+        }
+
+        if (serviceTracker != null) {
+            serviceTracker.close();
+            serviceTracker = null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder()
+            .append("SpecificReferenceListMetadata [interfaceName=").append(interfaceName)
+            .append(", serviceResourcePath=").append(serviceResourcePath).append("]")
+            .toString();
+    }
+}