2 * Copyright (c) 2013, 2017 Cisco 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.config.manager.impl.dependencyresolver;
10 import com.google.common.base.Preconditions;
11 import java.util.HashSet;
12 import java.util.LinkedHashSet;
14 import javax.annotation.concurrent.GuardedBy;
15 import javax.management.AttributeNotFoundException;
16 import javax.management.InstanceNotFoundException;
17 import javax.management.JMX;
18 import javax.management.MBeanException;
19 import javax.management.MBeanServer;
20 import javax.management.ObjectName;
21 import javax.management.ReflectionException;
22 import org.opendaylight.controller.config.api.DependencyResolver;
23 import org.opendaylight.controller.config.api.IdentityAttributeRef;
24 import org.opendaylight.controller.config.api.JmxAttribute;
25 import org.opendaylight.controller.config.api.JmxAttributeValidationException;
26 import org.opendaylight.controller.config.api.ModuleIdentifier;
27 import org.opendaylight.controller.config.api.ServiceReferenceReadableRegistry;
28 import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
29 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
30 import org.opendaylight.controller.config.manager.impl.TransactionStatus;
31 import org.opendaylight.controller.config.manager.impl.osgi.mapping.BindingContextProvider;
32 import org.opendaylight.controller.config.spi.Module;
33 import org.opendaylight.controller.config.spi.ModuleFactory;
34 import org.opendaylight.yangtools.yang.binding.BaseIdentity;
35 import org.opendaylight.yangtools.yang.common.QName;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
40 * Protect {@link org.opendaylight.controller.config.spi.Module#getInstance()}
41 * by creating proxy that would throw exception if those methods are called
42 * during validation. Tracks dependencies for ordering purposes.
44 public final class DependencyResolverImpl implements DependencyResolver, Comparable<DependencyResolverImpl> {
45 private static final Logger LOG = LoggerFactory.getLogger(DependencyResolverImpl.class);
47 private final ModulesHolder modulesHolder;
48 private final ModuleIdentifier name;
49 private final TransactionStatus transactionStatus;
51 private final Set<ModuleIdentifier> dependencies = new HashSet<>();
52 private final ServiceReferenceReadableRegistry readableRegistry;
53 private final BindingContextProvider bindingContextProvider;
54 private final String transactionName;
55 private final MBeanServer beanServer;
56 private Integer maxDependencyDepth;
58 public DependencyResolverImpl(final ModuleIdentifier currentModule, final TransactionStatus transactionStatus,
59 final ModulesHolder modulesHolder, final ServiceReferenceReadableRegistry readableRegistry,
60 final BindingContextProvider bindingContextProvider, final String transactionName,
61 final MBeanServer beanServer) {
62 this.bindingContextProvider = bindingContextProvider;
63 this.name = currentModule;
64 this.transactionStatus = transactionStatus;
65 this.modulesHolder = modulesHolder;
66 this.readableRegistry = readableRegistry;
67 this.transactionName = transactionName;
68 this.beanServer = beanServer;
74 // TODO: check for cycles
76 public void validateDependency(final Class<? extends AbstractServiceInterface> expectedServiceInterface,
77 final ObjectName dependentReadOnlyON, final JmxAttribute jmxAttribute) {
79 this.transactionStatus.checkNotCommitted();
80 if (expectedServiceInterface == null) {
81 throw new NullPointerException("Parameter 'expectedServiceInterface' is null");
83 if (jmxAttribute == null) {
84 throw new NullPointerException("Parameter 'jmxAttribute' is null");
87 JmxAttributeValidationException.checkNotNull(dependentReadOnlyON,
88 "is null, expected dependency implementing " + expectedServiceInterface, jmxAttribute);
90 // check that objectName belongs to this transaction - this should be
92 // in DynamicWritableWrapper
93 final boolean hasTransaction = ObjectNameUtil.getTransactionName(dependentReadOnlyON) != null;
94 JmxAttributeValidationException.checkCondition(!hasTransaction,
95 String.format("ObjectName should not contain " + "transaction name. %s set to %s. ", jmxAttribute,
99 final ObjectName newDependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
101 final ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(newDependentReadOnlyON,
102 ObjectNameUtil.TYPE_MODULE);
104 final ModuleFactory foundFactory = this.modulesHolder.findModuleFactory(moduleIdentifier, jmxAttribute);
106 final boolean implementsSI = foundFactory.isModuleImplementingServiceInterface(expectedServiceInterface);
108 final String message = String.format(
109 "Found module factory does not expose expected service interface. "
110 + "Module name is %s : %s, expected service interface %s, dependent module ON %s , "
112 foundFactory.getImplementationName(), foundFactory, expectedServiceInterface,
113 newDependentReadOnlyON, jmxAttribute);
114 throw new JmxAttributeValidationException(message, jmxAttribute);
116 synchronized (this) {
117 this.dependencies.add(moduleIdentifier);
121 // translate from serviceref to module ON
122 private ObjectName translateServiceRefIfPossible(final ObjectName dependentReadOnlyON) {
123 ObjectName translatedDependentReadOnlyON = dependentReadOnlyON;
124 if (ObjectNameUtil.isServiceReference(translatedDependentReadOnlyON)) {
125 final String serviceQName = ObjectNameUtil.getServiceQName(translatedDependentReadOnlyON);
126 final String refName = ObjectNameUtil.getReferenceName(translatedDependentReadOnlyON);
127 // strip again of transaction name
128 translatedDependentReadOnlyON = ObjectNameUtil.withoutTransactionName(
129 this.readableRegistry.lookupConfigBeanByServiceInterfaceName(serviceQName, refName));
131 return translatedDependentReadOnlyON;
134 // TODO: check for cycles
136 public <T> T resolveInstance(final Class<T> expectedType, final ObjectName dependentReadOnlyON,
137 final JmxAttribute jmxAttribute) {
138 final Module module = resolveModuleInstance(dependentReadOnlyON, jmxAttribute);
140 synchronized (this) {
141 this.dependencies.add(module.getIdentifier());
143 final AutoCloseable instance = module.getInstance();
144 if (instance == null) {
145 final String message = String.format(
146 "Error while %s resolving instance %s. getInstance() returned null. Expected type %s, attribute %s",
147 this.name, module.getIdentifier(), expectedType, jmxAttribute);
148 throw new JmxAttributeValidationException(message, jmxAttribute);
151 return expectedType.cast(instance);
152 } catch (final ClassCastException e) {
153 final String message = String.format(
154 "Instance cannot be cast to expected type. Instance class is %s, expected type %s , attribute %s",
155 instance.getClass(), expectedType, jmxAttribute);
156 throw new JmxAttributeValidationException(message, e, jmxAttribute);
160 private Module resolveModuleInstance(final ObjectName dependentReadOnlyON, final JmxAttribute jmxAttribute) {
161 Preconditions.checkArgument(dependentReadOnlyON != null, "dependentReadOnlyON");
162 Preconditions.checkArgument(jmxAttribute != null, "jmxAttribute");
163 final ObjectName translatedDependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
164 this.transactionStatus.checkCommitStarted();
165 this.transactionStatus.checkNotCommitted();
167 final ModuleIdentifier dependentModuleIdentifier = ObjectNameUtil.fromON(translatedDependentReadOnlyON,
168 ObjectNameUtil.TYPE_MODULE);
170 return Preconditions.checkNotNull(this.modulesHolder.findModule(dependentModuleIdentifier, jmxAttribute));
174 public boolean canReuseDependency(final ObjectName objectName, final JmxAttribute jmxAttribute) {
175 Preconditions.checkNotNull(objectName);
176 Preconditions.checkNotNull(jmxAttribute);
178 final Module currentModule = resolveModuleInstance(objectName, jmxAttribute);
179 final ModuleIdentifier identifier = currentModule.getIdentifier();
180 final ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = this.modulesHolder
181 .findModuleInternalTransactionalInfo(identifier);
183 if (moduleInternalTransactionalInfo.hasOldModule()) {
184 final Module oldModule = moduleInternalTransactionalInfo.getOldInternalInfo().getReadableModule()
186 return currentModule.canReuse(oldModule);
192 public <T extends BaseIdentity> Class<? extends T> resolveIdentity(final IdentityAttributeRef identityRef,
193 final Class<T> expectedBaseClass) {
194 final QName qName = QName.create(identityRef.getqNameOfIdentity());
195 final Class<?> deserialized = this.bindingContextProvider.getBindingContext().getIdentityClass(qName);
196 if (deserialized == null) {
197 throw new IllegalStateException("Unable to retrieve identity class for " + qName + ", null response from "
198 + this.bindingContextProvider.getBindingContext());
200 if (expectedBaseClass.isAssignableFrom(deserialized)) {
201 return (Class<T>) deserialized;
203 LOG.error("Cannot resolve class of identity {} : deserialized class {} is not a subclass of {}.", identityRef,
204 deserialized, expectedBaseClass);
205 throw new IllegalArgumentException(
206 "Deserialized identity " + deserialized + " cannot be cast to " + expectedBaseClass);
210 public <T extends BaseIdentity> void validateIdentity(final IdentityAttributeRef identityRef,
211 final Class<T> expectedBaseClass, final JmxAttribute jmxAttribute) {
212 resolveIdentity(identityRef, expectedBaseClass);
216 public int compareTo(final DependencyResolverImpl dependencyResolverImpl) {
217 this.transactionStatus.checkCommitStarted();
218 return Integer.compare(getMaxDependencyDepth(), dependencyResolverImpl.getMaxDependencyDepth());
221 int getMaxDependencyDepth() {
222 if (this.maxDependencyDepth == null) {
223 throw new IllegalStateException("Dependency depth was not computed");
225 return this.maxDependencyDepth;
228 void countMaxDependencyDepth(final DependencyResolverManager manager) {
229 // We can calculate the dependency after second phase commit was started
230 // Second phase commit starts after validation and validation adds the
231 // dependencies into the dependency resolver, which are necessary for the
233 // FIXME generated code for abstract module declares validate method as
235 // Overriding the validate would cause recreate every time instead of reuse +
236 // also possibly wrong close order if there is another module depending
237 this.transactionStatus.checkCommitStarted();
238 if (this.maxDependencyDepth == null) {
239 this.maxDependencyDepth = getMaxDepth(this, manager, new LinkedHashSet<>());
243 private static int getMaxDepth(final DependencyResolverImpl impl, final DependencyResolverManager manager,
244 final LinkedHashSet<ModuleIdentifier> chainForDetectingCycles) {
246 final LinkedHashSet<ModuleIdentifier> chainForDetectingCycles2 = new LinkedHashSet<>(chainForDetectingCycles);
247 chainForDetectingCycles2.add(impl.getIdentifier());
248 for (final ModuleIdentifier dependencyName : impl.dependencies) {
249 final DependencyResolverImpl dependentDRI = manager.getOrCreate(dependencyName);
250 if (chainForDetectingCycles2.contains(dependencyName)) {
251 throw new IllegalStateException(
252 String.format("Cycle detected, %s contains %s", chainForDetectingCycles2, dependencyName));
255 if (dependentDRI.maxDependencyDepth != null) {
256 subDepth = dependentDRI.maxDependencyDepth;
258 subDepth = getMaxDepth(dependentDRI, manager, chainForDetectingCycles2);
259 dependentDRI.maxDependencyDepth = subDepth;
261 if (subDepth + 1 > maxDepth) {
262 maxDepth = subDepth + 1;
265 impl.maxDependencyDepth = maxDepth;
270 public ModuleIdentifier getIdentifier() {
275 @SuppressWarnings("checkstyle:hiddenField")
276 public Object getAttribute(final ObjectName name, final String attribute)
277 throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException {
278 ObjectName newName = translateServiceRefIfPossible(name);
279 // add transaction name
280 newName = ObjectNameUtil.withTransactionName(newName, this.transactionName);
281 return this.beanServer.getAttribute(newName, attribute);
285 @SuppressWarnings("checkstyle:hiddenField")
286 public <T> T newMXBeanProxy(final ObjectName name, final Class<T> interfaceClass) {
287 ObjectName newName = translateServiceRefIfPossible(name);
288 // add transaction name
289 newName = ObjectNameUtil.withTransactionName(newName, this.transactionName);
290 return JMX.newMXBeanProxy(this.beanServer, newName, interfaceClass);