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

io.polyglotted.common.web.HttpResourceModel Maven / Gradle / Ivy

package io.polyglotted.common.web;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import static io.polyglotted.common.util.ListBuilder.immutableList;
import static io.polyglotted.common.util.ListBuilder.immutableSet;
import static io.polyglotted.common.util.MapBuilder.immutableMap;
import static io.polyglotted.common.web.ParamConvertUtils.createPathParamConverter;
import static io.polyglotted.common.web.ParamConvertUtils.createQueryParamConverter;
import static io.polyglotted.common.web.WebException.internalServerException;
import static java.util.Objects.requireNonNull;

@SuppressWarnings({"WeakerAccess", "unchecked"})
@Accessors(fluent = true) @ToString(of = {"httpMethods", "path", "method"})
public final class HttpResourceModel {
    private static final Set> SUPPORTED_PARAM_ANNOTATIONS = immutableSet(PathParam.class, QueryParam.class);

    @Getter private final Set httpMethods;
    @Getter private final String path;
    private final Method method;
    private final List, ParameterInfo>> paramsInfo;

    HttpResourceModel(Set httpMethods, String path, Method method) {
        this.httpMethods = httpMethods;
        this.path = path;
        this.method = method;
        this.paramsInfo = createParametersInfos(method);
    }

    private static List, ParameterInfo>> createParametersInfos(Method method) {
        if (method.getParameterTypes().length <= 2) { return ImmutableList.of(); }
        ImmutableList.Builder, ParameterInfo>> result = ImmutableList.builder();
        Type[] parameterTypes = method.getGenericParameterTypes();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();

        for (int i = 2; i < parameterAnnotations.length; i++) {
            Annotation[] annotations = parameterAnnotations[i];
            Map, ParameterInfo> paramAnnotations = Maps.newIdentityHashMap();
            for (Annotation annotation : annotations) {
                Class annotationType = annotation.annotationType();
                ParameterInfo parameterInfo;
                if (PathParam.class.isAssignableFrom(annotationType)) {
                    parameterInfo = new ParameterInfo<>(annotation, createPathParamConverter(parameterTypes[i]));
                }
                else if (QueryParam.class.isAssignableFrom(annotationType)) {
                    parameterInfo = new ParameterInfo<>(annotation, createQueryParamConverter(parameterTypes[i]));
                }
                else {
                    parameterInfo = new ParameterInfo<>(annotation, null);
                }
                paramAnnotations.put(annotationType, parameterInfo);
            }
            // Must have either @PathParam, @QueryParam or @HeaderParam, but not two or more.
            if (Sets.intersection(SUPPORTED_PARAM_ANNOTATIONS, paramAnnotations.keySet()).size() != 1) {
                throw new IllegalArgumentException(String.format("Must have exactly one annotation from %s " +
                    "for parameter %d in method %s", SUPPORTED_PARAM_ANNOTATIONS, i, method));
            }
            result.add(immutableMap(paramAnnotations));
        }
        return result.build();
    }

    void handle(AbstractHttpHandler handler, HttpRequest request, HttpResponder responder, Map groupValues) {
        try {
            Object[] args = new Object[paramsInfo.size() + 2];
            args[0] = request; args[1] = responder;
            int idx = 2;
            for (Map, ParameterInfo> info : paramsInfo) {
                if (info.containsKey(PathParam.class)) { args[idx++] = getPathParamValue(info, groupValues); }
                else if (info.containsKey(QueryParam.class)) { args[idx++] = getQueryParamValue(info, request); }
            }
            method.invoke(handler, args);
        } catch (Throwable e) {
            String deepMessage = e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
            throw internalServerException("Error in " + request.method + " " + request.uriPath + ": " + deepMessage);
        }
    }

    private static Object getPathParamValue(Map, ParameterInfo> annotations, Map groupValues) {
        ParameterInfo info = (ParameterInfo) annotations.get(PathParam.class);
        PathParam pathParam = info.getAnnotation();
        String value = groupValues.get(pathParam.value());
        return info.convert(requireNonNull(value, "Could not resolve value for parameter " + pathParam.value()));
    }

    private static Object getQueryParamValue(Map, ParameterInfo> annotations, HttpRequest request) {
        ParameterInfo> info = (ParameterInfo>) annotations.get(QueryParam.class);
        QueryParam queryParam = info.getAnnotation();
        List values = request.queryParams.get(queryParam.value());
        return (values.isEmpty()) ? info.convert(defaultValue(annotations)) : info.convert(values);
    }

    private static List defaultValue(Map, ParameterInfo> annotations) {
        ParameterInfo defaultInfo = annotations.get(DefaultValue.class);
        return (defaultInfo != null) ? immutableList(defaultInfo.getAnnotation().value()) : immutableList();
    }

    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
    private static final class ParameterInfo {
        private final Annotation annotation;
        private final Function converter;

        @SuppressWarnings("unchecked")  V getAnnotation() { return (V) annotation; }

        Object convert(T input) { return (converter == null) ? null : converter.apply(input); }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy