/* * Copyright (c) 2015, Jurriaan Mous and contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package mousio.client.promises; import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.Promise; import mousio.client.ConnectionState; import mousio.client.retry.ConnectionFailHandler; import mousio.client.retry.RetryHandler; import mousio.client.retry.RetryPolicy; import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeoutException; /** * A Response promise * * @param <T> Type of object returned by promise */ public class ResponsePromise<T> { private final RetryPolicy retryPolicy; private final ConnectionState connectionState; private final RetryHandler retryHandler; protected Promise<T> promise; protected T response; protected Throwable exception; private List<IsSimplePromiseResponseHandler<T>> handlers; private final GenericFutureListener<Promise<T>> promiseHandler; private final ConnectionFailHandler connectionFailHandler; /** * Constructor * * @param retryPolicy the policy for retries * @param connectionState which contains current connection details * @param retryHandler handler for retries */ public ResponsePromise(RetryPolicy retryPolicy, ConnectionState connectionState, RetryHandler retryHandler) { this.connectionState = connectionState; this.retryHandler = retryHandler; this.retryPolicy = retryPolicy; promiseHandler = new GenericFutureListener<Promise<T>>() { @Override public void operationComplete(Promise<T> future) throws Exception { handlePromise(future); } }; this.connectionFailHandler = new ConnectionFailHandler() { @Override public void catchException(IOException exception) { handleRetry(exception); } }; } /** * Attach Netty Promise * * @param promise netty promise to set up response promise with */ public void attachNettyPromise(Promise<T> promise) { promise.addListener(promiseHandler); Promise<T> oldPromise = this.promise; this.promise = promise; if (oldPromise != null) { oldPromise.removeListener(promiseHandler); oldPromise.cancel(true); } } /** * Add a promise to do when Response comes in * * @param listener to add */ public void addListener(IsSimplePromiseResponseHandler<T> listener) { if (handlers == null) { handlers = new LinkedList<>(); } handlers.add(listener); if (response != null || exception != null) { listener.onResponse(this); } } /** * Remove a listener * * @param listener to remove */ public void removeListener(IsSimplePromiseResponseHandler<T> listener) { if (handlers != null) { handlers.remove(listener); } } /** * Handle the promise * * @param promise to handle */ protected void handlePromise(Promise<T> promise) { if (!promise.isSuccess()) { this.setException(promise.cause()); } else { this.response = promise.getNow(); if (handlers != null) { for (IsSimplePromiseResponseHandler<T> h : handlers) { h.onResponse(this); } } } } /** * Sets exception * * @param exception to set. */ public void setException(Throwable exception) { this.exception = exception; if (handlers != null) { for (IsSimplePromiseResponseHandler<T> h : handlers) { h.onResponse(this); } } } /** * Get the response. (Blocking) * * Use addListener to fetch the value in a non blocking way. * * @return the response * @throws Exception on fail */ public T get() throws Exception { if (!waitForPromiseSuccess()) { return this.get(); } if (response != null) { return response; } else { if (this.exception instanceof IOException) { throw (IOException) this.exception; } else if (this.exception instanceof io.netty.handler.timeout.TimeoutException) { throw new TimeoutException(); } else { throw new IOException(this.exception); } } } /** * Wait for promise to be done * * @return true if success, false if fail * @throws IOException on IOException * @throws TimeoutException on timeout */ protected boolean waitForPromiseSuccess() throws IOException, TimeoutException { if (!promise.isDone() && !promise.isCancelled()) { Promise<T> listeningPromise = this.promise; listeningPromise.awaitUninterruptibly(); if (listeningPromise != this.promise) { return false; } this.handlePromise(promise); } return true; } /** * Get the result now even if it is not loaded yet by the promise. * Use get() to ensure in a blocking way that the value is loaded. * * @return the result */ public T getNow() { return response; } /** * Get internal Netty Promise * * @return Netty Promise */ public Promise<T> getNettyPromise() { return promise; } /** * Handles a retry * * @param cause of last connect fail */ public void handleRetry(Throwable cause) { try { this.retryPolicy.retry(connectionState, retryHandler, connectionFailHandler); } catch (RetryPolicy.RetryCancelled retryCancelled) { this.getNettyPromise().setFailure(cause); } } /** * Get the current connection state * * @return connection state */ public ConnectionState getConnectionState() { return connectionState; } /** * Cancel the request */ public void cancel() { promise.cancel(true); } /** * Cancel the request * * @param throwable the exception to be associated to this promise */ public void cancel(Throwable throwable) { this.promise.cancel(true); this.exception = throwable; } /** * Get exception * * @return the exception if set. Or null if no exception present */ public Throwable getException() { return exception; } /** * Response listener * * @param <T> Type contained */ public interface IsSimplePromiseResponseHandler<T> { /** * Fired on response * * @param response with result */ void onResponse(ResponsePromise<T> response); } }