Table of Contents
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.
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.
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 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) { ... }
// }}
}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();
}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();
}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).
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).
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);
}