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
ClaimItem
s. 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 Employee
s 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); }