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 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 final class DependencyResolverImpl implements DependencyResolver,
45 Comparable<DependencyResolverImpl> {
46 private static final Logger LOG = LoggerFactory.getLogger(DependencyResolverImpl.class);
48 private final ModulesHolder modulesHolder;
49 private final ModuleIdentifier name;
50 private final TransactionStatus transactionStatus;
52 private final Set<ModuleIdentifier> dependencies = new HashSet<>();
53 private final ServiceReferenceReadableRegistry readableRegistry;
54 private final BindingContextProvider bindingContextProvider;
55 private final String transactionName;
56 private final MBeanServer mBeanServer;
57 private Integer maxDependencyDepth;
59 DependencyResolverImpl(ModuleIdentifier currentModule,
60 TransactionStatus transactionStatus, ModulesHolder modulesHolder,
61 ServiceReferenceReadableRegistry readableRegistry, BindingContextProvider bindingContextProvider,
62 String transactionName, MBeanServer mBeanServer) {
63 this.bindingContextProvider = bindingContextProvider;
64 this.name = currentModule;
65 this.transactionStatus = transactionStatus;
66 this.modulesHolder = modulesHolder;
67 this.readableRegistry = readableRegistry;
68 this.transactionName = transactionName;
69 this.mBeanServer = mBeanServer;
75 //TODO: check for cycles
77 public void validateDependency(
78 Class<? extends AbstractServiceInterface> expectedServiceInterface,
79 ObjectName dependentReadOnlyON, JmxAttribute jmxAttribute) {
81 transactionStatus.checkNotCommitted();
82 if (expectedServiceInterface == null) {
83 throw new NullPointerException(
84 "Parameter 'expectedServiceInterface' is null");
86 if (jmxAttribute == null) {
87 throw new NullPointerException("Parameter 'jmxAttribute' is null");
90 JmxAttributeValidationException.checkNotNull(dependentReadOnlyON,
91 "is null, expected dependency implementing "
92 + expectedServiceInterface, jmxAttribute
96 // check that objectName belongs to this transaction - this should be
98 // in DynamicWritableWrapper
99 boolean hasTransaction = ObjectNameUtil
100 .getTransactionName(dependentReadOnlyON) != null;
101 JmxAttributeValidationException.checkCondition(
103 String.format("ObjectName should not contain "
104 + "transaction name. %s set to %s. ", jmxAttribute,
109 ObjectName newDependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
111 ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(newDependentReadOnlyON, ObjectNameUtil
114 ModuleFactory foundFactory = modulesHolder.findModuleFactory(moduleIdentifier, jmxAttribute);
116 boolean implementsSI = foundFactory
117 .isModuleImplementingServiceInterface(expectedServiceInterface);
119 String message = String.format(
120 "Found module factory does not expose expected service interface. "
121 + "Module name is %s : %s, expected service interface %s, dependent module ON %s , "
123 foundFactory.getImplementationName(), foundFactory,
124 expectedServiceInterface, newDependentReadOnlyON,
127 throw new JmxAttributeValidationException(message, jmxAttribute);
129 synchronized (this) {
130 dependencies.add(moduleIdentifier);
134 // translate from serviceref to module ON
135 private ObjectName translateServiceRefIfPossible(ObjectName dependentReadOnlyON) {
136 ObjectName translatedDependentReadOnlyON = dependentReadOnlyON;
137 if (ObjectNameUtil.isServiceReference(translatedDependentReadOnlyON)) {
138 String serviceQName = ObjectNameUtil.getServiceQName(translatedDependentReadOnlyON);
139 String refName = ObjectNameUtil.getReferenceName(translatedDependentReadOnlyON);
140 translatedDependentReadOnlyON = ObjectNameUtil.withoutTransactionName( // strip again of transaction name
141 readableRegistry.lookupConfigBeanByServiceInterfaceName(serviceQName, refName));
143 return translatedDependentReadOnlyON;
149 //TODO: check for cycles
151 public <T> T resolveInstance(Class<T> expectedType, ObjectName dependentReadOnlyON,
152 JmxAttribute jmxAttribute) {
153 Module module = resolveModuleInstance(dependentReadOnlyON, jmxAttribute);
155 synchronized (this) {
156 dependencies.add(module.getIdentifier());
158 AutoCloseable instance = module.getInstance();
159 if (instance == null) {
160 String message = String.format(
161 "Error while %s resolving instance %s. getInstance() returned null. "
162 + "Expected type %s , attribute %s", name,
163 module.getIdentifier(), expectedType, jmxAttribute
165 throw new JmxAttributeValidationException(message, jmxAttribute);
168 return expectedType.cast(instance);
169 } catch (ClassCastException e) {
170 String message = String.format(
171 "Instance cannot be cast to expected type. Instance class is %s , "
172 + "expected type %s , attribute %s",
173 instance.getClass(), expectedType, jmxAttribute
175 throw new JmxAttributeValidationException(message, e, jmxAttribute);
179 private Module resolveModuleInstance(ObjectName dependentReadOnlyON,
180 JmxAttribute jmxAttribute) {
181 Preconditions.checkArgument(dependentReadOnlyON != null ,"dependentReadOnlyON");
182 Preconditions.checkArgument(jmxAttribute != null, "jmxAttribute");
183 ObjectName translatedDependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
184 transactionStatus.checkCommitStarted();
185 transactionStatus.checkNotCommitted();
187 ModuleIdentifier dependentModuleIdentifier = ObjectNameUtil.fromON(
188 translatedDependentReadOnlyON, ObjectNameUtil.TYPE_MODULE);
190 return Preconditions.checkNotNull(modulesHolder.findModule(dependentModuleIdentifier, jmxAttribute));
194 public boolean canReuseDependency(ObjectName objectName, JmxAttribute jmxAttribute) {
195 Preconditions.checkNotNull(objectName);
196 Preconditions.checkNotNull(jmxAttribute);
198 Module currentModule = resolveModuleInstance(objectName, jmxAttribute);
199 ModuleIdentifier identifier = currentModule.getIdentifier();
200 ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = modulesHolder.findModuleInternalTransactionalInfo(identifier);
202 if(moduleInternalTransactionalInfo.hasOldModule()) {
203 Module oldModule = moduleInternalTransactionalInfo.getOldInternalInfo().getReadableModule().getModule();
204 return currentModule.canReuse(oldModule);
210 public <T extends BaseIdentity> Class<? extends T> resolveIdentity(IdentityAttributeRef identityRef, Class<T> expectedBaseClass) {
211 final QName qName = QName.create(identityRef.getqNameOfIdentity());
212 Class<?> deserialized = bindingContextProvider.getBindingContext().getIdentityClass(qName);
213 if (deserialized == null) {
214 throw new IllegalStateException("Unable to retrieve identity class for " + qName + ", null response from "
215 + bindingContextProvider.getBindingContext());
217 if (expectedBaseClass.isAssignableFrom(deserialized)) {
218 return (Class<T>) deserialized;
220 LOG.error("Cannot resolve class of identity {} : deserialized class {} is not a subclass of {}.",
221 identityRef, deserialized, expectedBaseClass);
222 throw new IllegalArgumentException("Deserialized identity " + deserialized + " cannot be cast to " + expectedBaseClass);
227 public <T extends BaseIdentity> void validateIdentity(IdentityAttributeRef identityRef, Class<T> expectedBaseClass, JmxAttribute jmxAttribute) {
229 resolveIdentity(identityRef, expectedBaseClass);
230 } catch (Exception e) {
231 throw JmxAttributeValidationException.wrap(e, jmxAttribute);
236 public int compareTo(DependencyResolverImpl o) {
237 transactionStatus.checkCommitStarted();
238 return Integer.compare(getMaxDependencyDepth(),
239 o.getMaxDependencyDepth());
242 int getMaxDependencyDepth() {
243 if (maxDependencyDepth == null) {
244 throw new IllegalStateException("Dependency depth was not computed");
246 return maxDependencyDepth;
249 void countMaxDependencyDepth(DependencyResolverManager manager) {
250 // We can calculate the dependency after second phase commit was started
251 // Second phase commit starts after validation and validation adds the dependencies into the dependency resolver, which are necessary for the calculation
252 // FIXME generated code for abstract module declares validate method as non-final
253 // Overriding the validate would cause recreate every time instead of reuse + also possibly wrong close order if there is another module depending
254 transactionStatus.checkCommitStarted();
255 if (maxDependencyDepth == null) {
256 maxDependencyDepth = getMaxDepth(this, manager,
257 new LinkedHashSet<>());
261 private static int getMaxDepth(DependencyResolverImpl impl,
262 DependencyResolverManager manager,
263 LinkedHashSet<ModuleIdentifier> chainForDetectingCycles) {
265 LinkedHashSet<ModuleIdentifier> chainForDetectingCycles2 = new LinkedHashSet<>(
266 chainForDetectingCycles);
267 chainForDetectingCycles2.add(impl.getIdentifier());
268 for (ModuleIdentifier dependencyName : impl.dependencies) {
269 DependencyResolverImpl dependentDRI = manager
270 .getOrCreate(dependencyName);
271 if (chainForDetectingCycles2.contains(dependencyName)) {
272 throw new IllegalStateException(String.format(
273 "Cycle detected, %s contains %s",
274 chainForDetectingCycles2, dependencyName));
277 if (dependentDRI.maxDependencyDepth != null) {
278 subDepth = dependentDRI.maxDependencyDepth;
280 subDepth = getMaxDepth(dependentDRI, manager,
281 chainForDetectingCycles2);
282 dependentDRI.maxDependencyDepth = subDepth;
284 if (subDepth + 1 > maxDepth) {
285 maxDepth = subDepth + 1;
288 impl.maxDependencyDepth = maxDepth;
293 public ModuleIdentifier getIdentifier() {
298 public Object getAttribute(ObjectName name, String attribute)
299 throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException {
300 ObjectName newName = translateServiceRefIfPossible(name);
301 // add transaction name
302 newName = ObjectNameUtil.withTransactionName(newName, transactionName);
303 return mBeanServer.getAttribute(newName, attribute);
307 public <T> T newMXBeanProxy(ObjectName name, Class<T> interfaceClass) {
308 ObjectName newName = translateServiceRefIfPossible(name);
309 // add transaction name
310 newName = ObjectNameUtil.withTransactionName(newName, transactionName);
311 return JMX.newMXBeanProxy(mBeanServer, newName, interfaceClass);