HtmlStapler is a nice tool for automatic packaging of web resources included by HTML page: javascript and CSS files. Just by enabling HtmlStapler in your web application, all multiple resource (javascript and css) links will be automatically and transparently replaced by a single link(s) - without any user modification of HTML page or using of server-side technologies.
This significantly speeds up the webpage load time and the page response as instead of loading multiple resource files, page will load just one/two links. In the same time, development would remain the same, since resource bundle management happens in the runtime, transparently from a user.

Ain't that cool? :)
Each HTML page is parsed (using fast Lagarto parser) and all javascript and CSS links are collected. The first resource link of the same type (javascript/css) is replaced with a link to HtmlStaplerServlet. All other links of the same type are removed from the page.
After parsing, HtmlStapler copies content of all collected links into single file, called the bundle file. These bundle files, therefore, contains the content of all javascript/CSS (i.e. resource) files that a page loads.
In other words, each set of resource files is wrapped in its unique single bundle file. If two pages loads the same resource files, they will share the same bundle file. The order of resources on page is important (by default) i.e. if two pages load the same resources in different order, they will not share the same bundle (this is configurable however).
Each bundle has its own unique id, a bundle id. It is actually a SHA-256 digest of the string that contains all resource links. It is also a file name of the bundle file stored on file system.
Resource content is downloaded/copied as it is, without any modification. Anyhow, HtmlStapler provides a hook to additionally process this content. For example, javascript and CSS can be minimized with some 3rd party tool before stored in bundle file.
Once bundle files are stored on file system, they will be recognized next time when server is (re)started. Therefore, bundle files will be not created again if bundle file with the same bundle id (file name) already exist on file system. To enforce new bundles on each server start, call reset() method after HtmlStaplerBundlesManager is created.
Resources defined between IEs optional condition tags are not collected into bundles. To explicitly ignore a resource, add dummy parameter: jodd-unstaple (for example: /dynamic.js?p1=v1&jodd-unstaple).
HtmlStapler may work in two ways, i.e. strategies, called ACTION_MANAGED and RESOURCES_ONLY. The difference is if the relation between page url and its bundle id is stored in memory, or if bundle id is resolved each time per page.
In this strategy, bundles manager (a component of HtmlStapler) saves relation between page and its resource bundles. For each request action path, resolved bundle ids are stored in memory, so next time when page is called there will be no additional processing at all.
First time when bundle is created, the page is fully parsed and the bundle receives a temporary id (a simple number) that is used on very first page loading. From that point, HtmlStapler binds the real bundle id to the action path and all further page loads will use real bundle id.
ACTION_MANAGED strategy stores relations between bundle files and requested pages. This strategy gives top performances, but consumes memory. Be careful if web application has big number of dynamic links - OutOfMemoryException may be thrown at some point in time.
HtmlStapler stores bundle id for each action path i.e. page of web application. Since many pages share the same resources, they will also share the same bundle id instances.
However, some web applications use REST-like urls for the same page, but for different content. For example, a web application that shows daily forecast, might have urls like: /weather/2011/10/28/SanFrancisco. The number of such urls may be virtually infinitely, so HtmlStapler may throw OutOfMemoryException at one point.
To solve this issue within this strategy, user should override resolveRealActionPath() and return the main action path for dynamic ones, i.e. /weather.html.
In this strategy, bundles manager doesn't store relations between page and bundle ids. Instead, the bundle id is resolved each time page is called from collected set of links. Of course, bundle file is created just for the first time, if it already does not exist on the file system under the same bundle name.
RESOURCE_ONLY strategy resolves bundle id every time page is loaded. This is very pragmatic strategy that gives slightly slower performances, but there is no additional memory consumption.
It's quite easy to setup HtmlStapler, easy like 1-2-3.
Create Lagarto filter that uses HtmlStaplerTagAdapter. Create and register HtmlStaplerBundlesManager:
public class AppLagartoServletFilter extends SimpleLagartoServletFilter {
protected HtmlStaplerBundlesManager bundlesManager;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
super.init(filterConfig);
bundlesManager = new HtmlStaplerBundlesManager(filterConfig.getServletContext(), STRATEGY);
bundlesManager.reset(); // if you do not want to preserve bundle files
}
@Override
protected LagartoParsingProcessor createParsingProcessor() {
return new LagartoParsingProcessor() {
@Override
protected char[] parse(TagWriter rootTagWriter, HttpServletRequest request) {
StripHtmlTagAdapter stripHtmlTagAdapter =
new StripHtmlTagAdapter(rootTagWriter);
HtmlStaplerTagAdapter htmlStaplerTagAdapter =
new HtmlStaplerTagAdapter(stripHtmlTagAdapter, request);
char[] content = invokeLagarto(htmlStaplerTagAdapter);
return htmlStaplerTagAdapter.postProcess(content);
}
};
}
@Override
protected boolean acceptActionPath(HttpServletRequest request, String actionPath) {
// skip html stapler servlet path from Lagarto processing!!!
if (actionPath.equals(bundlesManager.getStaplerServletPath())) {
return false;
}
return super.acceptActionPath(request, actionPath);
}
}
Note: in this example we have created two Lagarto TagAdapters: one is for stripping unnecessary characters and comments from HTML page and the other one is for HtmlStapler. Of course, you can add other adapters if you need or use just one.
Be sure to exclude HtmlStapler servlet path from Lagarto parsing (see: acceptActionPath() method in above example).
Register Lagarto filter in web.xml:
... <filter> <filter-name>lagarto</filter-name> <filter-class>com.app.filter.AppLagartoServletFilter</filter-class> </filter> <filter-mapping> <filter-name>lagarto</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ...
Register HtmlStapler servlet in web.xml:
... <servlet> <servlet-name>jodd-html-stapler</servlet-name> <servlet-class>jodd.lagarto.adapter.htmlstapler.HtmlStaplerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>jodd-html-stapler</servlet-name> <url-pattern>/jodd-bundle</url-pattern> </servlet-mapping> ...
And that is all!
HtmlStapler is quite flexible. The main configuration is done in HtmlStaplerBundlesManager. You can configure the following:
bundleFolder - path to bundles folder where bundles files will be created. By default it is a system temp folder, but it is recommended to use Lagarto-specific folder.staplerServletPath - servlet path of HtmlStaplerServlet, as defined in web.xmlbundleFilenamePrefix - prefix for bundle files names.sortResources - flag that specifies if resource links should be sorted before bundle id (i.e. a digest) is created.downloadLocal - by default, local resources are copied from file system, using web root folder. If this does not work, local files can be downloaded instead.localAddressAndPort - local address and port that will be used for downloading local resources if specified so.As said, HtmlStapler is easy to extend.
One nice idea might be to use FileLFUCache for loading bundle files in HtmlStaplerServlet. You can do this easily by overriding writeBundleFile().
Another idea might be to integrate javascript/css compressors - just override onResourceContent().
Consider to add a 'development' flag that disables HtmlStapler during development.
Not satisfied with the digest creation? Override createDigest() and use your own.
If you are not sure which strategy to use, go with pragmatic RESOURCE_ONLY.