/* * Copyright 2016, Stuart Douglas, and individual contributors as indicated * by the @authors tag. * * 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.fakereplace.integration.resteasy; import java.lang.instrument.IllegalClassFormatException; import java.lang.reflect.Modifier; import java.security.ProtectionDomain; import java.util.List; import java.util.Set; import javassist.bytecode.AccessFlag; import javassist.bytecode.BadBytecode; import javassist.bytecode.Bytecode; import javassist.bytecode.ClassFile; import javassist.bytecode.DuplicateMemberException; import javassist.bytecode.FieldInfo; import javassist.bytecode.MethodInfo; import javassist.bytecode.Opcode; import org.fakereplace.replacement.notification.ChangedClassImpl; import org.fakereplace.transformation.FakereplaceTransformer; /** * @author Stuart Douglas */ public class ResteasyTransformer implements FakereplaceTransformer { public static final String FIELD_NAME = "__CONFIG"; public static final String PARAMETER_FIELD_NAME = "__PARAMS"; public static final String FILTER_FIELD_TYPE = "Ljavax/servlet/FilterConfig;"; public static final String SERVLET_FIELD_TYPE = "Ljavax/servlet/ServletConfig;"; public static final String CONTEXT_PARAMS = "org.fakereplace.integration.resteasy.ResteasyContextParams"; public static final String SET_TYPE = "Ljava/util/Set;"; public static final String INIT_METHOD_DESC = "(Ljavax/servlet/ServletContext;Ljava/util/Set;)Ljava/util/Set;"; public static final String RESTEASY_FILTER_CONFIG = "org.fakereplace.integration.resteasy.ResteasyFilterConfig"; public static final String RESTEASY_SERVLET_CONFIG = "org.fakereplace.integration.resteasy.ResteasyServletConfig"; @Override public boolean transform(final ClassLoader loader, final String className, final Class<?> classBeingRedefined, final ProtectionDomain protectionDomain, final ClassFile file, Set<Class<?>> classesToRetransform, ChangedClassImpl changedClass, Set<MethodInfo> modifiedMethods) throws IllegalClassFormatException, BadBytecode { //we need to change the filter and servlet dispatchers to //capture the config they are initalized with. //we can then re-initalize them with this same info try { if (file.getName().equals(ResteasyExtension.FILTER_DISPATCHER)) { FieldInfo field = new FieldInfo(file.getConstPool(), FIELD_NAME, FILTER_FIELD_TYPE); field.setAccessFlags(Modifier.PUBLIC); file.addField(field); field = new FieldInfo(file.getConstPool(), PARAMETER_FIELD_NAME, SET_TYPE); field.setAccessFlags(Modifier.PUBLIC); file.addField(field); for (final MethodInfo method : (List<MethodInfo>) file.getMethods()) { if (method.getName().equals("init") && method.getDescriptor().equals("(Ljavax/servlet/FilterConfig;)V")) { method.setAccessFlags(method.getAccessFlags() | AccessFlag.SYNCHRONIZED); modifiedMethods.add(method); final Bytecode b = new Bytecode(file.getConstPool()); b.addAload(0); b.addNew(RESTEASY_FILTER_CONFIG); b.add(Opcode.DUP); b.addAload(1); b.addInvokespecial(RESTEASY_FILTER_CONFIG, "<init>", "(Ljavax/servlet/FilterConfig;)V"); b.addPutfield(ResteasyExtension.FILTER_DISPATCHER, FIELD_NAME, FILTER_FIELD_TYPE); b.addAload(1); b.addInvokeinterface("javax/servlet/FilterConfig", "getServletContext", "()Ljavax/servlet/ServletContext;", 1); b.addAload(0); b.addGetfield(ResteasyExtension.FILTER_DISPATCHER, PARAMETER_FIELD_NAME, SET_TYPE); b.addInvokestatic(CONTEXT_PARAMS, "init", INIT_METHOD_DESC); b.addAload(0); b.add(Opcode.SWAP); b.addPutfield(ResteasyExtension.FILTER_DISPATCHER, PARAMETER_FIELD_NAME, SET_TYPE); method.getCodeAttribute().iterator().insert(b.get()); method.getCodeAttribute().computeMaxStack(); } else if(method.getName().equals("<init>")) { //no idea why this is needed method.getCodeAttribute().setMaxStack(1); } else if(method.getName().equals("getDispatcher")) { method.setAccessFlags(method.getAccessFlags() | AccessFlag.SYNCHRONIZED); modifiedMethods.add(method); } } return true; } else if (file.getName().equals(ResteasyExtension.SERVLET_DISPATCHER)) { FieldInfo field = new FieldInfo(file.getConstPool(), FIELD_NAME, SERVLET_FIELD_TYPE); field.setAccessFlags(Modifier.PUBLIC); file.addField(field); field = new FieldInfo(file.getConstPool(), PARAMETER_FIELD_NAME, SET_TYPE); field.setAccessFlags(Modifier.PUBLIC); file.addField(field); for (final MethodInfo method : (List<MethodInfo>) file.getMethods()) { if (method.getName().equals("init") && method.getDescriptor().equals("(Ljavax/servlet/ServletConfig;)V")) { method.setAccessFlags(method.getAccessFlags() | AccessFlag.SYNCHRONIZED); modifiedMethods.add(method); final Bytecode b = new Bytecode(file.getConstPool()); b.addAload(0); b.addNew(RESTEASY_SERVLET_CONFIG); b.add(Opcode.DUP); b.addAload(1); b.addInvokespecial(RESTEASY_SERVLET_CONFIG, "<init>", "(Ljavax/servlet/ServletConfig;)V"); b.addPutfield(ResteasyExtension.SERVLET_DISPATCHER, FIELD_NAME, SERVLET_FIELD_TYPE); b.addAload(1); b.addInvokeinterface("javax/servlet/ServletConfig", "getServletContext", "()Ljavax/servlet/ServletContext;", 1); b.addAload(0); b.addGetfield(ResteasyExtension.SERVLET_DISPATCHER, PARAMETER_FIELD_NAME, SET_TYPE); b.addInvokestatic(CONTEXT_PARAMS, "init", INIT_METHOD_DESC); b.addAload(0); b.add(Opcode.SWAP); b.addPutfield(ResteasyExtension.SERVLET_DISPATCHER, PARAMETER_FIELD_NAME, SET_TYPE); method.getCodeAttribute().iterator().insert(b.get()); method.getCodeAttribute().computeMaxStack(); } else if(method.getName().equals("<init>")) { method.getCodeAttribute().setMaxStack(1); } else if(method.getName().equals("getDispatcher")) { method.setAccessFlags(method.getAccessFlags() | AccessFlag.SYNCHRONIZED); modifiedMethods.add(method); } } return true; } } catch (DuplicateMemberException e) { throw new RuntimeException(e); } return false; } }