| /* |
| * 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.openejb.javaagent; |
| |
| import java.io.Closeable; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.lang.instrument.ClassFileTransformer; |
| import java.lang.instrument.IllegalClassFormatException; |
| import java.lang.instrument.Instrumentation; |
| import java.lang.management.ManagementFactory; |
| import java.lang.management.RuntimeMXBean; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.ReflectPermission; |
| import java.net.JarURLConnection; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.security.Permission; |
| import java.security.ProtectionDomain; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipOutputStream; |
| |
| public class Agent { |
| |
| private static final Permission ACCESS_PERMISSION = new ReflectPermission("suppressAccessChecks"); |
| private static String agentArgs; |
| private static Instrumentation instrumentation; |
| private static boolean initialized; |
| |
| public static void premain(final String agentArgs, final Instrumentation instrumentation) { |
| if (Agent.instrumentation != null) { |
| return; |
| } |
| |
| Agent.agentArgs = agentArgs; |
| Agent.instrumentation = instrumentation; |
| initialized = true; |
| |
| instrumentation.addTransformer(new BootstrapTransformer()); |
| } |
| |
| public static void agentmain(final String agentArgs, final Instrumentation instrumentation) { |
| if (Agent.instrumentation != null) { |
| return; |
| } |
| |
| Agent.agentArgs = agentArgs; |
| Agent.instrumentation = instrumentation; |
| initialized = true; |
| } |
| |
| public static synchronized String getAgentArgs() { |
| final SecurityManager sm = System.getSecurityManager(); |
| if (sm != null) { |
| sm.checkPermission(ACCESS_PERMISSION); |
| } |
| checkInitialization(); |
| return agentArgs; |
| } |
| |
| /** |
| * Gets the instrumentation instance. |
| * You must have java.lang.ReflectPermission(suppressAccessChecks) to call this method |
| * |
| * @return the instrumentation instance |
| */ |
| public static synchronized Instrumentation getInstrumentation() { |
| final SecurityManager sm = System.getSecurityManager(); |
| if (sm != null) { |
| sm.checkPermission(ACCESS_PERMISSION); |
| } |
| checkInitialization(); |
| return instrumentation; |
| } |
| |
| private static synchronized void checkInitialization() { |
| if (!initialized) { |
| try { |
| checkSystemClassPath(); |
| dynamicLoadAgent(); |
| } catch (final Exception e) { |
| new IllegalStateException("Unable to initialize agent", e).printStackTrace(); |
| } finally { |
| initialized = true; |
| } |
| } |
| } |
| |
| private static void checkSystemClassPath() throws NoSuchFieldException, IllegalAccessException { |
| if (instrumentation != null) { |
| return; |
| } |
| |
| final Class<?> systemAgentClass; |
| try { |
| final ClassLoader systemCl = ClassLoader.getSystemClassLoader(); |
| systemAgentClass = systemCl.loadClass(Agent.class.getName()); |
| } catch (final ClassNotFoundException e) { |
| // java-agent jar was not on the system class path |
| return; |
| } |
| |
| final Field instrumentationField = systemAgentClass.getDeclaredField("instrumentation"); |
| instrumentationField.setAccessible(true); |
| instrumentation = (Instrumentation) instrumentationField.get(null); |
| |
| final Field agentArgsField = systemAgentClass.getDeclaredField("agentArgs"); |
| agentArgsField.setAccessible(true); |
| agentArgs = (String) agentArgsField.get(null); |
| } |
| |
| private static void dynamicLoadAgent() throws Exception { |
| if (instrumentation != null) { |
| return; |
| } |
| |
| try { |
| final Class<?> vmClass = Class.forName("com.sun.tools.attach.VirtualMachine"); |
| final Method attachMethod = vmClass.getMethod("attach", String.class); |
| final Method loadAgentMethod = vmClass.getMethod("loadAgent", String.class); |
| |
| // find the agentJar |
| final String agentPath = getAgentJar(); |
| |
| // get the pid of the current process (for attach command) |
| final String pid = getPid(); |
| |
| // attach to the vm |
| final Object vm = attachMethod.invoke(null, new String[]{pid}); |
| |
| // load our agent |
| loadAgentMethod.invoke(vm, agentPath); |
| |
| // The AgentJar is loaded into the system classpath, and this class could |
| // be in a child classloader, so we need to double check the system classpath |
| checkSystemClassPath(); |
| } catch (final ClassNotFoundException | NoSuchMethodException e) { |
| // not a Sun VM |
| } |
| } |
| |
| private static String getPid() { |
| // 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 |
| final RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean(); |
| String pid = bean.getName(); |
| if (pid.contains("@")) { |
| pid = pid.substring(0, pid.indexOf('@')); |
| } |
| return pid; |
| } |
| |
| /** |
| * Try to find the openejb-javaagent jar, and if not found create a new jar |
| * file for the sole purpose of specifying an Agent-Class to load into the JVM. |
| */ |
| private static String getAgentJar() throws IOException { |
| final URL resource = Agent.class.getClassLoader().getResource(Agent.class.getName().replace('.', '/') + ".class"); |
| if (resource == null) { |
| throw new IllegalStateException("Could not find Agent class file in class path"); |
| } |
| |
| final URLConnection urlConnection = resource.openConnection(); |
| if (urlConnection instanceof JarURLConnection) { |
| final JarURLConnection jarURLConnection = (JarURLConnection) urlConnection; |
| return jarURLConnection.getJarFile().getName(); |
| } |
| |
| final InputStream in = urlConnection.getInputStream(); |
| ZipOutputStream out = null; |
| File file = null; |
| try { |
| try { |
| file = File.createTempFile(Agent.class.getName(), ".jar"); |
| } catch (final Throwable e) { |
| final File tmp = new File("tmp"); |
| if (!tmp.exists() && !tmp.mkdirs()) { |
| throw new IOException("Failed to create local tmp directory: " + tmp.getAbsolutePath()); |
| } |
| |
| file = File.createTempFile(Agent.class.getName(), ".jar", tmp); |
| } |
| |
| file.deleteOnExit(); |
| |
| out = new ZipOutputStream(new FileOutputStream(file)); |
| |
| // write manifest |
| out.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); |
| try { |
| final PrintWriter writer = new PrintWriter(new OutputStreamWriter(out)); |
| writer.println("Agent-Class: " + Agent.class.getName()); |
| writer.println("Can-Redefine-Classes: true"); |
| writer.println("Can-Retransform-Classes: true"); |
| writer.flush(); |
| } finally { |
| out.closeEntry(); |
| } |
| |
| // write agent class |
| out.putNextEntry(new ZipEntry(Agent.class.getName().replace('.', '/') + ".class")); |
| try { |
| final byte[] buffer = new byte[4096]; |
| for (int count = in.read(buffer); count >= 0; count = in.read(buffer)) { |
| out.write(buffer, 0, count); |
| } |
| } finally { |
| out.closeEntry(); |
| } |
| |
| return file.getAbsolutePath(); |
| } catch (final IOException e) { |
| if (file != null) { |
| if (!file.delete()) { |
| file.deleteOnExit(); |
| } |
| } |
| throw e; |
| } finally { |
| close(in); |
| close(out); |
| } |
| } |
| |
| private static void close(final Closeable closeable) { |
| if (closeable != null) { |
| try { |
| closeable.close(); |
| } catch (final IOException ignored) { |
| // no-op |
| } |
| } |
| } |
| |
| private static class BootstrapTransformer implements ClassFileTransformer { |
| @Override |
| public byte[] transform(final ClassLoader loader, final String className, final Class<?> classBeingRedefined, final ProtectionDomain protectionDomain, final byte[] classfileBuffer) throws IllegalClassFormatException { |
| try { |
| bootstrap(loader); |
| } catch (final Throwable e) { |
| removeThis(); |
| } |
| return classfileBuffer; |
| } |
| |
| private void removeThis() { |
| try { |
| instrumentation.removeTransformer(this); |
| } catch (final Throwable th) { |
| // no-op |
| } |
| } |
| |
| private void bootstrap(final ClassLoader loader) { |
| if (loader == null) { |
| return; |
| } |
| |
| final String bootstrapClassName = "org.apache.openejb.persistence.PersistenceBootstrap"; |
| final String bootstrapClassFile = "org/apache/openejb/persistence/PersistenceBootstrap.class"; |
| |
| if (loader.getResource(bootstrapClassFile) == null) { |
| return; |
| } |
| |
| try { |
| final Class<?> bootstrapClass = loader.loadClass(bootstrapClassName); |
| final Method bootstrap = bootstrapClass.getMethod("bootstrap", ClassLoader.class); |
| bootstrap.invoke(null, loader); |
| } catch (final Throwable e) { |
| Logger.getLogger(Agent.class.getName()).log(Level.WARNING, "Failed to invoke bootstrap: " + e.getMessage()); |
| } finally { |
| removeThis(); |
| } |
| } |
| } |
| } |