ec9678fd2d7df3545f15bc5a8fa0e7b8129b5760
[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 org.opendaylight.controller.config.api.DependencyResolver;
11 import org.opendaylight.controller.config.api.IdentityAttributeRef;
12 import org.opendaylight.controller.config.api.JmxAttribute;
13 import org.opendaylight.controller.config.api.JmxAttributeValidationException;
14 import org.opendaylight.controller.config.api.ModuleIdentifier;
15 import org.opendaylight.controller.config.api.ServiceReferenceReadableRegistry;
16 import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
17 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
18 import org.opendaylight.controller.config.manager.impl.TransactionStatus;
19 import org.opendaylight.controller.config.spi.Module;
20 import org.opendaylight.controller.config.spi.ModuleFactory;
21 import org.opendaylight.yangtools.yang.binding.BaseIdentity;
22 import org.opendaylight.yangtools.yang.common.QName;
23 import org.opendaylight.yangtools.yang.data.impl.codec.CodecRegistry;
24 import org.opendaylight.yangtools.yang.data.impl.codec.IdentityCodec;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 import javax.annotation.concurrent.GuardedBy;
29 import javax.management.ObjectName;
30 import java.util.HashSet;
31 import java.util.LinkedHashSet;
32 import java.util.Set;
33
34 import static java.lang.String.format;
35
36 /**
37  * Protect {@link org.opendaylight.controller.config.spi.Module#getInstance()}
38  * by creating proxy that would throw exception if those methods are called
39  * during validation. Tracks dependencies for ordering purposes.
40  */
41 final class DependencyResolverImpl implements DependencyResolver,
42        Comparable<DependencyResolverImpl> {
43     private static final Logger logger = LoggerFactory.getLogger(DependencyResolverImpl.class);
44
45     private final ModulesHolder modulesHolder;
46     private final ModuleIdentifier name;
47     private final TransactionStatus transactionStatus;
48     @GuardedBy("this")
49     private final Set<ModuleIdentifier> dependencies = new HashSet<>();
50     private final ServiceReferenceReadableRegistry readableRegistry;
51     private final CodecRegistry codecRegistry;
52
53     DependencyResolverImpl(ModuleIdentifier currentModule,
54                            TransactionStatus transactionStatus, ModulesHolder modulesHolder,
55                            ServiceReferenceReadableRegistry readableRegistry, CodecRegistry codecRegistry) {
56         this.codecRegistry = codecRegistry;
57         this.name = currentModule;
58         this.transactionStatus = transactionStatus;
59         this.modulesHolder = modulesHolder;
60         this.readableRegistry = readableRegistry;
61     }
62
63     /**
64      * {@inheritDoc}
65      */
66     //TODO: check for cycles
67     @Override
68     public void validateDependency(
69             Class<? extends AbstractServiceInterface> expectedServiceInterface,
70             ObjectName dependentReadOnlyON, JmxAttribute jmxAttribute) {
71
72         transactionStatus.checkNotCommitted();
73         if (expectedServiceInterface == null) {
74             throw new NullPointerException(
75                     "Parameter 'expectedServiceInterface' is null");
76         }
77         if (jmxAttribute == null)
78             throw new NullPointerException("Parameter 'jmxAttribute' is null");
79
80         JmxAttributeValidationException.checkNotNull(dependentReadOnlyON,
81                 "is null, expected dependency implementing "
82                         + expectedServiceInterface, jmxAttribute);
83
84
85
86         // check that objectName belongs to this transaction - this should be
87         // stripped
88         // in DynamicWritableWrapper
89         boolean hasTransaction = ObjectNameUtil
90                 .getTransactionName(dependentReadOnlyON) != null;
91         JmxAttributeValidationException.checkCondition(
92                 hasTransaction == false,
93                 format("ObjectName should not contain "
94                         + "transaction name. %s set to %s. ", jmxAttribute,
95                         dependentReadOnlyON), jmxAttribute);
96
97         dependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
98
99         ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(dependentReadOnlyON, ObjectNameUtil
100                 .TYPE_MODULE);
101
102         ModuleFactory foundFactory = modulesHolder.findModuleFactory(moduleIdentifier, jmxAttribute);
103
104         boolean implementsSI = foundFactory
105                 .isModuleImplementingServiceInterface(expectedServiceInterface);
106         if (implementsSI == false) {
107             String message = format(
108                     "Found module factory does not expose expected service interface. "
109                             + "Module name is %s : %s, expected service interface %s, dependent module ON %s , "
110                             + "attribute %s",
111                     foundFactory.getImplementationName(), foundFactory,
112                     expectedServiceInterface, dependentReadOnlyON,
113                     jmxAttribute);
114             throw new JmxAttributeValidationException(message, jmxAttribute);
115         }
116         synchronized (this) {
117             dependencies.add(moduleIdentifier);
118         }
119     }
120
121     // transalate from serviceref to module ON
122     private ObjectName translateServiceRefIfPossible(ObjectName dependentReadOnlyON) {
123         if (ObjectNameUtil.isServiceReference(dependentReadOnlyON)) {
124             String serviceQName = ObjectNameUtil.getServiceQName(dependentReadOnlyON);
125             String refName = ObjectNameUtil.getReferenceName(dependentReadOnlyON);
126             dependentReadOnlyON = ObjectNameUtil.withoutTransactionName( // strip again of transaction name
127                     readableRegistry.lookupConfigBeanByServiceInterfaceName(serviceQName, refName));
128         }
129         return dependentReadOnlyON;
130     }
131
132     /**
133      * {@inheritDoc}
134      */
135     //TODO: check for cycles
136     @Override
137     public <T> T resolveInstance(Class<T> expectedType, ObjectName dependentReadOnlyON,
138             JmxAttribute jmxAttribute) {
139         if (expectedType == null || dependentReadOnlyON == null || jmxAttribute == null) {
140             throw new IllegalArgumentException(format(
141                     "Null parameters not allowed, got %s %s %s", expectedType,
142                     dependentReadOnlyON, jmxAttribute));
143         }
144         dependentReadOnlyON = translateServiceRefIfPossible(dependentReadOnlyON);
145         transactionStatus.checkCommitStarted();
146         transactionStatus.checkNotCommitted();
147
148         ModuleIdentifier dependentModuleIdentifier = ObjectNameUtil.fromON(
149                 dependentReadOnlyON, ObjectNameUtil.TYPE_MODULE);
150         Module module = modulesHolder.findModule(dependentModuleIdentifier,
151                 jmxAttribute);
152         synchronized (this) {
153             dependencies.add(dependentModuleIdentifier);
154         }
155         AutoCloseable instance = module.getInstance();
156         if (instance == null) {
157             String message = format(
158                     "Error while %s resolving instance %s. getInstance() returned null. "
159                             + "Expected type %s , attribute %s", name,
160                     dependentModuleIdentifier, expectedType, jmxAttribute);
161             throw new JmxAttributeValidationException(message, jmxAttribute);
162         }
163         try {
164             T result = expectedType.cast(instance);
165             return result;
166         } catch (ClassCastException e) {
167             String message = format(
168                     "Instance cannot be cast to expected type. Instance class is %s , "
169                             + "expected type %s , attribute %s",
170                     instance.getClass(), expectedType, jmxAttribute);
171             throw new JmxAttributeValidationException(message, e, jmxAttribute);
172         }
173     }
174
175     @Override
176     public <T extends BaseIdentity> Class<? extends T> resolveIdentity(IdentityAttributeRef identityRef, Class<T> expectedBaseClass) {
177         final QName qName = QName.create(identityRef.getqNameOfIdentity());
178         IdentityCodec<?> identityCodec = codecRegistry.getIdentityCodec();
179         Class<? extends BaseIdentity> deserialized = identityCodec.deserialize(qName);
180         if (deserialized == null) {
181             throw new RuntimeException("Unable to retrieve identity class for " + qName + ", null response from "
182                     + codecRegistry);
183         }
184         if (expectedBaseClass.isAssignableFrom(deserialized)) {
185             return (Class<T>) deserialized;
186         } else {
187             logger.error("Cannot resolve class of identity {} : deserialized class {} is not a subclass of {}.",
188                     identityRef, deserialized, expectedBaseClass);
189             throw new IllegalArgumentException("Deserialized identity " + deserialized + " cannot be cast to " + expectedBaseClass);
190         }
191     }
192
193     @Override
194     public <T extends BaseIdentity> void validateIdentity(IdentityAttributeRef identityRef, Class<T> expectedBaseClass, JmxAttribute jmxAttribute) {
195         try {
196             resolveIdentity(identityRef, expectedBaseClass);
197         } catch(Exception e) {
198             throw JmxAttributeValidationException.wrap(e, jmxAttribute);
199         }
200     }
201
202     @Override
203     public int compareTo(DependencyResolverImpl o) {
204         transactionStatus.checkCommitted();
205         return Integer.compare(getMaxDependencyDepth(),
206                 o.getMaxDependencyDepth());
207     }
208
209     private Integer maxDependencyDepth;
210
211     int getMaxDependencyDepth() {
212         if (maxDependencyDepth == null) {
213             throw new IllegalStateException("Dependency depth was not computed");
214         }
215         return maxDependencyDepth;
216     }
217
218     public void countMaxDependencyDepth(DependencyResolverManager manager) {
219         transactionStatus.checkCommitted();
220         if (maxDependencyDepth == null) {
221             maxDependencyDepth = getMaxDepth(this, manager,
222                     new LinkedHashSet<ModuleIdentifier>());
223         }
224     }
225
226     private static int getMaxDepth(DependencyResolverImpl impl,
227             DependencyResolverManager manager,
228             LinkedHashSet<ModuleIdentifier> chainForDetectingCycles) {
229         int maxDepth = 0;
230         LinkedHashSet<ModuleIdentifier> chainForDetectingCycles2 = new LinkedHashSet<>(
231                 chainForDetectingCycles);
232         chainForDetectingCycles2.add(impl.getIdentifier());
233         for (ModuleIdentifier dependencyName : impl.dependencies) {
234             DependencyResolverImpl dependentDRI = manager
235                     .getOrCreate(dependencyName);
236             if (chainForDetectingCycles2.contains(dependencyName)) {
237                 throw new IllegalStateException(format(
238                         "Cycle detected, %s contains %s",
239                         chainForDetectingCycles2, dependencyName));
240             }
241             int subDepth;
242             if (dependentDRI.maxDependencyDepth != null) {
243                 subDepth = dependentDRI.maxDependencyDepth;
244             } else {
245                 subDepth = getMaxDepth(dependentDRI, manager,
246                         chainForDetectingCycles2);
247                 dependentDRI.maxDependencyDepth = subDepth;
248             }
249             if (subDepth + 1 > maxDepth) {
250                 maxDepth = subDepth + 1;
251             }
252         }
253         impl.maxDependencyDepth = maxDepth;
254         return maxDepth;
255     }
256
257     @Override
258     public ModuleIdentifier getIdentifier() {
259         return name;
260     }
261 }