Archive for October, 2013

Disabling all EJB timers in Java EE 6

29 October 2013

Java EE 7 has finally added a method to obtain all timers in the system. With the help of this method you can fairly conveniently cancel all timers, or only specific ones.

But Java EE 7 is still fairly new and not many vendors have released a Java EE 7 compatible server yet. So is there any way at all to say disable all scheduled timers in Java EE 6?

As it appears this is possible, with a little help of CDI and the Interceptor spec. The idea is that we install a CDI extension that dynamically adds an interceptor to all @Schedule annotated methods. This interceptor then cancels the timer for which it intercepted the method that handles it. It would be great if the CDI extension was just able to remove the @Schedule annotation and we’d be done with it. Unfortunately this is yet another example why it’s not so great that EJB is not fully alligned with CDI; even if the @Schedule annotation is removed from the so-called AnnotatedType, the EJB container will still start the timer being obvlivious to the CDI representation of the bean.

The first step is to make an annotation that represents the interceptor we need:

@Inherited
@InterceptorBinding
@Target({ TYPE, METHOD })
@Retention(RUNTIME)
public @interface DisableTimers {}

We then proceed to the actual interceptor:

@Interceptor
@DisableTimers
public class DisableTimersInterceptor {
 
    @Inject
    private Logger logger;
 
    @AroundTimeout
    public Object disableTimers(InvocationContext context) throws Exception {
 
        try {
            Object timerObject = context.getTimer();
            if (timerObject instanceof Timer) {
                Timer timer = ((Timer) timerObject);
                logger.info("Canceling timer in bean " + context.getClass().getName() + " for timer " + timer.toString());
                timer.cancel();
            }
        } catch (Exception e) {
            logger.log(SEVERE, "Exception while canceling timer:", e);
        }
 
        return null;
    }
}

Note that while there’s the general concept of an @AroundTimeout and the context has a getTimer() method, the actual type for the timer has not been globally standardized for Java EE. This means we have to resort to instance testing. It would be great if some future version of Java EE could define a standard interface that all eligable timers have to implement.

Also note that there’s isn’t a clean universal way to print the timer details so I’ve used toString() here on the Timer instance. It’s vendor specific what this actually returns.

An alternative would have been here to inject the timer service and use it to cancel all timers for the bean right away. This is perhaps a bit less intuitive though. Also note that at least on JBoss you can not inject the timer service directly but have to specify a JNDI lookup name, e.g.:

@Resource(lookup="java:comp/TimerService")
public TimerService timerService;

Unfortunately in Java EE 6 we have to register the interceptor in beans.xml:

<beans>
    <interceptors>
        <class>com.example.DisableTimersInterceptor</class>
    </interceptors>
</beans>

Next is the actual extension:

public class EjbTimerDisableExtension implements Extension {
 
    private static final Logger logger = Logger.getLogger(EjbTimerDisableExtension.class.getName());
 
    public <T> void processAnnotatedType(@Observes ProcessAnnotatedType<T> processAnnotatedType, BeanManager beanManager) {
        if (hasScheduleMethods(processAnnotatedType.getAnnotatedType())) {
 
            logger.log(INFO, "Disabling timer in " + processAnnotatedType.getAnnotatedType().getJavaClass().getName());
 
            AnnotatedTypeWrapper<T> annotatedTypeWrapper = new AnnotatedTypeWrapper<>(processAnnotatedType.getAnnotatedType());
 
            for (AnnotatedMethod<? super T> annotatedMethod : processAnnotatedType.getAnnotatedType().getMethods()) {
                if (annotatedMethod.isAnnotationPresent(Schedule.class)) {
 
                    AnnotatedMethodWrapper<? super T> annotatedMethodWrapper = new AnnotatedMethodWrapper<>(annotatedMethod);
                    annotatedMethodWrapper.addAnnotation(createAnnotationInstance(DisableTimers.class));
 
                    annotatedTypeWrapper.getMethods().remove(annotatedMethod);
                    annotatedTypeWrapper.getMethods().add(annotatedMethodWrapper);
                }
            }
 
            processAnnotatedType.setAnnotatedType(annotatedTypeWrapper);
        }
    }
 
    private <T> boolean hasScheduleMethods(AnnotatedType<T> annotatedType) {
        for (AnnotatedMethod<?> annotatedMethod : annotatedType.getMethods()) {
            if (annotatedMethod.isAnnotationPresent(Schedule.class)) {
                return true;
            }
        }
 
        return false;
    }
}

In this extension we check if a bean has methods with an @Schedule annotation, and if it indeed has one we wrap the passed-in annotated type and wrap any method representation that has this annotation. Via these wrappers we can remove the existing method and then add our own method where we dynamically add the interceptor annotation.

We need to register this extension in /META-INF/services/javax.enterprise.inject.spi.Extension by putting its FQN there:

com.example.EjbTimerDisableExtension

It’s perhaps unfortunately that CDI 1.0 doesn’t offer many convenience methods for wrapping its most important types (which e.g. JSF does do) and doesn’t provide an easy way to create an annotation instance.

Luckily my co-worker Jan Beernink had already created some convenience types for those, which I could use:

The CDI type wrappers:

public class AnnotatedMethodWrapper<X> implements AnnotatedMethod<X> {
 
    private AnnotatedMethod<X> wrappedAnnotatedMethod;
 
    private Set<Annotation> annotations;
 
    public AnnotatedMethodWrapper(AnnotatedMethod<X> wrappedAnnotatedMethod) {
        this.wrappedAnnotatedMethod = wrappedAnnotatedMethod;
 
        annotations = new HashSet<>(wrappedAnnotatedMethod.getAnnotations());
    }
 
    @Override
    public List<AnnotatedParameter<X>> getParameters() {
        return wrappedAnnotatedMethod.getParameters();
    }
 
    @Override
    public AnnotatedType<X> getDeclaringType() {
        return wrappedAnnotatedMethod.getDeclaringType();
    }
 
    @Override
    public boolean isStatic() {
        return wrappedAnnotatedMethod.isStatic();
    }
 
    @Override
    public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
        for (Annotation annotation : annotations) {
            if (annotationType.isInstance(annotation)) {
                return annotationType.cast(annotation);
            }
        }
 
        return null;
    }
 
    @Override
    public Set<Annotation> getAnnotations() {
        return Collections.unmodifiableSet(annotations);
    }
 
    @Override
    public Type getBaseType() {
        return wrappedAnnotatedMethod.getBaseType();
    }
 
    @Override
    public Set<Type> getTypeClosure() {
        return wrappedAnnotatedMethod.getTypeClosure();
    }
 
    @Override
    public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
        for (Annotation annotation : annotations) {
            if (annotationType.isInstance(annotation)) {
                return true;
            }
        }
 
        return false;
    }
 
    @Override
    public Method getJavaMember() {
        return wrappedAnnotatedMethod.getJavaMember();
    }
 
    public void addAnnotation(Annotation annotation) {
        annotations.add(annotation);
    }
 
    public void removeAnnotation(Annotation annotation) {
        annotations.remove(annotation);
    }
 
    public void removeAnnotation(Class<? extends Annotation> annotationType) {
        Annotation annotation = getAnnotation(annotationType);
        if (annotation != null ) {
            removeAnnotation(annotation);
        }
    }
 
}
public class AnnotatedTypeWrapper<T> implements AnnotatedType<T> {
 
    private AnnotatedType<T> wrappedAnnotatedType;
 
    private Set<Annotation> annotations = new HashSet<>();
    private Set<AnnotatedMethod<? super T>> annotatedMethods = new HashSet<>();
    private Set<AnnotatedField<? super T>> annotatedFields = new HashSet<>();
 
    public AnnotatedTypeWrapper(AnnotatedType<T> wrappedAnnotatedType) {
        this.wrappedAnnotatedType = wrappedAnnotatedType;
 
        annotations.addAll(wrappedAnnotatedType.getAnnotations());
        annotatedMethods.addAll(wrappedAnnotatedType.getMethods());
        annotatedFields.addAll(wrappedAnnotatedType.getFields());
    }
 
    @Override
    public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
        return wrappedAnnotatedType.getAnnotation(annotationType);
    }
 
    @Override
    public Set<Annotation> getAnnotations() {
        return annotations;
    }
 
    @Override
    public Type getBaseType() {
        return wrappedAnnotatedType.getBaseType();
    }
 
    @Override
    public Set<AnnotatedConstructor<T>> getConstructors() {
        return wrappedAnnotatedType.getConstructors();
    }
 
    @Override
    public Set<AnnotatedField<? super T>> getFields() {
        return annotatedFields;
    }
 
    @Override
    public Class<T> getJavaClass() {
        return wrappedAnnotatedType.getJavaClass();
    }
 
    @Override
    public Set<AnnotatedMethod<? super T>> getMethods() {
        return annotatedMethods;
    }
 
    @Override
    public Set<Type> getTypeClosure() {
        return wrappedAnnotatedType.getTypeClosure();
    }
 
    @Override
    public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
        for (Annotation annotation : annotations) {
            if (annotationType.isInstance(annotation)) {
                return true;
            }
        }
 
        return false;
    }
 
}

And the utility code for instantiating an annotation type:

public class AnnotationUtils {
 
    private AnnotationUtils() {
    }
 
    /**
     * Create an instance of the specified annotation type. This method is only suited for annotations without any properties, for annotations with
     * properties, please see {@link #createAnnotationInstance(Class, InvocationHandler)}.
     *
     * @param annotationType
     *            the type of annotation
     * @return an instance of the specified type of annotation
     */
    public static <T extends Annotation> T createAnnotationInstance(Class<T> annotationType) {
        return createAnnotationInstance(annotationType, new AnnotationInvocationHandler<>(annotationType));
    }
 
    public static <T extends Annotation> T createAnnotationInstance(Class<T> annotationType, InvocationHandler invocationHandler) {
        return annotationType.cast(Proxy.newProxyInstance(AnnotationUtils.class.getClassLoader(), new Class<?>[] { annotationType },
                invocationHandler));
    }
}
/**
 * {@link InvocationHandler} implementation that implements the base methods required for a parameterless annotation. This handler only implements the
 * following methods: {@link Annotation#equals(Object)}, {@link Annotation#hashCode()}, {@link Annotation#annotationType()} and
 * {@link Annotation#toString()}.
 *
 * @param <T>
 *            the type of the annotation
 */
class AnnotationInvocationHandler<T extends Annotation> implements InvocationHandler {
 
    private Class<T> annotationType;
 
    /**
     * Create a new {@link AnnotationInvocationHandler} instance for the given annotation type.
     *
     * @param annotationType
     *            the annotation type this handler is for
     */
    public AnnotationInvocationHandler(Class<T> annotationType) {
        this.annotationType = annotationType;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        switch (method.getName()) {
        case "toString":
            return "@" + annotationType.getName() + "()";
        case "annotationType":
            return annotationType;
        case "equals":
            return annotationType.isInstance(args[0]);
        case "hashCode":
            return 0;
        }
 
        return null;
    }
 
}

Conclusion

This approach is by far not as elegant as just injecting an @Startup @Singleton with the timer service and canceling all timers in a simple loop as we can do in Java EE 7, but it does work on Java EE 6. Timers are canceled one by one as they fire and their handler methods are never invoked.

The approach of dynamically adding interceptors to specific methods can however be used for other things as well (e.g. logging exceptions from @Asynchronous methods that return void, just to name something) so it’s a generally useful technique.

Arjan Tijms

css.php best counter