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

com.pcloud.networking.api.MultiCallWrappedApiMethod Maven / Gradle / Ivy

There is a newer version: 2.8.0
Show newest version
/*
 * Copyright (c) 2017 pCloud AG
 *
 * 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 com.pcloud.networking.api;

import com.pcloud.networking.client.Request;
import com.pcloud.networking.client.MultiCall;
import com.pcloud.utils.Types;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static com.pcloud.utils.Types.getParameterUpperBound;
import static com.pcloud.utils.Types.getRawType;

class MultiCallWrappedApiMethod extends ApiMethod {

    static final ApiMethod.Factory FACTORY = new Factory();

    private interface RequestContainerAdapter {
        Type requestType();

        List convert(T requestsContainer);
    }

    private String apiMethodName;

    private RequestContainerAdapter argumentsRequestContainerAdapter;
    private RequestAdapter requestAdapter;
    private ResponseAdapter returnTypeAdapter;
    private CallAdapter callAdapter;

    private MultiCallWrappedApiMethod(String apiMethodName,
                                      RequestContainerAdapter argumentsRequestContainerAdapter,
                                      RequestAdapter requestAdapter,
                                      ResponseAdapter returnTypeAdapter,
                                      CallAdapter callAdapter) {
        this.apiMethodName = apiMethodName;
        this.argumentsRequestContainerAdapter = argumentsRequestContainerAdapter;
        this.requestAdapter = requestAdapter;
        this.returnTypeAdapter = returnTypeAdapter;
        this.callAdapter = callAdapter;
    }

    @Override
    @SuppressWarnings("unchecked")
    public R invoke(ApiComposer apiComposer, Object[] args) throws IOException {
        List requests = argumentsRequestContainerAdapter.convert(args[0]);
        if (requests == null) {
            throw new IllegalArgumentException("The requests container cannot be null!");
        }
        List rawRequests = new ArrayList<>(requests.size());
        final Request.Builder builder = Request.create().methodName(apiMethodName);

        for (final T request : requests) {
            requestAdapter.adapt(builder, request);
            rawRequests.add(builder.build());
        }

        MultiCall rawCall = apiComposer.apiClient()
                .newCall(rawRequests);
        return (R) callAdapter.adapt(new ApiClientMultiCall<>(apiComposer, rawCall, returnTypeAdapter, requests));
    }

    private static class Factory extends ApiMethod.Factory {

        @Override
        public ApiMethod create(ApiComposer composer, Method method,
                                   Type[] argumentTypes, Annotation[][] argumentAnnotations) {
            if (argumentTypes.length == 0) {
                return null;
            }

            Type requestContainerType = argumentTypes[0];
            String apiMethodName = parseMethodNameAnnotation(method);

            if (!hasRequestBodyAnnotation(argumentAnnotations) ||
                    !hasValidRequestContainerType(requestContainerType)) {
                return null;
            }

            checkArgumentAnnotations(method, argumentAnnotations);

            @SuppressWarnings("unchecked")
            RequestContainerAdapter containerAdapter =
                    (RequestContainerAdapter) getContainerAdapter(method, requestContainerType);

            RequestAdapter requestAdapter = getRequestAdapter(composer, method,
                    new Type[]{containerAdapter.requestType()}, argumentAnnotations);

            CallAdapter callAdapter = composer.nextCallAdapter(method);

            final Class innerReturnType = getRawType(callAdapter.responseType());

            if (!isAllowedResponseType(innerReturnType)) {
                return null;
            }

            if (DataApiResponse.class.isAssignableFrom(innerReturnType)) {
                throw apiMethodError(method, "Return types assignable from '%s' are " +
                        "not supported for MultiCall<>- returning API methods.", DataApiResponse.class);
            }

            ResponseAdapter returnTypeAdapter = getResponseAdapter(composer, method, innerReturnType);

            return new MultiCallWrappedApiMethod<>(apiMethodName, containerAdapter,
                    requestAdapter, returnTypeAdapter, callAdapter);
        }

        private static boolean hasValidRequestContainerType(Type requestContainerType) {
            return Types.arrayComponentType(requestContainerType) != null ||
                    List.class.isAssignableFrom(getRawType(requestContainerType));
        }

        private static  RequestContainerAdapter getContainerAdapter(Method method, Type requestContainerType) {
            final Type actualRequestType;
            RequestContainerAdapter containerAdapter;
            Type elementType = Types.arrayComponentType(requestContainerType);
            if (elementType != null) {
                // Requests are given as an array or var-args.
                actualRequestType = elementType;
                containerAdapter = new RequestContainerAdapter() {
                    @Override
                    public Type requestType() {
                        return actualRequestType;
                    }

                    @Override
                    public List convert(T[] requestsContainer) {
                        if (requestsContainer == null) {
                            throw new IllegalArgumentException("The requests container cannot be null!");
                        }
                        return Arrays.asList(requestsContainer);
                    }
                };
            } else if (List.class.isAssignableFrom(getRawType(requestContainerType))) {
                // Requests are given as a List/
                if (!(requestContainerType instanceof ParameterizedType)) {
                    throw new IllegalStateException("Requests List type must be parameterized" +
                            " as List or List");
                }
                actualRequestType = getParameterUpperBound(0, requestContainerType);
                containerAdapter = new RequestContainerAdapter, T>() {
                    @Override
                    public Type requestType() {
                        return actualRequestType;
                    }

                    @Override
                    public List convert(List requestsContainer) {
                        return requestsContainer;
                    }
                };

            } else {
                throw apiMethodError(method,
                        "MultiCall-returning methods should have a single, @RequestBody-annotated argument " +
                        ", either an array or a java.util.List<> of the Request type.");
            }

            return containerAdapter;
        }

        private static boolean hasRequestBodyAnnotation(Annotation[][] annotations) {
            if (annotations.length == 1) {
                Annotation[] containerAnnotations = annotations[0];
                for (Annotation annotation : containerAnnotations) {
                    Type annotationType = annotation.annotationType();
                    if (annotationType == RequestBody.class) {
                        return true;
                    }
                }
            }
            return false;
        }

        private static void checkArgumentAnnotations(Method method, Annotation[][] annotations) {

            Annotation[] containerAnnotations = annotations[0];
            for (Annotation annotation : containerAnnotations) {
                Type annotationType = annotation.annotationType();
                if (annotationType == Parameter.class) {
                    throw apiMethodError(method, "MultiCall methods cannot have @Parameter-annotated arguments.");
                } else if (annotationType == RequestData.class) {
                    throw apiMethodError(method, "MultiCall methods cannot have @RequestData-annotated arguments.");
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy