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.
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.
- Set
MAVEN_OPTS=-Xmx1024M
environtment variable - Checkout the current (Glassfish 5) source code
svn checkout https://svn.java.net/svn/glassfish~svn/trunk/main
- Build it!
mvn install -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -DskipTests
- 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 theasadmin 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.
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;
@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
public @interface 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.
svn checkout https://svn.java.net/svn/glassfish~svn/tags/javax.ejb-api-3.2
svn checkout https://svn.java.net/svn/glassfish~svn/tags/javax.annotation-api-1.2
svn checkout https://svn.java.net/svn/jms-spec~repository
git clone https://github.com/eclipse/eclipselink.runtime.git
svn checkout https://svn.java.net/svn/mojarra~svn/trunk
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.