Assets are most commonly stored in the web application's context folder ... stored inside the web application WAR file in the usual JEE fashion. In addition, Tapestry treats files stored on the classpath, with your Java class files, as assets visible to the web browser.
Assets are exposed to your code as instances of the Asset interface.
Assets in Templates
Assets can also be referenced directly in templates. Two binding prefixes exist for this: "asset:" and "context:". The "asset:" prefix can obtain assets from the classpath (the default) or from the web context (by specifying the "context:" domain explicitly):
Because accessing context assets is so common, the "context:" binding prefix was introduced:
Assets in Component Classes
Components learn about assets via injection. The @Inject annotation allows Assets to be injected into components as read-only properties. The path to the resource is specified using the Path annotation:
Assets are located within domains; these domains are identified by the prefix on the @Path annotation's
If the prefix is omitted, the value will be interpreted as a path relative to the Java class file itself, within the "classpath:" domain. This is often used when creating component libraries, where the assets used by the components are packaged in the JAR with the components themselves.
Unlike elsewhere in Tapestry, case matters. This is because Tapestry is dependent on the Servlet API and the Java runtime to access the underlying files, and those APIs, unlike Tapestry, are case sensitive. Be aware that some operating systems (such as Windows) are case insensitive, which may mask errors that will be revealed at deployment (if the deployment operating system is case sensitive, such as Linux).
You can use relative paths with domains (if you omit the prefix):
Since you must omit the prefix, this only makes sense for components packaged in a library for reuse.
Symbols For Assets
Symbols inside the annotation value are expanded. This allows you to define a symbol and reference it as part of the path. For example, you could contribute a symbol named "skin.root" as "context:skins/basic" and then reference an asset from within it:
An override of the skin.root symbol would affect all references to the named asset.
Localization of Assets
Main Article: Localization
Assets are localized; Tapestry will search for a variation of the file appropriate to the effective locale for the request. In the previous example, a German user of the application may see a file named
edit_de.png (if such a file exists).
New Asset Domains
If you wish to create new domains for assets, for example to allow assets to be stored on the file system or in a database, you may define a new AssetFactory and contribute it to the AssetSource service configuration.
Tapestry creates a new URL for assets (whether context or classpath). The URL is of the form /assets/version/folder/path.
- version: Application version number, defined by the
tapestry.application-versionsymbol in your application module (normally AppModule.java). The default is a random hex number.
- path: The path below the root package of the library to the specific asset file.
Assets are expected to be entirely static (not changing while the application is deployed). This allows Tapestry to perform some important performance optimizations.
Tapestry GZIP compresses the content of all assets – if the asset is compressible, the client supports it, and you don't explicitly disable it.
When Tapestry generates a URL for an asset, either on the classpath or from the context, the URL includes the application version number. Further, the asset will get a far future expires header, which will encourage the client browser to cache the asset.
You should have an explicit application version number for any production application. Client browsers will aggressively cache downloaded assets; they will usually not even send a request to see if the asset has changed once the asset is downloaded the first time. Because of this it is very important that each new deployment of your application has a new version number, to force existing clients to re-download all assets.
Because Tapestry directly exposes files on the classpath to the clients, some thought has gone into ensuring that malicious clients are not able to download assets that should not be visible to them.
First off all, there's a package limitation: classpath assets are only visible if there's a LibraryMapping for them, and the library mapping substitutes for the initial folders on the classpath. Since the most secure assets, things like
hibernate.cfg.xml are located in the unnamed package, they are always off limits.
But what about other files on the classpath? Imagine this scenario:
- Your Login page exposes a classpath asset,
A malicious client copies the URL,
/assets/1.0.0/app/pages/icon.png (which would indicate that the Login page is actually inside a library, which is unlikely. More likely, icon.png is a context asset and the malicious user guessed the path for Login.class by looking at the Tapestry source code.) and changes the file name to
The client decompiles the class file and spots your secret emergency password: goodbye security! (Never create such back doors, of course!)
Fortunately, this can't happen. Files with extension ".class" are secured; they must be accompanied in the URL with a query parameter that is the MD5 hash of the file's contents. If the query parameter is absent, or doesn't match the actual file's content, the request is rejected.
When your code exposes an Asset, the URL will automatically include the query parameter if the file type is secured. The malicious user is locked out of access to the files. (Unless they already have the files so that they can generate the MD5 checksum ... to get access to the files they already have.)
By default, Tapestry secures file extensions ".class', ".tml" and ".properties". The list can be extended by contributing to the ResourceDigestGenerator service:
If you want to add your own minimizer for particular types of assets, you can contribute to the ResourceMinimizer service. The service configuration maps the MIME-TYPE of your resource to an implementation of the ResourceMinimizer interface.