Mechanical code cleanup (config)
[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.manager.impl.osgi.mapping.BindingContextProvider;
34 import org.opendaylight.controller.config.spi.Module;
35 import org.opendaylight.controller.config.spi.ModuleFactory;
36 import org.opendaylight.yangtools.yang.binding.BaseIdentity;
37 import org.opendaylight.yangtools.yang.common.QName;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
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.
45  */
46 final class DependencyResolverImpl implements DependencyResolver,
47         Comparable<DependencyResolverImpl> {
48     private static final Logger LOG = LoggerFactory.getLogger(DependencyResolverImpl.class);
49
50     private final ModulesHolder modulesHolder;
51     private final ModuleIdentifier name;
52     private final TransactionStatus transactionStatus;
53     @GuardedBy("this")
54     private final Set<ModuleIdentifier> dependencies = new HashSet<>();
55     private final ServiceReferenceReadableRegistry readableRegistry;
56     private final BindingContextProvider bindingContextProvider;
57     private final String transactionName;
58     private final MBeanServer mBeanServer;
59
60     DependencyResolverImpl(ModuleIdentifier currentModule,
61                            TransactionStatus transactionStatus, ModulesHolder modulesHolder,
62                            ServiceReferenceReadableRegistry readableRegistry, BindingContextProvider bindingContextProvider,
63                            String transactionName, MBeanServer mBeanServer) {
64         this.bindingContextProvider = bindingContextProvider;
65         this.name = currentModule;
66         this.transactionStatus = transactionStatus;
67         this.modulesHolder = modulesHolder;
68         this.readableRegistry = readableRegistry;
69         this.transactionName = transactionName;
70         this.mBeanServer = mBeanServer;
71     }
72
73     /**
74      * {@inheritDoc}
75      */
76     //TODO: check for cycles
77     @Override
78     public void validateDependency(
79             Class<? extends AbstractServiceInterface> expectedServiceInterface,
80             ObjectName dependentReadOnlyON, JmxAttribute jmxAttribute) {
81
82         transactionStatus.checkNotCommitted();
83         if (expectedServiceInterface == null) {
84             throw new NullPointerException(
85                     "Parameter 'expectedServiceInterface' is null");
86         }
87         if (jmxAttribute == null) {
88             throw new NullPointerException("Parameter 'jmxAttribute' is null");
89         }
90
91         JmxAttributeValidationException.checkNotNull(dependentReadOnlyON,
92                 "is null, expected dependency implementing "
93                         + expectedServiceInterface, jmxAttribute
94         );
95
96
97         // check that objectName belongs to this transaction - this should be
98         // stripped
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,
106                         dependentReadOnlyON
107             ), jmxAttribute
108         );
109
110         ObjectName newDependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
111
112         ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(newDependentReadOnlyON, ObjectNameUtil
113                 .TYPE_MODULE);
114
115         ModuleFactory foundFactory = modulesHolder.findModuleFactory(moduleIdentifier, jmxAttribute);
116
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 , "
123                             + "attribute %s",
124                     foundFactory.getImplementationName(), foundFactory,
125                     expectedServiceInterface, newDependentReadOnlyON,
126                     jmxAttribute
127             );
128             throw new JmxAttributeValidationException(message, jmxAttribute);
129         }
130         synchronized (this) {
131             dependencies.add(moduleIdentifier);
132         }
133     }
134
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));
143         }
144         return translatedDependentReadOnlyON;
145     }
146
147     /**
148      * {@inheritDoc}
149      */
150     //TODO: check for cycles
151     @Override
152     public <T> T resolveInstance(Class<T> expectedType, ObjectName dependentReadOnlyON,
153                                  JmxAttribute jmxAttribute) {
154         Module module = resolveModuleInstance(dependentReadOnlyON, jmxAttribute);
155
156         synchronized (this) {
157             dependencies.add(module.getIdentifier());
158         }
159         AutoCloseable instance = module.getInstance();
160         if (instance == null) {
161             String message = format(
162                     "Error while %s resolving instance %s. getInstance() returned null. "
163                             + "Expected type %s , attribute %s", name,
164                     module.getIdentifier(), expectedType, jmxAttribute
165             );
166             throw new JmxAttributeValidationException(message, jmxAttribute);
167         }
168         try {
169             return expectedType.cast(instance);
170         } catch (ClassCastException e) {
171             String message = format(
172                     "Instance cannot be cast to expected type. Instance class is %s , "
173                             + "expected type %s , attribute %s",
174                     instance.getClass(), expectedType, jmxAttribute
175             );
176             throw new JmxAttributeValidationException(message, e, jmxAttribute);
177         }
178     }
179
180     private Module resolveModuleInstance(ObjectName dependentReadOnlyON,
181                                  JmxAttribute jmxAttribute) {
182         Preconditions.checkArgument(dependentReadOnlyON != null ,"dependentReadOnlyON");
183         Preconditions.checkArgument(jmxAttribute != null, "jmxAttribute");
184         ObjectName translatedDependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
185         transactionStatus.checkCommitStarted();
186         transactionStatus.checkNotCommitted();
187
188         ModuleIdentifier dependentModuleIdentifier = ObjectNameUtil.fromON(
189                 translatedDependentReadOnlyON, ObjectNameUtil.TYPE_MODULE);
190
191         return Preconditions.checkNotNull(modulesHolder.findModule(dependentModuleIdentifier, jmxAttribute));
192     }
193
194     @Override
195     public boolean canReuseDependency(ObjectName objectName, JmxAttribute jmxAttribute) {
196         Preconditions.checkNotNull(objectName);
197         Preconditions.checkNotNull(jmxAttribute);
198
199         Module currentModule = resolveModuleInstance(objectName, jmxAttribute);
200         ModuleIdentifier identifier = currentModule.getIdentifier();
201         ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = modulesHolder.findModuleInternalTransactionalInfo(identifier);
202
203         if(moduleInternalTransactionalInfo.hasOldModule()) {
204             Module oldModule = moduleInternalTransactionalInfo.getOldInternalInfo().getReadableModule().getModule();
205             return currentModule.canReuse(oldModule);
206         }
207         return false;
208     }
209
210     @Override
211     public <T extends BaseIdentity> Class<? extends T> resolveIdentity(IdentityAttributeRef identityRef, Class<T> expectedBaseClass) {
212         final QName qName = QName.create(identityRef.getqNameOfIdentity());
213         Class<?> deserialized  = bindingContextProvider.getBindingContext().getIdentityClass(qName);
214         if (deserialized == null) {
215             throw new IllegalStateException("Unable to retrieve identity class for " + qName + ", null response from "
216                     + bindingContextProvider.getBindingContext());
217         }
218         if (expectedBaseClass.isAssignableFrom(deserialized)) {
219             return (Class<T>) deserialized;
220         } else {
221             LOG.error("Cannot resolve class of identity {} : deserialized class {} is not a subclass of {}.",
222                     identityRef, deserialized, expectedBaseClass);
223             throw new IllegalArgumentException("Deserialized identity " + deserialized + " cannot be cast to " + expectedBaseClass);
224         }
225     }
226
227     @Override
228     public <T extends BaseIdentity> void validateIdentity(IdentityAttributeRef identityRef, Class<T> expectedBaseClass, JmxAttribute jmxAttribute) {
229         try {
230             resolveIdentity(identityRef, expectedBaseClass);
231         } catch (Exception e) {
232             throw JmxAttributeValidationException.wrap(e, jmxAttribute);
233         }
234     }
235
236     @Override
237     public int compareTo(DependencyResolverImpl o) {
238         transactionStatus.checkCommitStarted();
239         return Integer.compare(getMaxDependencyDepth(),
240                 o.getMaxDependencyDepth());
241     }
242
243     private Integer maxDependencyDepth;
244
245     int getMaxDependencyDepth() {
246         if (maxDependencyDepth == null) {
247             throw new IllegalStateException("Dependency depth was not computed");
248         }
249         return maxDependencyDepth;
250     }
251
252     void countMaxDependencyDepth(DependencyResolverManager manager) {
253         // We can calculate the dependency after second phase commit was started
254         // Second phase commit starts after validation and validation adds the dependencies into the dependency resolver, which are necessary for the calculation
255         // FIXME generated code for abstract module declares validate method as non-final
256         // Overriding the validate would cause recreate every time instead of reuse + also possibly wrong close order if there is another module depending
257         transactionStatus.checkCommitStarted();
258         if (maxDependencyDepth == null) {
259             maxDependencyDepth = getMaxDepth(this, manager,
260                     new LinkedHashSet<>());
261         }
262     }
263
264     private static int getMaxDepth(DependencyResolverImpl impl,
265                                    DependencyResolverManager manager,
266                                    LinkedHashSet<ModuleIdentifier> chainForDetectingCycles) {
267         int maxDepth = 0;
268         LinkedHashSet<ModuleIdentifier> chainForDetectingCycles2 = new LinkedHashSet<>(
269                 chainForDetectingCycles);
270         chainForDetectingCycles2.add(impl.getIdentifier());
271         for (ModuleIdentifier dependencyName : impl.dependencies) {
272             DependencyResolverImpl dependentDRI = manager
273                     .getOrCreate(dependencyName);
274             if (chainForDetectingCycles2.contains(dependencyName)) {
275                 throw new IllegalStateException(format(
276                         "Cycle detected, %s contains %s",
277                         chainForDetectingCycles2, dependencyName));
278             }
279             int subDepth;
280             if (dependentDRI.maxDependencyDepth != null) {
281                 subDepth = dependentDRI.maxDependencyDepth;
282             } else {
283                 subDepth = getMaxDepth(dependentDRI, manager,
284                         chainForDetectingCycles2);
285                 dependentDRI.maxDependencyDepth = subDepth;
286             }
287             if (subDepth + 1 > maxDepth) {
288                 maxDepth = subDepth + 1;
289             }
290         }
291         impl.maxDependencyDepth = maxDepth;
292         return maxDepth;
293     }
294
295     @Override
296     public ModuleIdentifier getIdentifier() {
297         return name;
298     }
299
300     @Override
301     public Object getAttribute(ObjectName name, String attribute)
302             throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException {
303         ObjectName newName = translateServiceRefIfPossible(name);
304         // add transaction name
305         newName = ObjectNameUtil.withTransactionName(newName, transactionName);
306         return mBeanServer.getAttribute(newName, attribute);
307     }
308
309     @Override
310     public <T> T newMXBeanProxy(ObjectName name, Class<T> interfaceClass) {
311         ObjectName newName = translateServiceRefIfPossible(name);
312         // add transaction name
313         newName = ObjectNameUtil.withTransactionName(newName, transactionName);
314         return JMX.newMXBeanProxy(mBeanServer, newName, interfaceClass);
315     }
316 }