Wednesday, February 09, 2011

@Resource and the new lookup attribute - How to avoid compilation and runtime problems

More and more users have started moving to Java EE6 functionality and one of the common question that keeps coming up in the forums these days is about the use of the "lookup" attribute of the @Resource annotation. Java EE6 introduced this new attribute to the @javax.annotation.Resource annotation (javadoc). In the previous version, this attribute wasn't available. So now users can start using this attribute as follows:

package org.myapp;

import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.sql.DataSource;

/**
 * Author: Jaikiran Pai
 */
@Stateless
public class SimpleSLSB
{

   @Resource(lookup = "test")
   private DataSource dataSource;


   public void doNothing()
   {
      // nothing!
   }
}


Simple enough! However, the problem arises when you try to compile this code or even run it. Compiling this code with JDK 1.6 leads to this compile time error:

[ERROR] /NotBackedUp/jpai/business/me/resource-lookup/src/main/java/org/myapp/SimpleSLSB.java:[36,13] cannot find symbol
[ERROR] symbol  : method lookup()
[ERROR] location: @interface javax.annotation.Resource


The root cause of this issue is that the standalone JDK itself ships its own version of @javax.annotation.Resource which doesn't have this lookup attribute (javadoc). The compiler ends up using the version shipped in the JDK.

So how do we get past this problem. The solution is simple - all you have to do is set the -Djava.endorsed.dirs system property to point to the folder containing the jar which has the new @javax.annotation.Resource annotation. The -Djava.endorsed.dirs needs to be set while compiling the program as well as while running the program. Depending on what tool you use to compile the program, there are different ways to set this system property. Let's see how we handle this in a Maven project.

Building through Maven:

Maven is a build tool which is used in many project these days. Maven uses a build file named pom.xml which can be used to setup the dependencies of the project as well as other build related configurations. I'll assume that those of you who are using Maven, already know all these details and just want to see how to get the @Resource(lookup="") working within a Maven project. So here it is:

1) Use the maven-dependency-plugin to "copy" the jar containing the new javax.annotation.* classes into some folder within your project. In this example, I'm copying it over to a folder named "endorsed" within the project's build directory:

   <build>
      <plugins>

         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <version>2.1</version>
            <executions>
               <execution>
                  <goals>
                     <goal>copy</goal>
                  </goals>
                  <configuration>
                     <!-- Configure the plugin to copy the jar containing javax.annotation.*
                        to a folder named "endorsed" within the project's build directory -->
                     <artifactItems>
                        <artifactItem>
                           <groupId>org.jboss.spec.javax.annotation</groupId>
                           <artifactId>jboss-annotations-api_1.1_spec</artifactId>
                        </artifactItem>
                     </artifactItems>
                     <outputDirectory>${project.build.directory}/endorsed</outputDirectory>
                  </configuration>
               </execution>
            </executions>
         </plugin>

       ...



2) Now let's instruct the compiler plugin and the surefire plugin (which runs your unit tests) to use the jar from the project's endorsed folder. As I said eariler, we just need to set the -Djava.endorsed.dirs property to make this happen. So here's how we do it for these 2 plugins:

...
<!-- Setup the compiler plugin to use the endorsed directory containing
          our javax.annotation.* classes -->
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
               <source>1.6</source>
               <target>1.6</target>
               <!-- Setup the compiler plugin to use the endorsed directory containing
               the jar for javax.annotation.* classes. Remember that we setup this folder
               via the maven-dependency-plugin configuration, above. -->
               <compilerArgument>-Djava.endorsed.dirs=${project.build.directory}/endorsed</compilerArgument>
            </configuration>
         </plugin>

         <!-- Setup surefire plugin to use the endoresed directory containing our javax.annotation.* classes -->
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.5</version>
            <configuration>
               <!-- Setup the surefire plugin to use the endorsed directory containing
               the jar for javax.annotation.* classes. Remember that we setup this folder
               via the maven-dependency-plugin configuration, above. -->
               <argLine>-Djava.endorsed.dirs=${project.build.directory}/endorsed</argLine>
            </configuration>
         </plugin>

      </plugins>
   </build>


That's it! We now have setup our project to use the correct endorsed jars. Here's the complete pom.xml for this sample project:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>jaikiran</groupId>
    <artifactId>resource-lookup</artifactId>
    <version>1.0</version>

   <build>
      <plugins>

         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <version>2.1</version>
            <executions>
               <execution>
                  <goals>
                     <goal>copy</goal>
                  </goals>
                  <configuration>
                     <!-- Configure the plugin to copy the jar containing javax.annotation.*
                        to a folder named "endorsed" within the project's build directory -->
                     <artifactItems>
                        <artifactItem>
                           <groupId>org.jboss.spec.javax.annotation</groupId>
                           <artifactId>jboss-annotations-api_1.1_spec</artifactId>
                        </artifactItem>
                     </artifactItems>
                     <outputDirectory>${project.build.directory}/endorsed</outputDirectory>
                  </configuration>
               </execution>
            </executions>
         </plugin>

         <!-- Setup the compiler plugin to use the endorsed directory containing
          our javax.annotation.* classes -->
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
               <source>1.6</source>
               <target>1.6</target>
               <!-- Setup the compiler plugin to use the endorsed directory containing
               the jar for javax.annotation.* classes. Remember that we setup this folder
               via the maven-dependency-plugin configuration, above. -->
               <compilerArgument>-Djava.endorsed.dirs=${project.build.directory}/endorsed</compilerArgument>
            </configuration>
         </plugin>

         <!-- Setup surefire plugin to use the endoresed directory containing our javax.annotation.* classes -->
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.5</version>
            <configuration>
               <!-- Setup the surefire plugin to use the endorsed directory containing
               the jar for javax.annotation.* classes. Remember that we setup this folder
               via the maven-dependency-plugin configuration, above. -->
               <argLine>-Djava.endorsed.dirs=${project.build.directory}/endorsed</argLine>
            </configuration>
         </plugin>

      </plugins>
   </build>

    <dependencies>
       <!-- EJB API dependency -->
       <dependency>
          <groupId>org.jboss.spec.javax.ejb</groupId>
          <artifactId>jboss-ejb-api_3.1_spec</artifactId>
          <version>1.0.0.Final</version>
       </dependency>

       <!-- javax.annotation.* dependency -->
       <dependency>
          <groupId>org.jboss.spec.javax.annotation</groupId>
          <artifactId>jboss-annotations-api_1.1_spec</artifactId>
          <version>1.0.0.Final</version>
       </dependency>
    </dependencies>
</project>


Running "mvn clean install" will now show a build success:

[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ resource-lookup ---
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /NotBackedUp/jpai/business/me/resource-lookup/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:testResources (default-testResources) @ resource-lookup ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /NotBackedUp/jpai/business/me/resource-lookup/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ resource-lookup ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-surefire-plugin:2.5:test (default-test) @ resource-lookup ---
[INFO] No tests to run.
[INFO] 
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.087s
[INFO] Finished at: Wed Feb 09 13:02:02 IST 2011
[INFO] Final Memory: 9M/129M
[INFO] ------------------------------------------------------------------------


Although it did not involve much to set this up in Maven, it did however require setting it up with the use of 3 separate plugins. It would have been far more easier if there was a way in Maven where we could just add a "dependency" to the endorsed dir and let all the relevant plugins use it without the developer having to configure each of those separately. But anyway, we got what we were after - a successful compilation of that class using @Resource(lookup="").

1 comment:

Anonymous said...

Heeeeyyyyy man!! you deserve like one thousand dollars for this blog. This helped me a lot!! I was going crazy with this problem!!.

Greetings from bogotá, Colombia