Wicket: Annotation-based Mounting of Resources

October 13th, 2009 by  |  Published in Java, Wicket, wicketstuff-merged-resources  |  19 Comments

Today, I’m happy to announce the availability of annotation-based mounting and merging of resources in wicketstuff-merged-resources (version 3.0-SNAPSHOT for Wicket 1.4, version 2.1-SNAPSHOT for Wicket 1.3). In order to mount resources, all that’s needed is adding annotations to component classes:

@JsContribution
@CssContribution(media = "print")
@ResourceContribution(value = "accept.png", path = "/img/accept.png")
public class PanelOne extends Panel {
 
    public PanelOne(String id) {
        super(id);
        // ...
    }
}


The example above mounts and merges PanelOne.js (default is simple name of class + “.js”) into all.js (another default) and PanelOne-print.css into print.css which will be included with scope print. Additionally, accept.png will be mounted to /img/accept.png (as path starts with a ‘/’) which makes it easy to use from css files.

There’s not much left for this to work. All it takes is a single method call in your Application’s init code:

ResourceMount.mountAnnotatedPackageResources("/files", "com.example.components", this, mount);

In this example, all components in package com.example.components (including sub-packages) will be scanned for annotations. If not defined otherwise, all resources will be merged into single files for JS (/files/all.js) and all CSS media types (/files/all.css, /files/screen.css, /files/print.css, …).

As an added benefit, you’ll get all the other features of wicketstuff-merged-resources:

  • merging of multiple files into one for less HTTP requests
  • adding of versions to resource paths for aggressive caching
  • pre-processing of resources (e.g. replacing colors in CSS files)
  • optionally uploading them to Amazon Cloudfront (well, at least you can expect this feature soon – we are using it already)

So you will speed up rendering of your pages while simplifying and reducing your code (there’s no need to merge, mount or add HeaderContributors manually anymore)!

Further instructions are available on wicketstfuff-merged-resources’ page on Wicket Stuff Wiki.

(This new feature is mainly based on the idea and code of Jörn Zaefferer – Tanks!)

Responses

  1. Thijs says:

    October 14th, 2009 at 9:12 am (#)

    Hi, this is great.
    But is it also possible to define the js and css files in the annotiations?

    For example I’m writing a component that has several js files and several css files.

    Can I do something like:

    @JsContribution(name=”xyz.js”)
    @JsContribution(name=”xyz2.js”)
    @JsContribution(name=”xyz3.js”)
    class Abc extends Panel

  2. Stefan Fußenegger says:

    October 14th, 2009 at 9:16 am (#)

    It is:

    @JsContribution(“xyz.js”)
    class Aby extends Panel {}

    for a single resource or

    @JsContribution(value = {“xyz.js”, “xyz2.js”, “xyz3.js”})
    class Aby extends Panel {}

    for multiple resources

    You can do the same for CSS, even if you need different media types:

    @CssContributions({@CssContribution(“my.css”),@CssContribution(value = {“my-print.css”, “my-second-print.css”}, media=”print”)})
    class Aby extends Panel {}

  3. dpeters says:

    October 14th, 2009 at 9:38 am (#)

    Hello, this is really nice! thank you for sharing!

    You have a small typo here: “…PanelOne-print.js into print.css…”

    Just some small questions without having looked at it yet:
    Is gzip-compression supported?
    Can comments be stripped automatically?
    Maybe strip unnecessary whitespaces etc and js-obfuscation, too? ;)

    regards,
    Daniel

  4. Stefan Fußenegger says:

    October 14th, 2009 at 9:46 am (#)

    @dpeters

    Typo: Even after telling me, I had to look twice go get it, thanks :)

    gzip: yes

    strip comments: yes (currently I’m using Wicket’s built-in JS compressor and YUI’s CSS compressor)

    obfuscation: no, as Wicket’s JS compressor doesn’t support it. However, it should be easy to implement using a custom IResourcePreProcessor (and an obfuscater written in Java of course)

  5. Thijs says:

    October 16th, 2009 at 2:25 pm (#)

    Other question:

    I have 2-3 javascript files that are depending on one another.
    4example: jquery, jquery.fancybox and query-qtip

    I have and abstract page
    @JsContribution(value={”jquery.js”,”query-qtip.js”})
    public abstract class BasePage extends WebPage

    and a Panel
    @JsContribution(value=”jquery.fancybox.js”)
    public ImagePanel exends Panel

    I now get a Javascript error telling me that jquery is undefined because in all.js it is the last appended javascript bit.
    Is there a way to define some sort of order?

  6. Stefan Fußenegger says:

    October 16th, 2009 at 3:37 pm (#)

    Hi Thijs,

    No, currently there is no way to define the order, at least not when using annotations. Feel free to add a ticket at http://wicketstuff.org/jira/browse/WMR

    I’ll try to come up with something ASAP.

  7. Stefan Fußenegger says:

    October 20th, 2009 at 2:48 pm (#)

    Hi Thijs,

    I’ve just committed a fix for http://wicketstuff.org/jira/browse/WMR-9 (thanks for adding it). A fresh build should already be available from wicketstuff’s maven repository.

    Cheers

  8. Thijs says:

    October 22nd, 2009 at 12:27 pm (#)

    Thanx!

  9. Oskar says:

    December 2nd, 2009 at 7:48 am (#)

    Hi again,

    First of all, thank you for your time and dedication with this project, I think is really useful and interesting.

    I just can’t have it working and I don’t know the reason, I would really appreciate if you can spend a few minutes trying to help me if you don’t mind.

    I’m using Wicket 1.4.3 and the annotations approach. My javascript files are all in /js not in the java folder.

    So what I do is go the init() of my webapp object and write this:
    ResourceMount mount = new ResourceMount();
    ResourceMount.mountAnnotatedPackageResources(“/js”, “MYApplication.Page”, this, mount);

    Then I go to MyApplication.Page.HomePage and write this annotation:
    @JsContribution(“jquery.min.js”)

    When I try to run the application, I get this error:
    failed to mount resource (‘/js/all.js’)
    WicketRuntimeException: js/all.js is already mounted for SharedResourceEncoder

    I’m sure I’m understanding something wrong… hope you can help me…

    Thank you again,
    Oskar

  10. Stefan Fußenegger says:

    December 2nd, 2009 at 9:14 am (#)

    Hi Oskar,

    once again, please send a quick-start (i.e. a small wicket application) that demonstrates your problem. I’ll have a look at it.

    Cheers, Stefan

  11. Matt says:

    January 28th, 2010 at 7:45 pm (#)

    Hi Stefan,

    I really like what I see with merged-resources 3.0. However I can’t go to production with my code while merged-resources is still a SNAPSHOT.

    Do you plan on tagging a release soon?

    If there are outstanding issues that need to be wrapped up prior to a release, I may be able to help by contributing to the project. Feel free to email me.

  12. Stefan Fußenegger says:

    January 29th, 2010 at 12:15 pm (#)

    Hi Matt,

    well, actually I planned to release 3.0 for a while. The only problem is that I’m still using 2.x (as I’m currently stuck on Wicket 1.3).

    However, if you think 3.0 works and is ready for prime time, I would release it. Therefore, my idea would be that you keep developing and testing with 3.0-SNAPSHOT, but as soon as your project is ready for production, I will create a release.

    If you’re interested in helping with open issues (currently, there aren’t any though), see http://wicketstuff.org/jira/browse/WMR

    Cheers, Stefan

  13. Brandon Fuller says:

    January 30th, 2010 at 2:16 am (#)

    First off, thanks for this package. It was exactly what I was looking for! I have everything working just fine on version 2.0. However, I keep getting serialization errors for your components. Here is a log:

    8:14:25,909 INFO [STDOUT] 2010-01-29 18:14:25,909 ERROR [org.apache.wicket.util.lang.Objects] – Error serializing object class com.foo.txbu.directory.markup.html.pages.LoginPage [object=[Page class = com.foo.txbu.directory.markup.html.pages.LoginPage, id = 5, version = 0]]
    org.apache.wicket.util.io.SerializableChecker$WicketNotSerializableException: Unable to serialize class: org.wicketstuff.mergedresources.ResourceSpec
    Field hierarchy is:
    5 [class=com.foo.txbu.directory.markup.html.pages.LoginPage, path=5]
    private java.lang.Object org.apache.wicket.MarkupContainer.children [class=[Ljava.lang.Object;]
    protected org.apache.wicket.util.collections.MiniMap org.apache.wicket.markup.html.link.BookmarkablePageLink.parameters[5] [class=com.foo.txbu.directory.markup.html.pages.BasePage$11, path=5:linkLogo]
    private java.lang.Object org.apache.wicket.MarkupContainer.children [class=org.apache.wicket.markup.html.image.Image, path=5:linkLogo:logo]
    private final org.apache.wicket.markup.html.image.resource.LocalizedImageResource org.apache.wicket.markup.html.image.Image.localizedImageResource [class=org.apache.wicket.markup.html.image.resource.LocalizedImageResource]
    private org.apache.wicket.Resource org.apache.wicket.markup.html.image.resource.LocalizedImageResource.resource [class=org.wicketstuff.mergedresources.resources.CachedResource]
    private final org.wicketstuff.mergedresources.resources.MergedResourceStream org.wicketstuff.mergedresources.resources.MergedResource._mergedResourceStream [class=org.wicketstuff.mergedresources.resources.MergedResourceStream]
    private final org.wicketstuff.mergedresources.ResourceSpec[] org.wicketstuff.mergedresources.resources.MergedResourceStream._specs [class=[Lorg.wicketstuff.mergedresources.ResourceSpec;]
    private final org.wicketstuff.mergedresources.ResourceSpec[] org.wicketstuff.mergedresources.resources.MergedResourceStream._specs[0] [class=org.wicketstuff.mergedresources.ResourceSpec] <—– field that is not serializable

    Any ideas?

  14. Matt says:

    January 30th, 2010 at 5:27 am (#)

    Stefan,

    I would appreciate a 3.0 release when you have the chance. I’ve only been using merged-resource on small projects, but it has held up well. I am not, however, using the annotation features, as those didn’t seem to fit my coding style.

    On a related note, I wrapped your code with a simple helper class of my own that has been working well for my purposes:

    http://opensource.55minutes.com/svn/java/trunk/fiftyfive-wicket/src/main/java/fiftyfive/wicket/resource/MergedResourceBuilder.java

    I also figured out how to unit test the merged-resources functionality:

    http://opensource.55minutes.com/svn/java/trunk/fiftyfive-wicket/src/test/java/fiftyfive/wicket/resource/MergedResourceBuilderTest.java

    Thought you might be curious…

  15. Stefan Fußenegger says:

    January 30th, 2010 at 3:32 pm (#)

    @Brandon this bug was already fixed in latest snapshot.

    @Matt looks interesting, especially the test case. I’ll have a closer look next week.

    I’ll release versions 2.1 and 3.0 on Monday.

    Have a nice weekend!

    Cheers, Stefan

  16. Stefan Fußenegger says:

    February 2nd, 2010 at 12:34 pm (#)

    @Brandon version 2.1 should soon be available from maven repository at http://wicketstuff.org/maven/repository/org/wicketstuff/wicketstuff-merged-resources/

    @Matt I’ve just committed changes to ResourceMount inspired by your ResourceMountBuilder. Actually, I don’t really see the main benefit of using yet another builder around a builder API. It’s best to simply extend ResourceMount if you want to change it’s default behaviour. There are lots of protected methods you may override in order to change the defaults. Additionally, you may use ResourceMount#clone() to reuse configuration of a single ResourceMount for multiple mounts. However, I’ve added a new constructor (ResourceMount(boolean)) and a new method (ResourceMount#build(WebApplication)). Please have a look and tell me what you think. It’s the last change to get any suggestions into version 3.0. Lastly, I’ve used your ResourceBuilderTest as ResourceMount test. The only thing it doesn’t support like your ResourceBuilder is detection of JS and CSS files being added to the same merged resource. If you’d like to see this feature in 3.1, please crate a JIRA issue.

    Cheers, Stefan

  17. Matt says:

    February 2nd, 2010 at 10:00 pm (#)

    Thanks, Stefan. This looks good. I will update my code to use your latest changes and also submit a JIRA issue for the JS/CSS detection.

    Give me a couple days to test this out; I’ll let you know later this week if I run into any problems that would need to be considered before the 3.0 release.

  18. Stefan Fußenegger says:

    February 2nd, 2010 at 10:01 pm (#)

    Ok

  19. Matt says:

    February 3rd, 2010 at 4:34 am (#)

    Stefan,

    I think I am going to stay with my builder-wrapper approach for the time being. Extending your class is hard because if I want to add a new public method signature, I’ll need to override every single public method that returns a “this” reference to return the narrower type.

    For example, if I want to add a setCssMedia() method (which as far as I can tell is something that ResourceMount does not currently support), my class will look like this:

    public class CssResourceMount extends ResourceMount
    {

    public CssResourceMount setCssMedia(String media)
    {

    }
    }

    But now if I try to use this new class like this:

    new CssResourceMount()
    .setPath(…)
    .setCssMedia(…)

    I’ll get this compilation error:

    cannot find symbol
    symbol : method setCssMedia(java.lang.String)
    location: class org.wicketstuff.mergedresources.ResourceMount

    Because setPath() is returning ResourceMount, not CssResourceMount. I’d have to override and redefine setPath() (and every other public method that returns a “this” reference) to have the CssResourceMount return signature.

    So yes, I can subclass and override existing methods, but I can’t subclass to effect the public API without a lot of extra work.

    Anyway, I like what I see otherwise in 3.0. Don’t let me hold you up. I’ll add JIRA issues later this week for the CSS media and CSS/JS detection enhancements.

Leave a Response