a2c43d0c73ba402f13bd7b604a9629b617637732
[controller.git] / opendaylight / md-sal / sal-dom-broker / src / main / java / org / opendaylight / controller / sal / dom / broker / impl / SchemaAwareRpcBroker.java
1 /*
2  * Copyright (c) 2014 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.sal.dom.broker.impl;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
12
13 import java.util.Set;
14 import java.util.concurrent.ConcurrentHashMap;
15 import java.util.concurrent.ConcurrentMap;
16
17 import org.opendaylight.controller.md.sal.common.api.routing.RouteChange;
18 import org.opendaylight.controller.md.sal.common.api.routing.RouteChangeListener;
19 import org.opendaylight.controller.md.sal.common.impl.routing.RoutingUtils;
20 import org.opendaylight.controller.sal.core.api.Broker.RoutedRpcRegistration;
21 import org.opendaylight.controller.sal.core.api.Broker.RpcRegistration;
22 import org.opendaylight.controller.sal.core.api.RoutedRpcDefaultImplementation;
23 import org.opendaylight.controller.sal.core.api.RpcImplementation;
24 import org.opendaylight.controller.sal.core.api.RpcRegistrationListener;
25 import org.opendaylight.controller.sal.core.api.RpcRoutingContext;
26 import org.opendaylight.controller.sal.dom.broker.spi.RpcRouter;
27 import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
28 import org.opendaylight.yangtools.concepts.Identifiable;
29 import org.opendaylight.yangtools.concepts.ListenerRegistration;
30 import org.opendaylight.yangtools.concepts.util.ListenerRegistry;
31 import org.opendaylight.yangtools.yang.common.QName;
32 import org.opendaylight.yangtools.yang.common.RpcResult;
33 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
34 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
36 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.Module;
39 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
40 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
41 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 import com.google.common.base.Optional;
46 import com.google.common.collect.FluentIterable;
47 import com.google.common.collect.ImmutableMap;
48 import com.google.common.collect.ImmutableSet;
49 import com.google.common.util.concurrent.ListenableFuture;
50
51 public class SchemaAwareRpcBroker implements RpcRouter, Identifiable<String>, RoutedRpcDefaultImplementation {
52
53     private static final Logger LOG = LoggerFactory.getLogger(SchemaAwareRpcBroker.class);
54
55     private static final QName CONTEXT_REFERENCE = QName.create("urn:opendaylight:yang:extension:yang-ext",
56             "2013-07-09", "context-reference");
57     private final ListenerRegistry<RpcRegistrationListener> rpcRegistrationListeners = new ListenerRegistry<>();
58     private final ListenerRegistry<RouteChangeListener<RpcRoutingContext, InstanceIdentifier>> routeChangeListeners = new ListenerRegistry<>();
59
60
61     private final String identifier;
62     private final ConcurrentMap<QName, RpcImplementation> implementations = new ConcurrentHashMap<>();
63     private RpcImplementation defaultImplementation;
64     private SchemaContextProvider schemaProvider;
65     private RoutedRpcDefaultImplementation defaultDelegate;
66
67     public SchemaAwareRpcBroker(String identifier, SchemaContextProvider schemaProvider) {
68         super();
69         this.identifier = identifier;
70         this.schemaProvider = schemaProvider;
71     }
72
73     public RpcImplementation getDefaultImplementation() {
74         return defaultImplementation;
75     }
76
77     public void setDefaultImplementation(RpcImplementation defaultImplementation) {
78         this.defaultImplementation = defaultImplementation;
79     }
80
81     public SchemaContextProvider getSchemaProvider() {
82         return schemaProvider;
83     }
84
85     public void setSchemaProvider(SchemaContextProvider schemaProvider) {
86         this.schemaProvider = schemaProvider;
87     }
88
89     public RoutedRpcDefaultImplementation getRoutedRpcDefaultDelegate() {
90         return defaultDelegate;
91     }
92
93     @Override
94     public void setRoutedRpcDefaultDelegate(RoutedRpcDefaultImplementation defaultDelegate) {
95         this.defaultDelegate = defaultDelegate;
96     }
97
98     @Override
99     public RoutedRpcRegistration addRoutedRpcImplementation(QName rpcType, RpcImplementation implementation) {
100         checkArgument(rpcType != null, "RPC Type should not be null");
101         checkArgument(implementation != null, "RPC Implementatoin should not be null");
102         return getOrCreateRoutedRpcRouter(rpcType).addRoutedRpcImplementation(rpcType, implementation);
103     }
104
105     private RoutedRpcSelector getOrCreateRoutedRpcRouter(QName rpcType) {
106         RoutedRpcSelector potential = getRoutedRpcRouter(rpcType);
107         if (potential != null) {
108             return potential;
109         }
110         synchronized (implementations) {
111             potential = getRoutedRpcRouter(rpcType);
112             if (potential != null) {
113                 return potential;
114             }
115             RpcDefinition definition = findRpcDefinition(rpcType);
116             RoutingStrategy strategy = getRoutingStrategy(definition);
117             checkState(strategy instanceof RoutedRpcStrategy, "Rpc %s is not routed.", rpcType);
118             potential = new RoutedRpcSelector((RoutedRpcStrategy) strategy, this);
119             implementations.put(rpcType, potential);
120             return potential;
121         }
122     }
123
124     private RoutedRpcSelector getRoutedRpcRouter(QName rpcType) {
125         RpcImplementation potential = implementations.get(rpcType);
126         if (potential != null) {
127             checkState(potential instanceof RoutedRpcSelector, "Rpc %s is not routed.", rpcType);
128             return (RoutedRpcSelector) potential;
129         }
130         return null;
131
132     }
133
134     @Override
135     public RpcRegistration addRpcImplementation(QName rpcType, RpcImplementation implementation)
136             throws IllegalArgumentException {
137         checkArgument(rpcType != null, "RPC Type should not be null");
138         checkArgument(implementation != null, "RPC Implementatoin should not be null");
139         checkState(!hasRpcImplementation(rpcType), "Implementation already registered");
140         RpcDefinition definition = findRpcDefinition(rpcType);
141         checkArgument(!isRoutedRpc(definition), "RPC Type must not be routed.");
142         GlobalRpcRegistration reg = new GlobalRpcRegistration(rpcType, implementation, this);
143         implementations.putIfAbsent(rpcType, implementation);
144         return reg;
145     }
146
147     private boolean isRoutedRpc(RpcDefinition definition) {
148         return getRoutingStrategy(definition) instanceof RoutedRpcStrategy;
149     }
150
151     @Override
152     public ListenerRegistration<RpcRegistrationListener> addRpcRegistrationListener(RpcRegistrationListener listener) {
153         return rpcRegistrationListeners.register(listener);
154     }
155
156     @Override
157     public String getIdentifier() {
158         return identifier;
159     }
160
161     @Override
162     public Set<QName> getSupportedRpcs() {
163         return ImmutableSet.copyOf(implementations.keySet());
164     }
165
166     @Override
167     public ListenableFuture<RpcResult<CompositeNode>> invokeRpc(QName rpc, CompositeNode input) {
168         return findRpcImplemention(rpc).invokeRpc(rpc, input);
169     }
170
171     private RpcImplementation findRpcImplemention(QName rpc) {
172         checkArgument(rpc != null, "Rpc name should not be null");
173         RpcImplementation potentialImpl = implementations.get(rpc);
174         if (potentialImpl != null) {
175             return potentialImpl;
176         }
177
178         potentialImpl = defaultImplementation;
179         if( potentialImpl == null ) {
180             throw new UnsupportedOperationException( "No implementation for this operation is available." );
181         }
182
183         return potentialImpl;
184     }
185
186     private boolean hasRpcImplementation(QName rpc) {
187         return implementations.containsKey(rpc);
188     }
189
190     private RpcDefinition findRpcDefinition(QName rpcType) {
191         checkArgument(rpcType != null, "Rpc name must be supplied.");
192         checkState(schemaProvider != null, "Schema Provider is not available.");
193         SchemaContext ctx = schemaProvider.getSchemaContext();
194         checkState(ctx != null, "YANG Schema Context is not available.");
195         Module module = ctx.findModuleByNamespaceAndRevision(rpcType.getNamespace(), rpcType.getRevision());
196         checkState(module != null, "YANG Module is not available.");
197         return findRpcDefinition(rpcType, module.getRpcs());
198     }
199
200     static private RpcDefinition findRpcDefinition(QName rpcType, Set<RpcDefinition> rpcs) {
201         checkState(rpcs != null, "Rpc schema is not available.");
202         for (RpcDefinition rpc : rpcs) {
203             if (rpcType.equals(rpc.getQName())) {
204                 return rpc;
205             }
206         }
207         throw new IllegalArgumentException("Supplied Rpc Type is not defined.");
208     }
209
210     private RoutingStrategy getRoutingStrategy(RpcDefinition rpc) {
211         ContainerSchemaNode input = rpc.getInput();
212         if (input != null) {
213             for (DataSchemaNode schemaNode : input.getChildNodes()) {
214                 Optional<QName> context = getRoutingContext(schemaNode);
215                 if (context.isPresent()) {
216                     return createRoutedStrategy(rpc, context.get(), schemaNode.getQName());
217                 }
218             }
219         }
220         return createGlobalStrategy(rpc);
221     }
222
223     private static RoutingStrategy createRoutedStrategy(RpcDefinition rpc, QName context, QName leafNode) {
224         return new RoutedRpcStrategy(rpc.getQName(), context, leafNode);
225     }
226
227     private Optional<QName> getRoutingContext(DataSchemaNode schemaNode) {
228         for (UnknownSchemaNode extension : schemaNode.getUnknownSchemaNodes()) {
229             if (CONTEXT_REFERENCE.equals(extension.getNodeType())) {
230                 return Optional.fromNullable(extension.getQName());
231             }
232             ;
233         }
234         return Optional.absent();
235     }
236
237     private static RoutingStrategy createGlobalStrategy(RpcDefinition rpc) {
238         GlobalRpcStrategy ret = new GlobalRpcStrategy(rpc.getQName());
239         return ret;
240     }
241
242     @Override
243     public ListenableFuture<RpcResult<CompositeNode>> invokeRpc(QName rpc, InstanceIdentifier identifier, CompositeNode input) {
244       checkState(defaultDelegate != null);
245       return defaultDelegate.invokeRpc(rpc, identifier, input);
246     }
247
248     private static abstract class RoutingStrategy implements Identifiable<QName> {
249
250         private final QName identifier;
251
252         public RoutingStrategy(QName identifier) {
253             super();
254             this.identifier = identifier;
255         }
256
257         @Override
258         public QName getIdentifier() {
259             return identifier;
260         }
261     }
262
263     private static class GlobalRpcStrategy extends RoutingStrategy {
264
265         public GlobalRpcStrategy(QName identifier) {
266             super(identifier);
267         }
268     }
269
270     private static class RoutedRpcStrategy extends RoutingStrategy {
271
272         private final QName context;
273         private final QName leaf;
274
275         public RoutedRpcStrategy(QName identifier, QName ctx, QName leaf) {
276             super(identifier);
277             this.context = ctx;
278             this.leaf = leaf;
279         }
280
281         public QName getContext() {
282             return context;
283         }
284
285         public QName getLeaf() {
286             return leaf;
287         }
288     }
289
290     private static class RoutedRpcSelector implements RpcImplementation, AutoCloseable, Identifiable<RpcRoutingContext> {
291
292         private final RoutedRpcStrategy strategy;
293         private final Set<QName> supportedRpcs;
294         private final RpcRoutingContext identifier;
295         private RpcImplementation defaultDelegate;
296         private final ConcurrentMap<InstanceIdentifier, RoutedRpcRegImpl> implementations = new ConcurrentHashMap<>();
297         private final SchemaAwareRpcBroker router;
298
299         public RoutedRpcSelector(RoutedRpcStrategy strategy, SchemaAwareRpcBroker router) {
300             super();
301             this.strategy = strategy;
302             supportedRpcs = ImmutableSet.of(strategy.getIdentifier());
303             identifier = RpcRoutingContext.create(strategy.context, strategy.getIdentifier());
304             this.router = router;
305         }
306
307         @Override
308         public RpcRoutingContext getIdentifier() {
309             return identifier;
310         }
311
312         @Override
313         public void close() throws Exception {
314
315         }
316
317         @Override
318         public Set<QName> getSupportedRpcs() {
319             return supportedRpcs;
320         }
321
322         public RoutedRpcRegistration addRoutedRpcImplementation(QName rpcType, RpcImplementation implementation) {
323             return new RoutedRpcRegImpl(rpcType, implementation, this);
324         }
325
326         @Override
327         public ListenableFuture<RpcResult<CompositeNode>> invokeRpc(QName rpc, CompositeNode input) {
328             CompositeNode inputContainer = input.getFirstCompositeByName(QName.create(rpc,"input"));
329             checkArgument(inputContainer != null, "Rpc payload must contain input element");
330             SimpleNode<?> routeContainer = inputContainer.getFirstSimpleByName(strategy.getLeaf());
331             checkArgument(routeContainer != null, "Leaf %s must be set with value", strategy.getLeaf());
332             Object route = routeContainer.getValue();
333             checkArgument(route instanceof InstanceIdentifier,
334                           "The routed node %s is not an instance identifier", route);
335             RpcImplementation potential = null;
336             if (route != null) {
337                 RoutedRpcRegImpl potentialReg = implementations.get(route);
338                 if (potentialReg != null) {
339                     potential = potentialReg.getInstance();
340                 }
341             }
342             if (potential == null) {
343                 return router.invokeRpc(rpc, (InstanceIdentifier) route, input);
344             }
345             checkState(potential != null, "No implementation is available for rpc:%s path:%s", rpc, route);
346             return potential.invokeRpc(rpc, input);
347         }
348
349         public void addPath(QName context, InstanceIdentifier path, RoutedRpcRegImpl routedRpcRegImpl) {
350             //checkArgument(strategy.getContext().equals(context),"Supplied context is not supported.");
351             RoutedRpcRegImpl previous = implementations.put(path, routedRpcRegImpl);
352             if (previous == null) {
353                 router.notifyPathAnnouncement(context,strategy.getIdentifier(), path);
354             }
355
356         }
357
358         public void removePath(QName context, InstanceIdentifier path, RoutedRpcRegImpl routedRpcRegImpl) {
359             boolean removed = implementations.remove(path, routedRpcRegImpl);
360             if (removed) {
361                 router.notifyPathWithdrawal(context, strategy.getIdentifier(), path);
362             }
363         }
364     }
365
366     private static class GlobalRpcRegistration extends AbstractObjectRegistration<RpcImplementation> implements
367             RpcRegistration {
368         private final QName type;
369         private SchemaAwareRpcBroker router;
370
371         public GlobalRpcRegistration(QName type, RpcImplementation instance, SchemaAwareRpcBroker router) {
372             super(instance);
373             this.type = type;
374             this.router = router;
375         }
376
377         @Override
378         public QName getType() {
379             return type;
380         }
381
382         @Override
383         protected void removeRegistration() {
384             if (router != null) {
385                 router.remove(this);
386                 router = null;
387             }
388         }
389     }
390
391     private static class RoutedRpcRegImpl extends AbstractObjectRegistration<RpcImplementation> implements
392             RoutedRpcRegistration {
393
394         private final QName type;
395         private final RoutedRpcSelector router;
396
397         public RoutedRpcRegImpl(QName rpcType, RpcImplementation implementation, RoutedRpcSelector routedRpcSelector) {
398             super(implementation);
399             this.type = rpcType;
400             router = routedRpcSelector;
401         }
402
403         @Override
404         public void registerPath(QName context, InstanceIdentifier path) {
405             router.addPath(context, path, this);
406         }
407
408         @Override
409         public void unregisterPath(QName context, InstanceIdentifier path) {
410             router.removePath(context, path, this);
411         }
412
413         @Override
414         protected void removeRegistration() {
415
416         }
417
418         @Override
419         public QName getType() {
420             return type;
421         }
422
423     }
424
425     private void remove(GlobalRpcRegistration registration) {
426         implementations.remove(registration.getType(), registration);
427     }
428
429     private void notifyPathAnnouncement(QName context, QName identifier, InstanceIdentifier path) {
430         RpcRoutingContext contextWrapped = RpcRoutingContext.create(context, identifier);
431         RouteChange<RpcRoutingContext, InstanceIdentifier> change = RoutingUtils.announcementChange(contextWrapped , path);
432         for(ListenerRegistration<RouteChangeListener<RpcRoutingContext, InstanceIdentifier>> routeListener : routeChangeListeners) {
433             try {
434                 routeListener.getInstance().onRouteChange(change);
435             } catch (Exception e) {
436                 LOG.error("Unhandled exception during invoking onRouteChange for {}",routeListener.getInstance(),e);
437
438             }
439         }
440
441     }
442
443
444
445     private void notifyPathWithdrawal(QName context,QName identifier, InstanceIdentifier path) {
446         RpcRoutingContext contextWrapped = RpcRoutingContext.create(context, identifier);
447         RouteChange<RpcRoutingContext, InstanceIdentifier> change = RoutingUtils.removalChange(contextWrapped , path);
448         for(ListenerRegistration<RouteChangeListener<RpcRoutingContext, InstanceIdentifier>> routeListener : routeChangeListeners) {
449             try {
450                 routeListener.getInstance().onRouteChange(change);
451             } catch (Exception e) {
452                 LOG.error("Unhandled exception during invoking onRouteChange for {}",routeListener.getInstance(),e);
453             }
454         }
455     }
456
457     @Override
458     public <L extends RouteChangeListener<RpcRoutingContext, InstanceIdentifier>> ListenerRegistration<L> registerRouteChangeListener(
459             L listener) {
460         ListenerRegistration<L> reg = routeChangeListeners.registerWithType(listener);
461         RouteChange<RpcRoutingContext, InstanceIdentifier> initial = createInitialRouteChange();
462         try {
463         listener.onRouteChange(initial);
464         } catch (Exception e) {
465             LOG.error("Unhandled exception during sending initial route change event {} to {}",initial,listener, e);
466         }
467         return reg;
468     }
469
470     private RouteChange<RpcRoutingContext, InstanceIdentifier> createInitialRouteChange() {
471         FluentIterable<RoutedRpcSelector> rpcSelectors = FluentIterable.from(implementations.values()).filter(RoutedRpcSelector.class);
472
473
474         ImmutableMap.Builder<RpcRoutingContext, Set<InstanceIdentifier>> announcements = ImmutableMap.builder();
475         ImmutableMap.Builder<RpcRoutingContext, Set<InstanceIdentifier>> removals = ImmutableMap.builder();
476         for (RoutedRpcSelector routedRpcSelector : rpcSelectors) {
477             final RpcRoutingContext context = routedRpcSelector.getIdentifier();
478             final Set<InstanceIdentifier> paths = ImmutableSet.copyOf(routedRpcSelector.implementations.keySet());
479             announcements.put(context, paths);
480         }
481         return RoutingUtils.change(announcements.build(), removals.build());
482     }
483 }