/*
* #%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<Object>() {
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;
}
}