A.2. Specialized Use Cases

Domain objects to support specialized use cases (solution space objects) are not persisted; instead their state is serialized into the Wicket page components.

A.2.1. ClaimWizard

The ClaimWizard uses an internal page field (of type Page enum) to determine which page the user is on; from this we determine which properties should be visible, and whether the previous(), next() and finish() actions are available.

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

import java.util.Calendar;
import java.util.List;

import org.nakedobjects.applib.AbstractDomainObject;
import org.nakedobjects.applib.annotation.Disabled;
import org.nakedobjects.applib.annotation.Hidden;
import org.nakedobjects.applib.annotation.Ignore;
import org.nakedobjects.applib.annotation.MemberOrder;
import org.nakedobjects.applib.annotation.NotPersistable;
import org.nakedobjects.applib.annotation.TypicalLength;
import org.nakedobjects.applib.clock.Clock;
import org.nakedobjects.examples.claims.dom.employee.EmployeeRepository;
import org.starobjects.wicket.applib.WizardPageDescription;

@NotPersistable
public class ClaimWizard extends AbstractDomainObject {

    public enum Page {
        INTRO("This wizard will take you through the process of creating a claim"), 
        CLAIMANT("Enter the claimant that is making this claim"), 
        APPROVER("By default, the claimant's own approver will approve this claim.  " +
                 "Update here if another approver will approve this claim."), 
        DESCRIPTION("Update the description if required."), 
        SUMMARY("Confirm all details, or go back and amend if needed");

        private String description;
        private Page(String description) {
            this.description = description;
        }

        public String getDescription() {
            return description;
        }

        public boolean hasPrevious() {
            return ordinal() > 0;
        }
        public Page previous() {
            if (hasPrevious()) {
                return values()[ordinal() - 1];
            } else {
                return this;
            }
        }

        public boolean hasNext() {
            return ordinal() < values().length - 1;
        }
        public Page next() {
            if (hasNext()) {
                return values()[ordinal() + 1];
            } else {
                return this;
            }
        }

        @Ignore
        public boolean is(Page... pages) {
            for (Page page : pages) {
                if (page == this) {
                    return true;
                }
            }
            return false;
        }
    }

    // {{ Lifecycle
    public void created() {
        setPage(Page.INTRO);
        setDescription("Expenses for week #" + weekNum());
    }
    private int weekNum() {
        return getTimeAsCalendar().get(Calendar.WEEK_OF_YEAR);
    }
    protected Calendar getTimeAsCalendar() {
        return Clock.getTimeAsCalendar();
    }
    // }}

    // {{ Page
    private Page page;
    @Hidden
    public Page getPage() { ... }
    public void setPage(final Page page) { ... }
    // }}

    // {{ Page Description
    @WizardPageDescription
    @TypicalLength(60)
    @MemberOrder(sequence = "1")
    public String getPageDescription() {
        return getPage().getDescription();
    }
    // }}

    // {{ Claimant
    private Claimant claimant;
    @MemberOrder(sequence = "2")
    public Claimant getClaimant() { ... }
    public void setClaimant(final Claimant claimant) { ... }
    public void modifyClaimant(final Claimant claimant) { ... }
    public void clearClaimant() { ... }
    }
    protected void onModifyClaimant(final Claimant oldClaimant,
            final Claimant newClaimant) {
        setApprover(newClaimant.getApprover());
    }
    protected void onClearClaimant(final Claimant oldClaimant) {
    }
    @SuppressWarnings("unchecked")
    public List<Claimant> choicesClaimant() {
        return employeeRepository.allEmployees();
    }
    public String disableClaimant() {
        return coalesce(claimCreated(), confirmIfOnSummaryPage());
    }
    public boolean hideClaimant() {
        return !getPage().is(Page.CLAIMANT, Page.SUMMARY);
    }
    // }}

    // {{ Approver
    private Approver approver;
    @MemberOrder(sequence = "3")
    public Approver getApprover() { ... }
    public void setApprover(final Approver approver) { ... }
    public String disableApprover() {
        return coalesce(claimCreated(), confirmIfOnSummaryPage());
    }
    public boolean hideApprover() {
        return !getPage().is(Page.APPROVER, Page.SUMMARY);
    }
    // }}

    // {{ Description
    private String description;
    @MemberOrder(sequence = "4")
    public String getDescription() { ... }
    public void setDescription(final String description) { ... }
    public String disableDescription() {
        return coalesce(claimCreated(), confirmIfOnSummaryPage());
    }
    public boolean hideDescription() {
        return !getPage().is(Page.DESCRIPTION, Page.SUMMARY);
    }
    private String claimCreated() {
        return claim != null ? "Claim created" : null;
    }
    // }}

    // {{ Claim
    private Claim claim;
    @Disabled
    @MemberOrder(sequence = "5")
    public Claim getClaim() { ... }
    public void setClaim(final Claim claim) { ... }
    public boolean hideClaim() { ... }
    // }}

    // {{ previous
    @MemberOrder(sequence = "1")
    public void previous() {
        setPage(getPage().previous());
    }
    public String disablePrevious() {
        return coalesce(noPreviousPage(), confirmIfOnSummaryPage());
    }
    private String noPreviousPage() {
        return !getPage().hasPrevious() ? "no previous page" : null;
    }
    // }}

    // {{ next
    @MemberOrder(sequence = "2")
    public void next() {
        setPage(getPage().next());
    }
    public String disableNext() {
        return coalesce(noNextPage(), confirmIfOnSummaryPage());
    }
    private String noNextPage() {
        return !getPage().hasNext() ? "no next page" : null;
    }
    // }}

    // {{ finish
    @MemberOrder(sequence = "3")
    public Claim finish() {
        Claim claim = newTransientInstance(Claim.class);
        claim.setClaimant(getClaimant());
        claim.setApprover(getApprover());
        claim.setDescription(getDescription());
        setClaim(claim);
        persist(claim);
        return claim;
    }
    public String disableFinish() {
        if (getPage().hasNext()) {
            return "wizard has further pages to complete";
        }
        return getContainer().validate(this);
    }
    // }}

    // {{ helpers
    private String confirmIfOnSummaryPage() {
        return getPage().is(Page.SUMMARY) ? "confirm" : null;
    }
    private static String coalesce(String... strings) {
        for (String string : strings) {
            if (string != null)
                return string;
        }
        return null;
    }
    // }}

    // {{ injected: EmployeeRepository
    private EmployeeRepository employeeRepository;
    public void setEmployeeRepository(
            final EmployeeRepository employeeRepository) { ... }
    // }}
}

A.2.2. ClaimExpenseSummary

The ClaimExpenseSummary is used as a report object:

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

import org.nakedobjects.applib.annotation.Disabled;
import org.nakedobjects.applib.annotation.Ignore;
import org.nakedobjects.applib.annotation.MemberOrder;
import org.nakedobjects.applib.annotation.NotPersistable;
import org.nakedobjects.applib.value.Money;
import org.starobjects.wicket.applib.PieChartable;

@NotPersistable
public class ClaimantExpenseSummary implements PieChartable,
        Comparable<ClaimantExpenseSummary> {

    // {{ Identification
    public String title() {
        return getClaimant() != null ? getClaimant().title() : "(untitled)";
    }
    // }}

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

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

    // {{ programmatic
    @Ignore
    public void addAmount(Money amount) {
        if (this.amount == null) {
            this.amount = amount;
        } else {
            this.amount = this.amount.add(amount);
        }
    }
    // }}

    // {{ PieChartable impl
    @Ignore
    @Override
    public double getPieChartValue() {
        return getAmount().doubleValue();
    }
    @Ignore
    @Override
    public String getPieChartLabel() {
        return title();
    }
    // }}

    @Override
    public int compareTo(ClaimantExpenseSummary o) {
        if (getPieChartValue() < o.getPieChartValue())
            return -1;
        if (getPieChartValue() > o.getPieChartValue())
            return +1;
        return 0;
    }
}

Note that it implements the (rather horribly named) PieChartable, which is picked up by the googlecharts custom component (see Section 5.3, “Google Charts”).