org.jclouds.rest.internal.AsyncRestClientProxy.java Source code

Java tutorial

Introduction

Here is the source code for org.jclouds.rest.internal.AsyncRestClientProxy.java

Source

/**
 *
 * Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
 *
 * ====================================================================
 * 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 org.jclouds.rest.internal;

/**
 * Generates RESTful clients from appropriately annotated interfaces.
 * 
 * @author Adrian Cole
 */
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;

import javax.annotation.Resource;
import javax.inject.Named;
import javax.inject.Singleton;

import org.jclouds.Constants;
import org.jclouds.concurrent.ExceptionParsingListenableFuture;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.TransformingHttpCommand;
import org.jclouds.internal.ClassMethodArgs;
import org.jclouds.logging.Logger;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.InvocationContext;
import org.jclouds.rest.annotations.Delegate;
import org.jclouds.util.Throwables2;

import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;

@Singleton
public class AsyncRestClientProxy<T> implements InvocationHandler {
    private final Injector injector;
    private final RestAnnotationProcessor<T> annotationProcessor;
    private final Class<T> declaring;
    private final Factory commandFactory;

    /**
     * maximum duration of an unwrapped http Request
     */
    @Inject(optional = true)
    @Named(Constants.PROPERTY_REQUEST_TIMEOUT)
    protected long requestTimeoutMilliseconds = 30000;

    @Resource
    protected Logger logger = Logger.NULL;
    private final ConcurrentMap<ClassMethodArgs, Object> delegateMap;

    @SuppressWarnings("unchecked")
    @Inject
    public AsyncRestClientProxy(Injector injector, Factory factory, RestAnnotationProcessor<T> util,
            TypeLiteral<T> typeLiteral, @Named("async") ConcurrentMap<ClassMethodArgs, Object> delegateMap) {
        this.injector = injector;
        this.annotationProcessor = util;
        this.declaring = (Class<T>) typeLiteral.getRawType();
        this.commandFactory = factory;
        this.delegateMap = delegateMap;
    }

    public Object invoke(Object o, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("equals")) {
            return this.equals(o);
        } else if (method.getName().equals("toString")) {
            return this.toString();
        } else if (method.getName().equals("hashCode")) {
            return this.hashCode();
        } else if (method.getName().startsWith("new")) {
            return injector.getInstance(method.getReturnType());
        } else if (method.isAnnotationPresent(Delegate.class)) {
            return delegateMap.get(new ClassMethodArgs(method.getReturnType(), method, args));
        } else if (annotationProcessor.getDelegateOrNull(method) != null
                && ListenableFuture.class.isAssignableFrom(method.getReturnType())) {
            return createListenableFuture(method, args);
        } else {
            throw new RuntimeException("method is intended solely to set constants: " + method);
        }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private ListenableFuture<?> createListenableFuture(Method method, Object[] args) throws ExecutionException {
        method = annotationProcessor.getDelegateOrNull(method);
        logger.trace("Converting %s.%s", declaring.getSimpleName(), method.getName());
        Function<Exception, ?> exceptionParser = annotationProcessor
                .createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(method);
        // in case there is an exception creating the request, we should at least
        // pass in args
        if (exceptionParser instanceof InvocationContext) {
            ((InvocationContext) exceptionParser).setContext((HttpRequest) null);
        }
        ListenableFuture<?> result;
        try {
            GeneratedHttpRequest<T> request = annotationProcessor.createRequest(method, args);
            if (exceptionParser instanceof InvocationContext) {
                ((InvocationContext) exceptionParser).setContext(request);
            }
            logger.trace("Converted %s.%s to %s", declaring.getSimpleName(), method.getName(),
                    request.getRequestLine());

            Function<HttpResponse, ?> transformer = annotationProcessor.createResponseParser(method, request);
            logger.trace("Response from %s.%s is parsed by %s", declaring.getSimpleName(), method.getName(),
                    transformer.getClass().getSimpleName());

            logger.debug("Invoking %s.%s", declaring.getSimpleName(), method.getName());
            result = commandFactory.create(request, transformer).execute();

        } catch (RuntimeException e) {
            AuthorizationException aex = Throwables2.getFirstThrowableOfType(e, AuthorizationException.class);
            if (aex != null)
                e = aex;
            if (exceptionParser != null) {
                try {
                    return Futures.immediateFuture(exceptionParser.apply(e));
                } catch (Exception ex) {
                    return Futures.immediateFailedFuture(ex);
                }
            }
            return Futures.immediateFailedFuture(e);
        }

        if (exceptionParser != null) {
            logger.trace("Exceptions from %s.%s are parsed by %s", declaring.getSimpleName(), method.getName(),
                    exceptionParser.getClass().getSimpleName());
            result = new ExceptionParsingListenableFuture(result, exceptionParser);
        }
        return result;
    }

    public static interface Factory {
        public TransformingHttpCommand<?> create(HttpRequest request, Function<HttpResponse, ?> transformer);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof AsyncRestClientProxy<?>))
            return false;
        AsyncRestClientProxy<?> other = (AsyncRestClientProxy<?>) obj;
        if (other == this)
            return true;
        if (other.declaring != this.declaring)
            return false;
        return super.equals(obj);
    }

    @Override
    public int hashCode() {
        return declaring.hashCode();
    }

    public String toString() {
        return "Client Proxy for :" + declaring.getName();
    }
}