e69aa8611af7f7aff96b543159a48b85b74e87c2
[controller.git] / opendaylight / config / config-manager / src / main / java / org / opendaylight / controller / config / manager / impl / dependencyresolver / DependencyResolverImpl.java
1 /*
2  * Copyright (c) 2013, 2017 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.config.manager.impl.dependencyresolver;
9
10 import com.google.common.base.Preconditions;
11 import java.util.HashSet;
12 import java.util.LinkedHashSet;
13 import java.util.Set;
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;
38
39 /**
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.
43  */
44 public final class DependencyResolverImpl implements DependencyResolver, Comparable<DependencyResolverImpl> {
45     private static final Logger LOG = LoggerFactory.getLogger(DependencyResolverImpl.class);
46
47     private final ModulesHolder modulesHolder;
48     private final ModuleIdentifier name;
49     private final TransactionStatus transactionStatus;
50     @GuardedBy("this")
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;
57
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;
69     }
70
71     /**
72      * {@inheritDoc}
73      */
74     // TODO: check for cycles
75     @Override
76     public void validateDependency(final Class<? extends AbstractServiceInterface> expectedServiceInterface,
77             final ObjectName dependentReadOnlyON, final JmxAttribute jmxAttribute) {
78
79         this.transactionStatus.checkNotCommitted();
80         if (expectedServiceInterface == null) {
81             throw new NullPointerException("Parameter 'expectedServiceInterface' is null");
82         }
83         if (jmxAttribute == null) {
84             throw new NullPointerException("Parameter 'jmxAttribute' is null");
85         }
86
87         JmxAttributeValidationException.checkNotNull(dependentReadOnlyON,
88                 "is null, expected dependency implementing " + expectedServiceInterface, jmxAttribute);
89
90         // check that objectName belongs to this transaction - this should be
91         // stripped
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,
96                         dependentReadOnlyON),
97                 jmxAttribute);
98
99         final ObjectName newDependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
100
101         final ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(newDependentReadOnlyON,
102                 ObjectNameUtil.TYPE_MODULE);
103
104         final ModuleFactory foundFactory = this.modulesHolder.findModuleFactory(moduleIdentifier, jmxAttribute);
105
106         final boolean implementsSI = foundFactory.isModuleImplementingServiceInterface(expectedServiceInterface);
107         if (!implementsSI) {
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 , "
111                             + "attribute %s",
112                     foundFactory.getImplementationName(), foundFactory, expectedServiceInterface,
113                     newDependentReadOnlyON, jmxAttribute);
114             throw new JmxAttributeValidationException(message, jmxAttribute);
115         }
116         synchronized (this) {
117             this.dependencies.add(moduleIdentifier);
118         }
119     }
120
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));
130         }
131         return translatedDependentReadOnlyON;
132     }
133
134     // TODO: check for cycles
135     @Override
136     public <T> T resolveInstance(final Class<T> expectedType, final ObjectName dependentReadOnlyON,
137             final JmxAttribute jmxAttribute) {
138         final Module module = resolveModuleInstance(dependentReadOnlyON, jmxAttribute);
139
140         synchronized (this) {
141             this.dependencies.add(module.getIdentifier());
142         }
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);
149         }
150         try {
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);
157         }
158     }
159
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();
166
167         final ModuleIdentifier dependentModuleIdentifier = ObjectNameUtil.fromON(translatedDependentReadOnlyON,
168                 ObjectNameUtil.TYPE_MODULE);
169
170         return Preconditions.checkNotNull(this.modulesHolder.findModule(dependentModuleIdentifier, jmxAttribute));
171     }
172
173     @Override
174     public boolean canReuseDependency(final ObjectName objectName, final JmxAttribute jmxAttribute) {
175         Preconditions.checkNotNull(objectName);
176         Preconditions.checkNotNull(jmxAttribute);
177
178         final Module currentModule = resolveModuleInstance(objectName, jmxAttribute);
179         final ModuleIdentifier identifier = currentModule.getIdentifier();
180         final ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = this.modulesHolder
181                 .findModuleInternalTransactionalInfo(identifier);
182
183         if (moduleInternalTransactionalInfo.hasOldModule()) {
184             final Module oldModule = moduleInternalTransactionalInfo.getOldInternalInfo().getReadableModule()
185                     .getModule();
186             return currentModule.canReuse(oldModule);
187         }
188         return false;
189     }
190
191     @Override
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());
199         }
200         if (expectedBaseClass.isAssignableFrom(deserialized)) {
201             return (Class<T>) deserialized;
202         }
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);
207     }
208
209     @Override
210     public <T extends BaseIdentity> void validateIdentity(final IdentityAttributeRef identityRef,
211             final Class<T> expectedBaseClass, final JmxAttribute jmxAttribute) {
212         resolveIdentity(identityRef, expectedBaseClass);
213     }
214
215     @Override
216     public int compareTo(final DependencyResolverImpl dependencyResolverImpl) {
217         this.transactionStatus.checkCommitStarted();
218         return Integer.compare(getMaxDependencyDepth(), dependencyResolverImpl.getMaxDependencyDepth());
219     }
220
221     int getMaxDependencyDepth() {
222         if (this.maxDependencyDepth == null) {
223             throw new IllegalStateException("Dependency depth was not computed");
224         }
225         return this.maxDependencyDepth;
226     }
227
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
232         // calculation
233         // FIXME generated code for abstract module declares validate method as
234         // non-final
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<>());
240         }
241     }
242
243     private static int getMaxDepth(final DependencyResolverImpl impl, final DependencyResolverManager manager,
244             final LinkedHashSet<ModuleIdentifier> chainForDetectingCycles) {
245         int maxDepth = 0;
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));
253             }
254             int subDepth;
255             if (dependentDRI.maxDependencyDepth != null) {
256                 subDepth = dependentDRI.maxDependencyDepth;
257             } else {
258                 subDepth = getMaxDepth(dependentDRI, manager, chainForDetectingCycles2);
259                 dependentDRI.maxDependencyDepth = subDepth;
260             }
261             if (subDepth + 1 > maxDepth) {
262                 maxDepth = subDepth + 1;
263             }
264         }
265         impl.maxDependencyDepth = maxDepth;
266         return maxDepth;
267     }
268
269     @Override
270     public ModuleIdentifier getIdentifier() {
271         return this.name;
272     }
273
274     @Override
275     public Object getAttribute(final ObjectName name, final String attribute)
276             throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException {
277         ObjectName newName = translateServiceRefIfPossible(name);
278         // add transaction name
279         newName = ObjectNameUtil.withTransactionName(newName, this.transactionName);
280         return this.beanServer.getAttribute(newName, attribute);
281     }
282
283     @Override
284     public <T> T newMXBeanProxy(final ObjectName name, final Class<T> interfaceClass) {
285         ObjectName newName = translateServiceRefIfPossible(name);
286         // add transaction name
287         newName = ObjectNameUtil.withTransactionName(newName, this.transactionName);
288         return JMX.newMXBeanProxy(this.beanServer, newName, interfaceClass);
289     }
290 }