Merge "Remove unnecessary declaration of <prerequisites> in protocol-framework"
[controller.git] / opendaylight / config / config-manager / src / main / java / org / opendaylight / controller / config / manager / impl / dependencyresolver / DependencyResolverImpl.java
1 /*
2  * Copyright (c) 2013 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 static java.lang.String.format;
11
12 import com.google.common.base.Preconditions;
13 import java.util.HashSet;
14 import java.util.LinkedHashSet;
15 import java.util.Set;
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;
41
42 /**
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.
46  */
47 final class DependencyResolverImpl implements DependencyResolver,
48         Comparable<DependencyResolverImpl> {
49     private static final Logger LOG = LoggerFactory.getLogger(DependencyResolverImpl.class);
50
51     private final ModulesHolder modulesHolder;
52     private final ModuleIdentifier name;
53     private final TransactionStatus transactionStatus;
54     @GuardedBy("this")
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;
60
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;
72     }
73
74     /**
75      * {@inheritDoc}
76      */
77     //TODO: check for cycles
78     @Override
79     public void validateDependency(
80             Class<? extends AbstractServiceInterface> expectedServiceInterface,
81             ObjectName dependentReadOnlyON, JmxAttribute jmxAttribute) {
82
83         transactionStatus.checkNotCommitted();
84         if (expectedServiceInterface == null) {
85             throw new NullPointerException(
86                     "Parameter 'expectedServiceInterface' is null");
87         }
88         if (jmxAttribute == null) {
89             throw new NullPointerException("Parameter 'jmxAttribute' is null");
90         }
91
92         JmxAttributeValidationException.checkNotNull(dependentReadOnlyON,
93                 "is null, expected dependency implementing "
94                         + expectedServiceInterface, jmxAttribute
95         );
96
97
98         // check that objectName belongs to this transaction - this should be
99         // stripped
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,
107                         dependentReadOnlyON
108             ), jmxAttribute
109         );
110
111         ObjectName newDependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
112
113         ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(newDependentReadOnlyON, ObjectNameUtil
114                 .TYPE_MODULE);
115
116         ModuleFactory foundFactory = modulesHolder.findModuleFactory(moduleIdentifier, jmxAttribute);
117
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 , "
124                             + "attribute %s",
125                     foundFactory.getImplementationName(), foundFactory,
126                     expectedServiceInterface, newDependentReadOnlyON,
127                     jmxAttribute
128             );
129             throw new JmxAttributeValidationException(message, jmxAttribute);
130         }
131         synchronized (this) {
132             dependencies.add(moduleIdentifier);
133         }
134     }
135
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));
144         }
145         return translatedDependentReadOnlyON;
146     }
147
148     /**
149      * {@inheritDoc}
150      */
151     //TODO: check for cycles
152     @Override
153     public <T> T resolveInstance(Class<T> expectedType, ObjectName dependentReadOnlyON,
154                                  JmxAttribute jmxAttribute) {
155         Module module = resolveModuleInstance(dependentReadOnlyON, jmxAttribute);
156
157         synchronized (this) {
158             dependencies.add(module.getIdentifier());
159         }
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
166             );
167             throw new JmxAttributeValidationException(message, jmxAttribute);
168         }
169         try {
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
176             );
177             throw new JmxAttributeValidationException(message, e, jmxAttribute);
178         }
179     }
180
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();
188
189         ModuleIdentifier dependentModuleIdentifier = ObjectNameUtil.fromON(
190                 translatedDependentReadOnlyON, ObjectNameUtil.TYPE_MODULE);
191
192         return Preconditions.checkNotNull(modulesHolder.findModule(dependentModuleIdentifier, jmxAttribute));
193     }
194
195     @Override
196     public boolean canReuseDependency(ObjectName objectName, JmxAttribute jmxAttribute) {
197         Preconditions.checkNotNull(objectName);
198         Preconditions.checkNotNull(jmxAttribute);
199
200         Module currentModule = resolveModuleInstance(objectName, jmxAttribute);
201         ModuleIdentifier identifier = currentModule.getIdentifier();
202         ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = modulesHolder.findModuleInternalTransactionalInfo(identifier);
203
204         if(moduleInternalTransactionalInfo.hasOldModule()) {
205             Module oldModule = moduleInternalTransactionalInfo.getOldInternalInfo().getReadableModule().getModule();
206             return currentModule.canReuse(oldModule);
207         }
208         return false;
209     }
210
211     @Override
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 "
218                     + codecRegistry);
219         }
220         if (expectedBaseClass.isAssignableFrom(deserialized)) {
221             return (Class<T>) deserialized;
222         } else {
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);
226         }
227     }
228
229     @Override
230     public <T extends BaseIdentity> void validateIdentity(IdentityAttributeRef identityRef, Class<T> expectedBaseClass, JmxAttribute jmxAttribute) {
231         try {
232             resolveIdentity(identityRef, expectedBaseClass);
233         } catch (Exception e) {
234             throw JmxAttributeValidationException.wrap(e, jmxAttribute);
235         }
236     }
237
238     @Override
239     public int compareTo(DependencyResolverImpl o) {
240         transactionStatus.checkCommitStarted();
241         return Integer.compare(getMaxDependencyDepth(),
242                 o.getMaxDependencyDepth());
243     }
244
245     private Integer maxDependencyDepth;
246
247     int getMaxDependencyDepth() {
248         if (maxDependencyDepth == null) {
249             throw new IllegalStateException("Dependency depth was not computed");
250         }
251         return maxDependencyDepth;
252     }
253
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>());
263         }
264     }
265
266     private static int getMaxDepth(DependencyResolverImpl impl,
267                                    DependencyResolverManager manager,
268                                    LinkedHashSet<ModuleIdentifier> chainForDetectingCycles) {
269         int maxDepth = 0;
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));
280             }
281             int subDepth;
282             if (dependentDRI.maxDependencyDepth != null) {
283                 subDepth = dependentDRI.maxDependencyDepth;
284             } else {
285                 subDepth = getMaxDepth(dependentDRI, manager,
286                         chainForDetectingCycles2);
287                 dependentDRI.maxDependencyDepth = subDepth;
288             }
289             if (subDepth + 1 > maxDepth) {
290                 maxDepth = subDepth + 1;
291             }
292         }
293         impl.maxDependencyDepth = maxDepth;
294         return maxDepth;
295     }
296
297     @Override
298     public ModuleIdentifier getIdentifier() {
299         return name;
300     }
301
302     @Override
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);
309     }
310
311     @Override
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);
317     }
318 }