/* * Copyright (c) 2013, 2017 Cisco 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.config.manager.impl; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.management.InstanceAlreadyExistsException; import javax.management.InstanceNotFoundException; import javax.management.ObjectName; import org.opendaylight.controller.config.api.LookupRegistry; import org.opendaylight.controller.config.api.ModuleIdentifier; import org.opendaylight.controller.config.api.ServiceReferenceReadableRegistry; import org.opendaylight.controller.config.api.ServiceReferenceWritableRegistry; import org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation; import org.opendaylight.controller.config.api.jmx.ObjectNameUtil; import org.opendaylight.controller.config.manager.impl.jmx.BaseJMXRegistrator; import org.opendaylight.controller.config.manager.impl.jmx.ServiceReference; import org.opendaylight.controller.config.manager.impl.jmx.ServiceReferenceMXBeanImpl; import org.opendaylight.controller.config.manager.impl.jmx.ServiceReferenceRegistrator; import org.opendaylight.controller.config.manager.impl.jmx.ServiceReferenceRegistrator.ServiceReferenceJMXRegistration; import org.opendaylight.controller.config.manager.impl.jmx.ServiceReferenceRegistrator.ServiceReferenceTransactionRegistratorFactory; import org.opendaylight.controller.config.manager.impl.jmx.ServiceReferenceRegistrator.ServiceReferenceTransactionRegistratorFactoryImpl; import org.opendaylight.controller.config.manager.impl.util.InterfacesHelper; import org.opendaylight.controller.config.spi.ModuleFactory; import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class ServiceReferenceRegistryImpl implements CloseableServiceReferenceReadableRegistry, SearchableServiceReferenceWritableRegistry { private static final Logger LOG = LoggerFactory.getLogger(ServiceReferenceRegistryImpl.class); private final Map factories; private final Map> factoryNamesToQNames; // validator of incoming ObjectNames - throws InstanceNotFoundException if not // found either in registry or transaction private final LookupRegistry lookupRegistry; private final ServiceReferenceRegistrator serviceReferenceRegistrator; // helper method for getting QName of SI from namespace + local name private final Map> namespacesToAnnotations; private final Map serviceQNamesToAnnotations; // all Service Interface qNames for sanity checking private final Set allQNames; Map> modulesToServiceRef = new HashMap<>(); // actual reference database private final Map refNames = new HashMap<>(); private final boolean writable; private final Map> managementBeans = new HashMap<>(); private ServiceReferenceRegistryImpl(final Map factories, final LookupRegistry lookupRegistry, final ServiceReferenceTransactionRegistratorFactory serviceReferenceRegistratorFactory, final boolean writable) { this.factories = factories; this.writable = writable; this.lookupRegistry = lookupRegistry; this.serviceReferenceRegistrator = serviceReferenceRegistratorFactory.create(); Map> modifiableFactoryNamesToQNames = new HashMap<>(); Set allAnnotations = new HashSet<>(); Set allQNameSet = new HashSet<>(); for (Entry entry : factories.entrySet()) { if (!entry.getKey().equals(entry.getValue().getImplementationName())) { LOG.error("Possible error in code: Mismatch between supplied and actual name of {}", entry); throw new IllegalArgumentException( "Possible error in code: Mismatch between supplied and actual name of " + entry); } Set siAnnotations = InterfacesHelper .getServiceInterfaceAnnotations(entry.getValue()); Set names = InterfacesHelper.getQNames(siAnnotations); allAnnotations.addAll(siAnnotations); allQNameSet.addAll(names); modifiableFactoryNamesToQNames.put(entry.getKey(), names); } this.factoryNamesToQNames = ImmutableMap.copyOf(modifiableFactoryNamesToQNames); this.allQNames = ImmutableSet.copyOf(allQNameSet); // fill namespacesToAnnotations Map> modifiableNamespacesToAnnotations = new HashMap<>(); Map modifiableServiceQNamesToAnnotations = new HashMap<>(); for (ServiceInterfaceAnnotation sia : allAnnotations) { Map ofNamespace = modifiableNamespacesToAnnotations .computeIfAbsent(sia.namespace(), k -> new HashMap<>()); if (ofNamespace.containsKey(sia.localName())) { LOG.error( "Cannot construct namespacesToAnnotations map, conflict between local names in {}," + " offending local name: {}, map so far {}", sia.namespace(), sia.localName(), modifiableNamespacesToAnnotations); throw new IllegalArgumentException( "Conflict between local names in " + sia.namespace() + " : " + sia.localName()); } ofNamespace.put(sia.localName(), sia); modifiableServiceQNamesToAnnotations.put(sia.value(), sia); } this.namespacesToAnnotations = ImmutableMap.copyOf(modifiableNamespacesToAnnotations); this.serviceQNamesToAnnotations = ImmutableMap.copyOf(modifiableServiceQNamesToAnnotations); LOG.trace("factoryNamesToQNames:{}", this.factoryNamesToQNames); } /** * Static constructor for config registry. Since only transaction can write to * this registry, it will return blank state. * * @return service reference registry */ public static CloseableServiceReferenceReadableRegistry createInitialSRLookupRegistry() { // since this is initial state, just throw exception: LookupRegistry lookupRegistry = new LookupRegistry() { @Override public Set lookupConfigBeans() { throw new UnsupportedOperationException(); } @Override public Set lookupConfigBeans(final String moduleName) { throw new UnsupportedOperationException(); } @Override public Set lookupConfigBeans(final String moduleName, final String instanceName) { throw new UnsupportedOperationException(); } @Override public ObjectName lookupConfigBean(final String moduleName, final String instanceName) throws InstanceNotFoundException { throw new UnsupportedOperationException(); } @Override public void checkConfigBeanExists(final ObjectName objectName) throws InstanceNotFoundException { throw new InstanceNotFoundException("Cannot find " + objectName + " - Tried to use mocking registry"); } @Override public Set getAvailableModuleFactoryQNames() { throw new UnsupportedOperationException(); } @Override public Set lookupRuntimeBeans() { throw new UnsupportedOperationException(); } @Override public Set lookupRuntimeBeans(final String moduleName, final String instanceName) { throw new UnsupportedOperationException(); } @Override public String toString() { return "initial"; } }; ServiceReferenceTransactionRegistratorFactory serviceReferenceRegistratorFactory = () -> new ServiceReferenceRegistrator() { @Override public String getNullableTransactionName() { throw new UnsupportedOperationException(); } @Override public ServiceReferenceJMXRegistration registerMBean(final ServiceReferenceMXBeanImpl object, final ObjectName on) throws InstanceAlreadyExistsException { throw new UnsupportedOperationException(); } @Override public void close() { } }; return new ServiceReferenceRegistryImpl(Collections.emptyMap(), lookupRegistry, serviceReferenceRegistratorFactory, false); } public static SearchableServiceReferenceWritableRegistry createSRWritableRegistry( final ServiceReferenceReadableRegistry oldReadableRegistry, final ConfigTransactionLookupRegistry txLookupRegistry, final Map> currentlyRegisteredFactories) { if (txLookupRegistry == null) { throw new IllegalArgumentException("txLookupRegistry is null"); } ServiceReferenceRegistryImpl old = (ServiceReferenceRegistryImpl) oldReadableRegistry; Map factories = extractFactoriesMap(currentlyRegisteredFactories); ServiceReferenceTransactionRegistratorFactory serviceReferenceRegistratorFactory = new ServiceReferenceTransactionRegistratorFactoryImpl(txLookupRegistry.getTxModuleJMXRegistrator(), txLookupRegistry.getTxModuleJMXRegistrator().getTransactionName()); ServiceReferenceRegistryImpl newRegistry = new ServiceReferenceRegistryImpl(factories, txLookupRegistry, serviceReferenceRegistratorFactory, true); copy(old, newRegistry, txLookupRegistry.getTransactionIdentifier().getName()); return newRegistry; } /** * Copy back state to config registry after commit. * * @param oldWritableRegistry * old registry * @param lookupRegistry * lookup registry * @param baseJMXRegistrator * JMX registrator * @return service reference */ public static CloseableServiceReferenceReadableRegistry createSRReadableRegistry( final ServiceReferenceWritableRegistry oldWritableRegistry, final LookupRegistry lookupRegistry, final BaseJMXRegistrator baseJMXRegistrator) { ServiceReferenceRegistryImpl old = (ServiceReferenceRegistryImpl) oldWritableRegistry; // even if factories do change, nothing in the mapping can change between // transactions ServiceReferenceTransactionRegistratorFactory serviceReferenceRegistratorFactory = new ServiceReferenceTransactionRegistratorFactoryImpl(baseJMXRegistrator); ServiceReferenceRegistryImpl newRegistry = new ServiceReferenceRegistryImpl(old.factories, lookupRegistry, serviceReferenceRegistratorFactory, false); copy(old, newRegistry, null); return newRegistry; } private static void copy(final ServiceReferenceRegistryImpl old, final ServiceReferenceRegistryImpl newRegistry, final String nullableDstTransactionName) { for (Entry> refNameEntry : old.managementBeans.entrySet()) { ObjectName currentImplementation; ObjectName currentImplementationSrc = refNameEntry.getValue().getKey().getCurrentImplementation(); if (nullableDstTransactionName != null) { currentImplementation = ObjectNameUtil.withTransactionName(currentImplementationSrc, nullableDstTransactionName); } else { currentImplementation = ObjectNameUtil.withoutTransactionName(currentImplementationSrc); } try { boolean skipChecks = true; newRegistry.saveServiceReference(refNameEntry.getKey(), currentImplementation, skipChecks); } catch (final InstanceNotFoundException e) { LOG.error("Cannot save service reference({}, {})", refNameEntry.getKey(), currentImplementation); throw new IllegalStateException("Possible code error", e); } } } private static Map extractFactoriesMap( final Map> currentlyRegisteredFactories) { Map result = new HashMap<>(); for (Entry> entry : currentlyRegisteredFactories.entrySet()) { result.put(entry.getKey(), entry.getValue().getKey()); } return result; } @Override public Map findServiceInterfaces( final ModuleIdentifier moduleIdentifier) { Map result = modulesToServiceRef .get(moduleIdentifier); if (result == null) { return Collections.emptyMap(); } return Collections.unmodifiableMap(result); } @Override public synchronized Set lookupServiceInterfaceNames(final ObjectName objectName) throws InstanceNotFoundException { lookupRegistry.checkConfigBeanExists(objectName); String factoryName = ObjectNameUtil.getFactoryName(objectName); Set serviceInterfaceAnnotations = factoryNamesToQNames.get(factoryName); if (serviceInterfaceAnnotations == null) { LOG.error("Possible error in code: cannot find factory annotations of '{}' extracted from ON {} in {}", factoryName, objectName, factoryNamesToQNames); throw new IllegalArgumentException("Cannot find factory with name " + factoryName); } return serviceInterfaceAnnotations; } @Override public synchronized String getServiceInterfaceName(final String namespace, final String localName) { Map ofNamespace = namespacesToAnnotations.get(namespace); if (ofNamespace == null) { LOG.error("Cannot find namespace {} in {}", namespace, namespacesToAnnotations); throw new IllegalArgumentException("Cannot find namespace " + namespace); } ServiceInterfaceAnnotation sia = ofNamespace.get(localName); if (sia == null) { LOG.error("Cannot find local name {} in namespace {}, found only {}", localName, namespace, ofNamespace); throw new IllegalArgumentException("Cannot find local name " + localName + " in namespace " + namespace); } return sia.value(); } // reading: @Override public synchronized Map> getServiceMapping() { Map> result = new HashMap<>(); for (Entry entry : refNames.entrySet()) { String name = entry.getKey().getServiceInterfaceQName(); Map innerMap = result.computeIfAbsent(name, k -> new HashMap<>()); innerMap.put(entry.getKey().getRefName(), getObjectName(entry.getValue())); } return result; } private ObjectName getObjectName(final ModuleIdentifier moduleIdentifier) { ObjectName on; try { on = lookupRegistry.lookupConfigBean(moduleIdentifier.getFactoryName(), moduleIdentifier.getInstanceName()); } catch (final InstanceNotFoundException e) { LOG.error("Cannot find instance {}", moduleIdentifier); throw new IllegalStateException("Cannot find instance " + moduleIdentifier, e); } return on; } @Override public synchronized ObjectName lookupConfigBeanByServiceInterfaceName(final String serviceInterfaceQName, final String refName) { ServiceReference serviceReference = new ServiceReference(serviceInterfaceQName, refName); ModuleIdentifier moduleIdentifier = refNames.get(serviceReference); if (moduleIdentifier == null) { LOG.error("Cannot find qname {} and refName {} in {}", serviceInterfaceQName, refName, refName); throw new IllegalArgumentException("Cannot find " + serviceReference); } return getObjectName(moduleIdentifier); } @Override public synchronized Map lookupServiceReferencesByServiceInterfaceName( final String serviceInterfaceQName) { Map> serviceMapping = getServiceMapping(); Map innerMap = serviceMapping.get(serviceInterfaceQName); if (innerMap == null) { LOG.error("Cannot find qname {} in {}", serviceInterfaceQName, refNames); throw new IllegalArgumentException("Cannot find " + serviceInterfaceQName); } return innerMap; } @Override public synchronized ObjectName getServiceReference(final String serviceInterfaceQName, final String refName) throws InstanceNotFoundException { ServiceReference serviceReference = new ServiceReference(serviceInterfaceQName, refName); if (!managementBeans.containsKey(serviceReference)) { throw new InstanceNotFoundException("Cannot find " + serviceReference); } return getServiceON(serviceReference); } @Override public synchronized void checkServiceReferenceExists(final ObjectName objectName) throws InstanceNotFoundException { String actualTransactionName = ObjectNameUtil.getTransactionName(objectName); String expectedTransactionName = serviceReferenceRegistrator.getNullableTransactionName(); if (writable && actualTransactionName == null || writable && !actualTransactionName.equals(expectedTransactionName)) { throw new IllegalArgumentException("Mismatched transaction name in " + objectName); } String serviceQName = ObjectNameUtil.getServiceQName(objectName); String referenceName = ObjectNameUtil.getReferenceName(objectName); ServiceReference serviceReference = new ServiceReference(serviceQName, referenceName); if (!refNames.containsKey(serviceReference)) { LOG.warn("Cannot find {} in {}", serviceReference, refNames); throw new InstanceNotFoundException("Service reference not found:" + objectName); } } // writing: private void assertWritable() { if (!writable) { throw new IllegalStateException("Cannot write to readable registry"); } } @Override public synchronized ObjectName saveServiceReference(final String serviceInterfaceName, final String refName, final ObjectName moduleON) throws InstanceNotFoundException { assertWritable(); ServiceReference serviceReference = new ServiceReference(serviceInterfaceName, refName); return saveServiceReference(serviceReference, moduleON); } private synchronized ObjectName saveServiceReference(final ServiceReference serviceReference, final ObjectName moduleON) throws InstanceNotFoundException { return saveServiceReference(serviceReference, moduleON, false); } private synchronized ObjectName saveServiceReference(final ServiceReference serviceReference, final ObjectName moduleON, final boolean skipChecks) throws InstanceNotFoundException { // make sure it is found if (!skipChecks) { lookupRegistry.checkConfigBeanExists(moduleON); } String factoryName = ObjectNameUtil.getFactoryName(moduleON); String instanceName = ObjectNameUtil.getInstanceName(moduleON); ModuleIdentifier moduleIdentifier = new ModuleIdentifier(factoryName, instanceName); // check that service interface name exist Set serviceInterfaceQNames = factoryNamesToQNames.get(moduleIdentifier.getFactoryName()); if (serviceInterfaceQNames == null) { LOG.error("Possible error in code: cannot find factoryName {} in {}, {}", moduleIdentifier.getFactoryName(), factoryNamesToQNames, moduleIdentifier); throw new IllegalStateException("Possible error in code: cannot find annotations of existing factory " + moduleIdentifier.getFactoryName()); } // supplied serviceInterfaceName must exist in this collection if (!serviceInterfaceQNames.contains(serviceReference.getServiceInterfaceQName())) { LOG.error("Cannot find qName {} with factory name {}, found {}", serviceReference.getServiceInterfaceQName(), moduleIdentifier.getFactoryName(), serviceInterfaceQNames); throw new IllegalArgumentException( "Cannot find service interface " + serviceReference.getServiceInterfaceQName() + " within factory " + moduleIdentifier.getFactoryName()); } // create service reference object name, put to mBeans ObjectName result = getServiceON(serviceReference); Entry mxBeanEntry = managementBeans.get(serviceReference); if (mxBeanEntry == null) { // create dummy mx bean ServiceReferenceMXBeanImpl dummyMXBean = new ServiceReferenceMXBeanImpl(moduleON); ServiceReferenceJMXRegistration dummyMXBeanRegistration; try { dummyMXBeanRegistration = serviceReferenceRegistrator.registerMBean(dummyMXBean, result); } catch (final InstanceAlreadyExistsException e) { throw new IllegalStateException("Possible error in code. Cannot register " + result, e); } managementBeans.put(serviceReference, new SimpleImmutableEntry<>(dummyMXBean, dummyMXBeanRegistration)); } else { // update mxBeanEntry.getKey().setCurrentImplementation(moduleON); } // save to refNames refNames.put(serviceReference, moduleIdentifier); Map refNamesToAnnotations = modulesToServiceRef .computeIfAbsent(moduleIdentifier, k -> new HashMap<>()); ServiceInterfaceAnnotation annotation = serviceQNamesToAnnotations .get(serviceReference.getServiceInterfaceQName()); Preconditions.checkNotNull(annotation, "Possible error in code, cannot find annotation for " + serviceReference); refNamesToAnnotations.put(annotation, serviceReference.getRefName()); return result; } private ObjectName getServiceON(final ServiceReference serviceReference) { if (writable) { return ObjectNameUtil.createTransactionServiceON(serviceReferenceRegistrator.getNullableTransactionName(), serviceReference.getServiceInterfaceQName(), serviceReference.getRefName()); } return ObjectNameUtil.createReadOnlyServiceON(serviceReference.getServiceInterfaceQName(), serviceReference.getRefName()); } @Override public synchronized void removeServiceReference(final String serviceInterfaceName, final String refName) throws InstanceNotFoundException { ServiceReference serviceReference = new ServiceReference(serviceInterfaceName, refName); removeServiceReference(serviceReference); } private synchronized void removeServiceReference(final ServiceReference serviceReference) throws InstanceNotFoundException { LOG.debug("Removing service reference {} from {}", serviceReference, this); assertWritable(); // is the qName known? if (!allQNames.contains(serviceReference.getServiceInterfaceQName())) { LOG.error("Cannot find qname {} in {}", serviceReference.getServiceInterfaceQName(), allQNames); throw new IllegalArgumentException( "Cannot find service interface " + serviceReference.getServiceInterfaceQName()); } ModuleIdentifier removed = refNames.remove(serviceReference); if (removed == null) { throw new InstanceNotFoundException("Cannot find " + serviceReference.getServiceInterfaceQName()); } Entry entry = managementBeans.remove(serviceReference); if (entry == null) { throw new IllegalStateException("Possible code error: cannot remove from mBeans: " + serviceReference); } entry.getValue().close(); } @Override public synchronized void removeAllServiceReferences() { assertWritable(); for (ServiceReference serviceReference : managementBeans.keySet()) { try { removeServiceReference(serviceReference); } catch (final InstanceNotFoundException e) { throw new IllegalStateException("Possible error in code", e); } } } @Override public synchronized boolean removeServiceReferences(final ObjectName moduleObjectName) throws InstanceNotFoundException { lookupRegistry.checkConfigBeanExists(moduleObjectName); String factoryName = ObjectNameUtil.getFactoryName(moduleObjectName); // check that service interface name exist Set serviceInterfaceQNames = factoryNamesToQNames.get(factoryName); return removeServiceReferences(moduleObjectName, serviceInterfaceQNames); } private boolean removeServiceReferences(final ObjectName moduleObjectName, final Set names) throws InstanceNotFoundException { ObjectNameUtil.checkType(moduleObjectName, ObjectNameUtil.TYPE_MODULE); assertWritable(); Set serviceReferencesLinkingTo = findServiceReferencesLinkingTo(moduleObjectName, names); for (ServiceReference sr : serviceReferencesLinkingTo) { removeServiceReference(sr); } return !serviceReferencesLinkingTo.isEmpty(); } private Set findServiceReferencesLinkingTo(final ObjectName moduleObjectName, final Set serviceInterfaceQNames) { String factoryName = ObjectNameUtil.getFactoryName(moduleObjectName); if (serviceInterfaceQNames == null) { LOG.warn("Possible error in code: cannot find factoryName {} in {}, object name {}", factoryName, factoryNamesToQNames, moduleObjectName); throw new IllegalStateException( "Possible error in code: cannot find annotations of existing factory " + factoryName); } String instanceName = ObjectNameUtil.getInstanceName(moduleObjectName); ModuleIdentifier moduleIdentifier = new ModuleIdentifier(factoryName, instanceName); Set result = new HashSet<>(); for (Entry entry : refNames.entrySet()) { if (entry.getValue().equals(moduleIdentifier)) { result.add(entry.getKey()); } } return result; } @Override public String toString() { return "ServiceReferenceRegistryImpl{" + "lookupRegistry=" + lookupRegistry + "refNames=" + refNames + ", factoryNamesToQNames=" + factoryNamesToQNames + '}'; } @Override public void close() { serviceReferenceRegistrator.close(); } }