2 * Copyright (c) 2013 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 static java.lang.String.format;
12 import com.google.common.base.Preconditions;
13 import java.util.HashSet;
14 import java.util.LinkedHashSet;
16 import javax.annotation.concurrent.GuardedBy;
17 import javax.management.AttributeNotFoundException;
18 import javax.management.InstanceNotFoundException;
19 import javax.management.JMX;
20 import javax.management.MBeanException;
21 import javax.management.MBeanServer;
22 import javax.management.ObjectName;
23 import javax.management.ReflectionException;
24 import org.opendaylight.controller.config.api.DependencyResolver;
25 import org.opendaylight.controller.config.api.IdentityAttributeRef;
26 import org.opendaylight.controller.config.api.JmxAttribute;
27 import org.opendaylight.controller.config.api.JmxAttributeValidationException;
28 import org.opendaylight.controller.config.api.ModuleIdentifier;
29 import org.opendaylight.controller.config.api.ServiceReferenceReadableRegistry;
30 import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
31 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
32 import org.opendaylight.controller.config.manager.impl.TransactionStatus;
33 import org.opendaylight.controller.config.spi.Module;
34 import org.opendaylight.controller.config.spi.ModuleFactory;
35 import org.opendaylight.yangtools.yang.binding.BaseIdentity;
36 import org.opendaylight.yangtools.yang.common.QName;
37 import org.opendaylight.yangtools.yang.data.impl.codec.CodecRegistry;
38 import org.opendaylight.yangtools.yang.data.impl.codec.IdentityCodec;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * Protect {@link org.opendaylight.controller.config.spi.Module#getInstance()}
44 * by creating proxy that would throw exception if those methods are called
45 * during validation. Tracks dependencies for ordering purposes.
47 final class DependencyResolverImpl implements DependencyResolver,
48 Comparable<DependencyResolverImpl> {
49 private static final Logger LOG = LoggerFactory.getLogger(DependencyResolverImpl.class);
51 private final ModulesHolder modulesHolder;
52 private final ModuleIdentifier name;
53 private final TransactionStatus transactionStatus;
55 private final Set<ModuleIdentifier> dependencies = new HashSet<>();
56 private final ServiceReferenceReadableRegistry readableRegistry;
57 private final CodecRegistry codecRegistry;
58 private final String transactionName;
59 private final MBeanServer mBeanServer;
61 DependencyResolverImpl(ModuleIdentifier currentModule,
62 TransactionStatus transactionStatus, ModulesHolder modulesHolder,
63 ServiceReferenceReadableRegistry readableRegistry, CodecRegistry codecRegistry,
64 String transactionName, MBeanServer mBeanServer) {
65 this.codecRegistry = codecRegistry;
66 this.name = currentModule;
67 this.transactionStatus = transactionStatus;
68 this.modulesHolder = modulesHolder;
69 this.readableRegistry = readableRegistry;
70 this.transactionName = transactionName;
71 this.mBeanServer = mBeanServer;
77 //TODO: check for cycles
79 public void validateDependency(
80 Class<? extends AbstractServiceInterface> expectedServiceInterface,
81 ObjectName dependentReadOnlyON, JmxAttribute jmxAttribute) {
83 transactionStatus.checkNotCommitted();
84 if (expectedServiceInterface == null) {
85 throw new NullPointerException(
86 "Parameter 'expectedServiceInterface' is null");
88 if (jmxAttribute == null) {
89 throw new NullPointerException("Parameter 'jmxAttribute' is null");
92 JmxAttributeValidationException.checkNotNull(dependentReadOnlyON,
93 "is null, expected dependency implementing "
94 + expectedServiceInterface, jmxAttribute
98 // check that objectName belongs to this transaction - this should be
100 // in DynamicWritableWrapper
101 boolean hasTransaction = ObjectNameUtil
102 .getTransactionName(dependentReadOnlyON) != null;
103 JmxAttributeValidationException.checkCondition(
104 hasTransaction == false,
105 format("ObjectName should not contain "
106 + "transaction name. %s set to %s. ", jmxAttribute,
111 ObjectName newDependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
113 ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(newDependentReadOnlyON, ObjectNameUtil
116 ModuleFactory foundFactory = modulesHolder.findModuleFactory(moduleIdentifier, jmxAttribute);
118 boolean implementsSI = foundFactory
119 .isModuleImplementingServiceInterface(expectedServiceInterface);
120 if (implementsSI == false) {
121 String message = format(
122 "Found module factory does not expose expected service interface. "
123 + "Module name is %s : %s, expected service interface %s, dependent module ON %s , "
125 foundFactory.getImplementationName(), foundFactory,
126 expectedServiceInterface, newDependentReadOnlyON,
129 throw new JmxAttributeValidationException(message, jmxAttribute);
131 synchronized (this) {
132 dependencies.add(moduleIdentifier);
136 // translate from serviceref to module ON
137 private ObjectName translateServiceRefIfPossible(ObjectName dependentReadOnlyON) {
138 ObjectName translatedDependentReadOnlyON = dependentReadOnlyON;
139 if (ObjectNameUtil.isServiceReference(translatedDependentReadOnlyON)) {
140 String serviceQName = ObjectNameUtil.getServiceQName(translatedDependentReadOnlyON);
141 String refName = ObjectNameUtil.getReferenceName(translatedDependentReadOnlyON);
142 translatedDependentReadOnlyON = ObjectNameUtil.withoutTransactionName( // strip again of transaction name
143 readableRegistry.lookupConfigBeanByServiceInterfaceName(serviceQName, refName));
145 return translatedDependentReadOnlyON;
151 //TODO: check for cycles
153 public <T> T resolveInstance(Class<T> expectedType, ObjectName dependentReadOnlyON,
154 JmxAttribute jmxAttribute) {
155 Module module = resolveModuleInstance(dependentReadOnlyON, jmxAttribute);
157 synchronized (this) {
158 dependencies.add(module.getIdentifier());
160 AutoCloseable instance = module.getInstance();
161 if (instance == null) {
162 String message = format(
163 "Error while %s resolving instance %s. getInstance() returned null. "
164 + "Expected type %s , attribute %s", name,
165 module.getIdentifier(), expectedType, jmxAttribute
167 throw new JmxAttributeValidationException(message, jmxAttribute);
170 return expectedType.cast(instance);
171 } catch (ClassCastException e) {
172 String message = format(
173 "Instance cannot be cast to expected type. Instance class is %s , "
174 + "expected type %s , attribute %s",
175 instance.getClass(), expectedType, jmxAttribute
177 throw new JmxAttributeValidationException(message, e, jmxAttribute);
181 private Module resolveModuleInstance(ObjectName dependentReadOnlyON,
182 JmxAttribute jmxAttribute) {
183 Preconditions.checkArgument(dependentReadOnlyON != null ,"dependentReadOnlyON");
184 Preconditions.checkArgument(jmxAttribute != null, "jmxAttribute");
185 ObjectName translatedDependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
186 transactionStatus.checkCommitStarted();
187 transactionStatus.checkNotCommitted();
189 ModuleIdentifier dependentModuleIdentifier = ObjectNameUtil.fromON(
190 translatedDependentReadOnlyON, ObjectNameUtil.TYPE_MODULE);
192 return Preconditions.checkNotNull(modulesHolder.findModule(dependentModuleIdentifier, jmxAttribute));
196 public boolean canReuseDependency(ObjectName objectName, JmxAttribute jmxAttribute) {
197 Preconditions.checkNotNull(objectName);
198 Preconditions.checkNotNull(jmxAttribute);
200 Module currentModule = resolveModuleInstance(objectName, jmxAttribute);
201 ModuleIdentifier identifier = currentModule.getIdentifier();
202 ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = modulesHolder.findModuleInternalTransactionalInfo(identifier);
204 if(moduleInternalTransactionalInfo.hasOldModule()) {
205 Module oldModule = moduleInternalTransactionalInfo.getOldInternalInfo().getReadableModule().getModule();
206 return currentModule.canReuse(oldModule);
212 public <T extends BaseIdentity> Class<? extends T> resolveIdentity(IdentityAttributeRef identityRef, Class<T> expectedBaseClass) {
213 final QName qName = QName.create(identityRef.getqNameOfIdentity());
214 IdentityCodec<?> identityCodec = codecRegistry.getIdentityCodec();
215 Class<? extends BaseIdentity> deserialized = identityCodec.deserialize(qName);
216 if (deserialized == null) {
217 throw new IllegalStateException("Unable to retrieve identity class for " + qName + ", null response from "
220 if (expectedBaseClass.isAssignableFrom(deserialized)) {
221 return (Class<T>) deserialized;
223 LOG.error("Cannot resolve class of identity {} : deserialized class {} is not a subclass of {}.",
224 identityRef, deserialized, expectedBaseClass);
225 throw new IllegalArgumentException("Deserialized identity " + deserialized + " cannot be cast to " + expectedBaseClass);
230 public <T extends BaseIdentity> void validateIdentity(IdentityAttributeRef identityRef, Class<T> expectedBaseClass, JmxAttribute jmxAttribute) {
232 resolveIdentity(identityRef, expectedBaseClass);
233 } catch (Exception e) {
234 throw JmxAttributeValidationException.wrap(e, jmxAttribute);
239 public int compareTo(DependencyResolverImpl o) {
240 transactionStatus.checkCommitStarted();
241 return Integer.compare(getMaxDependencyDepth(),
242 o.getMaxDependencyDepth());
245 private Integer maxDependencyDepth;
247 int getMaxDependencyDepth() {
248 if (maxDependencyDepth == null) {
249 throw new IllegalStateException("Dependency depth was not computed");
251 return maxDependencyDepth;
254 void countMaxDependencyDepth(DependencyResolverManager manager) {
255 // We can calculate the dependency after second phase commit was started
256 // Second phase commit starts after validation and validation adds the dependencies into the dependency resolver, which are necessary for the calculation
257 // FIXME generated code for abstract module declares validate method as non-final
258 // Overriding the validate would cause recreate every time instead of reuse + also possibly wrong close order if there is another module depending
259 transactionStatus.checkCommitStarted();
260 if (maxDependencyDepth == null) {
261 maxDependencyDepth = getMaxDepth(this, manager,
262 new LinkedHashSet<ModuleIdentifier>());
266 private static int getMaxDepth(DependencyResolverImpl impl,
267 DependencyResolverManager manager,
268 LinkedHashSet<ModuleIdentifier> chainForDetectingCycles) {
270 LinkedHashSet<ModuleIdentifier> chainForDetectingCycles2 = new LinkedHashSet<>(
271 chainForDetectingCycles);
272 chainForDetectingCycles2.add(impl.getIdentifier());
273 for (ModuleIdentifier dependencyName : impl.dependencies) {
274 DependencyResolverImpl dependentDRI = manager
275 .getOrCreate(dependencyName);
276 if (chainForDetectingCycles2.contains(dependencyName)) {
277 throw new IllegalStateException(format(
278 "Cycle detected, %s contains %s",
279 chainForDetectingCycles2, dependencyName));
282 if (dependentDRI.maxDependencyDepth != null) {
283 subDepth = dependentDRI.maxDependencyDepth;
285 subDepth = getMaxDepth(dependentDRI, manager,
286 chainForDetectingCycles2);
287 dependentDRI.maxDependencyDepth = subDepth;
289 if (subDepth + 1 > maxDepth) {
290 maxDepth = subDepth + 1;
293 impl.maxDependencyDepth = maxDepth;
298 public ModuleIdentifier getIdentifier() {
303 public Object getAttribute(ObjectName name, String attribute)
304 throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException {
305 ObjectName newName = translateServiceRefIfPossible(name);
306 // add transaction name
307 newName = ObjectNameUtil.withTransactionName(newName, transactionName);
308 return mBeanServer.getAttribute(newName, attribute);
312 public <T> T newMXBeanProxy(ObjectName name, Class<T> interfaceClass) {
313 ObjectName newName = translateServiceRefIfPossible(name);
314 // add transaction name
315 newName = ObjectNameUtil.withTransactionName(newName, transactionName);
316 return JMX.newMXBeanProxy(mBeanServer, newName, interfaceClass);