Appendix A. Example Application

Table of Contents

A.1. Domain Application (Problem Space / Persisted Objects)
A.1.1. claims package
A.1.2. employee package
A.2. Specialized Use Cases
A.2.1. ClaimWizard
A.2.2. ClaimExpenseSummary
A.3. Custom Views for Specialized Use Cases
A.3.1. ClaimWizardComponentFactory
A.3.2. ClaimWizardPanel
A.3.3. ClaimWizardPanel.html

Abstract

This appendix contains (almost) all the code that makes up the example application shown in the screenshots in Chapter 2, Application Walkthrough. The purpose in including these listings is just to give you an idea of what it takes to write a Wicket Objects application; this isn't a full tutorial on what it all means.

If you're interested in trying out the application, you'll find it at https://wicketobjects.svn.sourceforge.net/svnroot/wicketobjects/trunk/testapp/claims.

A.1. Domain Application (Problem Space / Persisted Objects)

Most of the application shown in the screenshots (see Chapter 2, Application Walkthrough) requires only the domain model. This is made up of three main entities, Employee, Claim and ClaimItem. The dependency between employee and claims package is acyclic; every Claim has a Claimant and an Approver, and Employee implements both the Approver and Claimant interfaces.

A.1.1. claims package

A.1.1.1. Claim

The Claim class is by far the largest domain class. Below is a listing of all the methods; the body of the getters and setters and some of the validation methods have been omitted.

package org.nakedobjects.examples.claims.dom.claim;

import java.util.ArrayList;
import java.util.List;

import org.nakedobjects.applib.AbstractDomainObject;
import org.nakedobjects.applib.annotation.Disabled;
import org.nakedobjects.applib.annotation.Ignore;
import org.nakedobjects.applib.annotation.MaxLength;
import org.nakedobjects.applib.annotation.MemberOrder;
import org.nakedobjects.applib.annotation.Named;
import org.nakedobjects.applib.annotation.Optional;
import org.nakedobjects.applib.value.Date;
import org.nakedobjects.applib.value.Money;
import org.starobjects.wicket.applib.CalendarEvent;
import org.starobjects.wicket.applib.Calendarable;

public class Claim extends AbstractDomainObject implements Calendarable {

    // {{ Title
    public String title() {
        return getStatus() + " - " + getDate();
    }
    // }}

    // {{ Lifecycle
    public void created() {
        status = "New";
        date = new Date();
    }
    // }}

    // {{ Rush
    private boolean rush;
    @MemberOrder(sequence = "1.2")
    public boolean getRush() { ... }
    public void setRush(final boolean flag) { ... }
    // }}

    // {{ Description
    private String description;
    @MemberOrder(sequence = "1")
    public String getDescription() { ... }
    public void setDescription(String description) { ... }
    public String validateDescription(final String description) { ... }
    // }}

    // {{ Date
    private Date date;
    @MemberOrder(sequence = "2")
    public Date getDate() { ... }
    public void setDate(Date date) { ... }
    public String disableDate() { ... }
    // }}

    // {{ Status
    private String status;
    @Disabled
    @MemberOrder(sequence = "3")
    @MaxLength(5)
    public String getStatus() { ... }
    public void setStatus(String status) { ... }
    // }}

    // {{ Claimant
    private Claimant claimant;
    @Disabled
    @MemberOrder(sequence = "4")
    public Claimant getClaimant() { ... }
    public void setClaimant(Claimant claimant) { ... }
    // }}

    // {{ Approver
    private Approver approver;
    @MemberOrder(sequence = "5")
    @Optional
    public Approver getApprover() { ... }
    public void setApprover(Approver approver) { ... }
    public String disableApprover() { ... }
    public String validateApprover(final Approver approver) { 
        if (approver == null)
            return null;
        return approver == getClaimant() ? "Can't approve own claims" : null;
    }
    // }}

    // {{ Items
    private List<ClaimItem> items = new ArrayList<ClaimItem>();
    @MemberOrder(sequence = "6")
    public List<ClaimItem> getItems() { ... }
    public void addToItems(ClaimItem item) { ... }
    // }}

    // {{ action: Submit
    public void submit(Approver approver) { ... }
    public String disableSubmit() {
        return getStatus().equals("New") ? null
                : "Claim has already been submitted";
    }
    public Object default0Submit() { 
        return getClaimant().getApprover();
    }
    // }}

    // {{ action: addItem
    public void addItem(@Named("Days since") int days,
            @Named("Amount") double amount,
            @Named("Description") String description) {
        ClaimItem claimItem = newTransientInstance(ClaimItem.class);
        Date date = new Date();
        date = date.add(0, 0, days);
        claimItem.setDateIncurred(date);
        claimItem.setDescription(description);
        claimItem.setAmount(new Money(amount, "USD"));
        persist(claimItem);
        addToItems(claimItem);
    }
    public String disableAddItem() { ... }
        return "Submitted".equals(getStatus()) ? "Already submitted" : null;
    }
    // }}

    // object-level validation
    public String validate() { ... }
}

Some points worth noting:

  • Although Claim is inheriting from Naked Objects' AbstractDomainObject class, this isn't mandatory.

  • Claim has reference properties of type Claimant and Approver. As we'll see below these are interfaces. References to both interface and classes is supported in Naked Objects.

  • The Claim uses a Money class, a value type provided by Naked Objects. It's also possible to write ones own value types (or indeed use third-party value types such as JodaTime).

A.1.1.2. ClaimItem

A Claim has a collection of ClaimItems. A ClaimItem is somewhat simpler than Claim, and doesn't have any particular behavior itself:

package org.nakedobjects.examples.claims.dom.claim;

import org.nakedobjects.applib.AbstractDomainObject;
import org.nakedobjects.applib.annotation.MemberOrder;
import org.nakedobjects.applib.value.Date;
import org.nakedobjects.applib.value.Money;

public class ClaimItem extends AbstractDomainObject {

    // {{ Title
    public String title() {
        return getDescription();
    }
    // }}

    // {{ DateIncurred
    private Date dateIncurred;
    @MemberOrder(sequence = "1")
    public Date getDateIncurred() { ... }
    public void setDateIncurred(Date dateIncurred) { ... }
    // }}

    // {{ Description
    private String description;
    @MemberOrder(sequence = "2")
    public String getDescription() { ... }
    public void setDescription(String description) { ... }
    // }}

    // {{ Amount
    private Money amount;
    @MemberOrder(sequence = "3")
    public Money getAmount() { ... }
    public void setAmount(Money price) { ... }
    // }}
}

A.1.1.3. Approver and Claimant

The Approver and Claimant interfaces decouple Claim from any classes outside the claims package. The Approver interface is, in fact, empty:

package org.nakedobjects.examples.claims.dom.claim;

public interface Approver {

}

There's not a lot more to Claimant:

package org.nakedobjects.examples.claims.dom.claim;

public interface Claimant {

    Approver getApprover();

    String title();
}

A.1.1.4. ClaimRepository

The ClaimRepository interface is one of the two domain services (as appearing in the menu bar), and is defined as:

package org.nakedobjects.examples.claims.dom.claim;

import java.util.List;

import org.nakedobjects.applib.annotation.Named;
import org.nakedobjects.applib.value.Date;

@Named("Claims")
public interface ClaimRepository {

    public List<Claim> allClaims();

    public List<Claim> findClaims(@Named("Description") String description);

    public List<Claim> claimsFor(Claimant claimant);

    public List<Claim> claimsSince(Claimant claimant, Date since);

    public ClaimWizard newClaim(Claimant claimant);

    public List<ClaimantExpenseSummary> analyseClaimantExpenses();
}

A.1.2. employee package

The employee package depends on the claim package in that the Employee class implements the Claimant and Approver interfaces. Among other things, this allows the actions of the ClaimRepository to be "contributed" to the Employee class (appear in a "claims" submenu for each Employee).

A.1.2.1. Employee

The Employee class is the other main class in this app:

package org.nakedobjects.examples.claims.dom.employee;

import org.nakedobjects.applib.AbstractDomainObject;
import org.nakedobjects.applib.annotation.Disabled;
import org.nakedobjects.applib.annotation.MemberOrder;
import org.nakedobjects.examples.claims.dom.claim.Approver;
import org.nakedobjects.examples.claims.dom.claim.Claimant;
import org.starobjects.wicket.applib.Locatable;
import org.starobjects.wicket.applib.Location;

public class Employee extends AbstractDomainObject implements Claimant,
        Approver, Locatable {

    // {{ Title
    public String title() {
        return getName();
    }

    // }}

    // {{ Icon
    public String iconName() {
        return getName().replaceAll(" ", "");
    }
    // }}

    // {{ Name
    private String name;
    @MemberOrder(sequence = "1")
    public String getName() { ... }
    public void setName(String lastName) { ... }
    // }}

    // {{ Approver
    private Approver approver;
    @MemberOrder(sequence = "2")
    public Approver getApprover() { ... }
    public void setApprover(Approver approver) { ... }
    // }}

    // {{ Location
    private Location location;
    @Disabled
    @MemberOrder(sequence = "1")
    public Location getLocation() { ... }
    public void setLocation(final Location location) { ... }
    // }}
}

A couple points worth noting:

  • The Employee class has an iconName() method. This is used to render Employees with a customized image for each instance.

  • Employee also implements Locatable. This is used to render the Employee in the gmap2 (google maps mashup) view (see Chapter 5, Custom Components).

A.1.2.2. EmployeeRepository

The EmployeeRepository interface defines the other domain service (on the services menu):

package org.nakedobjects.examples.claims.dom.employee;

import java.util.List;

import org.nakedobjects.applib.annotation.Named;

@Named("Employees")
public interface EmployeeRepository {

    public List<Employee> allEmployees();
    public List<Employee> findEmployees(@Named("Name") String name);
}