/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.tuscany.sca.core.invocation.impl;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.xml.ws.Response;
import org.apache.tuscany.sca.core.invocation.AsyncFaultWrapper;
import org.apache.tuscany.sca.core.invocation.AsyncResponseHandler;
/**
* A class which provides an Implementation of a Future<V> and Response<V> for use with the JAXWS defined client
* asynchronous APIs.
*
* This implementation class provides the interfaces for use by the client code, but also provides methods for the
* Tuscany system code to set the result of the asynchronous service invocation, both Regular and Fault responses.
*
* This class is constructed to be fully thread-safe
*
* @param <V> - this is the type of the response message from the invoked service.
*/
public class AsyncInvocationFutureImpl<V> implements Future<V>, Response<V>, AsyncResponseHandler<V> {
// Lock for handling the completion of this Future
private final Lock lock = new ReentrantLock();
private final Condition isDone = lock.newCondition();
// The result
private volatile V response = null;
private volatile Throwable fault = null;
private String uniqueID = UUID.randomUUID().toString();
private ClassLoader classLoader = null;
protected AsyncInvocationFutureImpl() {
super();
} // end constructor
/**
* Public constructor for AsyncInvocationFutureImpl - newInstance is necessary in order to enable the Type variable
* to be set for the class instances
* @param <V> - the type of the response from the asynchronously invoked service
* @param type - the type of the AsyncInvocationFutureImpl expressed as a parameter
* @param classLoader - the classloader used for the business interface to which this Future applies
* @return - an instance of AsyncInvocationFutureImpl<V>
*/
public static <V> AsyncInvocationFutureImpl<V> newInstance( Class<V> type, ClassLoader classLoader ) {
AsyncInvocationFutureImpl<V> future = new AsyncInvocationFutureImpl<V>();
future.setClassLoader( classLoader );
return future;
}
/**
* Cancels the asynchronous process
* - not possible in this version, so always returns false
*/
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
/**
* Gets the response value returned by the asynchronous process
* - waits forever
* @return - the response value of type V
* @throws InterruptedException if the get() method was interrupted while waiting for the async process to finish
* @throws ExecutionException if the async process threw an exception - the exception thrown is nested
*/
public V get() throws InterruptedException, ExecutionException {
try {
V response = get(Long.MAX_VALUE, TimeUnit.SECONDS);
return response;
} catch (TimeoutException t) {
throw new InterruptedException("Timed out waiting for Future to complete");
} // end try
} // end method get()
/**
* Gets the response value returned by the asynchronous process
* @return - the response value of type V
* @throws InterruptedException if the get() method was interrupted while waiting for the async process to finish
* @throws ExecutionException if the async process threw an exception - the exception thrown is nested
* @throws TimeoutException if the get() method timed out waiting for the async process to finish
*/
public V get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException {
lock.lock();
try {
// wait for result to be available
if( notSetYet() ) isDone.await( timeout, unit);
if( response != null ) return response;
if( fault != null ) throw new ExecutionException( fault );
throw new TimeoutException("get on this Future timed out");
} finally {
lock.unlock();
} // end try
} // end method get(long timeout, TimeUnit unit)
/**
* Indicates if the asynchronous process has been cancelled
* - not possible in this version so always returns false
*/
public boolean isCancelled() {
return false;
}
/**
* Indicates if the asynchronous process is completed
* @return - true if the process is completed, false otherwise
*/
public boolean isDone() {
lock.lock();
try {
return !notSetYet();
} finally {
lock.unlock();
} // end try
} // end method isDone
/**
* Async process completed with a Fault. Must only be invoked once
* @param e - the Fault to send
* @throws IllegalStateException if either the setResponse method or the setFault method have been called previously
*/
public void setFault(AsyncFaultWrapper w) {
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
Throwable e;
try {
// Set the TCCL to the classloader of the business interface
Thread.currentThread().setContextClassLoader(this.getClassLoader());
e = w.retrieveFault();
} finally {
Thread.currentThread().setContextClassLoader(tccl);
} // end try
if( e == null ) throw new IllegalArgumentException("AsyncFaultWrapper did not return an Exception");
lock.lock();
try {
if( notSetYet() ) {
fault = e;
isDone.signalAll();
} else {
throw new IllegalStateException("setResponse() or setFault() has been called previously");
} // end if
} finally {
lock.unlock();
} // end try
} // end method setFault
/**
* Async process completed with a response message. Must only be invoked once
* @throws IllegalStateException if either the setResponse method or the setFault method have been called previously
* @param res - the response message, which is of type V
*/
public void setResponse(V res) {
lock.lock();
try {
if( notSetYet() ) {
response = res;
isDone.signalAll();
} else {
throw new IllegalStateException("setResponse() or setFault() has been called previously");
}
} finally {
lock.unlock();
} // end try
} // end method setResponse
/**
* Gets the unique ID of this future as a String
*/
public String getUniqueID() { return uniqueID; }
/**
* Indicates that setting a response value is OK - can only set the response value or fault once
* @return - true if it is OK to set the response, false otherwise
*/
private boolean notSetYet() {
return ( response == null && fault == null );
}
/**
* Returns the JAXWS context for the response
* @return - a Map containing the context
*/
public Map<String, Object> getContext() {
// Intentionally returns null
return null;
}
/**
* Gets the classloader associated with the business interface to which this Future relates
* @return the ClassLoader of the business interface
*/
public ClassLoader getClassLoader() {
return classLoader;
}
/**
* Sets the classloader associated with the business interface to which this Future relates
* @param classLoader - the classloader of the business interface
*/
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
} // end class AsyncInvocationFutureImpl