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

org.glassfish.jersey.microprofile.restclient.InterfaceUtil Maven / Gradle / Ivy

/*
 * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.microprofile.restclient;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import jakarta.ws.rs.HttpMethod;

import org.eclipse.microprofile.rest.client.RestClientDefinitionException;
import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
import org.glassfish.jersey.internal.util.ReflectionHelper;

/**
 * Utils for interface handling.
 *
 * @author David Kral
 */
class InterfaceUtil {

    private static final String PARAMETER_PARSE_REGEXP = "(?<=\\{).+?(?=\\})";
    private static final Pattern PATTERN = Pattern.compile(PARAMETER_PARSE_REGEXP);

    /**
     * Parses all required parameters from template string.
     *
     * @param template template string
     * @return parsed parameters
     */
    static List parseParameters(String template) {
        List allMatches = new ArrayList<>();
        Matcher m = PATTERN.matcher(template);
        while (m.find()) {
            allMatches.add(m.group());
        }
        return allMatches;
    }

    /**
     * Parses all required parameters from expr string.
     * Parameters encapsulated by {} or parameters with regexp expressions like {param: (regex_here)}
     *
     * @param expr string expression
     * @return path params
     */
    static List getAllMatchingParams(String expr) {
        List allMatches = new ArrayList<>();
        if (expr == null || expr.isEmpty() || expr.indexOf('{') == -1) {
            return allMatches;
        }

        boolean matching = false;
        int parenthesisMatched = 0;
        StringBuilder matchingParameter = new StringBuilder();
        for (int i = 0; i < expr.length(); i++) {
            char x = expr.charAt(i);

            if (!matching && x == '{' && parenthesisMatched == 0) {
                matching = true;
            } else if (matching && x != ':' && x != '}') {
                matchingParameter.append(x);
            } else if (matching) {
                allMatches.add(matchingParameter.toString());
                matchingParameter.setLength(0);
                matching = false;
            }

            if (x == '}') {
                parenthesisMatched--;
            } else if (x == '{') {
                parenthesisMatched++;
            }
        }
        return allMatches;
    }

    /**
     * Validates and returns proper compute method defined in {@link ClientHeaderParam}.
     *
     * @param iClass interface class
     * @param headerValue value of the header
     * @return parsed method
     */
    static Method parseComputeMethod(Class iClass, String[] headerValue) {
        List computeMethodNames = InterfaceUtil.parseParameters(Arrays.toString(headerValue));
        /*if more than one string is specified as the value attribute, and one of the strings is a
          compute method (surrounded by curly braces), then the implementation will throw a
          RestClientDefinitionException*/
        if (headerValue.length > 1 && computeMethodNames.size() > 0) {
            throw new RestClientDefinitionException("@ClientHeaderParam annotation should not contain compute method "
                                                            + "when multiple values are present in value attribute. "
                                                            + "See " + iClass.getName());
        }
        if (computeMethodNames.size() == 1) {
            String methodName = computeMethodNames.get(0);
            List computeMethods = getAnnotationComputeMethod(iClass, methodName);
            if (computeMethods.size() != 1) {
                throw new RestClientDefinitionException("No valid compute method found for name: " + methodName);
            }
            return computeMethods.get(0);
        }
        return null;
    }

    private static List getAnnotationComputeMethod(Class iClass, String methodName) {
        if (methodName.contains(".")) {
            return getStaticComputeMethod(methodName);
        }
        return getComputeMethod(iClass, methodName);
    }

    private static List getStaticComputeMethod(String methodName) {
        int lastIndex = methodName.lastIndexOf(".");
        String className = methodName.substring(0, lastIndex);
        String staticMethodName = methodName.substring(lastIndex + 1);
        Class classWithStaticMethod = AccessController.doPrivileged(ReflectionHelper.classForNamePA(className));
        if (classWithStaticMethod == null) {
            throw new IllegalStateException("No class with following name found: " + className);
        }
        return getComputeMethod(classWithStaticMethod, staticMethodName);
    }

    private static List getComputeMethod(Class iClass, String methodName) {
        return Arrays.stream(iClass.getMethods())
                // filter out methods with specified name only
                .filter(method -> method.getName().equals(methodName))
                // filter out other methods than default and static
                .filter(method -> method.isDefault() || Modifier.isStatic(method.getModifiers()))
                // filter out methods without required return type
                .filter(method -> method.getReturnType().equals(String.class)
                        || method.getReturnType().equals(String[].class))
                // filter out methods without required parameter types
                .filter(method -> method.getParameterTypes().length == 0 || (
                        method.getParameterTypes().length == 1
                                && method.getParameterTypes()[0].equals(String.class)))
                .collect(Collectors.toList());
    }

    /**
     * Returns {@link List} of annotations which are type of {@link HttpMethod}.
     *
     * @param annotatedElement element with annotations
     * @return annotations of given type
     */
    static List> getHttpAnnotations(AnnotatedElement annotatedElement) {
        return Arrays.stream(annotatedElement.getDeclaredAnnotations())
                .filter(annotation -> annotation.annotationType().getAnnotation(HttpMethod.class) != null)
                .map(Annotation::annotationType)
                .collect(Collectors.toList());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy