Wiki Markup |
---|
{scrollbar} |
Excerpt | ||
---|---|---|
| ||
Adding an Annotation and a Filter to customize Tapestry's page rendering |
Meta-Programming Page Content
...
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 CompnentResources 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
Code Block | ||||
---|---|---|---|---|
| ||||
package com.snorgfnord.annotations; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Marker annotation for pages that should not allow framing. */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface ForbidFraming { } |
...
Defining the Constant
Code Block | ||||
---|---|---|---|---|
| ||||
package com.snorgfnord; import org.apache.tapestry5.http.services.BaseURLSource; import com.snorgfnord.annotations.ForbidFraming; public class SnorgSymbolsFnordSymbols { /** * Meta-data key; when true, MarkupRendererFilter will inject some extra * content into the response to enforce that the content may not be framed * (i.e., "stolen"). * * @see ForbidFraming */ public static final String FORBID_FRAMING = "forbid-framing"; } |
...
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.
Code Block | ||||
---|---|---|---|---|
| ||||
package com.snorgfnord.services.forbidframing; import org.apache.tapestry5.ioccommons.MappedConfiguration; import org.apache.tapestry5.ioc.annotations.Contribute; import org.apache.tapestry5.ioc.services.FactoryDefaults; import org.apache.tapestry5.ioc.services.SymbolProvider; import com.snorgfnord.SnorgSymbolsFnordSymbols; public class ForbidFramingModule { @Contribute(SymbolProvider.class) @FactoryDefaults public static void setupForbidFramingDefault( MappedConfiguration<String, String> configuration) { configuration.add(SnorgSymbolsFnordSymbols.FORBID_FRAMING, "false"); } } |
...
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.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@Contribute(MetaWorker.class) public static void mapAnnotationsToMetaDataValue( MappedConfiguration<Class, MetaDataExtractor> configuration) { configuration .add(ForbidFraming.class, new FixedExtractor<ForbidFraming>( SnorgSymbolsFnordSymbols.FORBID_FRAMING)); } |
If the ForbidFraming annotation has had attributes, we would have provided an implementation of MetaDataExtractor that examined those attributes to set the meta-data value. Since it has no annotationsattributes, the FixedExtractor class can be used. The argument is the meta-data key, and the default value is "true".
...
The work we ultimately want to do occurs when rendering a page. Tapestry defines a pipeline for that overall process. The point of a pipeline is that we can add filters to it. We'll add a filter that checks for the meta-data key and adds the response header and JavaScript.
...
We contribute into the pipeline; the order is important: since the filter will need to write JavaScript, it must be added after the built-in filter that provides the JavaScriptSupport environmental object.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@Contribute(MarkupRenderer.class)
public static void addFilter(
OrderedConfiguration<MarkupRendererFilter> configuration) {
configuration.addInstance("ForbidFraming", ForbidFramingFilter.class,
"after:JavascriptSupport");
}
|
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:
Code Block | ||||
---|---|---|---|---|
| ||||
package com.snorgfnord.services.forbidframing; import org.apache.tapestry5.MarkupWriter; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.services.MarkupRenderer; import org.apache.tapestry5.services.MarkupRendererFilter; import org.apache.tapestry5.services.MetaDataLocator; import org.apache.tapestry5.services.RequestGlobals; import org.apache.tapestry5.services.Response; import org.apache.tapestry5.services.javascript.InitializationPriority; import org.apache.tapestry5.services.javascript.JavaScriptSupport; import com.snorgfnord.SnorgSymbolsFnordSymbols; public class ForbidFramingFilter implements MarkupRendererFilter { @Inject private RequestGlobals requestGlobals; @Inject private MetaDataLocator metaDataLocator; @Inject private Response response; @Inject private JavaScriptSupport jsSupport; public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) { String pageName = requestGlobals.getActivePageName(); boolean forbidFraming = metaDataLocator.findMeta( SnorgSymbolsFnordSymbols.FORBID_FRAMING, pageName, boolean.class); if (forbidFraming) { response.setHeader("X-Frame-Options", "DENY"); jsSupport.addScript(InitializationPriority.IMMEDIATE, "SnorgFnord.popOutOfFrame();"); } renderer.renderMarkup(writer); } } |
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.
...
This code makes one assumption: that the snorg fnord application's Layout component added snorgfnord.js to every page. That's necessary for the JavaScript that's added:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
SnorgFnord = { popOutOfFrame : function() { if (top != self) top.location.replace(location); } } |
...
That's it: with the above code, simply adding the @ForbidFraming annotation to a page will add the response header and associated JavaScript; no inheritance hasselshassles. This basic pattern can be applied to a wide range of cross-cutting concerns, such as security, transaction management, logging, or virtually any other kind of situation that would normally be solved with inheritance or ugly boilerplate code.
Note |
---|
The code in this example was designed for Tapestry version 5.2 . Some names were changed to maintain the anonymity of the client (whose project is still secret at the time of writing)and later. |