/* * JBoss, Home of Professional Open Source * Copyright 2010 Red Hat and individual contributors * by the @authors tag. See the copyright.txt 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. * * (C) 2010, * @authors Andrew Dinn */ package org.jboss.byteman.agent; import org.jboss.byteman.rule.Rule; import org.jboss.byteman.modules.ModuleSystem; import org.jboss.byteman.rule.helper.Helper; import java.lang.instrument.Instrumentation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.concurrent.ConcurrentHashMap; /** * class used to manage lifecycle events for rule helpers */ public class HelperManager { // public API /** * construct a manager * @param inst will be non-null if we are running in an agent * @param moduleSystem must be non-null, use NonModuleSystem if nothing specal is needed */ public HelperManager(Instrumentation inst, ModuleSystem moduleSystem) { this.inst = inst; this.moduleSystem = moduleSystem; this.helperDetailsMap = new ConcurrentHashMap<Class<?>, LifecycleDetails>(); } public void installed(Rule rule) { Class helperClass = rule.getHelperClass(); Helper.verbose("HelperManager.install for helper class " + helperClass.getName()); // synchronize on the lifecycle class to ensure it is not uninstalled // while we are deciding whether or not to install it synchronized (helperClass) { LifecycleDetails details; details = getDetails(helperClass, true); if (details.installCount == 0 && details.activated != null) { Helper.verbose("calling activated() for helper class " + helperClass.getName()); try { details.activated.invoke(null); } catch (Exception e) { Helper.err("HelperManager.installed : unexpected exception from " + helperClass.getName() + ".activate() : " + e); Helper.errTraceException(e); } } if (details.installed != null) { Helper.verbose("calling installed(" + rule.getName() + ") for helper class" + helperClass.getName()); try { if (details.installedTakesRule) { details.installed.invoke(null, rule); } else { details.installed.invoke(null, rule.getName()); } } catch (Exception e) { Helper.err("HelperManager.installed : unexpected exception from " + helperClass.getName() + ".installed(String) : " + e); Helper.errTraceException(e); } } details.installCount++; } } public void uninstalled(Rule rule) { Class helperClass = rule.getHelperClass(); Helper.verbose("HelperManager.uninstall for helper class " + helperClass.getName()); // synchronize on the lifecycle class to ensure it is not uninstalled // while we are deciding whether or not to install it synchronized (helperClass) { LifecycleDetails details; details = getDetails(helperClass, false); if (details == null) { Helper.err("HelperManager.uninstalled : shouldn't happen! uninstall failed to locate helper details for " + helperClass.getName()); return; } details.installCount--; if (details.uninstalled != null) { Helper.verbose("calling uninstalled(" + rule.getName() + ") for helper class " + helperClass.getName()); try { if (details.uninstalledTakesRule) { details.uninstalled.invoke(null, rule); } else { details.uninstalled.invoke(null, rule.getName()); } } catch (Exception e) { Helper.err("HelperManager.installed : unexpected exception from " + helperClass.getName() + ".uninstalled(String) : " + e); Helper.errTraceException(e); } } if (details.installCount == 0 && details.deactivated != null) { Helper.verbose("calling deactivated() for helper class" + helperClass.getName()); try { details.deactivated.invoke(null); } catch (Exception e) { Helper.err("HelperManager.installed : unexpected exception from " + helperClass.getName() + ".deactivate() : " + e); Helper.errTraceException(e); } } if (details.installCount == 0) { purgeDetails(details); } } } /** * This method exposes a capability of the Byteman agent's * Instrumentation instance while avoiding exposing the instance * itself. It returns an estimate of the object size or -1 in case * an agent has not been installed. * @param o the object to be sized * @return an estimate of the size or -1 */ public long getObjectSize(Object o) { if (inst == null) { Helper.err("Cannot calculate object size since a Byteman agent has not been installed"); return -1; } return this.inst.getObjectSize(o); } public ModuleSystem getModuleSystem() { return moduleSystem; } // private parts /** * the instrumentation object used to install the transformer. If this is null then we are not running in * a real agent so we do no work. */ private Instrumentation inst; /** * the module system implementation. */ private ModuleSystem moduleSystem; /** * a hashmap from helper classes to their corresponding helper details. we don't use weak references here * because there is only ever an entry here if there is a rule installed which references the class. entries * get cleared when the rules are uninstalled. */ private ConcurrentHashMap<Class<?>, LifecycleDetails> helperDetailsMap; /** * a record of a specific helper class tracking the number of installed rules which reference it * and referencing a table detailing the lifecycle methods it implements */ private static class LifecycleDetails { /** * the helper class whose lifecycle this record details */ public Class<?> lifecycleClass; /** * reference count for installed rules which employ this helper class */ public int installCount; /** * method called when helper is activated */ public Method activated; /** * method called when helper is deactivated */ public Method deactivated; /** * method called when rule is installed */ public Method installed; /** * flag true if installed takes a Rule argument false if it takes a String argument */ public boolean installedTakesRule; /** * method called when rule is uninstalled */ public Method uninstalled; /** * flag true if uninstalled takes a Rule argument false if it takes a String argument */ public boolean uninstalledTakesRule; public LifecycleDetails(Class<?> lifecycleClass) { this.lifecycleClass = lifecycleClass; this.installCount = 0; } } /** * name of method invoked when helper installed count transitions from 0 to positive */ private final static String ACTIVATED_NAME = "activated"; /** * name of method invoked when helper installed count transitions from positive to 0 */ private final static String DEACTIVATED_NAME = "deactivated"; /** * name of method invoked when rule is installed for a given helper */ private final static String INSTALLED_NAME = "installed"; /** * name of method invoked when rule is uninstalled for a given helper */ private final static String UNINSTALLED_NAME = "uninstalled"; /** * param types of method invoked when helper installed count transitions from 0 to positive */ private final static Class[] ACTIVATED_SIGNATURE = null; /** * param types of method invoked when helper installed count transitions from positive to 0 */ private final static Class[] DEACTIVATED_SIGNATURE = null; /** * param types of method invoked when rule is installed for a given helper */ private final static Class[] INSTALLED_RULE_SIGNATURE = new Class<?>[] { Rule.class }; /** * param types of method invoked when rule is uninstalled for a given helper */ private final static Class[] UNINSTALLED_RULE_SIGNATURE = INSTALLED_RULE_SIGNATURE; /** * param types of method invoked when rule is installed for a given helper */ private final static Class[] INSTALLED_STRING_SIGNATURE = new Class<?>[] { String.class }; /** * param types of method invoked when rule is uninstalled for a given helper */ private final static Class[] UNINSTALLED_STRING_SIGNATURE = INSTALLED_STRING_SIGNATURE; /** * lookup or create a record describing the lifecycle methods of a helper class. this must only be * called when synchronized on the helper class. * @param helperClass * @param createIfAbsent if the details are not present and this is true then create and install new details * @return the relevant details */ private LifecycleDetails getDetails(Class<?> helperClass, boolean createIfAbsent) { LifecycleDetails details = helperDetailsMap.get(helperClass); if (details == null && createIfAbsent) { details = new LifecycleDetails(helperClass); details.activated = lookupLifecycleMethod(helperClass, ACTIVATED_NAME, ACTIVATED_SIGNATURE); details.deactivated = lookupLifecycleMethod(helperClass, DEACTIVATED_NAME, DEACTIVATED_SIGNATURE); // check for methods with Rule arguments first details.installed = lookupLifecycleMethod(helperClass, INSTALLED_NAME, INSTALLED_RULE_SIGNATURE); details.uninstalled = lookupLifecycleMethod(helperClass, UNINSTALLED_NAME, UNINSTALLED_RULE_SIGNATURE); if (details.installed != null) { details.installedTakesRule = true; } else { details.installed = lookupLifecycleMethod(helperClass, INSTALLED_NAME, INSTALLED_STRING_SIGNATURE); } if (details.uninstalled != null) { details.uninstalledTakesRule = true; } else { details.uninstalled = lookupLifecycleMethod(helperClass, UNINSTALLED_NAME, UNINSTALLED_STRING_SIGNATURE); } helperDetailsMap.put(helperClass, details); } return details; } /** * return a static public method with the given parameter types it exists otherwise null * @param name * @param paramTypes * @return the method if found otherwise null */ private Method lookupLifecycleMethod(Class<?> clazz, String name, Class<?>[] paramTypes) { try { Method m = clazz.getMethod(name, paramTypes); int mod = m.getModifiers(); if (Modifier.isStatic(mod)) { return m; } } catch (NoSuchMethodException e) { } return null; } /** * purge the details describing the lifecycle methods of a helper class. this must only be * called when synchronized on the helper class. * @param details */ private void purgeDetails(LifecycleDetails details) { helperDetailsMap.remove(details.lifecycleClass); } }