/* * RHQ Management Platform * Copyright (C) 2005-2008 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.communications.command.client; import java.lang.reflect.InvocationTargetException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.rhq.enterprise.communications.command.CommandResponse; import org.rhq.enterprise.communications.command.impl.remotepojo.RemotePojoInvocationCommandResponse; /** * This is kind of "future" that can be used to wait for a remote POJO invocation. You typically use this to wait for an * asynchronous remote pojo invocation. Because this implements {@link CommandResponseCallback}, it can be used as the * callback to {@link ClientRemotePojoFactory#setAsynch(boolean, CommandResponseCallback)}. * * <p>This does not implement Future because its semantics are slightly different, but it does work in a similar manner. * Unlike Future, this object is not cancelable, but like Future, you can {@link #get()} the results blocking * indefinitely or you can supply a timeout via {@link #get(long, TimeUnit)}. This object can support multiple * "calculation results" - once you retreive the results, you should {@link #reset()} it in order to prepare this object * to receive another result. If you do not reset this object, the get methods will never block again, they will always * immediately return the last results it received. For this reason, it is preferable that you retreive the results via * {@link #get(long, TimeUnit)} or {@link #getAndReset(long, TimeUnit)} to force the reset to occur.</p> * * <p>This class is multi-thread safe, however, you must ensure that you call {@link #get()} to retrieve the results * before the callback method {@link #commandSent(CommandResponse)} is called again. If you do not, you will lose the * results for that previous invocation.</p> * * <p>Example usage:</p> * * <pre> * ClientRemotePojoFactory factory = ... * RemotePojoInvocationFuture future = new RemotePojoInvocationFuture(); * factory.setAsync(true, future); * MyRemoteAPI pojo = factory.getRemotePojo(MyRemoteAPI.class); * * pojo.aMethodCall(); // will be sent asynchronously * MyObject o = (MyObject) future.getAndReset(); // blocks until aMethodCall really finishes * * pojo.anotherCall(); // another asynchronous request * AnotherObject o = (AnotherObject) future.getAndReset(); // blocks until anotherCall really finishes * </pre> * * @author John Mazzitelli */ public class RemotePojoInvocationFuture implements CommandResponseCallback { /** * the UID to identify the serializable version of this class */ private static final long serialVersionUID = 1L; /** * When non-<code>null</code>, is the results of the last pojo invocation. */ private RemotePojoInvocationCommandResponse m_results = null; /** * This will reset this object such that it can prepare to accept another remote pojo invocation result. Calling * this method will result in this object clearing out any current calculation result, thus making {@link #get()} * block until a new response is received via {@link #commandSent(CommandResponse)}. Note that this method will * block if another thread is currently waiting in {@link #get()} - it will unblock once that thread is done * waiting. */ public void reset() { synchronized (this) { m_results = null; } return; } /** * This stores the new response in this object as this future's calculation result (any previous result is lost). * This new response's results object will be returned by {@link #get()}. * * @see CommandResponseCallback#commandSent(CommandResponse) */ public void commandSent(CommandResponse response) { synchronized (this) { if (response instanceof RemotePojoInvocationCommandResponse) { m_results = (RemotePojoInvocationCommandResponse) response; } else { m_results = new RemotePojoInvocationCommandResponse(response); } this.notifyAll(); } return; } /** * Same as {@link #get()}, but before this method returns, this object is {@link #reset()}. * * @return the invocation results * * @throws InterruptedException * @throws ExecutionException * * @see java.util.concurrent.Future#get() */ public Object getAndReset() throws InterruptedException, ExecutionException { Object results = get(); reset(); // note that if a concurrent thread calls get again, this will block; this rarely, if ever, should occur return results; } /** * Same as {@link #get(long, TimeUnit)}, but before this method returns, this object is {@link #reset()}. * * @param timeout the maximum amount of time to wait * @param unit the unit of time that <code>timeout</code> is specified in * * @return the invocation results * * @throws InterruptedException * @throws ExecutionException * @throws TimeoutException * * @see #get(long, TimeUnit) */ public Object getAndReset(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { Object results = get(timeout, unit); reset(); // note that if a concurrent thread calls get again, this will block; this rarely, if ever, should occur return results; } /** * Blocks indefinitely until the remote invocation results are available, at which time those results are returned. * If an exception occurred during the invocation, that exception is stored as the cause in the thrown * {@link ExecutionException}. * * @return the remote invocation results * * @throws InterruptedException if the current thread waiting for the results is interrupted * @throws ExecutionException if the remote invocation threw an exception. */ public Object get() throws InterruptedException, ExecutionException { try { return get(Long.MAX_VALUE, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { // this should never happen - it would mean we waited for Long.MAX_VALUE milliseconds! throw new ExecutionException(e); } } /** * Blocks for, at most, the specified amount of time or until the remote invocation results are available, at which * time those results are returned. If an exception occurred during the invocation, that exception is stored as the * cause in the thrown {@link ExecutionException}. * * @param timeout the maximum amount of time to wait * @param unit the unit of time that <code>timeout</code> is specified in * * @return the remote invocation results * * @throws InterruptedException if the current thread waiting for the results is interrupted * @throws ExecutionException if the remote invocation threw an exception. * @throws TimeoutException if the given amount of time has expired before the results have been received */ public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { RemotePojoInvocationCommandResponse result; long wait_ms = unit.toMillis(timeout); synchronized (this) { if (m_results == null) { this.wait(wait_ms); } result = m_results; } if (result == null) { throw new TimeoutException(); } else if (!result.isSuccessful()) { ExecutionException exc_to_throw; Throwable result_exc = result.getException(); if ((result_exc instanceof InvocationTargetException) && (result_exc.getCause() != null)) { exc_to_throw = new ExecutionException(result_exc.getCause()); } else { exc_to_throw = new ExecutionException(result_exc); } throw exc_to_throw; } // the command response results object is the object returned by the remote method return result.getResults(); } /** * Returns <code>true</code> if results are available and can be retrieved via the get methods without blocking. * Once the first remote invocation is done, this method will always return <code>true</code>, until this object is * {@link #reset()}. * * @return <code>true</code> if results are available; <code>false</code> if the invocation has not completed yet */ public boolean isDone() { Object current_results = m_results; // assignment does not require synchronization return current_results != null; } }