Creation, dynamic loading and instrumentation with javaagents

by Dhruba Bandopadhyay on February 7, 2010

Sometime back I had to delve into the depths of how one could dynamically and programmatically load a javaagent at runtime – in other words how a javaagent could be attached to a running process. Since then I’ve been meaning to blog my findings. Finally, after quite some time, I’ve got around to it but here I take the opportunity to expand the scope of the article to a complete treatment of how to create a javaagent, load it statically at startup or dynamically at runtime and also how to perform instrumentation of a simple class. As always the code will reside in an eclipse maven project that will be made available for download.

Introduction

Java agents are self contained components through which application classes pass at the level of byte code instructions in the form of byte arrays. They were introduced in java5 along with the powerful java.lang.instrument package. Java agents can be loaded statically at startup or dynamically (programmatically) at runtime to attach to a running process in a fail-safe fashion.

Lifecycle of a javaagent

The most important thing to understand is the lifecycle of a javaagent and its behaviour in relation to the application itself. The lifecycle hook points are as follows.

  • PreMain – The PreMain method is invoked first prior to running the main method. This is the hookpoint for loading the javaagent statically at startup.
  • Main – The main method is always invoked after the PreMain invocation.
  • AgentMain – The AgentMain method can be invoked at any time before or after the Main method. This hookpoint is for loading the javaagent dynamically at runtime and attaching to a running process.

Creation of a javaagent

In order to create a java agent you must first define your chosen hook points from the lifecycle above as below.

package name.dhruba.javaagent;

import java.lang.instrument.Instrumentation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyJavaAgent {

    static final Logger logger = LoggerFactory.getLogger(MyJavaAgent.class);

    private static Instrumentation instrumentation;

    /**
     * JVM hook to statically load the javaagent at startup.
     *
     * After the Java Virtual Machine (JVM) has initialized, the premain method
     * will be called. Then the real application main method will be called.
     *
     * @param args
     * @param inst
     * @throws Exception
     */
    public static void premain(String args, Instrumentation inst) throws Exception {
        logger.info("premain method invoked with args: {} and inst: {}", args, inst);
        instrumentation = inst;
        instrumentation.addTransformer(new MyClassFileTransformer());
    }

    /**
     * JVM hook to dynamically load javaagent at runtime.
     *
     * The agent class may have an agentmain method for use when the agent is
     * started after VM startup.
     *
     * @param args
     * @param inst
     * @throws Exception
     */
    public static void agentmain(String args, Instrumentation inst) throws Exception {
        logger.info("agentmain method invoked with args: {} and inst: {}", args, inst);
        instrumentation = inst;
        instrumentation.addTransformer(new MyClassFileTransformer());
    }

    /**
     * Programmatic hook to dynamically load javaagent at runtime.
     */
    public static void initialize() {
        if (instrumentation == null) {
            MyJavaAgentLoader.loadAgent();
        }
    }

}

Once you’ve defined your hook points you must make the JVM aware by putting them in a manifest file.

Main-Class: name.dhruba.javaagent.MyMainClass
Agent-Class: name.dhruba.javaagent.MyJavaAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: name.dhruba.javaagent.MyJavaAgent

In the example project I’m doing so using maven and the maven assembly plugin which also packages the project as a single all inclusive uber executable jar for ease of testing.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-assembly-plugin</artifactId>
  <executions>
    <execution>
      <goals>
        <goal>attached</goal>
      </goals>
      <phase>package</phase>
      <configuration>
        <descriptorRefs>
          <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive>
          <manifest>
            <mainClass>name.dhruba.javaagent.MyMainClass</mainClass>
          </manifest>
          <manifestEntries>
            <Premain-Class>name.dhruba.javaagent.MyJavaAgent</Premain-Class>
            <Agent-Class>name.dhruba.javaagent.MyJavaAgent</Agent-Class>
            <Can-Redefine-Classes>true</Can-Redefine-Classes>
            <Can-Retransform-Classes>true</Can-Retransform-Classes>
          </manifestEntries>
        </archive>
      </configuration>
    </execution>
  </executions>
</plugin>

Instrumenting classes

Within the javaagent hook points one can define java.lang.instrument.ClassFileTransformer implementations that are invoked for all classes being loaded within an application. These receive classes as byte arrays and have the option of redefining them also in terms of byte arrays. Here I provide an example class file transformation.

The following User pojo returns ‘foo’ as its name.

package name.dhruba.user;

public class MyUser {

    public String getName() {
        return "foo";
    }

}

The following class file transformer (using ASM) redefines the User pojo to return ‘bar’ instead.

package name.dhruba.javaagent;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyClassFileTransformer implements ClassFileTransformer, Opcodes {

    static final Logger logger = LoggerFactory.getLogger(MyClassFileTransformer.class);

    @Override
    public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classfileBuffer)
            throws IllegalClassFormatException {
        logger.info("class file transformer invoked for className: {}", className);

        if (className.equals("name/dhruba/user/MyUser")) {

            ClassWriter cw = new ClassWriter(0);
            MethodVisitor mv;

            cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "name/dhruba/user/MyUser", null,
                    "java/lang/Object", null);

            cw.visitSource(null, null);

            {
                mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
                mv.visitCode();
                Label l0 = new Label();
                mv.visitLabel(l0);
                mv.visitLineNumber(3, l0);
                mv.visitVarInsn(ALOAD, 0);
                mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V");
                mv.visitInsn(RETURN);
                Label l1 = new Label();
                mv.visitLabel(l1);
                mv.visitLocalVariable("this", "Lname/dhruba/user/MyUser;", null, l0, l1, 0);
                mv.visitMaxs(1, 1);
                mv.visitEnd();
            }
            {
                mv = cw.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null);
                mv.visitCode();
                Label l0 = new Label();
                mv.visitLabel(l0);
                mv.visitLineNumber(6, l0);
                mv.visitLdcInsn("bar");
                mv.visitInsn(ARETURN);
                Label l1 = new Label();
                mv.visitLabel(l1);
                mv.visitLocalVariable("this", "Lname/dhruba/user/MyUser;", null, l0, l1, 0);
                mv.visitMaxs(1, 1);
                mv.visitEnd();
            }
            cw.visitEnd();

            return cw.toByteArray();
        }

        return classfileBuffer;
    }

}

Note that this is not standard AOP or proxy logic. The class file transformer is literally redefining the bytecode instructions incrementally in the form of byte arrays.

Static loading of a javaagent at startup

Static loading of a javaagent is done by using the -javaagent=/path/to/file.jar=options command line option to the java executable as below.

$ java -javaagent:target/javaagent-examples-jar-with-dependencies.jar=foobarbaz name.dhruba.javaagent.MyMainClass foo bar baz
21:37:50.783 [main] INFO  name.dhruba.javaagent.MyJavaAgent - premain method invoked with args: foobarbaz and inst: sun.instrument.InstrumentationImpl@1786e64
21:37:50.789 [main] INFO  n.d.javaagent.MyClassFileTransformer - class file transformer invoked for className: name/dhruba/javaagent/MyMainClass
21:37:50.789 [main] INFO  name.dhruba.javaagent.MyMainClass - main method invoked with args: [foo, bar, baz]
21:37:50.789 [main] INFO  n.d.javaagent.MyClassFileTransformer - class file transformer invoked for className: name/dhruba/user/MyUser
21:37:50.800 [main] INFO  name.dhruba.javaagent.MyMainClass - userName: bar
21:37:50.801 [DestroyJavaVM] INFO  n.d.javaagent.MyClassFileTransformer - class file transformer invoked for className: java/lang/Shutdown
21:37:50.801 [DestroyJavaVM] INFO  n.d.javaagent.MyClassFileTransformer - class file transformer invoked for className: java/lang/Shutdown$Lock

Above, the javaagent lifecycle is clearly visible. The premain (and not the agentmain) method is invoked first. The class file transformer is passed classes as they are loaded. The transformer chooses to redefine the User object prior to its use. Subsequently the classes loaded at shutdown also pass through the transformer.

Dynamic loading of a javaagent at runtime

Dynamic loading of a javaagent at runtime can be done quite easily in a programmatic fashion but requires the sun tools jar to be present on the classpath. Certain libraries like jmockit have avoided this by opting to absorb the relevant classes from the sun tools jar into its library under the same package names. In Maven the tools jar can be added to the classpath very easily.

<dependency>
  <groupId>com.sun</groupId>
  <artifactId>tools</artifactId>
  <version>1.6.0</version>
  <scope>system</scope>
  <systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>

The sun tools jar api can then be used to load the java agent simply by provided the path to the jar file as follows.

package name.dhruba.javaagent;

import java.lang.management.ManagementFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.tools.attach.VirtualMachine;

public class MyJavaAgentLoader {

    static final Logger logger = LoggerFactory.getLogger(MyJavaAgentLoader.class);

    private static final String jarFilePath = "/home/dhruba/.m2/repository/"
            + "javaagent-examples/javaagent-examples/1.0/"
            + "javaagent-examples-1.0-jar-with-dependencies.jar";

    public static void loadAgent() {
        logger.info("dynamically loading javaagent");
        String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName();
        int p = nameOfRunningVM.indexOf('@');
        String pid = nameOfRunningVM.substring(0, p);

        try {
            VirtualMachine vm = VirtualMachine.attach(pid);
            vm.loadAgent(jarFilePath, "");
            vm.detach();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

As you can see – the code above is querying the pid of the running process and attaching the javaagent to the process. If any kind of failure occurs at this point it is ignored silently whereas if an agent is loaded on startup failures result in termination of startup.

We can now initialise the java agent prior to invoking our main method using the MyJavaAgent.initialize() hookpoint we declared earlier.

package name.dhruba.javaagent;

import java.util.Arrays;

import name.dhruba.user.MyUser;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyMainClass {

    static final Logger logger = LoggerFactory.getLogger(MyMainClass.class);

    static {
        MyJavaAgent.initialize();
    }

    /**
     * Main method.
     *
     * @param args
     */
    public static void main(String[] args) {
        logger.info("main method invoked with args: {}", Arrays.asList(args));
        logger.info("userName: {}", new MyUser().getName());
    }

}

The output is very similar but with a subtely different path through the code.

20:58:50.923 [main] INFO  n.dhruba.javaagent.MyJavaAgentLoader - dynamically loading javaagent
20:58:51.249 [Attach Listener] INFO  name.dhruba.javaagent.MyJavaAgent - agentmain method invoked with args:  and inst: sun.instrument.InstrumentationImpl@c2ff5
20:58:51.266 [main] INFO  name.dhruba.javaagent.MyMainClass - main method invoked with args: []
20:58:51.267 [main] INFO  n.d.javaagent.MyClassFileTransformer - class file transformer invoked for className: name/dhruba/user/MyUser
20:58:51.276 [main] INFO  name.dhruba.javaagent.MyMainClass - userName: bar
20:58:51.276 [DestroyJavaVM] INFO  n.d.javaagent.MyClassFileTransformer - class file transformer invoked for className: java/lang/Shutdown
20:58:51.276 [DestroyJavaVM] INFO  n.d.javaagent.MyClassFileTransformer - class file transformer invoked for className: java/lang/Shutdown$Lock

Note that this time the agentmain method was invoked instead of the premain method. The rest of the output is the same.

Conclusion

Javaagents along with the java.lang.instrument package are a powerful feature set of the Java language that allow you complete control over the classes of any given application. Instrumentation also has far more liberty with its capabilities than proxying or pointcut weaving. This gives complete dynamic capability to an otherwise very static language. It has allowed development of powerful non-intrusive tools like JMockit which immediately gains numerous advantages over other mocking tools largely because it is based on instrumentation. I look forward to further exploring the possibilities going forward.

Resources

ASM, ByteCodeOutline Plugin for Eclipse (Eclipse 3.5 update site), java.lang.instrument, JMockit.

  • Share/Bookmark

{ 0 comments }

JMockit jar loading from remote file systems enabled

by Dhruba Bandopadhyay on February 7, 2010

Over the past few months we’ve faced significant hurdles integrating jmockit into our development environment at a client-site primarily but not entirely because it is not a conventional development environment. The most noteable of these has been the inability to load the jmockit jar file from a mounted drive which is a remote file system. Having remote paths in the classpath such as //foo/bar/baz/jmockit.jar would result in a java.net.URI being constructed with that remote path and the URI being passed to the following java.io.File constructor.

public File(URI uri) {
    if (!uri.isAbsolute())
        throw new IllegalArgumentException("URI is not absolute");
    if (uri.isOpaque())
        throw new IllegalArgumentException("URI is not hierarchical");
    String scheme = uri.getScheme();
    if ((scheme == null) || !scheme.equalsIgnoreCase("file"))
        throw new IllegalArgumentException("URI scheme is not \"file\"");
    if (uri.getAuthority() != null)
        throw new IllegalArgumentException("URI has an authority component");
    if (uri.getFragment() != null)
        throw new IllegalArgumentException("URI has a fragment component");
    if (uri.getQuery() != null)
        throw new IllegalArgumentException("URI has a query component");
    String p = uri.getPath();
    if (p.equals(""))
        throw new IllegalArgumentException("URI path component is empty");
    p = fs.fromURIPath(p);
    if (File.separatorChar != '/')
        p = p.replace('/', File.separatorChar);
    this.path = fs.normalize(p);
    this.prefixLength = fs.prefixLength(this.path);
}

The following snippet in the above code would then result in an IllegalArgumentException being thrown with the message URI has an authority component and of course the authority component would be //foo.

if (uri.getAuthority() != null)
    throw new IllegalArgumentException("URI has an authority component");

This morning, however, I happened to notice the following extract in the jmockit changelog for the latest preview version – 0.997 although I’m not sure how long ago this was done.

A few fixes and enhancements to JMockit’s internal auto-initialization mechanisms: jmockit.jar (or a versioned jar name) can now be loaded from a remote file system without triggering a security-related exception

I wonder if this was in response to Shankar’s post on the obstacles we faced. It remains to be seen whether this indeed refers to the above issue and whether it resolves it. I’ll try it out once this version is finally released. Just for the record, to place renewed emphasis on the points made in Shankar’s article, the changes outlined in the following changelog entry under version 0.993 have caused us integration difficulties also.

Enhancement: to make JMockit easier to use when running under JDK 1.6+ without the -javaagent JVM parameter, two simplifications were introduced:

  • The /lib/tools.jar file no longer needs to be added to classpath.
  • Test classes no longer need to be annotated with @RunWith(JMockit.class) (for JUnit 4 tests), nor extend a JUnit/TestNG integration base class (JMockitTest, JMockitTestCase, JMockitTestNG).

There is a requirement for the second item when using JUnit, though: jmockit.jar needs to come before junit-4.x.jar in the classpath.

The above changes placed a constraint on classpath ordering and, by default, loaded all tests using the jmockit junit runner rather than the original junit runner and if jmockit wasn’t properly initialised then all tests would fail as long as jmockit was on the classpath. It’s a tricky problem to approach for jmockit. Making life easier for certain environments makes it more difficult in others. I would say that jmockit should allow the user to define how they would like their agent loaded and also have jmockit enabled explicitly only and not behind the scenes. Once integrated, however, it is a fine mocking library with a unique feature set.

  • Share/Bookmark

{ 0 comments }

Java SE 6 1.6.0_18 update worthy of note

by Dhruba Bandopadhyay on January 16, 2010

Compared to other v6 release the release of Java SE 1.6.0_18 is definitely worthy of note.

Major changes

  • VisualVM 1.2
  • Performance improvements
  • 20% faster jar file creation
  • Java Hotspot VM 16.0
    • Improved NUMA-aware allocation giving a 30% (for 32-bit) to 40% (for 64-bit) increase in performance (which is due also to arrive in jdk7 as well)
    • Garbage collection improvements including new default java heap configuration in client and server vms
    • Classloading optimisations for faster startup
    • Code generation improvements including elision of needless conversions between integer primitive types and optimisations of common string concatenation patterns
    • Garbage First (G1) garbage collector improved reliability and performance
    • New options to request a heap dump or class histogram before or after a full GC
    • Escape analysis based optimisation which appeared in 6u14 has been disabled in 6u18 to be restored in a future update

Minor changes

  • Support for Windows 7
  • JavaDB 10.5.3.0
  • Application startup improvements
  • Runtime performance improvements for UI applications
  • Ability to read larger zip files of size up to 4GB

As the community eagerly awaits Java 7 in Q4 of 2010 releases such as this one serve to provide a little excitement in the mean time. It’s interesting to see that certain features that are due to arrive with Java 7 are also being offered in Java 6 such as NUMA-aware allocation, G1 collector and escape analysis. For whatever reason escape analysis has been disabled in this release but no reason has been given – it would be interesting to know why.

  • Share/Bookmark

{ 0 comments }

Eclipse AJDT intertypes and Push-In refactoring

by Dhruba Bandopadhyay on December 31, 2009

In the wake of the recent 1.0.0 release of Spring Roo, while going through Ben Alex’s captivating three part tutorial (1,2,3) on the tool, I experienced one of those moments that are normally few and far between when you know you’ve come across something new – something rare and revolutionary for the space of technology that it’s acting within that would never have occurred to you – something lateral in nature (no pun intended as we’ll see later on). Meet Eclipse ADJT, intertypes and Push-In refactoring.

Let’s take the Spring Roo project in the above tutorial as an example.

Spring Roo Project

Spring Roo Project

Imagine the following pojo.

package name.dhruba.wedding.domain;

import javax.persistence.Entity;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.tostring.RooToString;
import org.springframework.roo.addon.entity.RooEntity;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.springframework.format.annotation.DateTimeFormat;

@Entity
@RooJavaBean
@RooToString
@RooEntity(finders = { "findRsvpsByCodeEquals" })
public class Rsvp {

    @NotNull
    @Size(min = 1, max = 30)
    private String code;

    @Size(max = 30)
    private String email;

    private Integer attending;

    @Size(max = 100)
    private String specialRequests;

    @Temporal(TemporalType.TIMESTAMP)
    @DateTimeFormat(style = "S-")
    private Date confirmed;

}

It has no methods of any kind. Now imagine a separate compilation unit that provides these methods.

package name.dhruba.wedding.domain;

import java.lang.Integer;
import java.lang.Long;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.EntityManager;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PersistenceContext;
import javax.persistence.Version;
import name.dhruba.wedding.domain.Rsvp;
import org.springframework.transaction.annotation.Transactional;

privileged aspect Rsvp_Roo_Entity {

    @PersistenceContext
    transient EntityManager Rsvp.entityManager;    

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long Rsvp.id;    

    @Version
    @Column(name = "version")
    private Integer Rsvp.version;    

    public Long Rsvp.getId() {
        return this.id;
    }    

    public void Rsvp.setId(Long id) {
        this.id = id;
    }    

    public Integer Rsvp.getVersion() {
        return this.version;
    }    

    public void Rsvp.setVersion(Integer version) {
        this.version = version;
    }    

    @Transactional
    public void Rsvp.persist() {
        if (this.entityManager == null) this.entityManager = entityManager();
        this.entityManager.persist(this);
    }    

    @Transactional
    public void Rsvp.remove() {
        if (this.entityManager == null) this.entityManager = entityManager();
        if (this.entityManager.contains(this)) {
            this.entityManager.remove(this);
        } else {
            Rsvp attached = this.entityManager.find(Rsvp.class, this.id);
            this.entityManager.remove(attached);
        }
    }    

    @Transactional
    public void Rsvp.flush() {
        if (this.entityManager == null) this.entityManager = entityManager();
        this.entityManager.flush();
    }    

    @Transactional
    public void Rsvp.merge() {
        if (this.entityManager == null) this.entityManager = entityManager();
        Rsvp merged = this.entityManager.merge(this);
        this.entityManager.flush();
        this.id = merged.getId();
    }    

    public static final EntityManager Rsvp.entityManager() {
        EntityManager em = new Rsvp().entityManager;
        if (em == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
        return em;
    }    

    public static long Rsvp.countRsvps() {
        return (Long) entityManager().createQuery("select count(o) from Rsvp o").getSingleResult();
    }    

    public static List Rsvp.findAllRsvps() {
        return entityManager().createQuery("select o from Rsvp o").getResultList();
    }    

    public static Rsvp Rsvp.findRsvp(Long id) {
        if (id == null) throw new IllegalArgumentException("An identifier is required to retrieve an instance of Rsvp");
        return entityManager().find(Rsvp.class, id);
    }    

    public static List Rsvp.findRsvpEntries(int firstResult, int maxResults) {
        return entityManager().createQuery("select o from Rsvp o").setFirstResult(firstResult).setMaxResults(maxResults).getResultList();
    }    

}

Imagine that your editor handles the separation seamlessly and merges the two for you to work with the API at compile time.

    @RequestMapping(method = RequestMethod.POST)
    public String post(@ModelAttribute("rsvp") Rsvp rsvp, ModelMap modelMap) {
        rsvp.setConfirmed(new Date());
        if (rsvp.getId() == null) {
            rsvp.persist();
        } else {
            rsvp.merge();
        }
        if (rsvp.getEmail().length() > 0) {
            sendMessage("Ben Alex ", "RSVP to our wedding", rsvp.getEmail(),
                    "Your RSVP has been saved: " + rsvp.toString());
        }
        modelMap.put("rsvp", rsvp);
        return "thanks";
    }

This allows Spring Roo to achieve a true separation of concerns as well a multitude of other compelling benefits.

Now imagine you want to merge such duality into one, project wide, once your initial project harness is ready. You can.

Right click the project » Refactor » Push-In.

Eclipse AJDT Push-In Menu

Eclipse AJDT Push-In Menu

Check pending actions.

Push-In pending actions

Push-In pending actions

Preview the action.

Preview Push-In Refactoring

Preview Push-In Refactoring

Apply and you can get the following outcome.

package name.dhruba.wedding.domain;

import javax.persistence.Entity;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.tostring.RooToString;
import org.springframework.roo.addon.entity.RooEntity;
import org.springframework.transaction.annotation.Transactional;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.EntityManager;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.format.annotation.DateTimeFormat;

@Configurable
@Entity
@RooJavaBean
@RooToString
@RooEntity(finders = { "findRsvpsByCodeEquals" })
public class Rsvp {

    @NotNull
    @Size(min = 1, max = 30)
    private String code;

    @Size(max = 30)
    private String email;

    private Integer attending;

    @Size(max = 100)
    private String specialRequests;

    @Temporal(TemporalType.TIMESTAMP)
    @DateTimeFormat(style = "S-")
    private Date confirmed;

	public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Id: ").append(getId()).append(", ");
        sb.append("Version: ").append(getVersion()).append(", ");
        sb.append("Code: ").append(getCode()).append(", ");
        sb.append("Email: ").append(getEmail()).append(", ");
        sb.append("Attending: ").append(getAttending()).append(", ");
        sb.append("SpecialRequests: ").append(getSpecialRequests()).append(", ");
        sb.append("Confirmed: ").append(getConfirmed());
        return sb.toString();
    }

	@PersistenceContext
    transient EntityManager entityManager;

	@Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

	@Version
    @Column(name = "version")
    private Integer version;

	public Long getId() {
        return this.id;
    }

	public void setId(Long id) {
        this.id = id;
    }

	public Integer getVersion() {
        return this.version;
    }

	public void setVersion(Integer version) {
        this.version = version;
    }

	@Transactional
    public void persist() {
        if (this.entityManager == null) this.entityManager = entityManager();
        this.entityManager.persist(this);
    }

	@Transactional
    public void remove() {
        if (this.entityManager == null) this.entityManager = entityManager();
        if (this.entityManager.contains(this)) {
            this.entityManager.remove(this);
        } else {
            Rsvp attached = this.entityManager.find(Rsvp.class, this.id);
            this.entityManager.remove(attached);
        }
    }

	@Transactional
    public void flush() {
        if (this.entityManager == null) this.entityManager = entityManager();
        this.entityManager.flush();
    }

	@Transactional
    public void merge() {
        if (this.entityManager == null) this.entityManager = entityManager();
        Rsvp merged = this.entityManager.merge(this);
        this.entityManager.flush();
        this.id = merged.getId();
    }

	public static final EntityManager entityManager() {
        EntityManager em = new Rsvp().entityManager;
        if (em == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
        return em;
    }

	public static long countRsvps() {
        return (Long) entityManager().createQuery("select count(o) from Rsvp o").getSingleResult();
    }

	public static List findAllRsvps() {
        return entityManager().createQuery("select o from Rsvp o").getResultList();
    }

	public static Rsvp findRsvp(Long id) {
        if (id == null) throw new IllegalArgumentException("An identifier is required to retrieve an instance of Rsvp");
        return entityManager().find(Rsvp.class, id);
    }

	public static List findRsvpEntries(int firstResult, int maxResults) {
        return entityManager().createQuery("select o from Rsvp o").setFirstResult(firstResult).setMaxResults(maxResults).getResultList();
    }

	public String getCode() {
        return this.code;
    }

	public void setCode(String code) {
        this.code = code;
    }

	public String getEmail() {
        return this.email;
    }

	public void setEmail(String email) {
        this.email = email;
    }

	public Integer getAttending() {
        return this.attending;
    }

	public void setAttending(Integer attending) {
        this.attending = attending;
    }

	public String getSpecialRequests() {
        return this.specialRequests;
    }

	public void setSpecialRequests(String specialRequests) {
        this.specialRequests = specialRequests;
    }

	public Date getConfirmed() {
        return this.confirmed;
    }

	public void setConfirmed(Date confirmed) {
        this.confirmed = confirmed;
    }

	public static Query findRsvpsByCodeEquals(String code) {
        if (code == null || code.length() == 0) throw new IllegalArgumentException("The code argument is required");
        EntityManager em = Rsvp.entityManager();
        Query q = em.createQuery("SELECT Rsvp FROM Rsvp AS rsvp WHERE rsvp.code = :code");
        q.setParameter("code", code);
        return q;
    }
}

If we look at the difference in the number of files before and after you can clearly see the intertypes have been deleted.

$ find wedding.original/ | wc -l
     211
$ find wedding/ | wc -l
     191

Simply genius.

Support is upcoming for the opposite: Pull-Out.

Note that in order for the above to work you must either have STS which comes with the AJDT plugin or Eclipse with the AJDT plugin installed. Also you must either have the AspectJ nature or AJDT weaving enabled or both.

  • Share/Bookmark

{ 2 comments }

Spring Roo 1.0.0, EclipseLink 2.0.0 and JPA2 incompatible

December 31, 2009

Recently Spring Roo 1.0.0 was released. However for those interested in using Roo with EclipseLink (in other words for those with a fervent disdain of Hibernate) please note that this is not yet supported. The related online discussion is on the forum and there is an open JIRA for integrating it which you can vote [...]

Read the full article →

Spring Expression Language (SpEL) Primer

December 30, 2009

Spring Expression Language, abbreviated SpEL, is a powerful expression language, in the newly released Spring 3, that supports a plethora of ways of interacting with object graphs at runtime. It is intended to be a fully featured expression language for reuse across all products in the Spring Portfolio. This article explores all the various features [...]

Read the full article →

JMockit: No holds barred testing with instrumentation over mocking

November 8, 2009

Introduction
The transition from junit to stubs to mocking occurred long back in the world of testing. Traditionally mocking has used jdk proxies for interface based classes and cglib for non-final concrete classes. This article draws attention to the yet ongoing transition from mocking to instrumentation for testing. Particularly it highlights how to achieve [...]

Read the full article →

Happy Birthday

November 5, 2009

Happy Birthday to me.

Read the full article →

JDK7 NIO.2 FS API Primer

September 6, 2009

Earlier today I was watching Deep Dive: JDK 7 With Danny Coward and it prompted me to play around with the new NIO.2 java.nio.file.* APIs in JDK7 for filesystem access. They are one of the major new features in JDK7 being worked upon under JSR-203.
Here I enumerate almost all the new filesystem APIs by [...]

Read the full article →

Esper primer

September 2, 2009

Recently, feeling the need for more complex (no pun intended) event processing than what a simple enterprise integration patterns implementation such as Spring Integration provides, I started looking at Esper for more insight into CEP and ESP but from an open source perspective.
Here I reproduce an example that I followed from the Esper documentation. [...]

Read the full article →