Meta-Programming Page Content
It is likely that you have some cross-cutting concerns across your pages, specific features you would like to "mix in" to your pages without getting tied into knots by inheritance. This is one of those areas where Tapestry shines.
This specific example is adapted from a real client requirement: the client was concerned about other sites wrapping his content in a frameset and making the site content appear to be theirs. Not all pages (in some cases, that would be an advantage) but specific pages in the application. For those pages, the following behaviors were required:
- Set the X-Frame-Options response header to "DENY"
Again, this could be done by having a specific base-class that included a
beginRender() method, but I think you'll see that the meta-programming approach is nearly as easy and much more flexible.
In Tapestry, every component (and remember, pages are components) has meta data: an extra set of key/value pairs stored in the component's ComponentResources.
By hooking into the component class transformation pipeline, we can change an annotation into meta-data that can be accessed by a filter.
Defining the Annotation
This annotation presence is all that's needed; there aren't any additional attributes to configure it.
Converting the Annotation into Meta-Data
This is in three parts:
- Define the meta-data key, and define a constant for that key
- Set a default meta-data value for the key
- Set a different value for the key when the annotation is present
Our key is just "forbid-framing", with values "true" and "false". The default is "false".
Defining the Constant
Setting the Meta-Data Default
Next, we'll create a module just for the logic directly related to framing. In the module, we'll define the default value for the meta-data.
Mapping the Annotation
Most of the work has already been done for us: we just have to make a contribution to the MetaWorker service, which is already plugged into the component class transformation pipeline. MetaWorker spots the annotations we define and uses a second object, a MetaDataExtractor we provide, to convert the annotation into a meta-data value.
If the ForbidFraming annotation has attributes, we would provided an implementation of MetaDataExtractor that examined those attributes to set the meta-data value. Since it has no annotations, the FixedExtractor class. The argument is the meta-data key, and the default value is "true".
Plugging Into Page Rendering
How do you know what filters are built-in and where to add your own? The right starting point is the JavaDoc for the method of TapestryModule that contributes the base set: contributeMarkupRenderer()
Implementing the Filter
Everything comes together in the filter:
There's a bit going on in this short piece of code. The heart of the code is the MetaDataLocator service; given a meta-data key and a page name, it can not only extract the value, but then coerce it to a desired type, all in one go.
How do we know which page is being rendered? Before Tapestry 5.2 that was a small challenge, but 5.2 adds a method to RequestGlobals for this exact purpose.
Of course, it is vitally important that the filter re-invoke
markup() on the next renderer in the pipeline (you can see that as the last line of the method).
The code in this example was designed for Tapestry 5.2. Some names were changed to maintain the anonymity of the client (whose project is still secret at the time of writing).