Switch to Objects.requireNonNull
[mdsal.git] / binding2 / mdsal-binding2-dom-adapter / src / main / java / org / opendaylight / mdsal / binding / javav2 / dom / adapter / impl / operation / RpcServiceAdapter.java
1 /*
2  * Copyright (c) 2017 Pantheon Technologies s.r.o. 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.mdsal.binding.javav2.dom.adapter.impl.operation;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.base.Preconditions;
14 import com.google.common.util.concurrent.FutureCallback;
15 import com.google.common.util.concurrent.Futures;
16 import com.google.common.util.concurrent.JdkFutureAdapters;
17 import com.google.common.util.concurrent.ListenableFuture;
18 import com.google.common.util.concurrent.MoreExecutors;
19 import java.lang.reflect.InvocationHandler;
20 import java.lang.reflect.Method;
21 import java.lang.reflect.Proxy;
22 import java.util.Collection;
23 import java.util.Optional;
24 import org.opendaylight.mdsal.binding.javav2.dom.adapter.extractor.ContextReferenceExtractor;
25 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.BindingNormalizedNodeCodecRegistry;
26 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.BindingToNormalizedNodeCodec;
27 import org.opendaylight.mdsal.binding.javav2.dom.codec.serialized.LazySerializedContainerNode;
28 import org.opendaylight.mdsal.binding.javav2.runtime.reflection.BindingReflections;
29 import org.opendaylight.mdsal.binding.javav2.spec.base.Input;
30 import org.opendaylight.mdsal.binding.javav2.spec.base.InstanceIdentifier;
31 import org.opendaylight.mdsal.binding.javav2.spec.base.Instantiable;
32 import org.opendaylight.mdsal.binding.javav2.spec.base.Output;
33 import org.opendaylight.mdsal.binding.javav2.spec.base.Rpc;
34 import org.opendaylight.mdsal.binding.javav2.spec.base.RpcCallback;
35 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
36 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
37 import org.opendaylight.mdsal.dom.api.DOMRpcService;
38 import org.opendaylight.mdsal.dom.spi.RpcRoutingStrategy;
39 import org.opendaylight.yangtools.yang.common.QName;
40 import org.opendaylight.yangtools.yang.common.RpcError;
41 import org.opendaylight.yangtools.yang.common.RpcError.ErrorSeverity;
42 import org.opendaylight.yangtools.yang.common.RpcResult;
43 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
46 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
47 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
48 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
49 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
50 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
51 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
52
53 @Beta
54 class RpcServiceAdapter implements InvocationHandler {
55
56     private final RpcInvocationStrategy strategy;
57     private final Class<? extends Rpc<?, ?>> type;
58     private final BindingToNormalizedNodeCodec codec;
59     private final DOMRpcService delegate;
60     private final Rpc<?, ?> proxy;
61
62     RpcServiceAdapter(final Class<? extends Rpc<?, ?>> type, final BindingToNormalizedNodeCodec codec,
63             final DOMRpcService domService) {
64         this.type = requireNonNull(type);
65         this.codec = requireNonNull(codec);
66         this.delegate = requireNonNull(domService);
67         strategy = createStrategy(type);
68         proxy = (Rpc<?, ?>) Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, this);
69     }
70
71     private RpcInvocationStrategy createStrategy(final Class<? extends Rpc<?, ?>> rpcInterface) {
72         final RpcDefinition rpc = codec.getRpcDefinition(rpcInterface);
73         final RpcRoutingStrategy domStrategy = RpcRoutingStrategy.from(rpc);
74         if (domStrategy.isContextBasedRouted()) {
75             try {
76                 return new RoutedStrategy(rpc.getPath(),
77                     rpcInterface.getMethod("invoke", Input.class, RpcCallback.class), domStrategy.getLeaf());
78             } catch (final NoSuchMethodException e) {
79                 throw new IllegalStateException("Can not find 'invoke' method", e);
80             }
81         }
82         return new NonRoutedStrategy(rpc.getPath());
83     }
84
85     Rpc<?, ?> getProxy() {
86         return proxy;
87     }
88
89     @Override
90     @SuppressWarnings("checkstyle:hiddenField")
91     public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
92         switch (method.getName()) {
93             case "toString":
94                 return type.getName() + "$Adapter{delegate=" + delegate.toString() + "}";
95             case "hashCode":
96                 return System.identityHashCode(proxy);
97             case "equals":
98                 return proxy == args[0];
99             case "invoke":
100                 if (args.length == 2) {
101                     final Input<?> input = (Input<?>) requireNonNull(args[0]);
102                     final RpcCallback<Output> callback = (RpcCallback<Output>) requireNonNull(args[1]);
103                     ListenableFuture<RpcResult<?>> future =  strategy.invoke((TreeNode) input);
104                     Futures.addCallback(future, new FutureCallback<RpcResult<?>>() {
105
106                         @Override
107                         public void onSuccess(final RpcResult<?> result) {
108                             if (result.getErrors().isEmpty()) {
109                                 callback.onSuccess((Output) result.getResult());
110                             } else {
111                                 result.getErrors().forEach(e -> callback.onFailure(e.getCause()));
112                             }
113                         }
114
115                         @Override
116                         public void onFailure(final Throwable throwable) {
117                             callback.onFailure(throwable);
118                         }
119                     }, MoreExecutors.directExecutor());
120                 }
121                 return 0;
122             default:
123                 break;
124         }
125
126         throw new UnsupportedOperationException("Method " + method.toString() + "is unsupported.");
127     }
128
129     private abstract class RpcInvocationStrategy {
130
131         private final SchemaPath rpcName;
132
133         protected RpcInvocationStrategy(final SchemaPath path) {
134             rpcName = path;
135         }
136
137         final ListenableFuture<RpcResult<?>> invoke(final TreeNode input) {
138             return invoke0(rpcName, serialize(input));
139         }
140
141         abstract NormalizedNode<?, ?> serialize(TreeNode input);
142
143         final ListenableFuture<RpcResult<?>> invokeEmpty() {
144             return invoke0(rpcName, null);
145         }
146
147         final SchemaPath getRpcName() {
148             return rpcName;
149         }
150
151         private ListenableFuture<RpcResult<?>> invoke0(final SchemaPath schemaPath, final NormalizedNode<?, ?> input) {
152             final ListenableFuture<DOMRpcResult> listenInPoolThread =
153                     JdkFutureAdapters.listenInPoolThread(delegate.invokeRpc(schemaPath, input));
154             if (listenInPoolThread instanceof LazyDOMRpcResultFuture) {
155                 return ((LazyDOMRpcResultFuture) listenInPoolThread).getBindingFuture();
156             }
157
158             return transformFuture(schemaPath, listenInPoolThread, codec.getCodecFactory());
159         }
160
161         private ListenableFuture<RpcResult<?>> transformFuture(final SchemaPath rpc,
162                 final ListenableFuture<DOMRpcResult> domFuture, final BindingNormalizedNodeCodecRegistry resultCodec) {
163             return Futures.transform(domFuture, input -> {
164                 final NormalizedNode<?, ?> domData = input.getResult();
165                 final TreeNode bindingResult;
166                 if (domData != null) {
167                     final SchemaPath rpcOutput = rpc.createChild(QName.create(rpc.getLastComponent(), "output"));
168                     bindingResult = resultCodec.fromNormalizedNodeOperationData(rpcOutput, (ContainerNode) domData);
169                 } else {
170                     bindingResult = null;
171                 }
172
173                 // DOMRpcResult does not have a notion of success, hence we have to reverse-engineer it by looking
174                 // at reported errors and checking whether they are just warnings.
175                 final Collection<? extends RpcError> errors = input.getErrors();
176                 return RpcResult.class.cast(RpcResultBuilder.status(errors.stream()
177                     .noneMatch(error -> error.getSeverity() == ErrorSeverity.ERROR))
178                     .withResult(bindingResult).withRpcErrors(errors).build());
179             }, MoreExecutors.directExecutor());
180         }
181     }
182
183     private final class NonRoutedStrategy extends RpcInvocationStrategy {
184
185         protected NonRoutedStrategy(final SchemaPath path) {
186             super(path);
187         }
188
189         @Override
190         NormalizedNode<?, ?> serialize(final TreeNode input) {
191             return LazySerializedContainerNode.create(getRpcName(), input, codec.getCodecRegistry());
192         }
193
194     }
195
196     private final class RoutedStrategy extends RpcInvocationStrategy {
197
198         private final ContextReferenceExtractor refExtractor;
199         private final NodeIdentifier contextName;
200
201         protected RoutedStrategy(final SchemaPath path, final Method rpcMethod, final QName leafName) {
202             super(path);
203             final Optional<Class<? extends Instantiable<?>>> maybeInputType =
204                     BindingReflections.resolveOperationInputClass(rpcMethod);
205             Preconditions.checkState(maybeInputType.isPresent(), "RPC method %s has no input", rpcMethod.getName());
206             final Class<? extends Instantiable<?>> inputType = maybeInputType.get();
207             refExtractor = ContextReferenceExtractor.from(inputType);
208             this.contextName = new NodeIdentifier(leafName);
209         }
210
211         @SuppressWarnings("rawtypes")
212         @Override
213         NormalizedNode<?, ?> serialize(final TreeNode input) {
214             final InstanceIdentifier<?> bindingII = refExtractor.extract(input);
215             if (bindingII != null) {
216                 final YangInstanceIdentifier yangII = codec.toYangInstanceIdentifierCached(bindingII);
217                 final LeafNode contextRef = ImmutableNodes.leafNode(contextName, yangII);
218                 return LazySerializedContainerNode.withContextRef(getRpcName(), input, contextRef,
219                         codec.getCodecRegistry());
220             }
221             return LazySerializedContainerNode.create(getRpcName(), input, codec.getCodecRegistry());
222         }
223
224     }
225 }