2 * Copyright (c) 2017 Red Hat, Inc. and others. All rights reserved.
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
8 package org.opendaylight.genius.infra;
10 import static org.opendaylight.yangtools.yang.common.RpcError.ErrorType.APPLICATION;
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;
35 * Utility to simplify correctly handling transformation of Future of RpcResult to return.
37 * @author Michael Vorburger.ch
40 public final class FutureRpcResults {
42 // NB: The FutureRpcResultsTest unit test for this util is in mdsalutil-testutils's src/test, not this project's
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.)
47 private FutureRpcResults() {}
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
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.
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.
66 * @return a new Builder
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);
75 * Create a Builder for a ListenableFuture to Future<RpcResult<O>> 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
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.
87 * @return a new FutureRpcResultBuilder
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);
95 public enum LogLevel {
96 ERROR, WARN, INFO, DEBUG, TRACE,
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.
104 @SuppressWarnings({"SLF4J_UNKNOWN_ARRAY","SLF4J_FORMAT_SHOULD_BE_CONST"})
105 public void log(Logger logger, String format, Object... arguments) {
110 logger.trace(format, arguments);
113 logger.debug(format, arguments);
116 logger.info(format, arguments);
119 logger.warn(format, arguments);
121 default: // including ERROR
122 logger.error(format, arguments);
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);
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);
143 public static final class FutureRpcResultBuilder<I, O> implements Builder<Future<RpcResult<O>>> {
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 -> { };
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;
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;
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;
165 private FutureRpcResultBuilder(Logger logger, String rpcMethodName, @Nullable I input,
166 Callable<ListenableFuture<O>> callable) {
167 this.logger = logger;
168 this.rpcMethodName = rpcMethodName;
170 this.callable = callable;
174 * Builds the Future RpcResult.
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.
182 @SuppressWarnings("checkstyle:IllegalCatch")
183 public Future<RpcResult<O>> build() {
184 SettableFuture<RpcResult<O>> futureRpcResult = SettableFuture.create();
185 FutureCallback<O> callback = new FutureCallback<O>() {
187 public void onSuccess(O result) {
188 onSuccessLogLevel.log(logger, "RPC {}() successful; input = {}, output = {}", rpcMethodName,
190 onSuccessConsumer.accept(result);
191 futureRpcResult.set(RpcResultBuilder.success(result).build());
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());
204 rpcResultBuilder.withError(APPLICATION, rpcErrorMessageFunction.apply(cause), cause);
206 futureRpcResult.set(rpcResultBuilder.build());
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);
215 return futureRpcResult;
219 * Sets a custom on-failure action, for a given exception.
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");
225 this.onFailureConsumer = newOnFailureConsumer;
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.
234 public FutureRpcResultBuilder<I,O> onFailureLogLevel(LogLevel level) {
235 this.onFailureLogLevel = level;
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.
244 public FutureRpcResultBuilder<I,O> onSuccessLogLevel(LogLevel level) {
245 this.onSuccessLogLevel = level;
250 * Sets a custom on-enter SLF4J logging level. The log message mentions the RPC method name and the provided
252 * By default, it is {@code LOG.trace}. Setting {@code NONE} will disable this logging.
254 public FutureRpcResultBuilder<I,O> onEnterLogLevel(LogLevel level) {
255 this.onEnterLogLevel = level;
260 * Set a custom {@link RpcError} message function, for a given exception.
261 * By default, the message is just {@link Throwable#getMessage()}.
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");
267 this.rpcErrorMessageFunction = newRpcErrorMessageFunction;
272 * Sets a custom on-success action, for a given output.
274 public FutureRpcResultBuilder<I,O> onSuccess(Consumer<O> newOnSuccessFunction) {
275 if (onSuccessConsumer != defaultOnSuccess) {
276 throw new IllegalStateException("onSuccess can only be set once");
278 this.onSuccessConsumer = newOnSuccessFunction;