ByteBuddy and Proxies

Here’s another one that I am sure I’m going to forget how to do so I’m writing it down.

ByteBuddy is a terrific little tool for working with Java bytecode.  It, like many tools, however, is somehow both exquisitely documented and infuriatingly opaque.

ByteBuddy works with a domain-specific language (DSL) to represent the world of manipulating Java bytecode at runtime.  For a, uh, seasoned veteran (yeah, let’s go with that) like me, grappling with the so-called fluent API is quite difficult.  But I’ve figured out that everything is there if you need it.  You just need the magic recipe.  Sometimes even with the help of an IDE the magic recipe is akin to spellcasting.

So here is the magic recipe for defining a runtime proxy that forwards certain method invocations to the return value of a method that yields up the “real” object being proxied:

import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodCall;
import static net.bytebuddy.implementation.MethodCall.invoke;
import static net.bytebuddy.matcher.ElementMatchers.named;
DynamicType.Builder<?> builder = //… acquire the builder, then:
.defineField("proxiedInstance", theClassBeingProxied, Visibility.PRIVATE) // (1)
.implement(new DefaultParameterizedType(null, Proxy.class, theClassBeingProxied)) // (2)
.intercept(FieldAccessor.ofBeanProperty()) // (3)
.method(someMatcher) // (4)
.intercept(invoke(MethodCall.MethodLocator.ForInstrumentedMethod.INSTANCE).onMethodCall(invoke(named("getProxiedInstance"))));
// 1: Adds a field to the proxy class named proxiedInstance. It will hold the "real" object.
// 2: Proxy.class is a made-up interface defining getProxiedInstance()/setProxiedInstance(T),
// where T is the type of the thing being proxied; e.g. Proxy<Frob>.
// DefaultParameterizedType is a made-up implementation of java.lang.reflect.ParameterizedType.
// 3: Magic ByteBuddy incantation to implement the Proxy<Frob> interface by making two methods
// that read from and write to the proxiedInstance field just defined
// 4: Choose what methods to intercept here; see the net.bytebuddy.matcher.ElementMatchers class
// in particular
// 5: The serious magic is here. It means, roughly, "whatever the method the user just called,
// turn around and invoke it on the return value of the getProxiedInstance() method". That
// INSTANCE object is not documented anywhere, really; you just have to know that it is
// suitable for use here in this DSL "sentence".

Configuring Narayana

I always forget how to do this so I’m writing it down.

First, Narayana fundamentally accesses its properties from instances of environment beans, which are simple Java objects.  Here are all of the non-testing ones (the last five are the most relevant for most JTA situations):

narayana/XTS/WS-C/dev/src/org/jboss/jbossts/xts/environment/RecoveryEnvironmentBean.java
narayana/XTS/WS-C/dev/src/org/jboss/jbossts/xts/environment/XTSEnvironmentBean.java
narayana/XTS/WS-C/dev/src/org/jboss/jbossts/xts/environment/WSCEnvironmentBean.java
narayana/XTS/WS-C/dev/src/org/jboss/jbossts/xts/environment/WSCFEnvironmentBean.java
narayana/XTS/WS-C/dev/src/org/jboss/jbossts/xts/environment/WSTEnvironmentBean.java
narayana/ArjunaJTS/jts/classes/com/arjuna/ats/jts/common/JTSEnvironmentBean.java
narayana/ArjunaJTS/orbportability/classes/com/arjuna/orbportability/common/OrbPortabilityEnvironmentBean.java
narayana/ArjunaJTA/jta/classes/com/arjuna/ats/jta/common/JTAEnvironmentBean.java
narayana/ArjunaJTA/jdbc/classes/com/arjuna/ats/jdbc/common/JDBCEnvironmentBean.java
narayana/ArjunaCore/txoj/classes/com/arjuna/ats/txoj/common/TxojEnvironmentBean.java
narayana/ArjunaCore/arjuna/classes/com/arjuna/ats/internal/arjuna/objectstore/hornetq/HornetqJournalEnvironmentBean.java
narayana/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/common/RecoveryEnvironmentBean.java
narayana/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/common/ObjectStoreEnvironmentBean.java
narayana/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/common/CoreEnvironmentBean.java
narayana/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/common/MetaObjectStoreEnvironmentBean.java
narayana/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/common/CoordinatorEnvironmentBean.java

To instantiate them, it grabs a source of property information, one of the environment bean classes, and something called a BeanPopulator.  The BeanPopulator is sort of like a crippled version of java.beans.Introspector.  It instantiates a given environment bean class, and then calls relevant setter methods on the resulting instance with values sourced from whatever the source of property information is.

The source of property information has several restrictions.

First, it has to be in java.util.Properties XML format.  Elements are named entry and have key attributes; their content is that key’s value.

Second, if you do nothing else it will be named jbossts-properties.xml.  Weirdly, this most-default-of-all-possible-defaults is set during the build.

Third, if you want to rename it then you have to set a system property named com.arjuna.ats.arjuna.common.propertiesFile.

Fourth, there is a lookup algorithm.  First it treats this thing as an absolute path.  If it doesn’t exist, it treats it as a path relative to the current directory.  If it doesn’t exist, it treats it as a path relative to user.dir, user.home (!) and java.home (!).  If it doesn’t exist in any of those places, then it treats it as a classpath resource before giving up.

Fifth, there is no merging.

As you can see if you’re going to bother specifying this thing you should probably specify it as an absolute file path.  Hey, Narayana is old.

When you’re all done with this (or maybe you punt and decide to just let the defaults ride), you can selectively override certain properties by specifying them as System properties.

The environment beans each have a prefix defined via annotations and not in any other documentation that I can find so to understand how to configure them you have to look at the Narayana source code (!).  For example, JTAEnvironmentBean‘s @PropertyPrefix annotation sets its prefix to com.arjuna.ats.jta.  So an entry with a key attribute of com.arjuna.ats.jta.transactionManagerClassName will be used as the value of an invocation of the JTAEnvironmentBean#setTransactionManagerClassName(String) method.

Lastly, almost all you ever really want to do is set the default timeout for the transaction manager.  To do this, set a system property named  com.arjuna.ats.arjuna.coordinator.defaultTimeout to a numeric value denoting the timeout value in seconds.