All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.mobileconnectors.apigateway.ApiClientHandler Maven / Gradle / Ivy

Go to download

The AWS Android SDK for Amazon API Gateway Runtime module holds the runtime library needed by the generated client for APIs defined in Amazon API Gateway.

There is a newer version: 2.79.0
Show newest version
/*
 * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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 com.amazonaws.mobileconnectors.apigateway;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.DefaultRequest;
import com.amazonaws.Request;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.Signer;
import com.amazonaws.http.ExecutionContext;
import com.amazonaws.http.HttpClient;
import com.amazonaws.http.HttpMethodName;
import com.amazonaws.http.HttpRequest;
import com.amazonaws.http.HttpRequestFactory;
import com.amazonaws.http.HttpResponse;
import com.amazonaws.http.UrlHttpClient;
import com.amazonaws.mobileconnectors.apigateway.annotation.Operation;
import com.amazonaws.mobileconnectors.apigateway.annotation.Parameter;
import com.amazonaws.util.IOUtils;
import com.amazonaws.util.StringUtils;
import com.google.gson.Gson;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.Collection;
import java.util.Map;

/**
 * Invocation handler responsible for serializing a request and deserializing a
 * response.
 */
class ApiClientHandler implements InvocationHandler {
    private static final Gson gson = new Gson();

    private final String endpoint;
    private final String apiName;
    private final Signer signer;

    // credentials provider. If null, request won't be signed
    private final AWSCredentialsProvider provider;
    // api key to invoke a method. If non null, its value will be sent in
    // 'x-api-key' header.
    private final String apiKey;

    private final HttpClient client;
    private final HttpRequestFactory requestFactory;
    private final ClientConfiguration clientConfiguration;

    ApiClientHandler(String endpoint, String apiName,
            Signer signer, AWSCredentialsProvider provider, String apiKey) {
        this.endpoint = endpoint;
        this.apiName = apiName;
        this.signer = signer;
        this.provider = provider;
        this.apiKey = apiKey;

        clientConfiguration = new ClientConfiguration();
        client = new UrlHttpClient(clientConfiguration);
        requestFactory = new HttpRequestFactory();
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Request request = buildRequest(method, args);

        ExecutionContext context = new ExecutionContext();
        context.setContextUserAgent(apiName);
        HttpRequest httpRequest = requestFactory.createHttpRequest(request, clientConfiguration,
                context);
        HttpResponse response = client.execute(httpRequest);

        return handleResponse(response, method);
    }

    /**
     * Build a {@link Request} object for the given method.
     *
     * @param method method that annotated with {@link Operation}
     * @param args arguments of the method
     * @return a {@link Request} object
     */
    Request buildRequest(Method method, Object[] args) {
        Operation op = method.getAnnotation(Operation.class);
        if (op == null) {
            throw new IllegalArgumentException("Method isn't annotated with Operation");
        }

        Request request = new DefaultRequest(apiName);
        request.setResourcePath(op.path());
        request.setEndpoint(URI.create(endpoint));

        String content = null;
        Annotation[][] annotations = method.getParameterAnnotations();
        int length = annotations.length;
        for (int i = 0; i < length; i++) {
            // content body
            if (annotations[i].length == 0) {
                if (content != null) {
                    throw new IllegalStateException("Can't have more than one Body");
                }
                content = args[i] == null ? null : gson.toJson(args[i]);
                continue;
            }

            for (Annotation annotation : annotations[i]) {
                if (annotation instanceof Parameter) {
                    processParameter(request, (Parameter) annotation, args[i]);
                    break;
                }
            }
        }

        boolean hasContent = content != null;
        setHttpMethod(request, op.method(), hasContent);

        if (hasContent) {
            byte[] contentBytes = content.getBytes(StringUtils.UTF8);
            request.setContent(new ByteArrayInputStream(contentBytes));
            request.addHeader("Content-Length",
                    String.valueOf(contentBytes.length));
        }
        request.addHeader("Content-Type", "application/json");
        request.addHeader("Accept", "application/json");
        if (apiKey != null) {
            request.addHeader("x-api-key", apiKey);
        }

        if (provider != null && signer != null) {
            signer.sign(request, provider.getCredentials());
        }
        return request;
    }

    /**
     * Process an argument annotated with {@link Parameter}.
     *
     * @param request request to be set
     * @param p annotation
     * @param arg argument
     */
    void processParameter(Request request, Parameter p, Object arg) {
        String name = p.name();
        String location = p.location();

        if ("header".equals(location)) {
            request.addHeader(name, String.valueOf(arg));
        } else if ("path".equals(location)) {
            String path = request.getResourcePath();
            path = path.replaceAll("\\{" + name + "\\}", String.valueOf(arg));
            request.setResourcePath(path);
        } else if ("query".equals(location)) {
            if (Map.class.isAssignableFrom(arg.getClass())) {
                @SuppressWarnings("unchecked")
                Map map = (Map) arg;
                for (Map.Entry entry : map.entrySet()) {
                    request.addParameter(entry.getKey(), String.valueOf(entry.getValue()));
                }
            } else if (Collection.class.isAssignableFrom(arg.getClass())) {
                request.addParameter(name, joinList((Collection) arg));
            } else {
                request.addParameter(name, String.valueOf(arg));
            }
        } else {
            throw new IllegalArgumentException("unknown parameter location: " + location);
        }
    }

    /**
     * Sets HTTP method to the {@link Request} object. If the given method is
     * none of GET, POST, PUT, DELETE, and HEAD, then it will be tunneled via
     * X-HTTP-Method-Override. Note that not all servers support this header.
     *
     * @param request request to be set
     * @param httpMethod given http method
     * @param hasContent indicate whether the request has content body
     */
    void setHttpMethod(Request request, String httpMethod, boolean hasContent) {
        try {
            request.setHttpMethod(HttpMethodName.valueOf(httpMethod));
        } catch (IllegalArgumentException iae) {
            // if an HTTP method is unsupported, then 'tunnel' it through
            // another method by setting the intended method in the
            // X-HTTP-Method-Override header.
            request.addHeader("X-HTTP-Method-Override", httpMethod);
            // depending on whether the request has content or not, choose an
            // appropriate method.
            request.setHttpMethod(hasContent ? HttpMethodName.POST : HttpMethodName.GET);
        }
    }

    /**
     * Converts response to method's declared returned object
     *
     * @param response http response
     * @param method method
     * @return object of method's declared returned type
     * @throws Throwable
     */
    Object handleResponse(HttpResponse response, Method method) throws Throwable {
        int code = response.getStatusCode();
        InputStream content = response.getContent();
        // successful request if code is 2xx
        if (code >= 200 && code < 300) {
            Type t = method.getReturnType();
            if (t != void.class && content != null) {
                Reader reader = new InputStreamReader(response.getContent(), StringUtils.UTF8);
                Object obj = gson.fromJson(reader, t);
                reader.close();
                return obj;
            } else {
                // discard response
                if (content != null) {
                    content.close();
                }
                return null;
            }
        } else {
            String error = content == null ? "" : IOUtils.toString(content);
            ApiClientException ase = new ApiClientException(error);
            ase.setStatusCode(response.getStatusCode());
            ase.setServiceName(apiName);
            String requestId = response.getHeaders().get("x-amzn-RequestId");
            if (requestId != null) {
                ase.setRequestId(requestId);
            }
            throw ase;
        }
    }

    private String joinList(Collection objects) {
        if (objects == null || objects.isEmpty()) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (Object object : objects) {
            if (first) {
                first = false;
            } else {
                sb.append(",");
            }
            sb.append(object);
        }
        return sb.toString();
    }
}