14 Resource management with Wicket - Reference Documentation
Authors: Andrea Del Bene, Carsten Hufe, Christian Kroemer, Daniel Bartl
Version: 1.0.0.BUILD-SNAPSHOT
Table of Contents
14 Resource management with Wicket
One of the biggest challenge for a web framework is to offer an efficient and consistent mechanism to handle internal resources such as CSS/JavaScript files, picture files, pdf and so on. Resources can be static (like an icon used across the site) or dynamic (they can be generated on the fly) and they can be made available to users as a download or as a simple URL.In paragraph 4.6 we have already seen how to add CSS and JavaScript contents to the header section of the page. In the first half of this chapter we will learn a more sophisticated technique that allows us to manage static resources directly from code and “pack” them with our custom components.Then, in the second part of the chapter we will see how to implement custom resources to enrich our web application with more complex and dynamic functionalities.14.1 Static vs dynamic resources
In Wicket a resource is an entity that can interact with the current request and response and It must implement interface org.apache.wicket.request.resource.IResource. This interface defines just method respond(IResource.Attributes attributes) where the nested class IResource. Attributes provides access to request, response and page parameters objects.Resources can be static or dynamic. Static resources don't entail any computational effort to be generated and they generally correspond to a resource on the filesystem. On the contrary dynamic resources are generated on the fly when they are requested, following a specific logic coded inside them.An example of dynamic resource is the built-in class CaptchaImageResource in package org. apache.wicket.extensions.markup.html.captcha which generates a captcha image each time is rendered.As we will see in paragraph 13.6, developers can build custom resources extending base class org.apache.wicket.request.resource.AbstractResource.14.2 Resource references
Most of the times in Wicket we won't directly instantiate a resource but rather we will use a reference to it. Resource references are represented by abstract class org.apache.wicket.request.resource .ResourceReference which returns a concrete resource with factory method getResource(). In this way we can lazy-initialize resources loading them only the first time they are requested.14.3 Package resources
With HTML we use to include static resources in our pages using tags like <script>, <link> or <img>. This is what we have done so far writing our custom panels and pages. However, when we work with a component-oriented framework like Wicket, this classic approach becomes inadequate because it makes custom components hardly reusable. This happens when a component depends on a big number of resources. In such a case, if somebody wanted to use our custom component in his application, he would be forced to know which resources it depends on and make them available.To solve this problem Wicket allows us to place static resource files into component package (like we do with markup and properties files) and load them from component code.These kinds of resources are called package resources (a CSS and a JavaScript file in this screenshot):With package resources custom components become independent and self-contained and client code can use them without worrying about their dependencies.To load package resources Wicket provides class org.apache.wicket.request.resource. PackageResourceReference.To identify a package resource we need to specify a class inside the target package and the name of the desired resource (most of the times this will be a file name).In the following example taken from project ImageAsPackageRes, CustomPanel loads a picture file available as package resource and it displays it in a <img> tag using the built-in component org. apache.wicket.markup.html.image.Image:HTML:<html>
<head>...</head>
<body>
<wicket:panel>
Package resource image: <img wicket:id="packageResPicture"/>
</wicket:panel>
</body>
</html>
public class CustomPanel extends Panel { public CustomPanel(String id) { super(id); PackageResourceReference resourceReference = new PackageResourceReference(getClass(), "calendar.jpg"); add(new Image("packageResPicture", resourceReference)); } }
<path to application root>/wicket/resource/<fully qualified class name>/<resource file name> -<ver-<id>>.
In our example the URL for our picture file calendar.jpg is the following:./wicket/resource/org.wicketTutorial.CustomPanel/calendar-ver-1297887542000.jpg
The first part of the URL is the relative path to the application root. In our example our page is already at the application's root so we have only a single-dotted segment. The next two segments, wicket and resource, are respectively the namespace and the identifier for resources seen in paragraph 8.6.4.The fourth segment is the fully qualified name of the class used to locate the resource and it is the scope of the package resource. In the last segment of the URL we can find the name of the resource (the file name).As you can see Wicket has automatically appended to the file name a version identifier (ver-1297887542000). When Wicket runs in DEVELOPMENT mode this identifier contains the timestamp in millisecond indicating the last time the resource file was modified. This can be useful when we are developing our application and resource files are frequently modified. Appending the timestamp to the original name we are sure that our browser will use always the last version of the file and not an old, out of date, cached version.When instead Wicket is running in DEPLOYMENT mode, the version identifier will contain the MD5 digest of the file instead of the timestamp. The digest is computed only the first time the resource is requested. This perfectly makes sense as static resources don't change so often when our application runs into production environment and when this appends the application is redeployed.Package resources can be localized following the same rules seen for resource bundles and markup files:In the example illustrated in the picture above, if we try to retrieve package resource calendar.jpg when the current locale is set to French, the actual file returned will be calendar_fr.jpg.
Using package resources with tag <wicket:link>
In paragraph 8.3 we have used tag <wicket:link> to automatically create links to bookmarkable pages. The same technique can be used also for package resources in order to use them directly from markup file. Let's assume for example that we have a picture file called icon.png placed in the same package of the current page. Under these conditions we can display the picture file using the following markup fragment:<wicket:link>
<img src="icon.png"/>
</wicket:link>
14.4 Adding resources to page header section
Wicket comes with interface org.apache.wicket.markup.html.IHeaderContributor which allows components and behaviors (which will be introduced later in paragraph 15.1) to contribute to the header section of their page. The only method defined in this interface is renderHead (IHeaderResponse response) where IHeaderResponse is an interface which defines method render(HeaderItem item) to write static resources or free-form text into the header section of the page.Header entries are instances of abstract class org.apache.wicket.markup.head.HeaderItem. Wicket provides a set of built-in implementations of this class suited for the most common types of resources. With the exception of PriorityHeaderItem, every implementation of HeaderItem is an abstract factory class:- CssHeaderItem: represents a CSS resource. Factory methods provided by this class are forReference which takes in input a resource reference, forUrl which creates an CSS item from a given URL and forCSS which takes in input an arbitrary CSS string and an optional id value to identify the resource.
- JavaScriptHeaderItem: represents a JavaScript resource. Just like CssHeaderItem it provides factory methods forReference and forUrl along with method forScript which takes in input an arbitrary string representing the script and an optional id value to identify the resource.
- OnDomReadyHeaderItem: it adds JavaScript code that will be executed after the DOM has been built, but before external files (such as picture, CSS, etc...) have been loaded. The class provides a factory method forScript which takes in input an arbitrary string representing the script to execute.
- OnEventHeaderItem: the JavaScript code added with this class is executed when a specific JavaScript event is triggered on a given DOM element. The factory method is forScript(String target, String event, CharSequence javaScript), where target is the id of a DOM element (or the element itself), event is the event that must trigger our code and javaScript is the code to execute.
- OnLoadHeaderItem: the JavaScript code added with this class is executed after the whole page is loaded, external files included. The factory method is forScript(CharSequence javaScript).
- PriorityHeaderItem: it wraps another header item and ensures that it will have the priority over the other items during rendering phase.
- StringHeaderItem: with this class we can add an arbitrary text to the header section. Factory method is forString(CharSequence string).
public class MyComponent extends Component{ @Override public void renderHead(IHeaderResponse response) { PackageResourceReference cssFile = new PackageResourceReference(this.getClass(), "style.css"); CssHeaderItem cssItem = CssHeaderItem.forReference(cssFile); response.render(cssItem); } }
14.5 Resource dependencies
Class ResourceReference allows to specify the resources it depends on overriding method getDependencies(). The method returns an iterator over the set of HeaderItems that must be rendered before the resource referenced by ResourceReference can be used. This can be really helpful when our resources are JavaScript or CSS libraries that in turn depend on other libraries.For example we can use this method to ensure that a custom reference to JQueryUI library will find JQuery already loaded in the page:Url jqueyuiUrl = Url.parse("https://ajax.googleapis.com/ajax/libs/jqueryui/" + "1.10.2/jquery-ui.min.js");UrlResourceReference jqueryuiRef = new UrlResourceReference(jqueyuiUrl){ @Override public Iterable<? extends HeaderItem> getDependencies() { Application application = Application.get(); ResourceReference jqueryRef = application.getJavaScriptLibrarySettings(). getJQueryReference(); return Arrays.asList(JavaScriptHeaderItem.forReference(jqueryRef)); } };
14.6 Custom resources
In Wicket the best way to add dynamic functionalities to our application (such as csv export, a pdf generated on the fly, etc...) is implementing a custom resource. In this paragraph as example of custom resource we will build a basic RSS feeds generator which can be used to publish feeds on our site (project CustomResourceMounting). Instead of generating a RSS feed by hand we will use Rome framework and its utility classes.As hinted above in paragraph 13.1, class AbstractResource can be used as base class to implement new resources. This class defines abstract method newResourceResponse which is invoked when the resource is requested. The following is the code of our RSS feeds generator:public class RSSProducerResource extends AbstractResource { @Override protected ResourceResponse newResourceResponse(Attributes attributes) { ResourceResponse resourceResponse = new ResourceResponse(); resourceResponse.setContentType("text/xml"); resourceResponse.setTextEncoding("utf-8"); resourceResponse.setWriteCallback(new WriteCallback() { @Override public void writeData(Attributes attributes) throws IOException { OutputStream outputStream = attributes.getResponse().getOutputStream(); Writer writer = new OutputStreamWriter(outputStream); SyndFeedOutput output = new SyndFeedOutput(); try { output.output(getFeed(), writer); } catch (FeedException e) { throw new WicketRuntimeException("Problems writing feed to response..."); } } }); return resourceResponse; } // method getFeed()… }
add(new ResourceLink("rssLink", new RSSProducerResource()));
14.7 Mounting resources
Just like pages also resources can be mounted to a specific path. Class WebApplication provides method mountResource which is almost identical to mountPage seen in paragraph 8.6.1:@Override public void init() { super.init(); //resource mounted to path /foo/bar ResourceReference resourceReference = new ResourceReference("rssProducer"){ RSSReaderResource rssResource = new RSSReaderResource(); @Override public IResource getResource() { return rssResource; }}; mountResource("/foo/bar", resourceReference); }
@Override public void init() { super.init(); //resource mounted to path /foo with a required indexed parameter ResourceReference resourceReference = new ResourceReference("rssProducer"){ RSSReaderResource rssResource = new RSSReaderResource(); @Override public IResource getResource() { return rssResource; }}; mountResource("/bar/${baz}", resourceReference); }
14.8 Shared resources
Resources can be added to a global registry in order to share them at application-level. Shared resources are identified by an application-scoped key and they can be easily retrieved at a later time using reference class SharedResourceReference. The global registry can be accessed with Application's method getSharedResources. In the following excerpt of code (taken again from project CustomResourceMounting) we register an instance of our custom RSS feeds producer as application-shared resource://init application's method @Override public void init(){ RSSProducerResource rssResource = new RSSProducerResource(); // … getSharedResources().add("globalRSSProducer", rssResource); }
add(new ResourceLink("globalRssLink", new SharedResourceReference("globalRSSProducer")));
./wicket/resource/org.apache.wicket.Application/globalRSSProducer
The last segment of the URL is the key of the resource while the previous segment contains the scope of the resource. For application-scoped resources the scope is always the fully qualified name of class Application. This should not be surprising since global resources are visible at application level (i.e. the scope is the application).Package resources are also application-shared resources but they don't need to be explicitly registered.
Remember that we can get the URL of a resource reference using method urlFor(ResourceReference resourceRef, PageParameters params ) available with both class RequestCycle and class Component.
14.9 Customizing resource loading
Wicket loads application's resources delegating this task to a resource locator represented by interface org.apache.wicket.core.util.resource.locator.IResourceStreamLocator. To retrieve or modify the current resource locator we can use the getter and setter methods defined by setting interface IResourceSettings://init application's method
@Override
public void init(){
//get the resource locator
getResourceSettings().getResourceStreamLocator();
//set the resource locator
getResourceSettings().setResourceStreamLocator(myLocator);
}
@Override public void init() { getResourceSettings().getResourceFinders().add( new WebApplicationPath(getServletContext(), "markupFolder")); }
By default, if resource files can not be found inside application classpath, Wicket will search for them inside “resources” folder. You may have noted this folder in the previous picture. It is placed next to the folder “java” containing our source files:This folder can be used to store resource files without writing any configuration code.