/* * Copyright © 2014 Cask Data, Inc. * * 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 co.cask.cdap.internal.app.runtime.service.http; import co.cask.cdap.api.metrics.MetricsContext; import co.cask.cdap.api.service.http.HttpContentConsumer; import co.cask.cdap.api.service.http.HttpServiceHandler; import co.cask.cdap.api.service.http.HttpServiceRequest; import co.cask.cdap.api.service.http.HttpServiceResponder; import co.cask.cdap.common.lang.ClassLoaders; import co.cask.cdap.internal.asm.ClassDefinition; import co.cask.cdap.internal.asm.Methods; import co.cask.cdap.internal.asm.Signatures; import co.cask.http.BodyConsumer; import co.cask.http.HttpHandler; import co.cask.http.HttpResponder; import co.cask.tephra.TransactionContext; import co.cask.tephra.TransactionFailureException; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableSet; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.hash.Hashing; import com.google.common.reflect.TypeParameter; import com.google.common.reflect.TypeToken; import org.jboss.netty.handler.codec.http.HttpRequest; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.commons.Method; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; import org.objectweb.asm.signature.SignatureWriter; import org.objectweb.asm.tree.AnnotationNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Modifier; import java.util.List; import java.util.Map; import java.util.Set; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.HEAD; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; /** * A bytecode generator for generating class that implements {@link co.cask.http.HttpHandler} interface and * copy public methods annotated with {@link Path} of a delegating class by delegating to the delegation instance. * * It is needed for wrapping user class that annotated with {@link Path} into a class that implements * {@link co.cask.http.HttpHandler} for the netty http service to inspect. * * Also, the generated class can impose transaction boundary for calls to those {@link Path @Path} methods. * * The generated class has a skeleton looks like this: * * <pre>{@code * public final class GeneratedClassName extends AbstractHttpHandlerDelegator<UserHandlerClass> { * * public GeneratedClassName(DelegatorContext<UserHandlerClass> instantiator) { * super(context); * } * * @literal @GET * @literal @Path("/path") * public void userMethod(HttpRequest request, HttpResponder responder) { * // see generateTransactionalDelegateBody() for generated method body. * } * * @literal @PUT * @literal @Path("/upload") * public HttpContentConsumer userUpload(HttpRequest request, HttpResponder responder) { * // see generateTransactionalDelegateBody() for generated method body. * } * } * }</pre> */ final class HttpHandlerGenerator { private static final Set<Type> HTTP_ANNOTATION_TYPES = ImmutableSet.of( Type.getType(GET.class), Type.getType(POST.class), Type.getType(PUT.class), Type.getType(DELETE.class), Type.getType(HEAD.class) ); /** * Generates a new class that implements {@link HttpHandler} by copying methods signatures from the given * {@link HttpServiceHandler} class. Calls to {@link HttpServiceHandler} methods are transactional. * * @param delegateType type of the {@link HttpServiceHandler} * @param pathPrefix prefix for all {@code @PATH} annotation * @return A {@link ClassDefinition} containing information of the newly generated class. * @throws IOException if failed to generate the class. */ ClassDefinition generate(TypeToken<? extends HttpServiceHandler> delegateType, String pathPrefix) throws IOException { Class<?> rawType = delegateType.getRawType(); List<Class<?>> preservedClasses = Lists.newArrayList(); preservedClasses.add(rawType); ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); String internalName = Type.getInternalName(rawType); String className = internalName + Hashing.md5().hashString(internalName); // Generate the class Type classType = Type.getObjectType(className); classWriter.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, className, getClassSignature(delegateType), Type.getInternalName(AbstractHttpHandlerDelegator.class), null); // Inspect the delegate class hierarchy to generate public handler methods. for (TypeToken<?> type : delegateType.getTypes().classes()) { if (!Object.class.equals(type.getRawType())) { inspectHandler(delegateType, type, pathPrefix, classType, classWriter, preservedClasses); } } generateConstructor(delegateType, classWriter); generateLogger(classType, classWriter); ClassDefinition classDefinition = new ClassDefinition(classWriter.toByteArray(), className, preservedClasses); // DEBUG block. Uncomment for debug // co.cask.cdap.internal.asm.Debugs.debugByteCode(classDefinition, new java.io.PrintWriter(System.out)); // End DEBUG block return classDefinition; } /** * Inspects the given type and copy/rewrite handler methods from it into the newly generated class. * * @param delegateType The user handler type * @param inspectType The type that needs to be inspected. It's either the delegateType or one of its parents */ private void inspectHandler(final TypeToken<?> delegateType, final TypeToken<?> inspectType, final String pathPrefix, final Type classType, final ClassWriter classWriter, final List<Class<?>> preservedClasses) throws IOException { Class<?> rawType = inspectType.getRawType(); // Visit the delegate class, copy and rewrite handler method, with method body just do delegation try ( InputStream sourceBytes = rawType.getClassLoader().getResourceAsStream(Type.getInternalName(rawType) + ".class") ) { ClassReader classReader = new ClassReader(sourceBytes); classReader.accept(new ClassVisitor(Opcodes.ASM5) { // Only need to visit @Path at the class level if we are inspecting the user handler class private final boolean inspectDelegate = delegateType.equals(inspectType); private boolean visitedPath = !inspectDelegate; @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { // Copy the class annotation if it is @Path. Only do it for one time Type type = Type.getType(desc); if (inspectDelegate && type.equals(Type.getType(Path.class))) { visitedPath = true; AnnotationVisitor annotationVisitor = classWriter.visitAnnotation(desc, visible); return new AnnotationVisitor(Opcodes.ASM5, annotationVisitor) { @Override public void visit(String name, Object value) { // "value" is the key for the Path annotation string. if (name.equals("value")) { super.visit(name, pathPrefix + value.toString()); } else { super.visit(name, value); } } }; } else { return super.visitAnnotation(desc, visible); } } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { // Create a class-level annotation with the prefix, if the user has not specified any class-level // annotation. if (!visitedPath) { String pathDesc = Type.getType(Path.class).getDescriptor(); AnnotationVisitor annotationVisitor = classWriter.visitAnnotation(pathDesc, true); annotationVisitor.visit("value", pathPrefix); annotationVisitor.visitEnd(); visitedPath = true; } // Copy the method if it is public and annotated with one of the HTTP request method MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if (!Modifier.isPublic(access)) { return mv; } return new HandlerMethodVisitor(delegateType, mv, desc, signature, access, name, exceptions, classType, classWriter, preservedClasses); } }, ClassReader.SKIP_DEBUG); } } /** * Generates the constructor. The constructor generated has signature {@code (DelegatorContext, MetricsContext)}. */ private void generateConstructor(TypeToken<? extends HttpServiceHandler> delegateType, ClassWriter classWriter) { Method constructor = Methods.getMethod(void.class, "<init>", DelegatorContext.class, MetricsContext.class); String signature = Signatures.getMethodSignature(constructor, getContextType(delegateType), TypeToken.of(MetricsContext.class)); // Constructor(DelegatorContext, MetricsContext) GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, constructor, signature, null, classWriter); // super(context, metricsContext); mg.loadThis(); mg.loadArg(0); mg.loadArg(1); mg.invokeConstructor(Type.getType(AbstractHttpHandlerDelegator.class), Methods.getMethod(void.class, "<init>", DelegatorContext.class, MetricsContext.class)); mg.returnValue(); mg.endMethod(); } /** * Generates a static Logger field for logging and a static initialization block to initialize the logger. */ private void generateLogger(Type classType, ClassWriter classWriter) { // private static final Logger LOG = LoggerFactory.getLogger(classType); classWriter.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "LOG", Type.getType(Logger.class).getDescriptor(), null, null); Method init = Methods.getMethod(void.class, "<clinit>"); GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_STATIC, init, null, null, classWriter); mg.visitLdcInsn(classType); mg.invokeStatic(Type.getType(LoggerFactory.class), Methods.getMethod(Logger.class, "getLogger", Class.class)); mg.putStatic(classType, "LOG", Type.getType(Logger.class)); mg.returnValue(); mg.endMethod(); } /** * Returns true if the annotation is of type {@link javax.ws.rs.HttpMethod} annotations. */ private boolean isHandlerMethod(Type annotationType) { return HTTP_ANNOTATION_TYPES.contains(annotationType); } /** * Returns a {@link TypeToken} that represents the type {@code HandlerDelegatorContext<UserHandlerClass>}. */ private <T extends HttpServiceHandler> TypeToken<DelegatorContext<T>> getContextType(TypeToken<T> delegateType) { return new TypeToken<DelegatorContext<T>>() { }.where(new TypeParameter<T>() { }, delegateType); } /** * Generates the class signature of the generate class. The generated class is not parameterized, however * it extends from {@link AbstractHttpHandlerDelegator} with parameterized type of the user http handler. * * @param delegateType Type of the user http handler * @return The signature string */ private String getClassSignature(TypeToken<?> delegateType) { SignatureWriter writer = new SignatureWriter(); // Construct the superclass signature as "AbstractHttpHandlerDelegator<UserHandlerClass>" SignatureVisitor sv = writer.visitSuperclass(); sv.visitClassType(Type.getInternalName(AbstractHttpHandlerDelegator.class)); SignatureVisitor tv = sv.visitTypeArgument('='); tv.visitClassType(Type.getInternalName(delegateType.getRawType())); tv.visitEnd(); sv.visitEnd(); return writer.toString(); } /** * The ASM MethodVisitor for visiting handler class methods and optionally copy them if it is a handler * method. */ private class HandlerMethodVisitor extends MethodVisitor { private final TypeToken<?> delegateType; private final List<AnnotationNode> annotations; private final ListMultimap<Integer, AnnotationNode> paramAnnotations; private final String desc; private final String signature; private final int access; private final String name; private final String[] exceptions; private final Type classType; private final ClassWriter classWriter; private final List<Class<?>> preservedClasses; /** * Constructs a {@link HandlerMethodVisitor}. * * @param delegateType The user handler type * @param mv The {@link MethodVisitor} to delegate calls to if not handled by this class * @param desc Method description * @param signature Method signature * @param access Method access flag * @param name Method name * @param exceptions Method exceptions list * @param classType Type of the generated class * @param classWriter Writer for generating bytecode * @param preservedClasses List for storing classes that needs to be preserved for correct class loading. * See {@link ClassDefinition} for details. */ HandlerMethodVisitor(TypeToken<?> delegateType, MethodVisitor mv, String desc, String signature, int access, String name, String[] exceptions, Type classType, ClassWriter classWriter, List<Class<?>> preservedClasses) { super(Opcodes.ASM5, mv); this.delegateType = delegateType; this.desc = desc; this.signature = signature; this.access = access; this.name = name; this.exceptions = exceptions; this.annotations = Lists.newArrayList(); this.paramAnnotations = LinkedListMultimap.create(); this.classType = classType; this.classWriter = classWriter; this.preservedClasses = preservedClasses; } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { // Memorize all visible annotations if (visible) { AnnotationNode annotationNode = new AnnotationNode(Opcodes.ASM5, desc); annotations.add(annotationNode); return annotationNode; } return super.visitAnnotation(desc, visible); } @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { // Memorize all visible annotations for each parameter. // It needs to store in a Multimap because there can be multiple annotations per parameter. if (visible) { AnnotationNode annotationNode = new AnnotationNode(Opcodes.ASM5, desc); paramAnnotations.put(parameter, annotationNode); return annotationNode; } return super.visitParameterAnnotation(parameter, desc, visible); } @Override public void visitEnd() { // If any annotations of the method is one of those HttpMethod, // this is a handler process, hence need to copy. boolean handlerMethod = false; for (AnnotationNode annotation : annotations) { if (isHandlerMethod(Type.getType(annotation.desc))) { handlerMethod = true; break; } } if (!handlerMethod) { super.visitEnd(); return; } Type returnType = Type.getReturnType(desc); Type[] argTypes = Type.getArgumentTypes(desc); // If the first two parameters are not HttpServiceRequest and HttpServiceResponder, don't copy if (argTypes.length < 2 || !argTypes[0].equals(Type.getType(HttpServiceRequest.class)) || !argTypes[1].equals(Type.getType(HttpServiceResponder.class))) { super.visitEnd(); return; } argTypes[0] = Type.getType(HttpRequest.class); argTypes[1] = Type.getType(HttpResponder.class); preserveParameterClasses(argTypes); // If the return type is an instance of HttpContentConsumer, the generated method need to have // netty-http BodyConsumer as return type. if (returnType.getSort() == Type.OBJECT) { try { Class<?> returnClass = delegateType.getRawType().getClassLoader().loadClass(returnType.getClassName()); if (HttpContentConsumer.class.isAssignableFrom(returnClass)) { returnType = Type.getType(BodyConsumer.class); } } catch (ClassNotFoundException e) { // Shouldn't happen since the delegateType (user handler class) is already loaded and the method return // type should be loadable through the same classloader throw Throwables.propagate(e); } } // Copy the method signature with the first two parameter types changed and return type changed String methodDesc = Type.getMethodDescriptor(returnType, argTypes); MethodVisitor methodVisitor = classWriter.visitMethod(access, name, methodDesc, rewriteMethodSignature(signature), exceptions); final GeneratorAdapter mg = new GeneratorAdapter(methodVisitor, access, name, desc); // Replay all annotations before generating the body. for (AnnotationNode annotation : annotations) { annotation.accept(mg.visitAnnotation(annotation.desc, true)); } // Replay all parameter annotations for (Map.Entry<Integer, AnnotationNode> entry : paramAnnotations.entries()) { AnnotationNode annotation = entry.getValue(); annotation.accept(mg.visitParameterAnnotation(entry.getKey(), annotation.desc, true)); } // Each request method is wrapped by a transaction lifecycle. generateTransactionalDelegateBody(mg, new Method(name, desc)); super.visitEnd(); } /** * Preserves method parameter classes for class loading. The first two parameters are always * {@link HttpServiceRequest} and {@link HttpServiceResponder}, which don't need to be preserved since * they are always loaded by the CDAP system ClassLoader. The third and onward method parameter classes * need to be preserved since they can be defined by user, hence in the user ClassLoader. * * @see ClassDefinition */ private void preserveParameterClasses(Type[] argTypes) { if (argTypes.length <= 2) { return; } try { for (int i = 2; i < argTypes.length; i++) { // Only add object type parameter which is not Java class if (argTypes[i].getSort() == Type.OBJECT) { Class<?> cls = delegateType.getRawType().getClassLoader().loadClass(argTypes[i].getClassName()); // Classes loaded by bootstrap classloader are having null ClassLoader. They don't need to be preserved. if (cls.getClassLoader() != null) { preservedClasses.add(cls); } } } } catch (ClassNotFoundException e) { // Shouldn't happen, as the parameter class should be loading from the user handler ClassLoader throw Throwables.propagate(e); } } /** * Rewrite the handler method signature to have the first two parameters rewritten from * {@link HttpServiceRequest} and {@link HttpServiceResponder} into * {@link HttpRequest} and {@link HttpResponder}. */ private String rewriteMethodSignature(String signature) { if (signature == null) { return null; } SignatureReader reader = new SignatureReader(signature); SignatureWriter writer = new SignatureWriter() { @Override public void visitClassType(String name) { if (name.equals(Type.getInternalName(HttpServiceRequest.class))) { super.visitClassType(Type.getInternalName(HttpRequest.class)); return; } if (name.equals(Type.getInternalName(HttpServiceResponder.class))) { super.visitClassType(Type.getInternalName(HttpResponder.class)); return; } super.visitClassType(name); } }; reader.accept(writer); return writer.toString(); } /** * Wrap the user written Handler method in a transaction. * The transaction begins before calling the user method, and commit after the user method returns. * On errors the transaction is aborted and rolledback. * * The generated handler method body has the form: * * <pre>{@code * public void|BodyConsumer handle(HttpRequest request, HttpResponder responder, ...) { * T handler = getHandler(); * TransactionContext txContext = getTransactionContext(); * DelayedHttpServiceResponder wrappedResponder = wrapResponder(responder, txContext); * HttpContentConsumer contentConsumer = null; * try { * txContext.start(); * try { * ClassLoader classLoader = ClassLoaders.setContextClassLoader(handler.getClass().getClassLoader()); * try { * // Only do assignment if handler method returns HttpContentConsumer * [contentConsumer = ]handler.handle(wrapRequest(request), wrappedResponder, ...); * } finally { * ClassLoaders.setContextClassLoader(classLoader); * } * } catch (Throwable t) { * LOG.error("User handler exception", t); * txContext.abort(new TransactionFailureException("User handler exception", t)); * } * txContext.finish(); * } catch (TransactionFailureException e) { * LOG.error("Transaction failure: ", e); * wrappedResponder.setTransactionFailureResponse(e); * contentConsumer = null; * } * if (contentConsumer == null) { * wrappedResponder.execute(); * // Only return null if handler method returns HttpContentConsumer * [return null;] * } * * // Only generated if handler method returns HttpContentConsumer * [return wrapContentConsumer(httpContentConsumer, wrappedResponder, txContext);] * } * } * </pre> */ private void generateTransactionalDelegateBody(GeneratorAdapter mg, Method method) { Type handlerType = Type.getType(delegateType.getRawType()); Type txContextType = Type.getType(TransactionContext.class); Type txFailureExceptionType = Type.getType(TransactionFailureException.class); Type loggerType = Type.getType(Logger.class); Type throwableType = Type.getType(Throwable.class); Type delayedHttpServiceResponderType = Type.getType(DelayedHttpServiceResponder.class); Type httpContentConsumerType = Type.getType(HttpContentConsumer.class); Label txTryBegin = mg.newLabel(); Label txTryEnd = mg.newLabel(); Label txCatch = mg.newLabel(); Label txFinish = mg.newLabel(); Label handlerTryBegin = mg.newLabel(); Label handlerTryEnd = mg.newLabel(); Label handlerCatch = mg.newLabel(); Label handlerFinish = mg.newLabel(); mg.visitTryCatchBlock(txTryBegin, txTryEnd, txCatch, txFailureExceptionType.getInternalName()); mg.visitTryCatchBlock(handlerTryBegin, handlerTryEnd, handlerCatch, throwableType.getInternalName()); // T handler = getHandler(); int handler = mg.newLocal(handlerType); mg.loadThis(); mg.invokeVirtual(classType, Methods.getMethod(HttpServiceHandler.class, "getHandler")); mg.checkCast(handlerType); mg.storeLocal(handler, handlerType); // TransactionContext txContext = getTransactionContext(); int txContext = mg.newLocal(txContextType); mg.loadThis(); mg.invokeVirtual(classType, Methods.getMethod(TransactionContext.class, "getTransactionContext")); mg.storeLocal(txContext, txContextType); // DelayedHttpServiceResponder wrappedResponder = wrapResponder(responder, txContext); int wrappedResponder = mg.newLocal(delayedHttpServiceResponderType); mg.loadThis(); mg.loadArg(1); mg.loadLocal(txContext); mg.invokeVirtual(classType, Methods.getMethod(DelayedHttpServiceResponder.class, "wrapResponder", HttpResponder.class, TransactionContext.class)); mg.storeLocal(wrappedResponder, delayedHttpServiceResponderType); // HttpContentConsumer contentConsumer = null; int contentConsumer = mg.newLocal(httpContentConsumerType); mg.visitInsn(Opcodes.ACONST_NULL); mg.storeLocal(contentConsumer, httpContentConsumerType); // try { // Outer try for transaction failure mg.mark(txTryBegin); // txContext.start(); mg.loadLocal(txContext, txContextType); mg.invokeVirtual(txContextType, Methods.getMethod(void.class, "start")); // try { // Inner try for user handler failure mg.mark(handlerTryBegin); // If no body consumer, generates: // this.getHandler(wrapRequest(request), wrappedResponder, ...); // // otherwise, generates: // contentConsumer = this.getHandler(wrapRequest(reuqest), wrappedResponder, ...); generateInvokeDelegate(mg, handler, method, wrappedResponder); if (method.getReturnType().getSort() == Type.OBJECT) { mg.storeLocal(contentConsumer, httpContentConsumerType); } // } // end of inner try mg.mark(handlerTryEnd); mg.goTo(handlerFinish); // } catch (Throwable t) { // inner try-catch mg.mark(handlerCatch); int throwable = mg.newLocal(throwableType); mg.storeLocal(throwable); // LOG.error("User handler exception", e); mg.getStatic(classType, "LOG", loggerType); mg.visitLdcInsn("User handler exception: "); mg.loadLocal(throwable); mg.invokeInterface(loggerType, Methods.getMethod(void.class, "error", String.class, Throwable.class)); // transactionContext.abort(new TransactionFailureException("User handler exception: ", e)); mg.loadLocal(txContext, txContextType); mg.newInstance(txFailureExceptionType); mg.dup(); mg.visitLdcInsn("User handler exception: "); mg.loadLocal(throwable); mg.invokeConstructor(txFailureExceptionType, Methods.getMethod(void.class, "<init>", String.class, Throwable.class)); mg.invokeVirtual(txContextType, Methods.getMethod(void.class, "abort", TransactionFailureException.class)); // } // end of inner catch mg.mark(handlerFinish); // txContext.finish() mg.loadLocal(txContext, txContextType); mg.invokeVirtual(txContextType, Methods.getMethod(void.class, "finish")); mg.goTo(txTryEnd); // } // end of outer try mg.mark(txTryEnd); mg.goTo(txFinish); // } catch (TransactionFailureException e) { mg.mark(txCatch); int txFailureException = mg.newLocal(txFailureExceptionType); mg.storeLocal(txFailureException, txFailureExceptionType); // LOG.error("Transaction failure: ", e); mg.getStatic(classType, "LOG", loggerType); mg.visitLdcInsn("Transaction Failure: "); mg.loadLocal(txFailureException); mg.invokeInterface(loggerType, Methods.getMethod(void.class, "error", String.class, Throwable.class)); // wrappedResponder.setTransactionFailureResponse(e); mg.loadLocal(wrappedResponder); mg.loadLocal(txFailureException); mg.invokeVirtual(delayedHttpServiceResponderType, Methods.getMethod(void.class, "setTransactionFailureResponse", Throwable.class)); // contentConsumer = null; mg.visitInsn(Opcodes.ACONST_NULL); mg.storeLocal(contentConsumer); // } // end of outer catch mg.mark(txFinish); // If body consumer is used, generates: // // if (httpContentConsumer == null) { // wrappedResponder.execute(); // return null; // } // return wrapContentConsumer(httpContentConsumer, wrappedResponder, txContext); // // Otherwise, generates // wrappedResponder.execute(); if (method.getReturnType().getSort() == Type.OBJECT) { Label hasContentConsumer = mg.newLabel(); mg.loadLocal(contentConsumer); // if contentConsumer != null, goto label hasContentConsumer mg.ifNonNull(hasContentConsumer); // wrappedResponder.execute(); // return null; mg.loadLocal(wrappedResponder); mg.invokeVirtual(delayedHttpServiceResponderType, Methods.getMethod(void.class, "execute")); mg.visitInsn(Opcodes.ACONST_NULL); mg.returnValue(); mg.mark(hasContentConsumer); // IMPORTANT: If body consumer is used, calling wrapContentConsumer must be // the last thing to do in this generated method since the current context will be captured // return wrapContentConsumer(httpContentConsumer, wrappedResponder, txContext); mg.loadThis(); mg.loadLocal(contentConsumer); mg.loadLocal(wrappedResponder); mg.loadLocal(txContext); mg.invokeVirtual(classType, Methods.getMethod(BodyConsumer.class, "wrapContentConsumer", HttpContentConsumer.class, DelayedHttpServiceResponder.class, TransactionContext.class)); mg.returnValue(); } else { // wrappedResponder.execute() mg.loadLocal(wrappedResponder); mg.invokeVirtual(delayedHttpServiceResponderType, Methods.getMethod(void.class, "execute")); mg.returnValue(); } mg.endMethod(); } /** * Generates the code block for setting context ClassLoader, calling user handler method * and resetting context ClassLoader. */ private void generateInvokeDelegate(GeneratorAdapter mg, int handler, Method method, int responder) { Type classLoaderType = Type.getType(ClassLoader.class); Type handlerType = Type.getType(delegateType.getRawType()); Label contextTryBegin = mg.newLabel(); Label contextTryEnd = mg.newLabel(); Label contextCatch = mg.newLabel(); Label contextEnd = mg.newLabel(); Label contextCatchEnd = mg.newLabel(); // For the try-finally block // In bytecode, it's done by two try-catch instructions. The finally code are in both // the tryEnd and catchEnd blocks. mg.visitTryCatchBlock(contextTryBegin, contextTryEnd, contextCatch, null); mg.visitTryCatchBlock(contextCatch, contextCatchEnd, contextCatch, null); int throwable = mg.newLocal(Type.getType(Throwable.class)); // ClassLoader classLoader = ClassLoaders.setContextClassLoader(handler.getClass().getClassLoader()); int classLoader = mg.newLocal(classLoaderType); mg.loadLocal(handler, handlerType); mg.invokeVirtual(Type.getType(Object.class), Methods.getMethod(Class.class, "getClass")); mg.invokeVirtual(Type.getType(Class.class), Methods.getMethod(ClassLoader.class, "getClassLoader")); mg.invokeStatic(Type.getType(ClassLoaders.class), Methods.getMethod(ClassLoader.class, "setContextClassLoader", ClassLoader.class)); mg.storeLocal(classLoader, classLoaderType); // try { mg.mark(contextTryBegin); // handler.method(wrapRequest(request), responder, ...); mg.loadLocal(handler); mg.loadThis(); mg.loadArg(0); mg.invokeVirtual(classType, Methods.getMethod(HttpServiceRequest.class, "wrapRequest", HttpRequest.class)); mg.loadLocal(responder); for (int i = 2; i < method.getArgumentTypes().length; i++) { mg.loadArg(i); } mg.invokeVirtual(Type.getType(delegateType.getRawType()), method); // } mg.mark(contextTryEnd); // Reset ClassLoader, like in finally {} // ClassLoaders.setContextClassLoader(classLoader); setClassLoader(mg, classLoader); mg.goTo(contextEnd); // } catch and rethrow mg.mark(contextCatch); mg.storeLocal(throwable); mg.mark(contextCatchEnd); // A duplicate of finally block is needed in bytecode so that it gets executed when there is exception // ClassLoaders.setContextClassLoader(classLoader); setClassLoader(mg, classLoader); mg.loadLocal(throwable); mg.throwException(); mg.mark(contextEnd); } private void setClassLoader(GeneratorAdapter mg, int classLoader) { mg.loadLocal(classLoader); mg.invokeStatic(Type.getType(ClassLoaders.class), Methods.getMethod(ClassLoader.class, "setContextClassLoader", ClassLoader.class)); mg.pop(); } } }