Categories

Dynamically proxying annotations to avoid inducing API dependency

Annotations are API, and as API they need to be on the classpath at compile time. And usually, you do not want your (portable) business code to depend on yet another API.

In this article, I will present you how to allow your library users not to depend on the library API just for the annotations you provide, using java.lang.reflect and its proxy facilities.

My examples will be based on Ehcache source code, since it is for the @IgnoreSizeOf annotation that this whole idea of annotation proxying came up ! (for reference, @IgnoreSizeOf is the annotation used by Ehcache to filter out classes from the SizeOf computation)
Doing this on the Ehcache @IgnoreSizeOf annotation will allow framework developers, who embed Ehcache in their framework (and want to benefit from the sizeOf feature), to not introduce a dependency on Ehcache for their users.

Our use case : you want to use the original annotation but not to depend on it.

Instead of using and depending on Ehcache’s @IgnoreSizeOf (net.sf.ehcache.pool.sizeof.annotations.IgnoreSizeOf) :

package net.sf.ehcache.pool.sizeof.annotations;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.PACKAGE })
public @interface IgnoreSizeOf {
    boolean inherited() default false;
}

you want to annotate your classes with your custom com.myproject.ehcache.IgnoreSizeOf annotation , which of course has no relationship (well, it looks like it, acts like it; but it is not the same, see this as duck typing) with the original @IgnoreSizeOf annotation :

package com.myproject.ehcache;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.PACKAGE })
public @interface IgnoreSizeOf {
}

You can notice that the inherited method is missing in the custom annotation, more on this in the next chapter.

The big picture : use java.lang.proxy to transform the type of your annotation

Once you have scanned your class or field or package (any AnnotatedElement) for your custom annotation :

Annotation customAnnotation = element.getAnnotation(com.myproject.ehcache.IgnoreSizeOf.class);

It is as simple as writing those 2 lines of code :

import net.sf.ehcache.pool.sizeof.annotations.IgnoreSizeOf;
/* some class and method declarations here */
        InvocationHandler handler = new AnnotationInvocationHandler(customAnnotation);
        IgnoreSizeOf proxiedAnnotation = Proxy.newProxyInstance(IgnoreSize.getClassLoader(), new Class[] {referenceAnnotation}, handler);

Well not exactly… all the interesting code is actually in the InvocationHandler, that will redirect method calls to our custom annotation methods or, if it can’t, to the reference annotation methods.

    private static class AnnotationInvocationHandler implements InvocationHandler {
 
        private final Annotation customAnnotation;
 
        public AnnotationInvocationHandler(Annotation customAnnotation) {
            this.customAnnotation = customAnnotation;
        }
 
        public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
            //trying to call the method on the custom annotation, if it exists
            Method methodOnCustom = getMatchingMethodOnGivenAnnotation(method);
            if (methodOnCustom != null) {
                return  methodOnCustom.invoke(customAnnotation, args);
            } else {
                    //otherwise getting the default value of the reference annotation method
                    Object defaultValue = method.getDefaultValue();
                    if (defaultValue != null) {
                        return defaultValue;
                }
                throw new UnsupportedOperationException(
                        "The method \""
                         + method.getName()
                         + "\" does not exist in the custom annotation, and there is no default value for"
                         + " it in the reference annotation, please implement this method in your custom annotation.");
            }
        }
 
        private Method getMatchingMethodOnGivenAnnotation(Method method) {
            try {
                Method customMethod = customAnnotation.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
                if (customMethod.getReturnType().isAssignableFrom(method.getReturnType())) {
                    return customMethod;
                }
                return null;
            } catch (NoSuchMethodException e) {
                return null;
            }
        }
    }

One method to implement here : the invoke method, called on our proxied annotation every time we call a method on it.
If the method called on the proxy matches a method in our custom annotation, it just delegates the call to it.
But if not (for example remember our custom annotation does not have the inherited() method, but the reference one has), and this is where the JDK annotations are cool, the reference annotation default return value will be returned instead.
What if there is no default value in the reference annotation ? In that case you need to provide this method in your custom annotation (with a default or you can force your consumer to provide a value for this method).

In this example the consumer of Ehcache does not even need to write a complete annotation to mimic the original one ! (since the only method in the original annotation has a default value)

Actually, even in the Oracle JDK they use proxying to resolve annotations, in sun.reflect.annotation.AnnotationParser

/**
 * Returns an annotation of the given type backed by the given
 * member -> value map.
 */
 public static Annotation annotationForMap(Class type, Map memberValues) {
     return (Annotation) Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, new AnnotationInvocationHandler(type, memberValues));
 }

using their own sun.reflect.annotation.AnnotationInvocationHandler

Conclusion

Even if this code was written specifically with Ehcache @IgnoreSizeOf annotation in mind, it is generic enough for any framework authors to use (even more since it is Apache 2 licensed)

You can checkout Ehcache source code from http://svn.terracotta.org/svn/ehcache/trunk/ehcache/ehcache-core/ and look for AnnotationSizeOfFilter and AnnotationProxyFactory (or, even better, the test class : FilteredSizeOfTest) to learn more detail on how to provide a mechanism for letting your consumer not depend on your annotations !

Special thanks to Alex Snaps and Chris Dennis for their guidance !

Converting and publishing an Android application (.apk) to run on PlayBook 2.0 (.bar)

Since its version 2.0 the RIM BlackBerry PlayBook OS acccepts now slightly modified Android application.
Here are the steps to convert your Android application to run on PlayBook OS 2.0 and to publish it on BlackBerry App World.

Convert your Android application to run on PlayBook OS 2.0

Requirements for your Android app :

If  you can convert your Android app using an eclipse plugin provided by RIM, I preferred to use their online packaging tool, which actually worked great and did not force me to add yet another plugin inside my IDE ;-)

So, from the Online Packaging Tool page, follow the instructions :

  • After accepting the agreement, specify the location of your Android app (.apk) and your Android SDK folder, if everything went OK, you should get this confirmation message :

If you have a warning or error message, it means your app does not meet the requirements, then , have a look at the list of requirements

  • Request your signing keys to RIM, they’ll then send you (it takes few hours) emails containing 2 certificates : 1 client-RDK-xxx.csj certificate (Rim  Development Key) and 1 client-PBDT-xxx.csj certificate (PlayBook Debug Token)
  • Then configure your computer to actually generate a developer certificate (.p12), still using the online tool
  • Finally, you can go to the Packaging and Signing section, to sign your application using your developer certificate (.p12)

You finally obtained your BAR archive, ready for submission to BlackBerry App World !

Publish your converted Android application (.bar) to BlackBerry AppWorld

You must register to the vendor portal first (they will ask you to provide an official id such as a passport or driving license), it is free (actually all steps are free), then you’ll receive a notification by email (few days later) telling you your account is approved (read on this post to have some feedback about registration)

Once you are able to login,

  • you’ll have to create a new product : for that you will need to submit a series of forms about your app (description, screenshots, audience, etc…)

Unfortunately, they impose you with image resolutions.. oh well… gimp is here ;-)

 

  • Now add a release of your product, this is where you are going to upload and publish the bar version of your Android app (file bundle), target it for Tablet PlayBook 2.0 All
  • And finally submit it for review : the first time I did that, they got back to me more than a week after – well, it was during the period of the promotional PlayBook giveaway in exchange of a new app submission ;-)   – to finally deny my submission because there were some Android logos in the screenshots

I finally received a playbook after my app was accepted on the app world; it does not run as smoothly as the original android version, but is still usable.

All in all that was a pretty good experience, I only regret the long time I had to wait between all the validations; it seems that it’s getting better (according to the positive tweets I read on this subject); so you’re an Android developer and want to gain a (even) broader audience, go for it !

Confoo 2012 : notes about the conference

From Wednesday 29th February to Friday 2nd March 2012, took place Confoo 2012, a major conference in Montréal, mainly focused on web technologies; but I could also attend some real cool sessions about Java / JVM, Android, (J)Ruby, TDD, Continuous Integration, software architecture, and Agile/ Scrum / Lean ; talks were given mainly in English and some in French too.

Here are some extensive notes I took while attending those talks :

And below, some quicker notes (really too busy listening !) on some other talks :

  • I presented a session on Android Continuous Integration
  • Virtualize your environment by Sean Coates was an interactive session where the speaker would poll the audience about which virtualization environment they are familiar with, on which system (he introduced some tools such as Vagrant to administer Virtual Box VMs with the command line, puppet to centrally configure your system configurations, and OpenVPN for a robust VPN solution, accessible from even poor internet connections)
  • Propulsez votre architecture grâce aux mocks : Félix Antoine a présenté au public un TDD basé sur les mock objects : comment le fait d’écrire nos classes en partant des couches supérieures (en mockant les couches en dessous) fait émerger un TDD qu’il a qualifié de « London style » , dû au fait que ces techniques ont été documentées en premier par Steve Freeman auteur du livre Growing Object Oriented Dotware, guided by tests

You may now be interested in :

I really enjoyed speaking and attending sessions at Confoo (thanks to my employer who let me get there !) : the organization was really professional and friendly: some sponsor booths would organize some games and give gifts, sessions were on time and cancelled sessions were twitted in real time, 8 10 tracks in parallel, a very interesting keynote, some very good local and international speakers, talks in French and in English (mostly in English since most of the audience was from outside the province of Québec : 600 people from the US/Europe mainly); as a speaker I also could enjoy a very nice dinner with all the other speakers ( I could meet and re connect with developers and trainers I knew from Paris ! and connect with some local software developers) ; and finally this event took place in downtown Montreal, I am so proud I can attend such quality events from here !

I can’t wait for Confoo 2013 !