import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
+import javax.annotation.Nullable;
import org.apache.aries.blueprint.di.AbstractRecipe;
import org.apache.aries.blueprint.di.ExecutionContext;
import org.apache.aries.blueprint.di.Recipe;
import org.apache.aries.blueprint.ext.DependentComponentFactoryMetadata;
import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
+import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
+import org.osgi.framework.ServiceReference;
import org.osgi.service.blueprint.container.ComponentDefinitionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private final String id;
private final AtomicBoolean started = new AtomicBoolean();
private final AtomicBoolean satisfied = new AtomicBoolean();
+ private final AtomicBoolean restarting = new AtomicBoolean();
private final List<StaticServiceReferenceRecipe> serviceRecipes = new ArrayList<>();
private volatile ExtendedBlueprintContainer container;
private volatile SatisfactionCallback satisfactionCallback;
private volatile String failureMessage;
+ private volatile Throwable failureCause;
private volatile String dependendencyDesc;
protected AbstractDependentComponentFactoryMetadata(String id) {
protected abstract void startTracking();
protected void setFailureMessage(String failureMessage) {
+ setFailure(failureMessage, null);
+ }
+
+ protected void setFailure(String failureMessage, Throwable failureCause) {
this.failureMessage = failureMessage;
+ this.failureCause = failureCause;
}
protected void setDependendencyDesc(String dependendencyDesc) {
}
protected void setSatisfied() {
- satisfied.set(true);
- satisfactionCallback.notifyChanged();
+ if(satisfied.compareAndSet(false, true)) {
+ satisfactionCallback.notifyChanged();
+ }
}
protected void retrieveService(String name, Class<?> interfaceClass, Consumer<Object> onServiceRetrieved) {
protected void onCreate() throws ComponentDefinitionException {
if(failureMessage != null) {
- throw new ComponentDefinitionException(failureMessage);
+ throw new ComponentDefinitionException(failureMessage, failureCause);
}
// The following code is a bit odd so requires some explanation. A little background... If a bean
serviceRecipes.clear();
}
+
+ protected void restartContainer() {
+ if(restarting.compareAndSet(false, true)) {
+ BlueprintContainerRestartService restartService = getOSGiService(BlueprintContainerRestartService.class);
+ if(restartService != null) {
+ log.debug("{}: Restarting container", logName());
+ restartService.restartContainerAndDependents(container().getBundleContext().getBundle());
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Nullable
+ protected <T> T getOSGiService(Class<T> serviceInterface) {
+ try {
+ ServiceReference<T> serviceReference =
+ container().getBundleContext().getServiceReference(serviceInterface);
+ if(serviceReference == null) {
+ log.warn("{}: {} reference not found", logName(), serviceInterface.getSimpleName());
+ return null;
+ }
+
+ T service = (T)container().getService(serviceReference);
+ if(service == null) {
+ // This could happen on shutdown if the service was already unregistered so we log as debug.
+ log.debug("{}: {} was not found", logName(), serviceInterface.getSimpleName());
+ }
+
+ return service;
+ } catch(IllegalStateException e) {
+ // This is thrown if the BundleContext is no longer valid which is possible on shutdown so we
+ // log as debug.
+ log.debug("{}: Error obtaining {}", logName(), serviceInterface.getSimpleName(), e);
+ }
+
+ return null;
+ }
}
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
-import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-import org.osgi.framework.ServiceReference;
import org.osgi.service.blueprint.container.ComponentDefinitionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
return dataNode;
}
- private void restartContainer() {
- BlueprintContainerRestartService restartService = getOSGiService(BlueprintContainerRestartService.class);
- if(restartService != null) {
- restartService.restartContainerAndDependents(container().getBundleContext().getBundle());
- }
- }
-
- @SuppressWarnings("unchecked")
- @Nullable
- private <T> T getOSGiService(Class<T> serviceInterface) {
- try {
- ServiceReference<T> serviceReference =
- container().getBundleContext().getServiceReference(serviceInterface);
- if(serviceReference == null) {
- LOG.warn("{}: {} reference not found", logName(), serviceInterface.getSimpleName());
- return null;
- }
-
- T service = (T)container().getService(serviceReference);
- if(service == null) {
- // This could happen on shutdown if the service was already unregistered so we log as debug.
- LOG.debug("{}: {} was not found", logName(), serviceInterface.getSimpleName());
- }
-
- return service;
- } catch(IllegalStateException e) {
- // This is thrown if the BundleContext is no longer valid which is possible on shutdown so we
- // log as debug.
- LOG.debug("{}: Error obtaining {}", logName(), serviceInterface.getSimpleName(), e);
- }
-
- return null;
- }
@Override
public void destroy(Object instance) {
static final String ROUTED_RPC_REG_CONVERTER_NAME = "org.opendaylight.blueprint.RoutedRpcRegConverter";
static final String RPC_REGISTRY_NAME = "org.opendaylight.blueprint.RpcRegistry";
static final String NOTIFICATION_SERVICE_NAME = "org.opendaylight.blueprint.NotificationService";
+ static final String TYPE_ATTR = "type";
private static final Logger LOG = LoggerFactory.getLogger(OpendaylightNamespaceHandler.class);
private static final String COMPONENT_PROCESSOR_NAME = ComponentProcessor.class.getName();
private static final String RESTART_DEPENDENTS_ON_UPDATES = "restart-dependents-on-updates";
private static final String USE_DEFAULT_FOR_REFERENCE_TYPES = "use-default-for-reference-types";
private static final String CLUSTERED_APP_CONFIG = "clustered-app-config";
- private static final String TYPE_ATTR = "type";
private static final String INTERFACE = "interface";
private static final String REF_ATTR = "ref";
private static final String ID_ATTR = "id";
private static final String RPC_SERVICE = "rpc-service";
+ private static final String SPECIFIC_SERVICE_REF_LIST = "specific-reference-list";
@SuppressWarnings("rawtypes")
@Override
return parseNotificationListener(element, context);
} else if (nodeNameEquals(element, CLUSTERED_APP_CONFIG)) {
return parseClusteredAppConfig(element, context);
+ } else if (nodeNameEquals(element, SPECIFIC_SERVICE_REF_LIST)) {
+ return parseSpecificReferenceList(element, context);
}
throw new ComponentDefinitionException("Unsupported standalone element: " + element.getNodeName());
DataStoreAppConfigMetadata.LIST_KEY_VALUE), defaultAppConfigElement);
}
+ private Metadata parseSpecificReferenceList(Element element, ParserContext context) {
+ ComponentFactoryMetadata metadata = new SpecificReferenceListMetadata(getId(context, element),
+ element.getAttribute(INTERFACE));
+
+ LOG.debug("parseSpecificReferenceList returning {}", metadata);
+
+ return metadata;
+ }
+
private Element parseXML(String name, String xml) {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
builderFactory.setNamespaceAware(true);
--- /dev/null
+/*
+ * 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.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Resources;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+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(String id, String interfaceName) {
+ super(id);
+ this.interfaceName = interfaceName;
+ serviceResourcePath = "META-INF/services/" + interfaceName;
+ }
+
+ @Override
+ protected void startTracking() {
+ BundleTrackerCustomizer<Bundle> bundleListener = new BundleTrackerCustomizer<Bundle>() {
+ @Override
+ public Bundle addingBundle(Bundle bundle, BundleEvent event) {
+ bundleAdded(bundle);
+ return bundle;
+ }
+
+ @Override
+ public void modifiedBundle(Bundle bundle, BundleEvent event, Bundle object) {
+ }
+
+ @Override
+ public void removedBundle(Bundle bundle, BundleEvent event, 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<Object, Object>() {
+ @Override
+ public Object addingService(ServiceReference<Object> reference) {
+ return serviceAdded(reference);
+ }
+
+ @Override
+ public void modifiedService(ServiceReference<Object> reference, Object service) {
+ }
+
+ @Override
+ public void removedService(ServiceReference<Object> reference, Object service) {
+ container().getBundleContext().ungetService(reference);
+ }
+ };
+
+ setDependendencyDesc(interfaceName + " services with types " + expectedServiceTypes);
+
+ serviceTracker = new ServiceTracker<>(container().getBundleContext(), interfaceName, serviceListener);
+ serviceTracker.open();
+ }
+
+ private void bundleAdded(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, Charsets.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(IOException e) {
+ setFailure(String.format("%s: Error reading resource %s from bundle %s", logName(), resource,
+ bundle.getSymbolicName()), e);
+ }
+ }
+
+ private Object serviceAdded(ServiceReference<Object> reference) {
+ Object service = container().getBundleContext().getService(reference);
+ Object serviceType = 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.toString());
+ 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);
+ setDependendencyDesc(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 ImmutableList.copyOf(retrievedServices);
+ }
+ }
+
+ @Override
+ public void destroy(Object instance) {
+ super.destroy(instance);
+
+ if(bundleTracker != null) {
+ bundleTracker.close();
+ bundleTracker = null;
+ }
+
+ if(serviceTracker != null) {
+ serviceTracker.close();
+ serviceTracker = null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("SpecificReferenceListMetadata [interfaceName=").append(interfaceName)
+ .append(", serviceResourcePath=").append(serviceResourcePath).append("]");
+ return builder.toString();
+ }
+}