eb3676ccebff5625db2f68b64b9f834488433ea2
[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 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 final class DependencyResolverImpl implements DependencyResolver,
45         Comparable<DependencyResolverImpl> {
46     private static final Logger LOG = LoggerFactory.getLogger(DependencyResolverImpl.class);
47
48     private final ModulesHolder modulesHolder;
49     private final ModuleIdentifier name;
50     private final TransactionStatus transactionStatus;
51     @GuardedBy("this")
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;
58
59     DependencyResolverImpl(final ModuleIdentifier currentModule,
60                            final TransactionStatus transactionStatus, final ModulesHolder modulesHolder,
61                            final ServiceReferenceReadableRegistry readableRegistry, final BindingContextProvider bindingContextProvider,
62                            final String transactionName, final 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;
70     }
71
72     /**
73      * {@inheritDoc}
74      */
75     //TODO: check for cycles
76     @Override
77     public void validateDependency(
78             final Class<? extends AbstractServiceInterface> expectedServiceInterface,
79             final ObjectName dependentReadOnlyON, final JmxAttribute jmxAttribute) {
80
81         this.transactionStatus.checkNotCommitted();
82         if (expectedServiceInterface == null) {
83             throw new NullPointerException(
84                     "Parameter 'expectedServiceInterface' is null");
85         }
86         if (jmxAttribute == null) {
87             throw new NullPointerException("Parameter 'jmxAttribute' is null");
88         }
89
90         JmxAttributeValidationException.checkNotNull(dependentReadOnlyON,
91                 "is null, expected dependency implementing "
92                         + expectedServiceInterface, jmxAttribute
93         );
94
95
96         // check that objectName belongs to this transaction - this should be
97         // stripped
98         // in DynamicWritableWrapper
99         final boolean hasTransaction = ObjectNameUtil
100                 .getTransactionName(dependentReadOnlyON) != null;
101         JmxAttributeValidationException.checkCondition(
102                 !hasTransaction,
103                 String.format("ObjectName should not contain "
104                                 + "transaction name. %s set to %s. ", jmxAttribute,
105                         dependentReadOnlyON
106             ), jmxAttribute
107         );
108
109         final ObjectName newDependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
110
111         final ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(newDependentReadOnlyON, ObjectNameUtil
112                 .TYPE_MODULE);
113
114         final ModuleFactory foundFactory = this.modulesHolder.findModuleFactory(moduleIdentifier, jmxAttribute);
115
116         final boolean implementsSI = foundFactory
117                 .isModuleImplementingServiceInterface(expectedServiceInterface);
118         if (!implementsSI) {
119             final 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 , "
122                             + "attribute %s",
123                     foundFactory.getImplementationName(), foundFactory,
124                     expectedServiceInterface, newDependentReadOnlyON,
125                     jmxAttribute
126             );
127             throw new JmxAttributeValidationException(message, jmxAttribute);
128         }
129         synchronized (this) {
130             this.dependencies.add(moduleIdentifier);
131         }
132     }
133
134     // translate from serviceref to module ON
135     private ObjectName translateServiceRefIfPossible(final ObjectName dependentReadOnlyON) {
136         ObjectName translatedDependentReadOnlyON = dependentReadOnlyON;
137         if (ObjectNameUtil.isServiceReference(translatedDependentReadOnlyON)) {
138             final String serviceQName = ObjectNameUtil.getServiceQName(translatedDependentReadOnlyON);
139             final String refName = ObjectNameUtil.getReferenceName(translatedDependentReadOnlyON);
140             translatedDependentReadOnlyON = ObjectNameUtil.withoutTransactionName( // strip again of transaction name
141                     this.readableRegistry.lookupConfigBeanByServiceInterfaceName(serviceQName, refName));
142         }
143         return translatedDependentReadOnlyON;
144     }
145
146     /**
147      * {@inheritDoc}
148      */
149     //TODO: check for cycles
150     @Override
151     public <T> T resolveInstance(final Class<T> expectedType, final ObjectName dependentReadOnlyON,
152                                  final JmxAttribute jmxAttribute) {
153         final Module module = resolveModuleInstance(dependentReadOnlyON, jmxAttribute);
154
155         synchronized (this) {
156             this.dependencies.add(module.getIdentifier());
157         }
158         final AutoCloseable instance = module.getInstance();
159         if (instance == null) {
160             final String message = String.format(
161                     "Error while %s resolving instance %s. getInstance() returned null. "
162                             + "Expected type %s , attribute %s", this.name,
163                     module.getIdentifier(), expectedType, jmxAttribute
164             );
165             throw new JmxAttributeValidationException(message, jmxAttribute);
166         }
167         try {
168             return expectedType.cast(instance);
169         } catch (final ClassCastException e) {
170             final 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
174             );
175             throw new JmxAttributeValidationException(message, e, jmxAttribute);
176         }
177     }
178
179     private Module resolveModuleInstance(final ObjectName dependentReadOnlyON,
180                                  final JmxAttribute jmxAttribute) {
181         Preconditions.checkArgument(dependentReadOnlyON != null ,"dependentReadOnlyON");
182         Preconditions.checkArgument(jmxAttribute != null, "jmxAttribute");
183         final ObjectName translatedDependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
184         this.transactionStatus.checkCommitStarted();
185         this.transactionStatus.checkNotCommitted();
186
187         final ModuleIdentifier dependentModuleIdentifier = ObjectNameUtil.fromON(
188                 translatedDependentReadOnlyON, ObjectNameUtil.TYPE_MODULE);
189
190         return Preconditions.checkNotNull(this.modulesHolder.findModule(dependentModuleIdentifier, jmxAttribute));
191     }
192
193     @Override
194     public boolean canReuseDependency(final ObjectName objectName, final JmxAttribute jmxAttribute) {
195         Preconditions.checkNotNull(objectName);
196         Preconditions.checkNotNull(jmxAttribute);
197
198         final Module currentModule = resolveModuleInstance(objectName, jmxAttribute);
199         final ModuleIdentifier identifier = currentModule.getIdentifier();
200         final ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = this.modulesHolder.findModuleInternalTransactionalInfo(identifier);
201
202         if(moduleInternalTransactionalInfo.hasOldModule()) {
203             final Module oldModule = moduleInternalTransactionalInfo.getOldInternalInfo().getReadableModule().getModule();
204             return currentModule.canReuse(oldModule);
205         }
206         return false;
207     }
208
209     @Override
210     public <T extends BaseIdentity> Class<? extends T> resolveIdentity(final IdentityAttributeRef identityRef, final Class<T> expectedBaseClass) {
211         final QName qName = QName.create(identityRef.getqNameOfIdentity());
212         final Class<?> deserialized  = this.bindingContextProvider.getBindingContext().getIdentityClass(qName);
213         if (deserialized == null) {
214             throw new IllegalStateException("Unable to retrieve identity class for " + qName + ", null response from "
215                     + this.bindingContextProvider.getBindingContext());
216         }
217         if (expectedBaseClass.isAssignableFrom(deserialized)) {
218             return (Class<T>) deserialized;
219         } else {
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);
223         }
224     }
225
226     @Override
227     public <T extends BaseIdentity> void validateIdentity(final IdentityAttributeRef identityRef, final Class<T> expectedBaseClass, final JmxAttribute jmxAttribute) {
228         try {
229             resolveIdentity(identityRef, expectedBaseClass);
230         } catch (final Exception e) {
231             throw JmxAttributeValidationException.wrap(e, jmxAttribute);
232         }
233     }
234
235     @Override
236     public int compareTo(final DependencyResolverImpl o) {
237         this.transactionStatus.checkCommitStarted();
238         return Integer.compare(getMaxDependencyDepth(),
239                 o.getMaxDependencyDepth());
240     }
241
242     int getMaxDependencyDepth() {
243         if (this.maxDependencyDepth == null) {
244             throw new IllegalStateException("Dependency depth was not computed");
245         }
246         return this.maxDependencyDepth;
247     }
248
249     void countMaxDependencyDepth(final 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         this.transactionStatus.checkCommitStarted();
255         if (this.maxDependencyDepth == null) {
256             this.maxDependencyDepth = getMaxDepth(this, manager,
257                     new LinkedHashSet<>());
258         }
259     }
260
261     private static int getMaxDepth(final DependencyResolverImpl impl,
262                                    final DependencyResolverManager manager,
263                                    final LinkedHashSet<ModuleIdentifier> chainForDetectingCycles) {
264         int maxDepth = 0;
265         final LinkedHashSet<ModuleIdentifier> chainForDetectingCycles2 = new LinkedHashSet<>(
266                 chainForDetectingCycles);
267         chainForDetectingCycles2.add(impl.getIdentifier());
268         for (final ModuleIdentifier dependencyName : impl.dependencies) {
269             final 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));
275             }
276             int subDepth;
277             if (dependentDRI.maxDependencyDepth != null) {
278                 subDepth = dependentDRI.maxDependencyDepth;
279             } else {
280                 subDepth = getMaxDepth(dependentDRI, manager,
281                         chainForDetectingCycles2);
282                 dependentDRI.maxDependencyDepth = subDepth;
283             }
284             if ((subDepth + 1) > maxDepth) {
285                 maxDepth = subDepth + 1;
286             }
287         }
288         impl.maxDependencyDepth = maxDepth;
289         return maxDepth;
290     }
291
292     @Override
293     public ModuleIdentifier getIdentifier() {
294         return this.name;
295     }
296
297     @Override
298     public Object getAttribute(final ObjectName name, final String attribute)
299             throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException {
300         ObjectName newName = translateServiceRefIfPossible(name);
301         // add transaction name
302         newName = ObjectNameUtil.withTransactionName(newName, this.transactionName);
303         return this.mBeanServer.getAttribute(newName, attribute);
304     }
305
306     @Override
307     public <T> T newMXBeanProxy(final ObjectName name, final Class<T> interfaceClass) {
308         ObjectName newName = translateServiceRefIfPossible(name);
309         // add transaction name
310         newName = ObjectNameUtil.withTransactionName(newName, this.transactionName);
311         return JMX.newMXBeanProxy(this.mBeanServer, newName, interfaceClass);
312     }
313 }