All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.broadleafcommerce.common.extensibility.InstrumentationRuntimeFactory Maven / Gradle / Ivy

/*
 * #%L
 * BroadleafCommerce Common Libraries
 * %%
 * Copyright (C) 2009 - 2013 Broadleaf Commerce
 * %%
 * 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.
 * #L%
 */
package org.broadleafcommerce.common.extensibility;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * This class is based on the OpenJPA's org.apache.openjpa.enhance.InstrumentationFactory.  It essentially does
 * its best to install an instrumentation agent.  The preferred or prescribed way to install an instrumentation agent
 * is to add the agent as an argument on the command line when starting the JVM.  This class attempts to do the
 * same thing after the JVM has already started.  Unfortunately, this is the only way we know of to attach an agent to
 * the JVM except by adding a "javaagent:..." flag on the command line.
 *
 * @author Kelly Tisdell
 * @deprecated Because of classloader differences, this approach is not reliable for some containers. Use the javaagent jvm argument instead to set instrumentation.
 */
@Deprecated
public class InstrumentationRuntimeFactory {
    private static final Log LOG = LogFactory.getLog(InstrumentationRuntimeFactory.class);
    private static final String IBM_VM_CLASS = "com.ibm.tools.attach.VirtualMachine";
    private static final String SUN_VM_CLASS = "com.sun.tools.attach.VirtualMachine";
    private static boolean isIBM = false;
    private static Instrumentation inst;

    /**
     * This method is called by the JVM to set the instrumentation.  We can't synchronize this because it will cause
     * a deadlock with the thread calling the getInstrumentation() method when the instrumentation is installed.
     *
     * @param agentArgs
     * @param instrumentation
     */
    public static void agentmain(String agentArgs, Instrumentation instrumentation) {
        inst = instrumentation;
    }

    /**
     * This method returns the Instrumentation object provided by the JVM. If the Instrumentation object is null,
     * it does its best to add an instrumentation agent to the JVM and then the instrumentation object.
     * @return Instrumentation
     */
    public static synchronized Instrumentation getInstrumentation() {
        if (inst != null) {
            return inst;
        }
        
        if (System.getProperty("java.vendor").toUpperCase().contains("IBM")) {
            isIBM = true;
        }

        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                try {
                    if (!InstrumentationRuntimeFactory.class.getClassLoader().equals(
                            ClassLoader.getSystemClassLoader())) {
                        return null;
                    }
                } catch (Throwable t) {
                    return null;
                }
                File toolsJar = null;
                // When running on IBM, the attach api classes are packaged in vm.jar which is a part
                // of the default vm classpath.
                if (! isIBM) {
                    // If we can't find the tools.jar and we're not on IBM we can't load the agent.
                    toolsJar = findToolsJar();
                    if (toolsJar == null) {
                        return null;
                    }
                }

                Class vmClass = loadVMClass(toolsJar);
                if (vmClass == null) {
                    return null;
                }
                String agentPath = getAgentJar();
                if (agentPath == null) {
                    return null;
                }
                loadAgent(agentPath, vmClass);
                return null;
            }
        });

        return inst;
    }

    private static File findToolsJar() {
        String javaHome = System.getProperty("java.home");
        File javaHomeFile = new File(javaHome);

        File toolsJarFile = new File(javaHomeFile, "lib" + File.separator + "tools.jar");
        if (!toolsJarFile.exists()) {
            // If we're on an IBM SDK, then remove /jre off of java.home and try again.
            if (javaHomeFile.getAbsolutePath().endsWith(File.separator + "jre")) {
                javaHomeFile = javaHomeFile.getParentFile();
                toolsJarFile = new File(javaHomeFile, "lib" + File.separator + "tools.jar");
            } else if (System.getProperty("os.name").toLowerCase().contains("mac")) {
                // If we're on a Mac, then change the search path to use ../Classes/classes.jar.
                if (javaHomeFile.getAbsolutePath().endsWith(File.separator + "Home")) {
                    javaHomeFile = javaHomeFile.getParentFile();
                    toolsJarFile = new File(javaHomeFile, "Classes" + File.separator + "classes.jar");

                }
            }
        }

        if (! toolsJarFile.exists()) {
            return null;
        } else {
            return toolsJarFile;
        }
    }

    private static String createAgentJar() throws IOException {
        File file =
                File.createTempFile(InstrumentationRuntimeFactory.class.getName(), ".jar");
        file.deleteOnExit();

        ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(file));
        zout.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));

        PrintWriter writer = new PrintWriter(new OutputStreamWriter(zout));

        writer.println("Agent-Class: " + InstrumentationRuntimeFactory.class.getName());
        writer.println("Can-Redefine-Classes: true");
        // IBM doesn't support retransform
        writer.println("Can-Retransform-Classes: " + Boolean.toString(!isIBM));

        writer.close();

        return file.getAbsolutePath();
    }

    private static String getAgentJar() {
        File agentJarFile = null;
        // Find the name of the File that this class was loaded from. That
        // jar *should* be the same location as our agent.
        CodeSource cs =
                InstrumentationRuntimeFactory.class.getProtectionDomain().getCodeSource();
        if (cs != null) {
            URL loc = cs.getLocation();
            if (loc != null) {
                agentJarFile = new File(loc.getFile());
            }
        }

        // Determine whether the File that this class was loaded from has this
        // class defined as the Agent-Class.
        boolean createJar = false;
        if (cs == null || agentJarFile == null
                || agentJarFile.isDirectory()) {
            createJar = true;
        } else if (!validateAgentJarManifest(agentJarFile, InstrumentationRuntimeFactory.class.getName())) {
            // We have an agentJarFile, but this class isn't the Agent-Class.
            createJar = true;
        }

        String agentJar;
        if (createJar) {
            try {
                agentJar = createAgentJar();
            } catch (IOException ioe) {
                agentJar = null;
            }
        } else {
            agentJar = agentJarFile.getAbsolutePath();
        }

        return agentJar;
    }

    private static void loadAgent(String agentJar, Class vmClass) {
        try {
            // first obtain the PID of the currently-running process
            // ### this relies on the undocumented convention of the
            // RuntimeMXBean's
            // ### name starting with the PID, but there appears to be no other
            // ### way to obtain the current process' id, which we need for
            // ### the attach process
            RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
            String pid = runtime.getName();
            if (pid.contains("@"))
                pid = pid.substring(0, pid.indexOf("@"));

            // JDK1.6: now attach to the current VM so we can deploy a new agent
            // ### this is a Sun JVM specific feature; other JVMs may offer
            // ### this feature, but in an implementation-dependent way
            Object vm = vmClass.getMethod("attach", new Class[]{String.class}).invoke(null, pid);
            vmClass.getMethod("loadAgent", new Class[]{String.class}).invoke(vm, agentJar);
            vmClass.getMethod("detach", new Class[]{}).invoke(vm);
        } catch (Throwable t) {
            if (LOG.isTraceEnabled()) {
                LOG.trace("Problem loading the agent", t);
            }
        }
    }

    private static Class loadVMClass(File toolsJar) {
        try {
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            String cls = SUN_VM_CLASS;
            if (isIBM) {
                cls = IBM_VM_CLASS;
            } else {
                loader = new URLClassLoader(new URL[]{toolsJar.toURI().toURL()}, loader);
            }
            return loader.loadClass(cls);
        } catch (Exception e) {
            if (LOG.isTraceEnabled()) {
                LOG.trace("Failed to load the virtual machine class", e);
            }
        }
        return null;
    }

    private static boolean validateAgentJarManifest(File agentJarFile,
                                                    String agentClassName) {
        try {
            JarFile jar = new JarFile(agentJarFile);
            Manifest manifest = jar.getManifest();
            if (manifest == null) {
                return false;
            }
            Attributes attributes = manifest.getMainAttributes();
            String ac = attributes.getValue("Agent-Class");
            if (ac != null && ac.equals(agentClassName)) {
                return true;
            }
        } catch (Exception e) {
            if (LOG.isTraceEnabled()) {
                LOG.trace("Unexpected exception occured.", e);
            }
        }
        return false;
    }
}