package com.linkedin.parseq.lambda; import com.ea.agentloader.AgentLoader; import com.ea.agentloader.ClassPathUtils; import com.linkedin.parseq.TaskDescriptor; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.lang.reflect.Field; import java.security.ProtectionDomain; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; /** * An ASM based implementation of {@link TaskDescriptor} to provide description for generated Lambda class. * Description of Lambda expression includes source code location of lambda, function call or method reference * within lambda. */ public class ASMBasedTaskDescriptor implements TaskDescriptor { private static ConcurrentMap<String, String> _names = new ConcurrentHashMap<>(); static { if (ASMBasedTaskDescriptor.Agent.class.getClassLoader() != ClassLoader.getSystemClassLoader()) { try { // Agent needs to be loaded through system class loader // This piece of code adds Agent to system class path and then loads the Agent ClassPathUtils.appendToSystemPath(ClassPathUtils.getClassPathFor(ASMBasedTaskDescriptor.Agent.class)); AgentLoader.loadAgentClass(ASMBasedTaskDescriptor.Agent.class.getName(), null, null, true, true, false); // Reference the names field to names field of instance loaded through system class loader // Its the system class loaded instance names field which is populated with lambda descriptions // because agent is loaded through system class loader Class<?> systemClazz = ClassLoader.getSystemClassLoader().loadClass(ASMBasedTaskDescriptor.class.getName()); Object _systemClassDescriptor = systemClazz.newInstance(); Field field = systemClazz.getDeclaredField("_names"); field.setAccessible(true); ConcurrentMap<String, String> systemsNamesMap = (ConcurrentMap<String, String>) field.get(_systemClassDescriptor); _names = systemsNamesMap; } catch (Throwable e) { System.err.println("Unable to refer to names map of ASMBasedTaskDescriptor loaded from system classpath"); } } else { try { //for cases such as test executions which have only one class loader. AgentLoader.loadAgentClass(ASMBasedTaskDescriptor.Agent.class.getName(), null); } catch (Throwable e) { // ignore this // in case of multiple class loaders, this can throw an error as SystemClassLoaded loaded it already // look above (ClassLoader.getSystemClassLoader().loadClass(ASMBasedTaskDescriptor.class.getName());) } } } @Override public String getDescription(String className) { Optional<String> lambdaClassDescription = getLambdaClassDescription(className); if (lambdaClassDescription.isPresent()) { return lambdaClassDescription.get(); } else { return className; } } /*package private */ Optional<String> getLambdaClassDescription(String className) { int slashIndex = className.lastIndexOf('/'); if (slashIndex > 0) { String name = className.substring(0, slashIndex); String desc = _names.get(name); if (desc == null || desc.isEmpty()) { return Optional.empty(); } else { return Optional.of(desc); } } return Optional.empty(); } private static void add(String lambdaClassName, String description) { _names.put(lambdaClassName, description); } public static class Agent { private static final AtomicBoolean _initialized = new AtomicBoolean(false); public static void agentmain(String agentArgs, Instrumentation instrumentation) { if (_initialized.compareAndSet(false, true)) { instrumentation.addTransformer(new Analyzer()); } } private static class Analyzer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (className == null && loader != null) { analyze(classfileBuffer, loader); } return classfileBuffer; } private void analyze(byte[] byteCode, ClassLoader loader) { ClassReader reader = new ClassReader(byteCode); LambdaClassLocator cv = new LambdaClassLocator(Opcodes.ASM5, loader); reader.accept(cv, 0); if (cv.isLambdaClass()) { LambdaClassDescription lambdaClassDescription = cv.getLambdaClassDescription(); add(lambdaClassDescription.getClassName(), lambdaClassDescription.getDescription()); } } } } }