Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: fixed language param of code macro
Wiki Markup
{scrollbar}
Excerpt
hiddentrue

Adding an Annotation and a Filter to customize Tapestry's page rendering

Meta-Programming Page Content

...

Defining the Annotation

Code Block
languagejava
titleForbidFraming.java

package com.fnord.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
titleFnordSymbols.java

package com.fnord;

import org.apache.tapestry5.services.BaseURLSource;

import com.fnord.annotations.ForbidFraming;

public class FnordSymbols {

  /**
   * 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.fnord.services.forbidframing;

import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.annotations.Contribute;
import org.apache.tapestry5.ioc.services.FactoryDefaults;
import org.apache.tapestry5.ioc.services.SymbolProvider;

import com.fnord.FnordSymbols;

public class ForbidFramingModule {

  @Contribute(SymbolProvider.class)
  @FactoryDefaults
  public static void setupForbidFramingDefault(
      MappedConfiguration<String, String> configuration) {
    configuration.add(FnordSymbols.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
titleForbidFramingModule.java (partial)
langjava

  @Contribute(MetaWorker.class)
  public static void mapAnnotationsToMetaDataValue(
      MappedConfiguration<Class, MetaDataExtractor> configuration) {
    configuration
        .add(ForbidFraming.class, new FixedExtractor<ForbidFraming>(
            FnordSymbols.FORBID_FRAMING));
  }

...

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

  @Contribute(MarkupRenderer.class)
  public static void addFilter(
      OrderedConfiguration<MarkupRendererFilter> configuration) {
    configuration.addInstance("ForbidFraming", ForbidFramingFilter.class,
        "after:JavascriptSupport");
  }

...

Everything comes together in the filter:

Code Block
languagejava
titleForbidFramingFilter.java

package com.fnord.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.fnord.FnordSymbols;

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(
        FnordSymbols.FORBID_FRAMING, pageName, boolean.class);

    if (forbidFraming) {
      response.setHeader("X-Frame-Options", "DENY");

      jsSupport.addScript(InitializationPriority.IMMEDIATE,
          "Fnord.popOutOfFrame();");

    }

    renderer.renderMarkup(writer);

  }

}

...

This code makes one assumption: that the fnord application's Layout component added fnord.js to every page. That's necessary for the JavaScript that's added:

Code Block
languagejs
titlefnord.js (partial)
langjavascript

Fnord = {
  popOutOfFrame : function() {
    if (top != self)
      top.location.replace(location);
  }
}

...