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

org.jboss.byteman.contrib.dtest.RuleConstructor Maven / Gradle / Ivy

/*
 * JBoss, Home of Professional Open Source
 * Copyright 2016, Red Hat, Inc. and/or its affiliates,
 * and individual contributors as indicated by the @author tags.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * 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,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *
 * (C) 2016,
 * @author JBoss, by Red Hat.
 */
package org.jboss.byteman.contrib.dtest;

import java.util.concurrent.atomic.AtomicReference;

import org.jboss.byteman.rule.helper.Helper;

/**
 * 

* Provides a fluent API for creating Byteman rules without needing * to mess around with String concatenation. *

* Example: *

* * RuleConstructor rb = RuleConstructor.createRule("myRule")
* .onClass("org.jboss.byteman.ExampleClass")
* .inMethod("doInterestingStuff")
* .atEntry()
* .ifTrue()
* .doAction("myAction()");
* System.out.println(rb.build()); *
*

* will print: *

* * RULE myRule
* CLASS org.jboss.byteman.ExampleClass
* METHOD doInterestingStuff
* AT ENTRY
* IF true
* DO myAction()
* ENDRULE *
*/ public final class RuleConstructor { private static AtomicReference defaultInstrumentor = new AtomicReference(); private static final String LINEBREAK = String.format("%n"); private static final String CONSTRUCTOR_METHOD = ""; private static final String CLASS_CONSTRUCTOR = ""; private String ruleName; private String className; private boolean isInterface; private boolean isIncludeSubclases; private String methodName; private String helperName = Helper.class.getName(); private String where = "AT ENTRY"; private String bind; private String ifcondition = "true"; private String action; private String imports; private String compile; /** * No use of constructor, new rule creation with: * RuleConstructor.createRule("myRule") */ private RuleConstructor(String ruleName) { this.ruleName = ruleName; } /** *

* Setting default initialize instance of {@link Instrumentor} class * that will be used when {@link #install()}/{@link #submit()} method * is used for the created rule.
* You can define this default Instrumentor which could be used whenever * the new rule is submitted to the Byteman agent.
* null is permitted then {@link #install()} method throws exception *

* * @param instrumentor initiated instrumentor instance or null */ public static final void setDefaultInstrumentor(Instrumentor instrumentor) { defaultInstrumentor.set(instrumentor); } /** * Undefinining value of default instrumentor. * Methods {@link #install()}/{@link #submit()} are illegal to be used from now * up to time it's set again. */ public static final void undefineDefaultInstrumentor() { RuleConstructor.setDefaultInstrumentor(null); } /** * Returning value of the previously set default {@link Instrumentor} instance. * * @return instrumentor instance or null, when was not set */ public static final Instrumentor getDefaultInstrumentor() { return defaultInstrumentor.get(); } /** *

* This is where you start. *

* Byteman rule builder initialization method. * * @param ruleName name of rule is required to construct any rule * @return a rule constructor ready to have its class or interface specified */ public static final RuleConstructor.ClassClause createRule(String ruleName) { return new RuleConstructor(ruleName).new ClassClause(); } /** *

* Installing/submitting rule to the Byteman agent via instance of instrumentor * defined as default to the {@link RuleConstructor} class. *

*

* Internally this: *

    *
  • build the rule where {@link #build()} is called to generate rule as {@link String}
  • *
  • calling submit of the rule over instance of {@link Instrumentor}
  • *

* Prerequisite: you need * set up the instrumentor by call {@link #setDefaultInstrumentor(Instrumentor)} * * @return rule constructor if expected to be used later again * @throws IllegalStateException if default instrumentor is not set * @throws RuntimeException if error happens during installation rule * via default instrumentor instance */ public RuleConstructor install() { if(defaultInstrumentor.get() == null) throw new IllegalStateException("Can't install rule '" + this.getRuleName() + "' as default instrumentor was not set-up"); return install(defaultInstrumentor.get()); } /** *

* Installing/submitting rule to the Byteman agent via instance of instrumentor. *

*

* Internally this: *

    *
  • build the rule where {@link #build()} is called to generate rule as {@link String}
  • *
  • calling submit of the rule over instance of {@link Instrumentor}
  • *
* * @param instrumentor instance of instrumentor to be used to submit the rule to * @return rule constructor if expected to be used later again * @throws NullPointerException if instrumentor param is provided as null * @throws RuntimeException if error happens during installation rule * via default instrumentor instance */ public RuleConstructor install(Instrumentor instrumentor) { if(instrumentor == null) throw new NullPointerException("instrumentor"); try { instrumentor.installRule(this); } catch (Exception e) { throw new RuntimeException("Can't install rule '" + this.getRuleName() + "'", e); } return this; } /** * Facade to method {@link #install()}. * * @return rule constructor, if expected to be used later again * @throws IllegalStateException if default instrumentor is not set * @throws RuntimeException if error happens during installation rule */ public RuleConstructor submit() { return install(); } /** * Facade to method {@link #install(Instrumentor)}. * * @param instrumentor instance of instrumentor to be used to submit the rule to * @return rule constructor, if expected to be used later again * @throws NullPointerException if instrumentor param is provided as null * @throws RuntimeException if error happens during installation rule */ public RuleConstructor submit(Instrumentor instrumentor) { return install(instrumentor); } /** * Builds the rule defined by this instance of {@link RuleConstructor} * and returns its representation as string. * * @return the rule as a string */ public String build() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("RULE "); stringBuilder.append(ruleName); stringBuilder.append(LINEBREAK); if(isInterface) { stringBuilder.append("INTERFACE "); } else { stringBuilder.append("CLASS "); } if(isIncludeSubclases) { stringBuilder.append("^"); } stringBuilder.append(className); stringBuilder.append(LINEBREAK); stringBuilder.append("METHOD "); stringBuilder.append(methodName); stringBuilder.append(LINEBREAK); stringBuilder.append(where); stringBuilder.append(LINEBREAK); if(helperName != null && !helperName.isEmpty()) { stringBuilder.append("HELPER "); stringBuilder.append(helperName); stringBuilder.append(LINEBREAK); } if(imports != null) { stringBuilder.append(imports); } if(compile != null && !compile.isEmpty()) { stringBuilder.append(compile); stringBuilder.append(LINEBREAK); } if(bind != null && !bind.isEmpty()) { stringBuilder.append("BIND "); stringBuilder.append(bind); stringBuilder.append(LINEBREAK); } stringBuilder.append("IF "); stringBuilder.append(ifcondition); stringBuilder.append(LINEBREAK); stringBuilder.append("DO "); stringBuilder.append(action); stringBuilder.append(LINEBREAK); stringBuilder.append("ENDRULE"); stringBuilder.append(LINEBREAK); return stringBuilder.toString(); } public final class ClassClause { /** * Class that rule event is associated to. *

* Example: *

* * new RuleBuilder("rule name")
* .onClass("java.lang.String.class")
* ... *
* * @param clazz class as target of rule injection * @return this, for having fluent api */ public RuleConstructor.MethodClause onClass(Class clazz) { return onSpecifier(clazz.getCanonicalName(), false); } /** * Class name that rule event is associated to. *

* Example: *

* * new RuleBuilder("rule name")
* .onClass("java.lang.String")
* ... *
* * @param className class name as target of rule injection * @return this, for having fluent api */ public RuleConstructor.MethodClause onClass(String className) { return onSpecifier(className, false); } /** * Interface that rule event is associated to. *

* Example: *

* * new RuleBuilder("rule name")
* .onInterface("javax.transaction.xa.XAResource.class")
* ... *
* * @param clazz interface class as target of rule injection * @return this, for having fluent api */ public RuleConstructor.MethodClause onInterface(Class clazz) { return onSpecifier(clazz.getCanonicalName(), true); } /** * Interface name that rule event is associated to. *

* Example: *

* * new RuleBuilder("rule name")
* .onInterface("javax.transaction.xa.XAResource")
* ... *
* * @param className interface class name as target of rule injection * @return this, for having fluent api */ public RuleConstructor.MethodClause onInterface(String className) { return onSpecifier(className, true); } private RuleConstructor.MethodClause onSpecifier(String className, boolean isInterface) { RuleConstructor.this.className = className; RuleConstructor.this.isInterface = isInterface; return RuleConstructor.this.new MethodClause(); } } public final class MethodClause { /** *

* Defining that the rule will be injected to all sub-classes * or classes implementing the interface. *

* By default Byteman injects the rule only to the specified * class and children classes are not instrumented. *

* The rule class definition is enriched with ^. * As example: CLASS ^org.jboss.byteman.ExampleClass. * * @return this, for having fluent api */ public RuleConstructor.MethodClause includeSubclases() { RuleConstructor.this.isIncludeSubclases = true; return this; } /** *

* Defining method where the rule is injected to. *

* Example: *

* * new RuleBuilder("rule name")
* .onInterface("javax.transaction.xa.XAResource")
* .inMethod("commit")
* ... *
* * @param methodName method name for rule injection * @return this, for having fluent api */ public RuleConstructor.LocationClause inMethod(String methodName) { RuleConstructor.this.methodName = methodName; return RuleConstructor.this.new LocationClause(); } /** *

* Defining method specified by argument types where the rule is injected to. * Arguments restrict which methods are instrumented based on parameters definition. *

* Example: *

* * new RuleBuilder("rule name")
* .onInterface("javax.transaction.xa.XAResource")
* .inMethod("commit", "Xid" , "boolean")
* ... *
* * @param methodName method name for rule injection * @param argTypes method argument types to closer specify what method is instrumented * @return this, for having fluent api */ public RuleConstructor.LocationClause inMethod(String methodName, String... argTypes) { RuleConstructor.this.methodName = methodName + "(" + stringJoin(",", argTypes) + ")"; return RuleConstructor.this.new LocationClause(); } /** * Defining constructor, special method type, * as place for rule injection. * * @return this, for having fluent api */ public RuleConstructor.LocationClause inConstructor() { return inMethod(CONSTRUCTOR_METHOD); } /** *

* Defining constructor, special method type, * as place for rule injection. *

* The type of constructor method is specified by its arguments. * * @param argTypes method argument types to closer specify which method * @return this, for having fluent api */ public RuleConstructor.LocationClause inConstructor(String... argTypes) { return inMethod(CONSTRUCTOR_METHOD, argTypes); } /** * Defining class initialization method as place for rule injection. * * @return this, for having fluent api */ public RuleConstructor.LocationClause inClassInitMethod() { return inMethod(CLASS_CONSTRUCTOR); } /** * Defining class initialization method as place for rule injection. * * @param argTypes method argument types to closer specify which method * @return this, for having fluent api */ public RuleConstructor.LocationClause inClassInitMethod(String... argTypes) { return inMethod(CLASS_CONSTRUCTOR, argTypes); } } public final class LocationClause { /** * Byteman helper class to be used in rule definition. * * @param helperClass byteman helper class * @return this, for having fluent api */ public RuleConstructor.LocationClause helper(Class helperClass) { return helper(helperClass.getCanonicalName()); } /** * Class name of Byteman helper class. * * @param helperClassName byteman helper class name * @return this, for having fluent api */ public RuleConstructor.LocationClause helper(String helperClassName) { RuleConstructor.this.helperName = helperClassName; return this; } /** *

* Rule is invoked at entry point of method. *

* Location specifier is set as AT ENTRY. * * @return this, for having fluent api */ public RuleConstructor.ConditionClause atEntry() { return at("ENTRY"); } /** *

* Rule is invoked at exit point of method. *

* Location specifier is set as AT EXIT. * * @return this, for having fluent api */ public RuleConstructor.ConditionClause atExit() { return at("EXIT"); } /** *

* Rule is invoked at specific line of code * within the method. *

* Location specifier is set as AT LINE <line>. * * @param line line number to be rule injection point * @return this, for having fluent api */ public RuleConstructor.ConditionClause atLine(int line) { return at("LINE " + line); } /** *

* Rule is invoked at point where method reads a variable. *

* Location specifier is set as AT READ <variable>. * * @param variable rule is triggered at write from this variable happen * @return this, for having fluent api */ public RuleConstructor.ConditionClause atRead(String variable) { return at("READ " + variable); } /** *

* Rule is invoked at point where method reads a variable * where occurencePosition defines Nth * textual occurrence of the field access. *

* Location specifier is set as AT READ <variable> <occurencePosition>. * * @param variable rule is triggered at write from this variable happen * @param occurencePosition Nth textual occurrence of reading the field * @return this, for having fluent api */ public RuleConstructor.ConditionClause atRead(String variable, int occurencePosition) { return at("READ " + variable + " " + occurencePosition); } /** *

* Rule is invoked after point where method reads a variable. *

* Location specifier is set as AFTER READ <variable>. * * @param variable rule is triggered after write from this variable happen * @return this, for having fluent api */ public RuleConstructor.ConditionClause afterRead(String variable) { return after("READ " + variable); } /** *

* Rule is invoked after point where method reads a variable where occurencePosition * defines Nth textual occurrence of the field access. *

* Location specifier is set as AFTER READ <variable> <occurencePosition>. * * @param variable rule is triggered after write from this variable happen * @param occurencePosition Nth textual occurrence of reading the field * @return this, for having fluent api */ public RuleConstructor.ConditionClause afterRead(String variable, int occurencePosition) { return after("READ " + variable + " " + occurencePosition); } /** *

* Rule is invoked at point where method writes to a variable. *

* Location specifier is set as AT WRITE <variable>. * * @param variable rule is triggered at write to this variable happen * @return this, for having fluent api */ public RuleConstructor.ConditionClause atWrite(String variable) { return at("WRITE " + variable); } /** *

* Rule is invoked at point where method writes to a variable where occurencePosition * defines Nth textual occurrence of the field write. *

* Location specifier is set as AT WRITE <variable> <occurencePosition>. * * @param variable rule is triggered at write to this variable happen * @param occurencePosition Nth textual occurrence of the field write * @return this, for having fluent api */ public RuleConstructor.ConditionClause atWrite(String variable, int occurencePosition) { return at("WRITE " + variable + " " + occurencePosition); } /** *

* Rule is invoked after point where method writes to a variable. *

* Location specifier is set as AFTER WRITE <variable>. * * @param variable rule is triggered after write to this variable happen * @return this, for having fluent api */ public RuleConstructor.ConditionClause afterWrite(String variable) { return after("WRITE " + variable); } /** *

* Rule is invoked after point where method writes to a variable where occurencePosition * defines Nth textual occurrence of the field write. *

* Location specifier is set as AFTER WRITE <variable> <occurencePosition>. * * @param variable rule is triggered after write to this variable happen * @param occurencePosition Nth textual occurrence of the field write * @return this, for having fluent api */ public RuleConstructor.ConditionClause afterWrite(String variable, int occurencePosition) { return after("WRITE " + variable + " " + occurencePosition); } /** *

* Rule is invoked at point of invocation of method within the trigger method. *

* Location specifier is set as AT INVOKE <variable>. * * @param method method name after which invocation the rule is executed * @return this, for having fluent api */ public RuleConstructor.ConditionClause atInvoke(String method) { return at("INVOKE " + method); } /** *

* Rule is invoked at point of invocation of method within the trigger method * where occurencePosition defines Nth textual occurrence * of the method invocation. *

* Location specifier is set as AT INVOKE <variable> <occurencePosition>. * * @param method method name after which invocation the rule is executed * @param occurencePosition Nth textual occurrence of the method invocation * @return this, for having fluent api */ public RuleConstructor.ConditionClause atInvoke(String method, int occurencePosition) { return at("INVOKE " + method + " " + occurencePosition); } /** *

* Rule is invoked after invocation of method within the trigger method. *

* Location specifier is set as AFTER INVOKE <variable>. * * @param method method name after which invocation the rule is executed * @return this, for having fluent api */ public RuleConstructor.ConditionClause afterInvoke(String method) { return after("INVOKE " + method); } /** *

* Rule is invoked after invocation of method within the trigger method * where occurencePosition defines Nth textual occurrence * of the method invocation. *

* Location specifier is set as AFTER INVOKE <variable> <occurencePosition>. * * @param method method name after which invocation the rule is executed * @param occurencePosition Nth textual occurrence of the method invocation * @return this, for having fluent api */ public RuleConstructor.ConditionClause afterInvoke(String method, int occurencePosition) { return after("INVOKE " + method + " " + occurencePosition); } /** *

* Rule is invoked at entry of synchronization block in the target method. *

* Location specifier is set as AT SYNCHRONIZE. * * @return this, for having fluent api */ public RuleConstructor.ConditionClause atSynchronize() { return at("SYNCHRONIZE"); } /** *

* Rule is invoked at point of invocation of method within the trigger method * where occurencePosition defines Nth textual * occurrence of the method invocation. *

* Location specifier is set as AT SYNCHRONIZE <occurencePosition>. * * @param occurencePosition Nth textual occurrence of the method invocation * @return this, for having fluent api */ public RuleConstructor.ConditionClause atSynchronize(int occurencePosition) { return at("SYNCHRONIZE " + occurencePosition); } /** * Rule is invoked after invocation of method within the trigger method.
* Location specifier is set as AFTER SYNCHRONIZE. * * @return this, for having fluent api */ public RuleConstructor.ConditionClause afterSynchronize() { return after("SYNCHRONIZE"); } /** *

* Rule is invoked after invocation of method within the trigger method * where occurencePosition defines Nth textual occurrence * of the method invocation. *

* Location specifier is set as AFTER SYNCHRONIZE <occurencePosition>. * * @param occurencePosition Nth textual occurrence of the method invocation * @return this, for having fluent api */ public RuleConstructor.ConditionClause afterSynchronize(int occurencePosition) { return after("SYNCHRONIZE " + occurencePosition); } /** *

* Identifies a throw operation within the trigger method. *

* Location specifier is set as AT THROW. * * @return this, for having fluent api */ public RuleConstructor.ConditionClause atThrow() { return at("THROW"); } /** *

* Identifies a throw operation within the trigger method, * specified with count as Nth textual occurrence of a throw * inside of the method defining only that occurrence to trigger * execution of the rule. *

* Location specifier is set as AT THROW <occurencePosition>. * * @param occurencePosition which Nth textual occurrence of a throw triggers the rule * @return this, for having fluent api */ public RuleConstructor.ConditionClause atThrow(int occurencePosition) { return at("THROW " + occurencePosition); } /** *

* Identifies the point where a method returns control back to its caller via * unhandled exceptional control flow. *

* Location specifier is set as AT EXCEPTION EXIT. * * @return this, for having fluent api */ public RuleConstructor.ConditionClause atExceptionExit() { return at("EXCEPTION EXIT"); } /** *

* Rule is invoked AT point which is specified with parameter. *

* Location specifier is predefined with AT keyword * and the rest is up to parameter you provide. *

* When you provide LINE 123 as parameter the location specifier * is set as AT LINE 123. * * @param at specifying rule injection point that is enriched * with AT keyword * @return this, for having fluent api */ public RuleConstructor.ConditionClause at(String at) { return where("AT " + at); } /** *

* Rule is invoked AFTER point which is specified with parameter. *

* Location specifier is predefined with AFTER keyword * and the rest is up to parameter you provide. *

* When you provide READ $0 as parameter the location specifier * is set as AFTER READ $0. * * @param after specifying rule injection point that is enriched * with AFTER keyword * @return this, for having fluent api */ public RuleConstructor.ConditionClause after(String after) { return where("AFTER " + after); } /** *

* Location specifier definition. *

* Defined string is directly used in rule definition. *

* The parameter is expected to be defined as whole location specifier. * For example when you provide AT LINE 123 as parameter * the location specifier is set as AT LINE 123. * * @param where location specifier * @return this, for having fluent api */ RuleConstructor.ConditionClause where(String where) { RuleConstructor.this.where = where; return RuleConstructor.this.new ConditionClause(); } } public final class ConditionClause { /** * Byteman helper class to be used in rule definition. * * @param helperClass byteman helper class * @return this, for having fluent api */ public RuleConstructor.ConditionClause helper(Class helperClass) { return helper(helperClass.getCanonicalName()); } /** * Class name of Byteman helper class. * * @param helperClassName byteman helper class name * @return this, for having fluent api */ public RuleConstructor.ConditionClause helper(String helperClassName) { RuleConstructor.this.helperName = helperClassName; return this; } /** *

* Definition of bind clause. *

* When called as * bind("engine:CoordinatorEngine = $0", "identifier:String = engine.getId()") *

* rule looks *

* * BIND bind("engine:CoordinatorEngine = $0"; * "identifier:String = engine.getId() * * * @param bindClauses bind clauses to be part of the rule * @return this, for having fluent api */ public RuleConstructor.ConditionClause bind(String... bindClauses) { RuleConstructor.this.bind = stringifyClauses(bindClauses); return this; } /** *

* Setting module import definition for the rule. *

* For module import functionality works you need to use parameter * -javaagent modules:. The only provided implementation class * which is to manage module imports is * org.jboss.byteman.modules.jbossmodules.JbossModulesSystem * * @param imports specifying imports clauses which will be added * to byteman rule configuration * @return this, for having fluent api */ public RuleConstructor.ConditionClause imports(String... imports) { StringBuffer importsBuf = new StringBuffer(); for(String importString: imports) { importsBuf .append("IMPORT ") .append(importString) .append(LINEBREAK); } RuleConstructor.this.imports = importsBuf.toString(); return this; } /** *

* Defines rule for being compiled. *

* Default behaviour is to use the interpreter. * * @return this, for having fluent api */ public RuleConstructor.ConditionClause compile() { RuleConstructor.this.compile = "COMPILE"; return this; } /** *

* Defines rule for not being compiled. *

* Default behaviour is to use the interpreter * but byteman system property could be used to change * the default behaviour for rules being compiled every time * then this settings could be useful. * * @return this, for having fluent api */ public RuleConstructor.ConditionClause nocompile() { RuleConstructor.this.compile = "NOCOMPILE"; return this; } /** *

* Rule condition when rule will be executed. *

* Defined string is directly used in rule definition. *

* Rule condition is set as IF <condition>. * * @param condition rule condition string that is used for the rule * @return this, for having fluent api */ public RuleConstructor.ActionClause ifCondition(String condition) { RuleConstructor.this.ifcondition = condition; return RuleConstructor.this.new ActionClause(); } /** *

* Condition ensuring that rule will be executed. *

* Rule condition is set as IF true. * * @return this, for having fluent api */ public RuleConstructor.ActionClause ifTrue() { return ifCondition("true"); } /** *

* Condition ensuring that rule won't be executed. *

* Rule condition is set as IF false. * * @return this, for having fluent api */ public RuleConstructor.ActionClause ifFalse() { return ifCondition("false"); } } public final class ActionClause { /** *

* Definition of actions for the rule. *

* When called as *

* doAction("debug(\"killing JVM\")", "killJVM()") *

* rule looks *

* * DO debug("killing JVM"); * killJVM() * * * @param actions actions definitions to be part of the rule * @return this, for having fluent api */ public RuleConstructor doAction(String... actions) { RuleConstructor.this.action = stringifyClauses(actions); return RuleConstructor.this; } } String getRuleName() { return RuleConstructor.this.ruleName; } private String stringJoin(String join, String... strings) { if (strings == null || strings.length == 0) { return ""; } else if (strings.length == 1) { return strings[0]; } else { StringBuilder sb = new StringBuilder(); sb.append(strings[0]); for (int i = 1; i < strings.length; i++) { sb.append(join).append(strings[i]); } return sb.toString(); } } private String stringifyClauses(String... clauses) { StringBuffer actionsBuffer = new StringBuffer(); boolean isFirstAddition = true; boolean isSemicolon = true; for(String clause: clauses) { if(!isFirstAddition && !isSemicolon) actionsBuffer.append(";"); if(!isFirstAddition) actionsBuffer.append(LINEBREAK); if(!clause.trim().endsWith(";")) isSemicolon = false; if(isFirstAddition) isFirstAddition = false; actionsBuffer.append(clause.trim()); } return actionsBuffer.toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy