/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.engine.resource;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.logging.Level;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Uniform;
import org.restlet.data.ClientInfo;
import org.restlet.data.Form;
import org.restlet.data.Status;
import org.restlet.engine.application.StatusInfo;
import org.restlet.representation.Representation;
import org.restlet.representation.Variant;
import org.restlet.resource.ClientProxy;
import org.restlet.resource.ClientResource;
import org.restlet.resource.ResourceException;
import org.restlet.resource.Result;
// [excludes gwt]
/**
* Reflection proxy invocation handler created for the
* {@link ClientResource#wrap(Class)} and related methods.
*
* @author Jerome Louvel
*
* @param <T>
* The annotated resource interface.
*/
public class ClientInvocationHandler<T> implements InvocationHandler {
/** The annotations of the resource interface. */
private final List<AnnotationInfo> annotations;
/** The associated annotation utils. */
private AnnotationUtils annotationUtils;
/** The associated client resource. */
private final ClientResource clientResource;
/**
* Constructor.
*
* @param clientResource
* The associated client resource.
* @param resourceInterface
* The annotated resource interface.
*/
public ClientInvocationHandler(ClientResource clientResource,
Class<? extends T> resourceInterface) {
this(clientResource, resourceInterface, AnnotationUtils.getInstance());
}
/**
* Constructor.
*
* @param clientResource
* The associated client resource.
* @param resourceInterface
* The annotated resource interface.
* @param annotationUtils
* The annotationUtils class.
*/
public ClientInvocationHandler(ClientResource clientResource,
Class<? extends T> resourceInterface,
AnnotationUtils annotationUtils) {
this.clientResource = clientResource;
this.annotationUtils = annotationUtils;
// Introspect the interface for Restlet annotations
this.annotations = getAnnotationUtils().getAnnotations(
resourceInterface);
}
/**
* Returns the annotations of the resource interface.
*
* @return The annotations of the resource interface.
*/
public List<AnnotationInfo> getAnnotations() {
return annotations;
}
/**
* Returns the associated annotation utils.
*
* @return The associated annotation utils.
*/
public AnnotationUtils getAnnotationUtils() {
return annotationUtils;
}
/**
* Returns the associated client resource.
*
* @return The associated client resource.
*/
public ClientResource getClientResource() {
return clientResource;
}
/**
* Allows for child classes to modify the request.
*/
protected Request getRequest(Method javaMethod, Object[] args)
throws Throwable {
return getClientResource().createRequest();
}
/**
* Effectively invokes a Java method on the given proxy object.
*/
@SuppressWarnings("rawtypes")
public Object invoke(Object proxy, java.lang.reflect.Method javaMethod,
Object[] args) throws Throwable {
Object result = null;
if (javaMethod.equals(Object.class.getMethod("toString"))) {
// Help debug
result = "ClientProxy for resource: " + clientResource;
} else if (javaMethod.equals(ClientProxy.class
.getMethod("getClientResource"))) {
result = clientResource;
} else {
MethodAnnotationInfo annotationInfo = getAnnotationUtils()
.getMethodAnnotation(getAnnotations(), javaMethod);
if (annotationInfo != null) {
Representation requestEntity = null;
if ((args != null) && args.length > 0) {
// Checks if the user has defined its own callback.
for (int i = 0; i < args.length; i++) {
Object o = args[i];
if (o == null) {
requestEntity = null;
} else if (Result.class.isAssignableFrom(o.getClass())) {
// Asynchronous mode where a callback object is to
// be called.
// Get the kind of result expected.
final Result rCallback = (Result) o;
Type[] genericParameterTypes = javaMethod
.getGenericParameterTypes();
Type genericParameterType = genericParameterTypes[i];
ParameterizedType parameterizedType = (genericParameterType instanceof java.lang.reflect.ParameterizedType) ? (java.lang.reflect.ParameterizedType) genericParameterType
: null;
final Class<?> actualType = (parameterizedType
.getActualTypeArguments()[0] instanceof Class<?>) ? (Class<?>) parameterizedType
.getActualTypeArguments()[0] : null;
// Define the callback
Uniform callback = new Uniform() {
@SuppressWarnings("unchecked")
public void handle(Request request,
Response response) {
if (response.getStatus().isError()) {
rCallback
.onFailure(new ResourceException(
response.getStatus()));
} else {
if (actualType != null) {
Object result = null;
boolean serializationError = false;
try {
result = getClientResource()
.toObject(
response.getEntity(),
actualType);
} catch (Exception e) {
serializationError = true;
rCallback
.onFailure(new ResourceException(
e));
}
if (!serializationError) {
rCallback.onSuccess(result);
}
} else {
rCallback.onSuccess(null);
}
}
}
};
getClientResource().setOnResponse(callback);
} else {
requestEntity = getClientResource()
.toRepresentation(o);
}
}
}
// Clone the prototype request
Request request = getRequest(javaMethod, args);
// The Java method was annotated
request.setMethod(annotationInfo.getRestletMethod());
// Add the mandatory query parameters
String query = annotationInfo.getQuery();
if (query != null) {
Form queryParams = new Form(annotationInfo.getQuery());
request.getResourceRef().addQueryParameters(queryParams);
}
// Set the entity
request.setEntity(requestEntity);
// Updates the client preferences if they weren't changed
if ((request.getClientInfo().getAcceptedCharacterSets()
.isEmpty())
&& (request.getClientInfo().getAcceptedEncodings()
.isEmpty())
&& (request.getClientInfo().getAcceptedLanguages()
.isEmpty())
&& (request.getClientInfo().getAcceptedMediaTypes()
.isEmpty())) {
List<Variant> responseVariants = annotationInfo
.getResponseVariants(getClientResource()
.getMetadataService(), getClientResource()
.getConverterService());
if (responseVariants != null) {
request.setClientInfo(new ClientInfo(responseVariants));
}
}
// Effectively handle the call
Response response = getClientResource().handleOutbound(request);
// Handle the response, synchronous call
if (getClientResource().getOnResponse() == null) {
if ((response != null) && response.getStatus().isError()) {
ThrowableAnnotationInfo tai = getAnnotationUtils()
.getThrowableAnnotationInfo(javaMethod,
response.getStatus().getCode());
if (tai != null) {
Class<?> throwableClazz = tai.getJavaClass();
Throwable t = null;
if (tai.isSerializable()
&& response.isEntityAvailable()) {
t = (Throwable) getClientResource().toObject(
response.getEntity(), throwableClazz);
} else {
try {
t = (Throwable) throwableClazz
.newInstance();
} catch (Exception e) {
Context.getCurrentLogger()
.log(Level.FINE,
"Unable to instantiate the client-side exception using the default constructor.");
}
if (response.isEntityAvailable()) {
StatusInfo si = getClientResource()
.toObject(response.getEntity(),
StatusInfo.class);
if (si != null) {
response.setStatus(new Status(si
.getCode(), si
.getReasonPhrase(), si
.getDescription()));
}
}
}
if (t != null) {
throw t;
}
// TODO cf issues 1004 and 1018.
// this code has been commented as the automatic
// deserialization is problematic. We may rethink a
// way to recover the status info.
// } else if (response.isEntityAvailable()) {
// StatusInfo si = getClientResource().toObject(
// response.getEntity(), StatusInfo.class);
//
// if (si != null) {
// response.setStatus(new Status(si.getCode(), si
// .getReasonPhrase(), si.getDescription()));
// }
}
getClientResource().doError(response.getStatus());
} else if (!annotationInfo.getJavaOutputType().equals(
void.class)) {
result = getClientResource()
.toObject(
(response == null ? null
: response.getEntity()),
annotationInfo.getJavaOutputType());
}
}
}
}
return result;
}
}