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 java.util.HashSet;
13 import java.util.LinkedHashSet;
15 import javax.annotation.concurrent.GuardedBy;
16 import javax.management.AttributeNotFoundException;
17 import javax.management.InstanceNotFoundException;
18 import javax.management.JMX;
19 import javax.management.MBeanException;
20 import javax.management.MBeanServer;
21 import javax.management.ObjectName;
22 import javax.management.ReflectionException;
23 import org.opendaylight.controller.config.api.DependencyResolver;
24 import org.opendaylight.controller.config.api.IdentityAttributeRef;
25 import org.opendaylight.controller.config.api.JmxAttribute;
26 import org.opendaylight.controller.config.api.JmxAttributeValidationException;
27 import org.opendaylight.controller.config.api.ModuleIdentifier;
28 import org.opendaylight.controller.config.api.ServiceReferenceReadableRegistry;
29 import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
30 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
31 import org.opendaylight.controller.config.manager.impl.TransactionStatus;
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.opendaylight.yangtools.yang.data.impl.codec.CodecRegistry;
37 import org.opendaylight.yangtools.yang.data.impl.codec.IdentityCodec;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * Protect {@link org.opendaylight.controller.config.spi.Module#getInstance()}
43 * by creating proxy that would throw exception if those methods are called
44 * during validation. Tracks dependencies for ordering purposes.
46 final class DependencyResolverImpl implements DependencyResolver,
47 Comparable<DependencyResolverImpl> {
48 private static final Logger LOGGER = LoggerFactory.getLogger(DependencyResolverImpl.class);
50 private final ModulesHolder modulesHolder;
51 private final ModuleIdentifier name;
52 private final TransactionStatus transactionStatus;
54 private final Set<ModuleIdentifier> dependencies = new HashSet<>();
55 private final ServiceReferenceReadableRegistry readableRegistry;
56 private final CodecRegistry codecRegistry;
57 private final String transactionName;
58 private final MBeanServer mBeanServer;
60 DependencyResolverImpl(ModuleIdentifier currentModule,
61 TransactionStatus transactionStatus, ModulesHolder modulesHolder,
62 ServiceReferenceReadableRegistry readableRegistry, CodecRegistry codecRegistry,
63 String transactionName, MBeanServer mBeanServer) {
64 this.codecRegistry = codecRegistry;
65 this.name = currentModule;
66 this.transactionStatus = transactionStatus;
67 this.modulesHolder = modulesHolder;
68 this.readableRegistry = readableRegistry;
69 this.transactionName = transactionName;
70 this.mBeanServer = mBeanServer;
76 //TODO: check for cycles
78 public void validateDependency(
79 Class<? extends AbstractServiceInterface> expectedServiceInterface,
80 ObjectName dependentReadOnlyON, JmxAttribute jmxAttribute) {
82 transactionStatus.checkNotCommitted();
83 if (expectedServiceInterface == null) {
84 throw new NullPointerException(
85 "Parameter 'expectedServiceInterface' is null");
87 if (jmxAttribute == null) {
88 throw new NullPointerException("Parameter 'jmxAttribute' is null");
91 JmxAttributeValidationException.checkNotNull(dependentReadOnlyON,
92 "is null, expected dependency implementing "
93 + expectedServiceInterface, jmxAttribute
97 // check that objectName belongs to this transaction - this should be
99 // in DynamicWritableWrapper
100 boolean hasTransaction = ObjectNameUtil
101 .getTransactionName(dependentReadOnlyON) != null;
102 JmxAttributeValidationException.checkCondition(
103 hasTransaction == false,
104 format("ObjectName should not contain "
105 + "transaction name. %s set to %s. ", jmxAttribute,
110 ObjectName newDependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
112 ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(newDependentReadOnlyON, ObjectNameUtil
115 ModuleFactory foundFactory = modulesHolder.findModuleFactory(moduleIdentifier, jmxAttribute);
117 boolean implementsSI = foundFactory
118 .isModuleImplementingServiceInterface(expectedServiceInterface);
119 if (implementsSI == false) {
120 String message = format(
121 "Found module factory does not expose expected service interface. "
122 + "Module name is %s : %s, expected service interface %s, dependent module ON %s , "
124 foundFactory.getImplementationName(), foundFactory,
125 expectedServiceInterface, newDependentReadOnlyON,
128 throw new JmxAttributeValidationException(message, jmxAttribute);
130 synchronized (this) {
131 dependencies.add(moduleIdentifier);
135 // translate from serviceref to module ON
136 private ObjectName translateServiceRefIfPossible(ObjectName dependentReadOnlyON) {
137 ObjectName translatedDependentReadOnlyON = dependentReadOnlyON;
138 if (ObjectNameUtil.isServiceReference(translatedDependentReadOnlyON)) {
139 String serviceQName = ObjectNameUtil.getServiceQName(translatedDependentReadOnlyON);
140 String refName = ObjectNameUtil.getReferenceName(translatedDependentReadOnlyON);
141 translatedDependentReadOnlyON = ObjectNameUtil.withoutTransactionName( // strip again of transaction name
142 readableRegistry.lookupConfigBeanByServiceInterfaceName(serviceQName, refName));
144 return translatedDependentReadOnlyON;
150 //TODO: check for cycles
152 public <T> T resolveInstance(Class<T> expectedType, ObjectName dependentReadOnlyON,
153 JmxAttribute jmxAttribute) {
154 if (expectedType == null || dependentReadOnlyON == null || jmxAttribute == null) {
155 throw new IllegalArgumentException(format(
156 "Null parameters not allowed, got %s %s %s", expectedType,
157 dependentReadOnlyON, jmxAttribute));
159 ObjectName translatedDependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
160 transactionStatus.checkCommitStarted();
161 transactionStatus.checkNotCommitted();
163 ModuleIdentifier dependentModuleIdentifier = ObjectNameUtil.fromON(
164 translatedDependentReadOnlyON, ObjectNameUtil.TYPE_MODULE);
165 Module module = modulesHolder.findModule(dependentModuleIdentifier,
167 synchronized (this) {
168 dependencies.add(dependentModuleIdentifier);
170 AutoCloseable instance = module.getInstance();
171 if (instance == null) {
172 String message = format(
173 "Error while %s resolving instance %s. getInstance() returned null. "
174 + "Expected type %s , attribute %s", name,
175 dependentModuleIdentifier, expectedType, jmxAttribute
177 throw new JmxAttributeValidationException(message, jmxAttribute);
180 return expectedType.cast(instance);
181 } catch (ClassCastException e) {
182 String message = format(
183 "Instance cannot be cast to expected type. Instance class is %s , "
184 + "expected type %s , attribute %s",
185 instance.getClass(), expectedType, jmxAttribute
187 throw new JmxAttributeValidationException(message, e, jmxAttribute);
192 public <T extends BaseIdentity> Class<? extends T> resolveIdentity(IdentityAttributeRef identityRef, Class<T> expectedBaseClass) {
193 final QName qName = QName.create(identityRef.getqNameOfIdentity());
194 IdentityCodec<?> identityCodec = codecRegistry.getIdentityCodec();
195 Class<? extends BaseIdentity> deserialized = identityCodec.deserialize(qName);
196 if (deserialized == null) {
197 throw new IllegalStateException("Unable to retrieve identity class for " + qName + ", null response from "
200 if (expectedBaseClass.isAssignableFrom(deserialized)) {
201 return (Class<T>) deserialized;
203 LOGGER.error("Cannot resolve class of identity {} : deserialized class {} is not a subclass of {}.",
204 identityRef, deserialized, expectedBaseClass);
205 throw new IllegalArgumentException("Deserialized identity " + deserialized + " cannot be cast to " + expectedBaseClass);
210 public <T extends BaseIdentity> void validateIdentity(IdentityAttributeRef identityRef, Class<T> expectedBaseClass, JmxAttribute jmxAttribute) {
212 resolveIdentity(identityRef, expectedBaseClass);
213 } catch (Exception e) {
214 throw JmxAttributeValidationException.wrap(e, jmxAttribute);
219 public int compareTo(DependencyResolverImpl o) {
220 transactionStatus.checkCommitted();
221 return Integer.compare(getMaxDependencyDepth(),
222 o.getMaxDependencyDepth());
225 private Integer maxDependencyDepth;
227 int getMaxDependencyDepth() {
228 if (maxDependencyDepth == null) {
229 throw new IllegalStateException("Dependency depth was not computed");
231 return maxDependencyDepth;
234 void countMaxDependencyDepth(DependencyResolverManager manager) {
235 transactionStatus.checkCommitted();
236 if (maxDependencyDepth == null) {
237 maxDependencyDepth = getMaxDepth(this, manager,
238 new LinkedHashSet<ModuleIdentifier>());
242 private static int getMaxDepth(DependencyResolverImpl impl,
243 DependencyResolverManager manager,
244 LinkedHashSet<ModuleIdentifier> chainForDetectingCycles) {
246 LinkedHashSet<ModuleIdentifier> chainForDetectingCycles2 = new LinkedHashSet<>(
247 chainForDetectingCycles);
248 chainForDetectingCycles2.add(impl.getIdentifier());
249 for (ModuleIdentifier dependencyName : impl.dependencies) {
250 DependencyResolverImpl dependentDRI = manager
251 .getOrCreate(dependencyName);
252 if (chainForDetectingCycles2.contains(dependencyName)) {
253 throw new IllegalStateException(format(
254 "Cycle detected, %s contains %s",
255 chainForDetectingCycles2, dependencyName));
258 if (dependentDRI.maxDependencyDepth != null) {
259 subDepth = dependentDRI.maxDependencyDepth;
261 subDepth = getMaxDepth(dependentDRI, manager,
262 chainForDetectingCycles2);
263 dependentDRI.maxDependencyDepth = subDepth;
265 if (subDepth + 1 > maxDepth) {
266 maxDepth = subDepth + 1;
269 impl.maxDependencyDepth = maxDepth;
274 public ModuleIdentifier getIdentifier() {
279 public Object getAttribute(ObjectName name, String attribute)
280 throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException {
281 ObjectName newName = translateServiceRefIfPossible(name);
282 // add transaction name
283 newName = ObjectNameUtil.withTransactionName(newName, transactionName);
284 return mBeanServer.getAttribute(newName, attribute);
288 public <T> T newMXBeanProxy(ObjectName name, Class<T> interfaceClass) {
289 ObjectName newName = translateServiceRefIfPossible(name);
290 // add transaction name
291 newName = ObjectNameUtil.withTransactionName(newName, transactionName);
292 return JMX.newMXBeanProxy(mBeanServer, newName, interfaceClass);