package com.sebastian_daschner.jaxrs_analyzer.analysis.classes; import com.sebastian_daschner.jaxrs_analyzer.LogProvider; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.annotation.ApplicationPathAnnotationVisitor; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.annotation.ConsumesAnnotationVisitor; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.annotation.PathAnnotationVisitor; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.annotation.ProducesAnnotationVisitor; import com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils; import com.sebastian_daschner.jaxrs_analyzer.model.Types; import com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier; import com.sebastian_daschner.jaxrs_analyzer.model.results.ClassResult; import com.sebastian_daschner.jaxrs_analyzer.model.results.MethodResult; import org.objectweb.asm.*; import javax.ws.rs.*; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.LinkedBlockingQueue; import java.util.stream.Stream; import static com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils.isAnnotationPresent; import static org.objectweb.asm.Opcodes.*; /** * @author Sebastian Daschner */ public class JAXRSClassVisitor extends ClassVisitor { private static final Class<? extends Annotation>[] RELEVANT_METHOD_ANNOTATIONS = new Class[]{Path.class, GET.class, PUT.class, POST.class, DELETE.class, OPTIONS.class, HEAD.class}; private final ClassResult classResult; public JAXRSClassVisitor(final ClassResult classResult) { super(ASM5); this.classResult = classResult; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { classResult.setOriginalClass(name); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { switch (desc) { case Types.PATH: return new PathAnnotationVisitor(classResult); case Types.APPLICATION_PATH: return new ApplicationPathAnnotationVisitor(classResult); case Types.CONSUMES: return new ConsumesAnnotationVisitor(classResult); case Types.PRODUCES: return new ProducesAnnotationVisitor(classResult); case Types.DEPRECATED: classResult.setDeprecated(true); break; } return null; } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { if ((access & ACC_STATIC) == 0) return new JAXRSFieldVisitor(classResult, desc, signature); return null; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { final boolean legalModifiers = ((access & ACC_SYNTHETIC) | (access & ACC_STATIC) | (access & ACC_NATIVE)) == 0; final String methodSignature = signature == null ? desc : signature; final MethodIdentifier identifier = MethodIdentifier.of(classResult.getOriginalClass(), name, methodSignature, false); if (legalModifiers && !"<init>".equals(name)) { final MethodResult methodResult = new MethodResult(); if (hasJAXRSAnnotations(classResult.getOriginalClass(), name, methodSignature)) return new JAXRSMethodVisitor(identifier, classResult, methodResult, true); else { final Method annotatedSuperMethod = searchAnnotatedSuperMethod(classResult.getOriginalClass(), name, methodSignature); if (annotatedSuperMethod != null) { try { return new JAXRSMethodVisitor(identifier, classResult, methodResult, false); } finally { classResult.getMethods().stream().filter(m -> m.equals(methodResult)).findAny().ifPresent(m -> visitJAXRSSuperMethod(annotatedSuperMethod, m)); } } } } return null; } private static boolean hasJAXRSAnnotations(final String className, final String methodName, final String signature) { final Method method = JavaUtils.findMethod(className, methodName, signature); return method != null && hasJAXRSAnnotations(method); } private static Method searchAnnotatedSuperMethod(final String className, final String methodName, final String methodSignature) { final List<Class<?>> superTypes = determineSuperTypes(className); return superTypes.stream().map(c -> { final Method superAnnotatedMethod = JavaUtils.findMethod(c, methodName, methodSignature); if (superAnnotatedMethod != null && hasJAXRSAnnotations(superAnnotatedMethod)) return superAnnotatedMethod; return null; }).filter(Objects::nonNull).findAny().orElse(null); } private static List<Class<?>> determineSuperTypes(final String className) { final Class<?> loadedClass = JavaUtils.loadClassFromName(className); if (loadedClass == null) return Collections.emptyList(); final List<Class<?>> superClasses = new ArrayList<>(); final Queue<Class<?>> classesToCheck = new LinkedBlockingQueue<>(); Class<?> currentClass = loadedClass; do { if (currentClass.getSuperclass() != null && Object.class != currentClass.getSuperclass()) classesToCheck.add(currentClass.getSuperclass()); Stream.of(currentClass.getInterfaces()).forEach(classesToCheck::add); if (currentClass != loadedClass) superClasses.add(currentClass); } while ((currentClass = classesToCheck.poll()) != null); return superClasses; } private static boolean hasJAXRSAnnotations(final Method method) { for (final Object annotation : method.getDeclaredAnnotations()) { // TODO test both if (Stream.of(RELEVANT_METHOD_ANNOTATIONS).map(a -> JavaUtils.getAnnotation(method, a)) .filter(Objects::nonNull).anyMatch(a -> a.getClass().isAssignableFrom(annotation.getClass()))) return true; if (isAnnotationPresent(annotation.getClass(), HttpMethod.class)) return true; } return false; } private void visitJAXRSSuperMethod(Method method, MethodResult methodResult) { try { final ClassReader classReader = new ContextClassReader(method.getDeclaringClass().getCanonicalName()); final ClassVisitor visitor = new JAXRSAnnotatedSuperMethodClassVisitor(methodResult, method); classReader.accept(visitor, ClassReader.EXPAND_FRAMES); } catch (IOException e) { LogProvider.error("Could not analyze JAX-RS super annotated method " + method); LogProvider.debug(e); } } }