Remove redundant type information
[genius.git] / mdsalutil / mdsalutil-api / src / main / java / org / opendaylight / genius / infra / FutureRpcResults.java
1 /*
2  * Copyright (c) 2017 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.genius.infra;
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.SuppressWarnings;
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     public static <I, O> FutureRpcResultBuilder<I, O> fromListenableFuture(Logger logger, String rpcMethodName,
91             @Nullable I input, Callable<ListenableFuture<O>> callable) {
92         return new FutureRpcResultBuilder<>(logger, rpcMethodName, input, callable);
93     }
94
95     public enum LogLevel {
96         ERROR, WARN, INFO, DEBUG, TRACE,
97         /**
98          * Note that when using LogLevel NONE for failures, then you should set a
99          * {@link FutureRpcResultBuilder#onFailure(Consumer)} which does better logging,
100          * or be 100% sure that all callers of the RPC check the returned Future RpcResult appropriately;
101          * otherwise you will lose error messages.
102          */
103         NONE;
104         @SuppressWarnings({"SLF4J_UNKNOWN_ARRAY","SLF4J_FORMAT_SHOULD_BE_CONST"})
105         public void log(Logger logger, String format, Object... arguments) {
106             switch (this) {
107                 case NONE:
108                     break;
109                 case TRACE:
110                     logger.trace(format, arguments);
111                     break;
112                 case DEBUG:
113                     logger.debug(format, arguments);
114                     break;
115                 case INFO:
116                     logger.info(format, arguments);
117                     break;
118                 case WARN:
119                     logger.warn(format, arguments);
120                     break;
121                 default: // including ERROR
122                     logger.error(format, arguments);
123                     break;
124             }
125         }
126     }
127
128     @CheckReturnValue
129     public static <I, O> FutureRpcResultBuilder<I, O> fromBuilder(Logger logger, String rpcMethodName,
130             @Nullable I input, Callable<Builder<O>> builder) {
131         Callable<ListenableFuture<O>> callable = () -> Futures.immediateFuture(builder.call().build());
132         return fromListenableFuture(logger, rpcMethodName, input, callable);
133     }
134
135     @CheckReturnValue
136     public static <I, O> FutureRpcResultBuilder<I, O> fromBuilder(Logger logger, @Nullable I input,
137             Callable<Builder<O>> builder) {
138         Callable<ListenableFuture<O>> callable = () -> Futures.immediateFuture(builder.call().build());
139         return fromListenableFuture(logger, StackTraces.getCallersCallerMethodName(), input, callable);
140     }
141
142     @NotThreadSafe
143     public static final class FutureRpcResultBuilder<I, O> implements Builder<Future<RpcResult<O>>> {
144
145         private static final Function<Throwable, String> DEFAULT_ERROR_MESSAGE_FUNCTION = Throwable::getMessage;
146         private static final Consumer<Throwable> DEFAULT_ON_FAILURE = throwable -> { };
147         private final Consumer<O> defaultOnSuccess = result -> { };
148
149         // fixed (final) builder values
150         private final Logger logger;
151         private final String rpcMethodName;
152         @Nullable private final I input;
153         private final Callable<ListenableFuture<O>> callable;
154
155         // optional builder values, which can be overridden by users
156         private Function<Throwable, String> rpcErrorMessageFunction = DEFAULT_ERROR_MESSAGE_FUNCTION;
157         private Consumer<O> onSuccessConsumer = defaultOnSuccess;
158         private Consumer<Throwable> onFailureConsumer = DEFAULT_ON_FAILURE;
159
160         // defaulted builder values, which can be overridden by users
161         private LogLevel onEnterLogLevel = LogLevel.TRACE;
162         private LogLevel onSuccessLogLevel = LogLevel.DEBUG;
163         private LogLevel onFailureLogLevel = LogLevel.ERROR;
164
165         private FutureRpcResultBuilder(Logger logger, String rpcMethodName, @Nullable I input,
166                 Callable<ListenableFuture<O>> callable) {
167             this.logger = logger;
168             this.rpcMethodName = rpcMethodName;
169             this.input = input;
170             this.callable = callable;
171         }
172
173         /**
174          * Builds the Future RpcResult.
175          *
176          * @return Future RpcResult. Note that this will NEVER be a failed Future; any
177          *         errors are reported as !{@link RpcResult#isSuccessful()}, with
178          *         details in {@link RpcResult#getErrors()}, and not the Future itself.
179          */
180         @Override
181         @CheckReturnValue
182         @SuppressWarnings("checkstyle:IllegalCatch")
183         public Future<RpcResult<O>> build() {
184             SettableFuture<RpcResult<O>> futureRpcResult = SettableFuture.create();
185             FutureCallback<O> callback = new FutureCallback<O>() {
186                 @Override
187                 public void onSuccess(O result) {
188                     onSuccessLogLevel.log(logger, "RPC {}() successful; input = {}, output = {}", rpcMethodName,
189                             input, result);
190                     onSuccessConsumer.accept(result);
191                     futureRpcResult.set(RpcResultBuilder.success(result).build());
192                 }
193
194                 @Override
195                 public void onFailure(Throwable cause) {
196                     onFailureLogLevel.log(logger, "RPC {}() failed; input = {}", rpcMethodName, input, cause);
197                     onFailureConsumer.accept(cause);
198                     RpcResultBuilder<O> rpcResultBuilder =  RpcResultBuilder.failed();
199                     if (cause instanceof OperationFailedException) {
200                         // NB: This looses (not not propagate) the cause, and only preserves the error list
201                         // But we did log the cause above, so it can still be found.
202                         rpcResultBuilder.withRpcErrors(((OperationFailedException) cause).getErrorList());
203                     } else {
204                         rpcResultBuilder.withError(APPLICATION, rpcErrorMessageFunction.apply(cause), cause);
205                     }
206                     futureRpcResult.set(rpcResultBuilder.build());
207                 }
208             };
209             try {
210                 onEnterLogLevel.log(logger, "RPC {}() entered; input = {}", rpcMethodName, input);
211                 Futures.addCallback(callable.call(), callback, MoreExecutors.directExecutor());
212             } catch (Exception cause) {
213                 callback.onFailure(cause);
214             }
215             return futureRpcResult;
216         }
217
218         /**
219          * Sets a custom on-failure action, for a given exception.
220          */
221         public FutureRpcResultBuilder<I,O> onFailure(Consumer<Throwable> newOnFailureConsumer) {
222             if (onFailureConsumer != DEFAULT_ON_FAILURE) {
223                 throw new IllegalStateException("onFailure can only be set once");
224             }
225             this.onFailureConsumer = newOnFailureConsumer;
226             return this;
227         }
228
229         /**
230          * Sets a custom on-failure SLF4J logging level, in case of an exception. The log message mentions the RPC
231          * method name, the provided input, the exception and its stack trace (depending on logger settings).
232          * By default, it is {@code LOG.error}. Setting {@code NONE} will disable this logging.
233          */
234         public FutureRpcResultBuilder<I,O> onFailureLogLevel(LogLevel level) {
235             this.onFailureLogLevel = level;
236             return this;
237         }
238
239         /**
240          * Sets a custom on-success SLF4J logging level. The log message mentions the RPC method name, the provided
241          * input, and the resulting output.
242          * By default, it is {@code LOG.debug}. Setting {@code NONE} will disable this logging.
243          */
244         public FutureRpcResultBuilder<I,O> onSuccessLogLevel(LogLevel level) {
245             this.onSuccessLogLevel = level;
246             return this;
247         }
248
249         /**
250          * Sets a custom on-enter SLF4J logging level. The log message mentions the RPC method name and the provided
251          * input.
252          * By default, it is {@code LOG.trace}. Setting {@code NONE} will disable this logging.
253          */
254         public FutureRpcResultBuilder<I,O> onEnterLogLevel(LogLevel level) {
255             this.onEnterLogLevel = level;
256             return this;
257         }
258
259         /**
260          * Set a custom {@link RpcError} message function, for a given exception.
261          * By default, the message is just {@link Throwable#getMessage()}.
262          */
263         public FutureRpcResultBuilder<I,O> withRpcErrorMessage(Function<Throwable, String> newRpcErrorMessageFunction) {
264             if (rpcErrorMessageFunction != DEFAULT_ERROR_MESSAGE_FUNCTION) {
265                 throw new IllegalStateException("rpcErrorMessage can only be set once");
266             }
267             this.rpcErrorMessageFunction = newRpcErrorMessageFunction;
268             return this;
269         }
270
271         /**
272          * Sets a custom on-success action, for a given output.
273          */
274         public FutureRpcResultBuilder<I,O> onSuccess(Consumer<O> newOnSuccessFunction) {
275             if (onSuccessConsumer != defaultOnSuccess) {
276                 throw new IllegalStateException("onSuccess can only be set once");
277             }
278             this.onSuccessConsumer = newOnSuccessFunction;
279             return this;
280         }
281
282     }
283 }