Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migration of unmigrated content due to installation of a new plugin


Wiki Markup
{scrollbar}


Excerpt
hiddentrue

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
languagejava
titleForbidFraming.java

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
languagejava
titleSnorgSymbolsFnordSymbols.java

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
languagejava
titleForbidFramingModule.class

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
languagejava
langjava
titleForbidFramingModule.java (partial)

  @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
languagejava
langjava
titleForbidFramingModule.java (partial)

  @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
languagejava
titleForbidFramingFilter.java

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
languagejs
langjavascript
titlesnorgfnord.js (partial)

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.