This Confluence has been LDAP enabled, if you are an ASF Committer, please use your LDAP Credentials to login. Any problems file an INFRA jira ticket please.

Child pages
  • Page construction strategies
Skip to end of metadata
Go to start of metadata

Table of contents

The Partially Constructed Object Dilemma

Wicket naturally lends itself to object-oriented representations of web pages, and one aspect of this approach is using inheritance to pull behavior common to a set of pages into a superclass shared by those pages. For example, several pages might have a shared base class in Template.java (with corresponding markup in Template.html) which sets up a header and side navigation menu that is common to those pages.

A problem is encountered, however, when it comes to allowing subclasses to modify the base class behavior through method overrides. For example, let's assume the Template has a header with the page title, and that this title changes per page. A plausible solution is to provide an abstract getPageTitle method which subclasses will override. In the Template constructor, it will add a label with the result of this method:

public abstract class Template extends WebPage {

  public Template(PageParameters params) {
     super(params);
     add(new Label("headerTitle",getPageTitle()));
  }
  protected abstract String getPageTitle();
}

Let's also assume that a certain page subclass of Template wants the title in question to depend upon data passed in through PageParameters at construction:

public class SomePage extends Template {

  Object myObject;
  public SomePage(PageParameters params) {
    super(params);
    long objectId = params.getLong("objectId");
    myObject = SomeService.lookupById(objectId);
  }

  protected String getPageTitle() {
    return "Editing: " + myObject.getSomeProperty(); //Null Pointer Exception
  }
}

The problem we encounter is that the code to look up myObject above has not yet run when getPageTitle() is invoked in the constructor of Template. In general, invoking overridable methods from a java constructor is considered bad practice, because the object in question has not necessarily been completely constructed (meaning that subclass constructor(s) have not yet run) when a base class constructor is being invoked.

Possible Solutions

There is no consensus in the wicket community on a standard pattern for solving this problem.

This is not entirely true. The core-devs have a consensus - which is to use Component#onBeforeRender() to construct subclass-driven dynamic component hierarchies. This approach is demonstrated towards the end of the page. - ivaynberg

. There are however a number of typical solutions.

Use Model

public abstract class Template extends WebPage {

  public Template(PageParameters params) {
     super(params);
     add(new Label("headerTitle", getPageTitle()));
  }
  protected abstract IModel<String> getPageTitle();
}

This way the model implementation will be constructed at component construction time but its #getObject() will be called at component rendering time and the object we need will be already properly constructed.

Two-phase construction with an internal init() method called by subclasses

In this solution, an init() method is declared in the base class, and subclasses are expected to:

  1. Override init() to do any setup work, rather than doing that work in the constructor. The constructor is instead used to do setup of the model as well as the initialization of any local data that is dependent on PageParameter data.
  2. Call super.init() as the first line in init(), ensuring that init() mimics a constructor, so that the base class is set up before the subclass
  3. Call init() at the end of their constructor(s), so that initialization is sure to happen
  4. Use Component#onBeforeRender to construct dynamic subclass driven hierarchy

There is no way to ensure that the above set up constraints are obeyed, but it nevertheless provides a simple solution to the partially-constructed-object problem without reliance on any class outside the pages in question. The above example, adopting this strategy, would look like:

public abstract class Template extends WebPage {

  public Template(PageParameters params) {
     super(params);
  }

  protected void init() {
     add(new Label("headerTitle",getPageTitle()));
  }

  protected abstract String getPageTitle();
}

public class class SomePage extends Template {

  Object myObject;
  public SomePage(PageParameters params) {
    super(params);
    long objectId = params.getLong("objectId");
    myObject = SomeService.lookupById(objectId);
    init();
  }

  protected String getPageTitle() {
    return "Editing: " + myObject.getSomeProperty();
  }
}

One remaining problem with this strategy is that if SomePage is itself subclassed, there is no clean way to ensure that init() is called after the most-derived class is constructed, and ensure that it is called only once, without testing and setting an isInitialized field.

Two-phase construction with an init() method called by the base class

In this variant of the last solution, subclasses do no work in their constructor, but instead set up all data in an init() method that is called by the base class before it calls any other overridable methods:

This is very error prone. You must ensure that no overridable methods are called, which is not really doable as code is refactored, etc. In fact this is equivalent to calling overridable methods from the constructor

public abstract class Template extends WebPage {

  public Template(PageParameters params) {
     super(params);
     init();
     add(new Label("headerTitle",getPageTitle()));
  }

  protected void init() { }

  protected abstract String getPageTitle();
}

public class class SomePage extends Template {

  Object myObject;
  public SomePage(PageParameters params) {
    super(params);
  }

  /**
   * override init() to setup myObject
   */
  protected void init() {
    super.init();
    long objectId = params.getLong("objectId");
    myObject = SomeService.lookupById(objectId);
  }

  protected String getPageTitle() {
    return "Editing: " + myObject.getSomeProperty();
  }
}

This variant does not rely on subclasses to call init(), and allows for subclasses existing subclasses of Template, provided all overrides of init() call super.init().

Using Component#onBeforeRender

The problem of subclass-driven component hieararchies is easily solved using Component#onBeforeRender() callback and simple factory methods

public abstract class Template extends WebPage {

  protected void onBeforeRender() {
     if (get("left")==null) {
       // subclass-driven components not yet initilized
       add(newLeftPanel("left"));
       add(newRightPanel("right"));
     }
     /** cascades the call to its children */
     super.onBeforeRender();
  }
  /** factory method to create left panel */
  protected abstract Component newLeftPanel(String id);
  /** factory method to create right panel */
  protected abstract Component newRightPanel(String id);
}

// concrete template implementation which fills in factory methods

public class MyPage extends Template {
  protected Component newLeftPanel(String id) {
    return new Label(id, "i am left");
  }

  protected Component newRightPanel(String id) {
    return new Label(id, "i am right");
  }
}
  • No labels