Add .gitreview
[serviceutils.git] / tools / api / src / main / java / org / opendaylight / serviceutils / tools / mdsal / rpc / FutureRpcResults.java
1 /*
2  * Copyright (c) 2018 Red Hat, 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.serviceutils.tools.mdsal.rpc;
9
10 import static org.opendaylight.yangtools.yang.common.RpcError.ErrorType.APPLICATION;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.util.concurrent.FutureCallback;
14 import com.google.common.util.concurrent.Futures;
15 import com.google.common.util.concurrent.ListenableFuture;
16 import com.google.common.util.concurrent.MoreExecutors;
17 import com.google.common.util.concurrent.SettableFuture;
18 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19 import java.util.concurrent.Callable;
20 import java.util.concurrent.Future;
21 import java.util.function.Consumer;
22 import java.util.function.Function;
23 import javax.annotation.CheckReturnValue;
24 import javax.annotation.Nullable;
25 import javax.annotation.concurrent.NotThreadSafe;
26 import org.opendaylight.infrautils.utils.StackTraces;
27 import org.opendaylight.yangtools.concepts.Builder;
28 import org.opendaylight.yangtools.yang.common.OperationFailedException;
29 import org.opendaylight.yangtools.yang.common.RpcError;
30 import org.opendaylight.yangtools.yang.common.RpcResult;
31 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
32 import org.slf4j.Logger;
33
34 /**
35  * Utility to simplify correctly handling transformation of Future of RpcResult to return.
36  *
37  * @author Michael Vorburger.ch
38  */
39 @Beta
40 public final class FutureRpcResults {
41
42     // NB: The FutureRpcResultsTest unit test for this util is in mdsalutil-testutils's src/test, not this project's
43
44     // TODO Once matured in genius, this class could be proposed to org.opendaylight.yangtools.yang.common
45     // (This was proposed in Oct on yangtools-dev list, but there little interest due to plans to change RpcResult.)
46
47     private FutureRpcResults() {}
48
49     /**
50      * Create a Builder for a ListenableFuture to Future<RpcResult<O>> transformer. By default, the future
51      * will log success or failure, with configurable log levels; the caller can also add handlers for success and/or
52      * failure.
53      *
54      * <p>The RPC's method name is automatically obtained using {@link StackTraces}.  This has some cost, which in
55      * the overall scheme of a typical RPC is typically negligible, but on a highly optimized fast path could
56      * theoretically be an issue; if you see this method as a hot spot in a profiler, then (only) use the
57      * alternative signature where you manually pass the String rpcMethodName.
58      *
59      * @param logger the slf4j Logger of the caller
60      * @param input the RPC input DataObject of the caller (may be null)
61      * @param callable the Callable (typically lambda) creating a ListenableFuture.  Note that the
62      *        functional interface Callable's call() method declares throws Exception, so your lambda
63      *        does not have to do any exception handling (specifically it does NOT have to catch and
64      *        wrap any exception into a failed Future); this utility does that for you.
65      *
66      * @return a new Builder
67      */
68     @CheckReturnValue
69     public static <I, O> FutureRpcResultBuilder<I, O> fromListenableFuture(Logger logger,
70             @Nullable I input, Callable<ListenableFuture<O>> callable) {
71         return new FutureRpcResultBuilder<>(logger, StackTraces.getCallersCallerMethodName(), input, callable);
72     }
73
74     /**
75      * Create a Builder for a ListenableFuture to Future&lt;RpcResult&lt;O&gt;&gt; transformer. By default, the future
76      * will log success or failure, with configurable log levels; the caller can also add handlers for success and/or
77      * failure.
78      *
79      * @param logger the slf4j Logger of the caller
80      * @param rpcMethodName Java method name (without "()") of the RPC operation, used for logging
81      * @param input the RPC input DataObject of the caller (may be null)
82      * @param callable the Callable (typically lambda) creating a ListenableFuture.  Note that the
83      *        functional interface Callable's call() method declares throws Exception, so your lambda
84      *        does not have to do any exception handling (specifically it does NOT have to catch and
85      *        wrap any exception into a failed Future); this utility does that for you.
86      *
87      * @return a new FutureRpcResultBuilder
88      */
89     @CheckReturnValue
90     @SuppressWarnings("InconsistentOverloads") // Error Prone is too strict here; we do want the Callable last
91     public static <I, O> FutureRpcResultBuilder<I, O> fromListenableFuture(Logger logger, String rpcMethodName,
92             @Nullable I input, Callable<ListenableFuture<O>> callable) {
93         return new FutureRpcResultBuilder<>(logger, rpcMethodName, input, callable);
94     }
95
96     public enum LogLevel {
97         ERROR, WARN, INFO, DEBUG, TRACE,
98         /**
99          * Note that when using LogLevel NONE for failures, then you should set a
100          * {@link FutureRpcResultBuilder#onFailure(Consumer)} which does better logging,
101          * or be 100% sure that all callers of the RPC check the returned Future RpcResult appropriately;
102          * otherwise you will lose error messages.
103          */
104         NONE;
105         @SuppressFBWarnings({"SLF4J_UNKNOWN_ARRAY","SLF4J_FORMAT_SHOULD_BE_CONST"})
106         public void log(Logger logger, String format, Object... arguments) {
107             switch (this) {
108                 case NONE:
109                     break;
110                 case TRACE:
111                     logger.trace(format, arguments);
112                     break;
113                 case DEBUG:
114                     logger.debug(format, arguments);
115                     break;
116                 case INFO:
117                     logger.info(format, arguments);
118                     break;
119                 case WARN:
120                     logger.warn(format, arguments);
121                     break;
122                 default: // including ERROR
123                     logger.error(format, arguments);
124                     break;
125             }
126         }
127     }
128
129     @CheckReturnValue
130     @SuppressWarnings("InconsistentOverloads") // Error Prone is too strict here; we do want the Callable last
131     public static <I, O> FutureRpcResultBuilder<I, O> fromBuilder(Logger logger, String rpcMethodName,
132             @Nullable I input, Callable<Builder<O>> builder) {
133         Callable<ListenableFuture<O>> callable = () -> Futures.immediateFuture(builder.call().build());
134         return fromListenableFuture(logger, rpcMethodName, input, callable);
135     }
136
137     @CheckReturnValue
138     public static <I, O> FutureRpcResultBuilder<I, O> fromBuilder(Logger logger, @Nullable I input,
139             Callable<Builder<O>> builder) {
140         Callable<ListenableFuture<O>> callable = () -> Futures.immediateFuture(builder.call().build());
141         return fromListenableFuture(logger, StackTraces.getCallersCallerMethodName(), input, callable);
142     }
143
144     @NotThreadSafe
145     public static final class FutureRpcResultBuilder<I, O> implements Builder<Future<RpcResult<O>>> {
146
147         private static final Function<Throwable, String> DEFAULT_ERROR_MESSAGE_FUNCTION = Throwable::getMessage;
148         private static final Consumer<Throwable> DEFAULT_ON_FAILURE = throwable -> { };
149         private final Consumer<O> defaultOnSuccess = result -> { };
150
151         // fixed (final) builder values
152         private final Logger logger;
153         private final String rpcMethodName;
154         @Nullable private final I input;
155         private final Callable<ListenableFuture<O>> callable;
156
157         // optional builder values, which can be overridden by users
158         private Function<Throwable, String> rpcErrorMessageFunction = DEFAULT_ERROR_MESSAGE_FUNCTION;
159         private Consumer<O> onSuccessConsumer = defaultOnSuccess;
160         private Consumer<Throwable> onFailureConsumer = DEFAULT_ON_FAILURE;
161
162         // defaulted builder values, which can be overridden by users
163         private LogLevel onEnterLogLevel = LogLevel.TRACE;
164         private LogLevel onSuccessLogLevel = LogLevel.DEBUG;
165         private LogLevel onFailureLogLevel = LogLevel.ERROR;
166
167         private FutureRpcResultBuilder(Logger logger, String rpcMethodName, @Nullable I input,
168                 Callable<ListenableFuture<O>> callable) {
169             this.logger = logger;
170             this.rpcMethodName = rpcMethodName;
171             this.input = input;
172             this.callable = callable;
173         }
174
175         /**
176          * Builds the Future RpcResult.
177          *
178          * @return Future RpcResult. Note that this will NEVER be a failed Future; any
179          *         errors are reported as !{@link RpcResult#isSuccessful()}, with
180          *         details in {@link RpcResult#getErrors()}, and not the Future itself.
181          */
182         @Override
183         @CheckReturnValue
184         @SuppressWarnings("checkstyle:IllegalCatch")
185         public ListenableFuture<RpcResult<O>> build() {
186             SettableFuture<RpcResult<O>> futureRpcResult = SettableFuture.create();
187             FutureCallback<O> callback = new FutureCallback<O>() {
188                 @Override
189                 public void onSuccess(O result) {
190                     onSuccessLogLevel.log(logger, "RPC {}() successful; input = {}, output = {}", rpcMethodName,
191                             input, result);
192                     onSuccessConsumer.accept(result);
193                     futureRpcResult.set(RpcResultBuilder.success(result).build());
194                 }
195
196                 @Override
197                 public void onFailure(Throwable cause) {
198                     onFailureLogLevel.log(logger, "RPC {}() failed; input = {}", rpcMethodName, input, cause);
199                     onFailureConsumer.accept(cause);
200                     RpcResultBuilder<O> rpcResultBuilder =  RpcResultBuilder.failed();
201                     if (cause instanceof OperationFailedException) {
202                         // NB: This looses (not not propagate) the cause, and only preserves the error list
203                         // But we did log the cause above, so it can still be found.
204                         rpcResultBuilder.withRpcErrors(((OperationFailedException) cause).getErrorList());
205                     } else {
206                         rpcResultBuilder.withError(APPLICATION, rpcErrorMessageFunction.apply(cause), cause);
207                     }
208                     futureRpcResult.set(rpcResultBuilder.build());
209                 }
210             };
211             try {
212                 onEnterLogLevel.log(logger, "RPC {}() entered; input = {}", rpcMethodName, input);
213                 Futures.addCallback(callable.call(), callback, MoreExecutors.directExecutor());
214             } catch (Exception cause) {
215                 callback.onFailure(cause);
216             }
217             return futureRpcResult;
218         }
219
220         /**
221          * Sets a custom on-failure action, for a given exception.
222          */
223         public FutureRpcResultBuilder<I,O> onFailure(Consumer<Throwable> newOnFailureConsumer) {
224             if (onFailureConsumer != DEFAULT_ON_FAILURE) {
225                 throw new IllegalStateException("onFailure can only be set once");
226             }
227             this.onFailureConsumer = newOnFailureConsumer;
228             return this;
229         }
230
231         /**
232          * Sets a custom on-failure SLF4J logging level, in case of an exception. The log message mentions the RPC
233          * method name, the provided input, the exception and its stack trace (depending on logger settings).
234          * By default, it is {@code LOG.error}. Setting {@code NONE} will disable this logging.
235          */
236         public FutureRpcResultBuilder<I,O> onFailureLogLevel(LogLevel level) {
237             this.onFailureLogLevel = level;
238             return this;
239         }
240
241         /**
242          * Sets a custom on-success SLF4J logging level. The log message mentions the RPC method name, the provided
243          * input, and the resulting output.
244          * By default, it is {@code LOG.debug}. Setting {@code NONE} will disable this logging.
245          */
246         public FutureRpcResultBuilder<I,O> onSuccessLogLevel(LogLevel level) {
247             this.onSuccessLogLevel = level;
248             return this;
249         }
250
251         /**
252          * Sets a custom on-enter SLF4J logging level. The log message mentions the RPC method name and the provided
253          * input.
254          * By default, it is {@code LOG.trace}. Setting {@code NONE} will disable this logging.
255          */
256         public FutureRpcResultBuilder<I,O> onEnterLogLevel(LogLevel level) {
257             this.onEnterLogLevel = level;
258             return this;
259         }
260
261         /**
262          * Set a custom {@link RpcError} message function, for a given exception.
263          * By default, the message is just {@link Throwable#getMessage()}.
264          */
265         public FutureRpcResultBuilder<I,O> withRpcErrorMessage(Function<Throwable, String> newRpcErrorMessageFunction) {
266             if (rpcErrorMessageFunction != DEFAULT_ERROR_MESSAGE_FUNCTION) {
267                 throw new IllegalStateException("rpcErrorMessage can only be set once");
268             }
269             this.rpcErrorMessageFunction = newRpcErrorMessageFunction;
270             return this;
271         }
272
273         /**
274          * Sets a custom on-success action, for a given output.
275          */
276         public FutureRpcResultBuilder<I,O> onSuccess(Consumer<O> newOnSuccessFunction) {
277             if (onSuccessConsumer != defaultOnSuccess) {
278                 throw new IllegalStateException("onSuccess can only be set once");
279             }
280             this.onSuccessConsumer = newOnSuccessFunction;
281             return this;
282         }
283
284     }
285 }