/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, Helios Development Group and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.helios.gmx.classloading;
import groovy.lang.GroovyClassLoader;
import groovyjarjarasm.asm.ClassReader;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.lang.management.ManagementFactory;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import javax.management.MBeanServerInvocationHandler;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.helios.gmx.util.DequeuedSoftReferenceValueMap;
import org.helios.gmx.util.LoggingConfig;
import org.helios.gmx.util.LoggingConfig.GLogger;
import org.helios.vm.agent.AgentInstrumentationMBean;
import org.helios.vm.agent.LocalAgentInstaller;
/**
* <p>Title: ByteCodeRepository</p>
* <p>Description: Manages an indexed repository of byte code for dynamically generated classes.</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.gmx.classloading.ByteCodeRepository</code></p>
*/
public class ByteCodeRepository implements ClassFileTransformer {
/** The singleton instance */
private static volatile ByteCodeRepository instance = null;
/** The singleton instance ctor lock */
private static final Object lock = new Object();
/** A map of byte code keyed by the class the bytecode represents */
protected final Map<Class<?>, byte[]> classToByteCode = Collections.synchronizedMap(new WeakHashMap<Class<?>, byte[]>());
/** A map of Classes keyed by the class name */
protected final DequeuedSoftReferenceValueMap<String, Class<?>> nameToClass = new DequeuedSoftReferenceValueMap<String, Class<?>>();
/** A map of Classes keyed by the class resource name */
protected final DequeuedSoftReferenceValueMap<String, Class<?>> resourceNameToClass = new DequeuedSoftReferenceValueMap<String, Class<?>>();
/** A map of {@link DeferredClass}es keyed by the class name */
protected final DequeuedSoftReferenceValueMap<String, DeferredClass> nameToDeferredClass = new DequeuedSoftReferenceValueMap<String, DeferredClass>();
/** The AgentInstrumentation MBean that provides byte code for dynamically generated closures */
protected final AgentInstrumentationMBean agentInstrumentation;
/** An instance GLogger */
protected final GLogger log = LoggingConfig.getInstance().getLogger(getClass());
/** The resource class name of the GeneratedClosure interface */
public static final String generatedClosureName = GeneratedClosure.class.getName().replace('.', '/');
/**
* Acquires the ByteCodeRepository singleton
* @return the ByteCodeRepository singleton
*/
public static ByteCodeRepository getInstance() {
if(instance==null) {
synchronized(lock) {
if(instance==null) {
instance = new ByteCodeRepository();
}
}
}
return instance;
}
/**
* Returns the instrumentation instance
* @return the instrumentation instance
*/
public Instrumentation getInstrumentation() {
return agentInstrumentation.getInstrumentation();
}
/**
* Creates a new ByteCodeRepository
*/
private ByteCodeRepository() {
LocalAgentInstaller.getInstrumentation();
agentInstrumentation = MBeanServerInvocationHandler.newProxyInstance(ManagementFactory.getPlatformMBeanServer(), AgentInstrumentationMBean.AGENT_INSTR_ON, AgentInstrumentationMBean.class, false);
agentInstrumentation.addTransformer(this, true);
}
/**
* Adds a class to the repository
* @param clazz The class to add
* @param bytecode The class bytecode
*/
public void put(Class<?> clazz, byte[] bytecode) {
classToByteCode.put(clazz, bytecode);
nameToClass.put(clazz.getName(), clazz);
resourceNameToClass.put(clazz.getName().replace('.', '/') + ".class", clazz);
}
/**
* Adds a deferred class to the repository.
* This method is called by the class file transformer, but the full class cannot be created at that time.
* These parameters are stored until the requester requests the bytecode for a class, at which point we resolve
* the class and index it normally.
* @param className The class name
* @param classLoader The class loader
* @param bytecode The class byte code
*/
public void put(String className, ClassLoader classLoader, byte[] bytecode) {
DeferredClass dc = DeferredClass.newInstance(classLoader, className, bytecode);
nameToDeferredClass.put(className, dc);
}
/**
* Returns the bytecode for the named class
* @param className The class name
* @return the class bytecode or null if it was not found.
*/
public byte[] getByteCode(String className) {
byte[] bytecode = null;
Class<?> clazz = nameToClass.get(className);
if(clazz==null) {
bytecode = getDeferredByteCode(className, null);
} else {
bytecode = classToByteCode.get(clazz);
if(bytecode==null) {
bytecode = getDeferredByteCode(className, clazz);
}
}
return bytecode;
}
/**
* Processes a deferred class
* @param className The class name to process
* @param clazz If available
* @return the bytecode or null
*/
protected byte[] getDeferredByteCode(String className, Class<?> clazz) {
byte[] bytecode = null;
DeferredClass dc = nameToDeferredClass.get(className);
if(dc==null && clazz != null) {
try {
agentInstrumentation.retransformClasses(clazz);
} catch (UnmodifiableClassException e) {
}
}
if(dc!=null) {
bytecode = dc.getBytecode();
synchronized(dc) {
if(nameToDeferredClass.containsKey(className)) {
try {
Class<?> cl = Class.forName(className, true, dc.getClassLoader());
put(cl, dc.getBytecode());
nameToDeferredClass.remove(className);
} catch (Exception e) {}
}
}
}
return bytecode;
}
/**
* Returns the bytecode for the named class using the class resource name
* @param className The class resource name
* @return the class bytecode or null if it was not found.
*/
public byte[] getByteCodeFromResource(String className) {
byte[] bytecode = null;
Class<?> clazz = resourceNameToClass.get(className);
if(clazz==null) {
bytecode = getDeferredByteCode(className.replace('/', '.').replace(".class", ""), null);
} else {
bytecode = classToByteCode.get(clazz);
}
return bytecode;
}
/**
* Returns the bytecode for the passed class
* @param clazz The class
* @return the class bytecode or null if it was not found.
*/
public byte[] getByteCode(Class<?> clazz) {
byte[] bytecode = null;
bytecode = classToByteCode.get(clazz);
if(bytecode==null) {
bytecode = getByteCode(clazz.getName());
if(bytecode==null) {
try {
agentInstrumentation.retransformClasses(clazz);
bytecode = classToByteCode.get(clazz);
} catch (UnmodifiableClassException e) {
}
}
}
return bytecode;
}
/**
* Determines if the passed bytecode represents a class that implements {@link GeneratedClosure}.
* @param bytecode The byte array
* @return true if the bytecode represents a class that implements {@link GeneratedClosure}, false otherwise.
*/
protected boolean isGeneratedClosure(byte[] bytecode) {
try {
for(String iface: new ClassReader(bytecode).getInterfaces()) {
if(generatedClosureName.equals(iface)) return true;
}
return false;
} catch (Exception e) {
return false;
}
}
/**
* {@inheritDoc}
* @see java.lang.instrument.ClassFileTransformer#transform(java.lang.ClassLoader, java.lang.String, java.lang.Class, java.security.ProtectionDomain, byte[])
*/
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] bytecode = classfileBuffer;
if(classBeingRedefined==null) {
if(loader instanceof GroovyClassLoader && isGeneratedClosure(bytecode)) {
put(className, loader, bytecode);
log.log("Class Load Stored [" , bytecode.length , "] Bytes for deferred class [" , className , "]");
}
} else {
if(GeneratedClosure.class.isAssignableFrom(classBeingRedefined)) {
put(classBeingRedefined, bytecode);
log.log("Class Retransform Stored [" , bytecode.length , "] Bytes for deferred class [" , className , "]");
}
}
return bytecode;
}
}