Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Assets are exposed to your code as instances of the Asset interface.

Injecting Assets

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.

Code Block
java
java
  @Inject
  @Path("context:images/tapestry_banner.gif")
  private Asset banner;

...

Unlike elsewhere in Tapestry, case matters. This is because Tapestry is dependenent 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 insenitive, which may mask errors that will be revealed at deployment (if the deployment operating system is case sensitive, such as Linux).

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):

Code Block
java
java
  <img src="${asset:context:image/tapestry_banner.gif}" alt="Banner"/>
Info

This is an example of using a template expansion inside an ordinary element (rather than a component).

Because accessing context assets is so common, the "context:" binding prefix was introduced:

Code Block
java
java
  <img src="${context:image/tapestry_banner.gif}" alt="Banner"/>

Relative Assets

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:

Code Block
java
java
  @Inject
  @Path("${skin.root}/style.css")
  private Asset style;
Note

The use of the ${...} syntax here is a symbol expansion (because it occurs in an annotation in Java code), rather than a template expansion (which occurs only in Tapestry template files).

An override of the skin.root symbol would affect all references to the named asset.

Localization of Assets

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.

Simplified Paths

Private assets (assets on the classpath) normally have the form: /assets/foo/bar/Baz.css where Baz.css is a file inside the foo.bar Java package. In other words, the package name is converted into a path underneath the virtual folder, /assets/.

You are given some control over this, allowing for shorter paths. The ClasspathAssetAliasManager service has a mapped configuration. The map keys are logical folder names, and the map values are the complete classpath. For example, Tapestry itself makes a contribution similar to the following:

...


    public static void contributeClasspathAssetAliasManager(
            MappedConfiguration<String, String> configuration)
    {
        configuration.add("tapestry/5.1", "org/apache/tapestry5");
    }

Thus, the generated URLs may say /assets/tapestry/5.1/Foo.gif but the underlying file will be /org/apache/tapestry5/Foo.gif (within the classpath).

There are built-in contributions for:

  • tapestry/version
  • scriptaculous/version
  • datepicker/version
  • app/app-version (for assets obtained at or beneath the application package)
  • classpath/app-version (for assets obtained from any otherwise unmapped package)
    Where version is the Tapestry framework version, and app-version is the application version (which will be a random string if not explicitly configured).

If you give your application a version number, it is important to change the version number for each deployment. Assets within the application (whether in the context, or the classpath) will be given a far-future expires header and therefore will be aggresively cached in the client browser. Changing the version number changes the path the browser sees, which forces the browser to load the new and changed version of the asset.

In addition, context assets will use the URL prefix /assets/ctx/app-version/.

Securing Assets

Securing assets is an important consideration for any web application. Many assets, such as hibernate configuration files, sit in the classpath and are exposable via the Asset service, which is not desirable. To protect these and other sensitive assets, Tapestry provides the AssetProtectionDispatcher. This dispatcher sits in front of the AssetDispatcher, the service responsible for streaming assets to the client, and watches for Asset requests. When an asset request comes in, the protection dispatcher checks for authorization to view the file against a contributed list of AssetPathAuthorizer implementations. Determination of whether the client can view the requested resource is then made based on whether any of the contributed AssetPathAuthorizer implementations explicitly allowed or denied access to the resource.

Tapestry provides two AssetPathAuthorizer implemenations "out of the box" to which users may contribute: RegexAuthorizer and WhitelistAuthorizer. RegexAuthorizer uses regular expressions to determine assets which are viewable by the client; any assets that match one of its (contributed) regular expressions are authorized. Anything not matched is passed through to the WhitelistAuthorizer. WhitelistAuthorizer uses an exact-matching whitelist. Anything matching exactly one its contributions is allowed; all other asset requests are denied. The default tapestry configuration contributes nothing to WhitelistAuthorizer (access will be denied to all asset requests passed through to it), and explicitly allows access to css, jpg, jpeg, js, png, and gif files associated with tapestry (tapestry.js, blackbird files, date picker files, etc.). The default contribution also enables access to the css, jpg, jpeg, js, png, and gif files inside any subpackage of the configured app package. The default configuration denies access to all other assets. To enable access to your application's assets, either contribute a custom AssetPathAnalyzer, or contribute appropriate regular expression or exact path contributions to RegexAuthorizer or WhitelistAuthorizer, respectively. See TapestryModule.contribteRegexAuthorizer for examples.

Imagine you wish to allow access to resources outside the app package. Let 'org.example' be your app package and 'org.resources' the package where your assets are located. To allow access to the 'styles.css' file inside 'org.resources' package you need to contribute to WhitelistAuthorizer.

...


    public class AppModule
    {        
        public void contributeWhitelistAuthorizer(final Configuration<String> configuration) 
        {
            configuration.add("org/resources/styles.css");
        }
    }

If you wish to allow access to several resources then a contribution to RegexAuthorizer is a better alternative.

...


    public class AppModule
    {        
        public void contributeRegexAuthorizer(final Configuration<String> configuration) 
        {
                String pattern = "([^/.]+/)*[^/.]+\\.((css)|(js)|(jpg)|(jpeg)|(png)|(gif))$";
                
                configuration.add("^org/resources/" + pattern);
        }
    }

And finally if you wish to blacklist a path that is allowed by Tapestry you should write your own AssetPathAuthorizer. The following one denies access to all configured paths. Note that AssetPathAuthorizer.html#order() defines the order in which methods AssetPathAuthorizer.html#accessAllowed and AssetPathAuthorizer.html#accessDenied are invoked. In this example the method 'AssetPathAuthorizer#accessDenied' is invoked first. If the current path is to be blacklisted 'AssetPathAuthorizer#accessDenied' returns the value 'true' and 'AssetPathAuthorizer#accessAllowed' is not invoked. That's why 'AssetPathAuthorizer#accessAllowed' returns true.

...


    public class BlacklistAuthorizer implements AssetPathAuthorizer 
    {

        private final Collection<String> configuration;

        public DenyAssetPathAuthorizer(final Collection<String> configuration) 
        {
            this.configuration = configuration;
        }

        public boolean accessAllowed(final String someResourcePath) 
        {
            return true;
        }

        public boolean accessDenied(final String someResourcePath) 
        {
            return this.configuration.contains(someResourcePath);
        }

        public List<Order> order() 
        {
            return Arrays.asList(Order.DENY, Order.ALLOW);
        }
    }

Asset URLs

Tapestry creates a new URL for assets (whether context or classpath). The URL is of the form /assets/application version number/folder/path.

  • application version number: Defined by symbol tapestry.application-version, the default value is a random hex number.
  • folder: Identifies the library containing the asset, or "ctx" for a context asset, or "stack" (used when combining multiple JavaScript files into a single virtual asset).
  • path: The path below the root package of the library to the specific asset file.

Then contribute the BlacklistAuthorizer and place it before all other AssetPathAuthorizer.

...


    public class AppModule
    {

        public static void bind(final ServiceBinder binder) 
        {
            binder.bind(AssetPathAuthorizer.class, BlacklistAuthorizer.class).withId("BlacklistAuthorizer");
        }
        
        public void contributeBlacklistAuthorizer(final Configuration<String> configuration) 
        {
            configuration.add("org/example/pages/secret.jpg");
        }
        
        public static void contributeAssetProtectionDispatcher(
                OrderedConfiguration<AssetPathAuthorizer> configuration,
                
                @InjectService("BlacklistAuthorizer") AssetPathAuthorizer blacklist)
        {
                configuration.add("blacklist",blacklist,"before:*");
        }
    }

Performance Notes

Assets are expected to be entirely static (not changing while the application is deployed). When Tapestry generates a URL for an asset, either on the classpath or from the context, the URL includes a version number (as discussed in the previous section). Further, the asset will get a far future expires header, which will encourage the client browser to cache the asset.

In addition, Tapestry will GZIP compress the content of all assets (if the asset is compressable, and the client supports it).

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 is is very important that each new deployment of your application get a new version number: this will force existing clients to re-download all files.