/** | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you 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.apache.aries.spifly.weaver; | |
import java.util.Arrays; | |
import java.util.HashSet; | |
import java.util.ServiceLoader; | |
import java.util.Set; | |
import org.apache.aries.spifly.Util; | |
import org.apache.aries.spifly.WeavingData; | |
import org.objectweb.asm.ClassVisitor; | |
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; | |
/** | |
* This class implements an ASM ClassVisitor which puts the appropriate ThreadContextClassloader | |
* calls around applicable method invocations. It does the actual bytecode weaving. | |
*/ | |
public class TCCLSetterVisitor extends ClassVisitor implements Opcodes { | |
private static final Type CLASSLOADER_TYPE = Type.getType(ClassLoader.class); | |
private static final String GENERATED_METHOD_NAME = "$$FCCL$$"; | |
private static final Type UTIL_CLASS = Type.getType(Util.class); | |
private static final Type CLASS_TYPE = Type.getType(Class.class); | |
private static final Type String_TYPE = Type.getType(String.class); | |
private final Type targetClass; | |
private final Set<WeavingData> weavingData; | |
// Set to true when the weaving code has changed the client such that an additional import | |
// (to the Util.class.getPackage()) is needed. | |
private boolean additionalImportRequired = false; | |
// This field is true when the class was woven | |
private boolean woven = false; | |
public TCCLSetterVisitor(ClassVisitor cv, String className, Set<WeavingData> weavingData) { | |
super(Opcodes.ASM5, cv); | |
this.targetClass = Type.getType("L" + className.replace('.', '/') + ";"); | |
this.weavingData = weavingData; | |
} | |
public boolean isWoven() { | |
return woven; | |
} | |
@Override | |
public MethodVisitor visitMethod(int access, String name, String desc, | |
String signature, String[] exceptions) { | |
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); | |
return new TCCLSetterMethodVisitor(mv, access, name, desc); | |
} | |
@Override | |
public void visitEnd() { | |
if (!woven) { | |
// if this class wasn't woven, then don't add the synthesized method either. | |
super.visitEnd(); | |
return; | |
} | |
// Add generated static method | |
Set<String> methodNames = new HashSet<String>(); | |
for (WeavingData wd : weavingData) { | |
/* Equivalent to: | |
* private static void $$FCCL$$<className>$<methodName>(Class<?> cls) { | |
* Util.fixContextClassLoader("java.util.ServiceLoader", "load", cls, WovenClass.class.getClassLoader()); | |
* } | |
*/ | |
String methodName = getGeneratedMethodName(wd); | |
if (methodNames.contains(methodName)) | |
continue; | |
methodNames.add(methodName); | |
Method method = new Method(methodName, Type.VOID_TYPE, new Type[] {CLASS_TYPE}); | |
GeneratorAdapter mv = new GeneratorAdapter(cv.visitMethod(ACC_PRIVATE + ACC_STATIC, methodName, | |
method.getDescriptor(), null, null), ACC_PRIVATE + ACC_STATIC, methodName, | |
method.getDescriptor()); | |
//Load the strings, method parameter and target | |
mv.visitLdcInsn(wd.getClassName()); | |
mv.visitLdcInsn(wd.getMethodName()); | |
mv.loadArg(0); | |
mv.visitLdcInsn(targetClass); | |
//Change the class on the stack into a classloader | |
mv.invokeVirtual(CLASS_TYPE, new Method("getClassLoader", | |
CLASSLOADER_TYPE, new Type[0])); | |
//Call our util method | |
mv.invokeStatic(UTIL_CLASS, new Method("fixContextClassloader", Type.VOID_TYPE, | |
new Type[] {String_TYPE, String_TYPE, CLASS_TYPE, CLASSLOADER_TYPE})); | |
mv.returnValue(); | |
mv.endMethod(); | |
} | |
super.visitEnd(); | |
} | |
private String getGeneratedMethodName(WeavingData wd) { | |
StringBuilder name = new StringBuilder(GENERATED_METHOD_NAME); | |
name.append(wd.getClassName().replace('.', '#')); | |
name.append("$"); | |
name.append(wd.getMethodName()); | |
if (wd.getArgClasses() != null) { | |
for (String cls : wd.getArgClasses()) { | |
name.append("$"); | |
name.append(cls.replace('.', '#')); | |
} | |
} | |
return name.toString(); | |
} | |
private class TCCLSetterMethodVisitor extends GeneratorAdapter { | |
Type lastLDCType; | |
public TCCLSetterMethodVisitor(MethodVisitor mv, int access, String name, String descriptor) { | |
super(Opcodes.ASM5, mv, access, name, descriptor); | |
} | |
/** | |
* Store the last LDC call. When ServiceLoader.load(Class cls) is called | |
* the last LDC call before the ServiceLoader.load() visitMethodInsn call | |
* contains the class being passed in. We need to pass this class to $$FCCL$$ as well | |
* so we can copy the value found in here. | |
*/ | |
@Override | |
public void visitLdcInsn(Object cst) { | |
if (cst instanceof Type) { | |
lastLDCType = ((Type) cst); | |
} | |
super.visitLdcInsn(cst); | |
} | |
/** | |
* Wrap selected method calls with | |
* Util.storeContextClassloader(); | |
* $$FCCL$$(<class>) | |
* Util.restoreContextClassloader(); | |
*/ | |
@Override | |
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { | |
WeavingData wd = findWeavingData(owner, name, desc); | |
if (opcode == INVOKESTATIC && wd != null) { | |
additionalImportRequired = true; | |
woven = true; | |
Label startTry = newLabel(); | |
Label endTry = newLabel(); | |
//start try block | |
visitTryCatchBlock(startTry, endTry, endTry, null); | |
mark(startTry); | |
// Add: Util.storeContextClassloader(); | |
invokeStatic(UTIL_CLASS, new Method("storeContextClassloader", Type.VOID_TYPE, new Type[0])); | |
// Add: MyClass.$$FCCL$$<classname>$<methodname>(<class>); | |
if (ServiceLoader.class.getName().equals(wd.getClassName()) && | |
"load".equals(wd.getMethodName()) && | |
(wd.getArgClasses() == null || Arrays.equals(new String [] {Class.class.getName()}, wd.getArgClasses()))) { | |
// ServiceLoader.load() is a special case because it's a general-purpose service loader, | |
// therefore, the target class it the class being passed in to the ServiceLoader.load() | |
// call itself. | |
mv.visitLdcInsn(lastLDCType); | |
} else { | |
// In any other case, we're not dealing with a general-purpose service loader, but rather | |
// with a specific one, such as DocumentBuilderFactory.newInstance(). In that case the | |
// target class is the class that is being invoked on (i.e. DocumentBuilderFactory). | |
Type type = Type.getObjectType(owner); | |
mv.visitLdcInsn(type); | |
} | |
invokeStatic(targetClass, new Method(getGeneratedMethodName(wd), | |
Type.VOID_TYPE, new Type[] {CLASS_TYPE})); | |
//Call the original instruction | |
super.visitMethodInsn(opcode, owner, name, desc, itf); | |
//If no exception then go to the finally (finally blocks are a catch block with a jump) | |
Label afterCatch = newLabel(); | |
goTo(afterCatch); | |
//start the catch | |
mark(endTry); | |
//Run the restore method then throw on the exception | |
invokeStatic(UTIL_CLASS, new Method("restoreContextClassloader", Type.VOID_TYPE, new Type[0])); | |
throwException(); | |
//start the finally | |
mark(afterCatch); | |
//Run the restore and continue | |
invokeStatic(UTIL_CLASS, new Method("restoreContextClassloader", Type.VOID_TYPE, new Type[0])); | |
} else { | |
super.visitMethodInsn(opcode, owner, name, desc, itf); | |
} | |
} | |
private WeavingData findWeavingData(String owner, String methodName, String methodDesc) { | |
owner = owner.replace('/', '.'); | |
Type[] argTypes = Type.getArgumentTypes(methodDesc); | |
String [] argClassNames = new String[argTypes.length]; | |
for (int i = 0; i < argTypes.length; i++) { | |
argClassNames[i] = argTypes[i].getClassName(); | |
} | |
for (WeavingData wd : weavingData) { | |
if (wd.getClassName().equals(owner) && | |
wd.getMethodName().equals(methodName) && | |
(wd.getArgClasses() != null ? Arrays.equals(argClassNames, wd.getArgClasses()) : true)) { | |
return wd; | |
} | |
} | |
return null; | |
} | |
} | |
public boolean additionalImportRequired() { | |
return additionalImportRequired ; | |
} | |
} |