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

org.glowroot.instrumentation.jaxrs.ResourceMethodMeta Maven / Gradle / Ivy

/*
 * Copyright 2016-2019 the original author or authors.
 *
 * 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.glowroot.instrumentation.jaxrs;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.glowroot.instrumentation.api.Logger;
import org.glowroot.instrumentation.api.MethodInfo;
import org.glowroot.instrumentation.api.checker.Nullable;
import org.glowroot.instrumentation.api.util.Reflection;

// TODO, from JAX-RS spec "If a subclass or implementation method has any JAX-RS annotations then
// all of the annotations on the superclass or interface method are ignored"
public class ResourceMethodMeta {

    private static final Logger logger = Logger.getLogger(ResourceMethodMeta.class);

    private final String resourceClassName;
    private final String methodName;
    private final boolean hasHttpMethodAnnotation;

    private final String path;

    private final String altTransactionName;

    public ResourceMethodMeta(MethodInfo methodInfo) {
        resourceClassName = methodInfo.getDeclaringClassName();
        methodName = methodInfo.getName();
        Class clazz = getClass(methodInfo);
        String classPath = getPath(clazz);
        MethodAnnotations methodAnnotations = getMethodAnnotations(methodInfo, clazz);

        if (methodAnnotations == null) {
            hasHttpMethodAnnotation = false;
            path = combine(classPath, null);
        } else {
            hasHttpMethodAnnotation = methodAnnotations.hasHttpMethodAnnotation;
            path = combine(classPath, methodAnnotations.pathAnnotation);
        }
        altTransactionName = getSimpleName(resourceClassName) + "#" + methodName;
    }

    String getResourceClassName() {
        return resourceClassName;
    }

    String getMethodName() {
        return methodName;
    }

    boolean hasHttpMethodAnnotation() {
        return hasHttpMethodAnnotation;
    }

    String getPath() {
        return path;
    }

    String getAltTransactionName() {
        return altTransactionName;
    }

    private static @Nullable String getPath(@Nullable Class clazz) {
        if (clazz == null) {
            return null;
        }
        try {
            for (Annotation annotation : clazz.getDeclaredAnnotations()) {
                Class annotationClass = annotation.annotationType();
                if (annotationClass.getName().equals("javax.ws.rs.Path")) {
                    return getPathAttribute(annotationClass, annotation, "value");
                }
            }
        } catch (Throwable t) {
            logger.error(t.getMessage(), t);
        }
        return null;
    }

    private static @Nullable MethodAnnotations getMethodAnnotations(MethodInfo methodInfo,
            @Nullable Class clazz) {
        if (clazz == null) {
            return null;
        }
        MethodAnnotations methodAnnotations = getMethodAnnotations(getMethod(methodInfo, clazz));
        if (methodAnnotations != null) {
            return methodAnnotations;
        }
        Class superclass = clazz.getSuperclass();
        if (superclass != null) {
            methodAnnotations = getMethodAnnotations(methodInfo, superclass);
            if (methodAnnotations != null) {
                return methodAnnotations;
            }
        }
        for (Class iface : clazz.getInterfaces()) {
            methodAnnotations = getMethodAnnotations(methodInfo, iface);
            if (methodAnnotations != null) {
                return methodAnnotations;
            }
        }
        return null;
    }

    private static @Nullable MethodAnnotations getMethodAnnotations(@Nullable Method method) {
        if (method == null) {
            return null;
        }
        try {
            String pathAnnotation = null;
            boolean hasHttpMethodAnnotation = false;
            for (Annotation annotation : method.getDeclaredAnnotations()) {
                Class annotationClass = annotation.annotationType();
                if (annotationClass.getName().equals("javax.ws.rs.Path")) {
                    pathAnnotation = getPathAttribute(annotationClass, annotation, "value");
                } else if (isHttpMethodAnnotation(annotationClass)) {
                    hasHttpMethodAnnotation = true;
                }
            }
            if (pathAnnotation != null || hasHttpMethodAnnotation) {
                return new MethodAnnotations(pathAnnotation, hasHttpMethodAnnotation);
            }
        } catch (Throwable t) {
            logger.error(t.getMessage(), t);
        }
        return null;
    }

    private static boolean isHttpMethodAnnotation(Class annotationClass) {
        for (Annotation annotation : annotationClass.getDeclaredAnnotations()) {
            Class metaClass = annotation.annotationType();
            if (metaClass.getName().equals("javax.ws.rs.HttpMethod")) {
                return true;
            }
        }
        return false;
    }

    private static @Nullable String getPathAttribute(Class pathClass, Object path,
            String attributeName) throws Exception {
        Method method = pathClass.getMethod(attributeName);
        return (String) method.invoke(path);
    }

    private static @Nullable Class getClass(MethodInfo methodInfo) {
        return Reflection.getClass(methodInfo.getDeclaringClassName(), methodInfo.getLoader());
    }

    private static @Nullable Method getMethod(MethodInfo methodInfo,
            @Nullable Class declaringClass) {
        if (declaringClass == null) {
            // declaring class is probably a lambda class
            return null;
        }
        Class[] parameterTypes = methodInfo.getParameterTypes()
                .toArray(new Class[methodInfo.getParameterTypes().size()]);
        try {
            return declaringClass.getDeclaredMethod(methodInfo.getName(), parameterTypes);
        } catch (Exception e) {
            logger.debug(e.getMessage(), e);
        }
        return null;
    }

    // VisibleForTesting
    static String combine(@Nullable String classPath, @Nullable String methodPath) {
        if (classPath == null || classPath.isEmpty() || classPath.equals("/")) {
            return normalize(methodPath);
        }
        if (methodPath == null || methodPath.isEmpty() || methodPath.equals("/")) {
            return normalize(classPath);
        }
        return normalize(classPath) + normalize(methodPath);
    }

    private static String normalize(@Nullable String path) {
        if (path == null || path.isEmpty() || path.equals("/")) {
            return "/";
        }
        boolean addLeadingSlash = path.charAt(0) != '/';
        if (addLeadingSlash) {
            return '/' + replacePathSegmentsWithAsterisk(path);
        } else {
            return replacePathSegmentsWithAsterisk(path);
        }
    }

    private static String replacePathSegmentsWithAsterisk(String path) {
        return path.replaceAll("\\{[^}]*\\}", "*");
    }

    private static String getSimpleName(String className) {
        return substringAfterLast(substringAfterLast(className, '.'), '$');
    }

    private static String substringAfterLast(String str, char c) {
        int index = str.lastIndexOf(c);
        if (index == -1) {
            return str;
        } else {
            return str.substring(index + 1);
        }
    }

    private static class MethodAnnotations {

        private final @Nullable String pathAnnotation;
        private final boolean hasHttpMethodAnnotation;

        private MethodAnnotations(@Nullable String pathAnnotation,
                boolean hasHttpMethodAnnotation) {
            this.pathAnnotation = pathAnnotation;
            this.hasHttpMethodAnnotation = hasHttpMethodAnnotation;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy