Hacking Glassfish 5 Supporting Repeatable Annotations in Java EE 8

Java EE 8 will come with many new features but there was no work to support repeatable annotations in Java EE 8. After seeing a thread from Reza Rahman on Java EE slack group, I engaged to hack GF 5 to support this.

rezas thread.png

 

Steps were generally easy but it was the my first hack on Glassfish 5. I’ve needed to build GF 5 from its source and needed to update repeatable candidate annotations by its specification APIs.

Step 1: Build Glassfish 5 from source

Building Glassfish from its source is easy. There is a good tutorial for that but I want to simply repeat it.

  1. Set MAVEN_OPTS=-Xmx1024M environtment variable
  2. Checkout the current (Glassfish 5) source code
    svn checkout https://svn.java.net/svn/glassfish~svn/trunk/main
  3. Build it!
    mvn install -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -DskipTests
  4. After this is completed you will see the build in appserver/distributions/glassfish/target/stage path which is relative to the GF source path. You can normally start it with the asadmin start-domain command.

Step 2: Find repeatable annotation candidates

You may not know what is repeatable annotations ? Repeatable annotations is a new feature that is available from Java 8. Before Java 8, we couldn’t apply the same annotation more than once on an annotation target (method, type, constructor etc. )

For example;

Assume that you use Timer Service on an EJB method like below;

@Singleton // Singleton EJB
public class MyTimer {

    @Schedule(second = "30", minute = "*", hour = "*") (1)
    public void myLovelyWork(){
     //
    }

}
1 It invokes myLovelyWork method every 30 seconds

It is okay but if you want to apply one more @Schedule on same method, you had must wrap it by its wrapper @Schedules annotation (before the Java 8)

@Singleton // Singleton EJB
public class MyTimer {


    @Schedules({
      @Schedule(second = "30", minute = "*", hour = "*"), (1)
      @Schedule(second = "15", minute = "*", hour = "*") (2)
    })
    public void myLovelyWork(){
     //
    }

}
1 It invokes myLovelyWork method every 30 seconds
2 It invokes myLovelyWork method every 15 seconds

After Java 8, we can now apply the same annotation at the same place with the repeatable annotation feature. This tutorial is about trying and testing this feature in Java EE 8 APIs.

@Singleton // Singleton EJB
public class MyTimer {

    // We don't need the wrapper anymore
    @Schedule(second = "30", minute = "*", hour = "*") (1)
    @Schedule(second = "15", minute = "*", hour = "*") (2)
    public void myLovelyWork(){
     //
    }

}
1 It invokes myLovelyWork method every 30 seconds
2 It invokes myLovelyWork method every 15 seconds

There are some repeatable candidate annotations in Java EE APIs. To bring the repeatability feature on these annotations, first we should find them. I wrote a grep script that finds all annotations in javaee-api-7.0-sources.jar. After extracting the jar, I ran the following command;

grep -R "@interface" javaee-api-7.0-sources | cut -d: -f1 | grep "s\.java$"

The list below shows all annotations (has @interface) whose filename ends with s.java (to find wrappers). Not all of them are repeatable wrappers but we reduced the manual work with a simple command.

List of plural Java EE annotations
javax/annotation/Resources.java
javax/annotation/security/DeclareRoles.java
javax/annotation/security/RunAs.java
javax/annotation/sql/DataSourceDefinitions.java
javax/ejb/Asynchronous.java
javax/ejb/EJBs.java
javax/ejb/Schedules.java
javax/ejb/Stateless.java
javax/enterprise/event/Observes.java
javax/enterprise/inject/Disposes.java
javax/enterprise/inject/Produces.java
javax/enterprise/inject/Specializes.java
javax/enterprise/inject/spi/WithAnnotations.java
javax/faces/application/ResourceDependencies.java
javax/interceptor/ExcludeClassInterceptors.java
javax/interceptor/ExcludeDefaultInterceptors.java
javax/interceptor/Interceptors.java
javax/jms/JMSConnectionFactoryDefinitions.java
javax/jms/JMSDestinationDefinitions.java
javax/jws/soap/SOAPMessageHandlers.java
javax/mail/MailSessionDefinitions.java
javax/persistence/Access.java
javax/persistence/AssociationOverrides.java
javax/persistence/AttributeOverrides.java
javax/persistence/Converts.java
javax/persistence/EntityListeners.java
javax/persistence/ExcludeDefaultListeners.java
javax/persistence/ExcludeSuperclassListeners.java
javax/persistence/IdClass.java
javax/persistence/JoinColumns.java
javax/persistence/MapKeyClass.java
javax/persistence/MapKeyJoinColumns.java
javax/persistence/MappedSuperclass.java
javax/persistence/NamedEntityGraphs.java
javax/persistence/NamedNativeQueries.java
javax/persistence/NamedQueries.java
javax/persistence/NamedStoredProcedureQueries.java
javax/persistence/PersistenceContexts.java
javax/persistence/PersistenceUnits.java
javax/persistence/PrimaryKeyJoinColumns.java
javax/persistence/SecondaryTables.java
javax/persistence/SqlResultSetMappings.java
javax/resource/AdministeredObjectDefinitions.java
javax/resource/ConnectionFactoryDefinitions.java
javax/resource/spi/ConnectionDefinitions.java
javax/servlet/annotation/HandlesTypes.java
javax/validation/constraints/Digits.java
javax/validation/constraints/Digits.java
javax/ws/rs/Consumes.java
javax/ws/rs/Produces.java
javax/xml/bind/annotation/adapters/XmlJavaTypeAdapters.java
javax/xml/bind/annotation/XmlElementRefs.java
javax/xml/bind/annotation/XmlElements.java
javax/xml/bind/annotation/XmlNs.java
javax/xml/bind/annotation/XmlSchemaTypes.java
javax/xml/ws/WebServiceRefs.java

Formula for a repeatable candidate annotation;

If we see an annotation <Name>s above has a <Name>[] value(); property, then we can use @<Name> annotation as repeatable annotation. For example, look at @Converts annotation in JPA 2.1;

@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
public @interface Converts { (1)

  Convert[] value(); (2)
}
1 Name ends with plural s
2 @Converts can wrap one and more @Convert annotation

Step 3: Update the found annotations in code

After finding repeatable annotations over their wrappers in List of plural Java EE annotations, we can then make them repeatable. It is easy to make an annotation repeatable. For example;

Not repeatable @Convert
@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
public @interface Convert {

  ...

}
Repeatable @Convert
@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)

@Repeatable(Converts.class) // Attention!! (1)
public @interface Convert {

  ...

}
1 @Repeatable(<Name>s.class) makes applied annotation repeatable.

We should apply the same @Repeatable(<Name>s.class) rule in the candidate annotations. I want to list the SCMs of some Java EE APIs.

EJB
svn checkout https://svn.java.net/svn/glassfish~svn/tags/javax.ejb-api-3.2
Common Annotations
svn checkout https://svn.java.net/svn/glassfish~svn/tags/javax.annotation-api-1.2
JMS
svn checkout https://svn.java.net/svn/jms-spec~repository
JPA
git clone https://github.com/eclipse/eclipselink.runtime.git
JSF
svn checkout https://svn.java.net/svn/mojarra~svn/trunk
JavaMail
hg clone https://hg.java.net/hg/javamail~mercurial

Step 4: Rebuild JavaEE APIs and GF

After updating candidate annotations to bring repeatability support, we should recompile their source code with the mvn clean install command. After recompiling is finished, we should recompile the GF source like in Step 1: Build Glassfish 5 from source , then we can use repeatable annotations with our new build.

Hope to see you again.

Bir Cevap Yazın