Putting it all together

Here are full listings of the entity interface and implementation example used in this chapter:

Figure 1. Lifecycle entity interface example
package curam.mypackage;

import com.google.inject.ImplementedBy;

import curam.mypackage.codetable.impl.MYLIFECYCLEENTITYSTATEEntry;
import curam.util.exception.InformationalException;
import curam.util.persistence.Insertable;
import curam.util.persistence.OptimisticLockModifiable;
import curam.util.persistence.StandardEntity;
import curam.util.persistence.helper.Lifecycle;
import curam.util.type.Date;
import curam.util.type.DateRanged;

/**
 * Description of my state-machine entity.
 */
@ImplementedBy(MyLifecycleEntityImpl.class)
public interface MyLifecycleEntity extends StandardEntity,
    DateRanged, Lifecycle<MYLIFECYCLEENTITYSTATEEntry>, Insertable,
    OptimisticLockModifiable {

  /**
   * Suspends business pending investigation.
   *
   * Transitions the state to
   * {@linkplain MYLIFECYCLEENTITYSTATEEntry#SUSPENDED}, if it is
   * valid to suspend.
   *
   * @param reason
   *          the reason for suspension
   *
   * @param versionNo
   *          the version number as previously retrieved
   *
   * @throws InformationalException
   *           if the entity is not in a valid state to transition
   *           to
   *           {@linkplain MYLIFECYCLEENTITYSTATEEntry#SUSPENDED},
   *           or if any other validation errors are found
   */
  public void suspend(final String reason, final int versionNo)
      throws InformationalException;

  /**
   * Resumes business following a suspension investigation resulting
   * in acquittal.
   *
   * Transitions the state to
   * {@linkplain MYLIFECYCLEENTITYSTATEEntry#OPEN}, if it is valid
   * to resume business.
   *
   * @param versionNo
   *          the version number as previously retrieved
   *
   * @throws InformationalException
   *           if the entity is not in a valid state to transition
   *           to {@linkplain MYLIFECYCLEENTITYSTATEEntry#OPEN}, or
   *           if any other validation errors are found
   */
  public void resume(final int versionNo)
      throws InformationalException;

  /**
   * Ceases business with the agency.
   *
   * Transitions the state to
   * {@linkplain MYLIFECYCLEENTITYSTATEEntry#CLOSED}, if it is
   * valid to cease conducting business.
   *
   * @param endDate
   *          the date on which business with the agency was ceased
   *
   * @param versionNo
   *          the version number as previously retrieved
   *
   * @throws InformationalException
   *           if the entity is not in a valid state to transition
   *           to {@linkplain MYLIFECYCLEENTITYSTATEEntry#CLOSED},
   *           or if any other validation errors are found
   */
  public void close(final Date endDate, final int versionNo)
      throws InformationalException;

}
Figure 2. Lifecycle entity implementation example
package curam.mypackage;

import java.util.HashMap;
import java.util.Map;

import curam.mypackage.codetable.MYLIFECYCLEENTITYSTATEEntry;
import curam.mypackage.struct.MyLifecycleEntityDtls;
import curam.util.exception.InformationalException;
import curam.util.exception.UnimplementedException;
import curam.util.persistence.ValidationHelper;
import curam.util.persistence.helper.CodetableState;
import curam.util.persistence.helper.SingleTableEntityImpl;
import curam.util.persistence.helper.State;
import curam.util.persistence.helper.Transition;
import curam.util.type.Date;
import curam.util.type.DateRange;

/**
 * Standard implementation of {@linkplain MyLifecycleEntity}.
 */
public class MyLifecycleEntityImpl extends
    SingleTableEntityImpl<MyLifecycleEntityDtls> implements
    MyLifecycleEntity {

  protected MyLifecycleEntityImpl() {
    /*
     * Protected no-arg constructor for use only by Guice
     */
  }

  /*
   * Persistence methods
   */
  /**
   * {@inheritDoc}
   */
  @Override
  public void modify(Integer versionNo)
      throws InformationalException {

    if (!getState().isModifyAllowed()) {
      ValidationHelper
          .addValidationError(
"You are not allowed to modify this record when it is in this state"
           );
    }

    super.modify(versionNo);
  }

  /*
   * Getters
   */
  /**
   * {@inheritDoc}
   */
  public MYLIFECYCLEENTITYSTATEEntry getLifecycleState() {
    return MYLIFECYCLEENTITYSTATEEntry.get(getDtls().state);
  }

  public DateRange getDateRange() {
    throw new UnimplementedException();
  }

  /*
   * Setters
   */
  private void setEndDate(final Date value) {
    throw new UnimplementedException();
  }

  private void setSuspensionReason(final String value) {
    throw new UnimplementedException();
  }

  void sendClosureEmail() {
    throw new UnimplementedException();
  }

  /*
   * State transitions
   */

  /**
   * {@inheritDoc}
   */
  public void close(Date endDate, int versionNo)
      throws InformationalException {
    // store the date of closure
    setEndDate(endDate);

    // transition to "closed"
    transitionTo(CLOSED, versionNo);
  }

  /**
   * {@inheritDoc}
   */
  public void resume(int versionNo) throws InformationalException {
    // blank the suspension reason
    setSuspensionReason(null);

    // transition to "open"
    transitionTo(OPEN, versionNo);
  }

  /**
   * {@inheritDoc}
   */
  public void suspend(String reason, int versionNo)
      throws InformationalException {
    // store the suspension reason
    setSuspensionReason(reason);

    // transition to "suspended"
    transitionTo(SUSPENDED, versionNo);
  }

  /*
   * State Transitions
   */

  /**
   * A map of the states for this entity
   */
  private final Map<MYLIFECYCLEENTITYSTATEEntry,
    State<MYLIFECYCLEENTITYSTATEEntry>>
    states =
      new HashMap<MYLIFECYCLEENTITYSTATEEntry,
      State<MYLIFECYCLEENTITYSTATEEntry>>();

  /**
   * @return The State object representing the current state of this
   *         entity
   */
  private State<MYLIFECYCLEENTITYSTATEEntry> getState() {
    return states.get(getLifecycleState());
  }

  /**
   * Sets the state codetable code field from the State object
   * supplied.
   *
   * @param value
   *          the State supplied
   */
  private void setState(
      final State<MYLIFECYCLEENTITYSTATEEntry> state) {
    getDtls().state = state.getValue().getCode();
  }

  /**
   * Transitions this entity to the new state specified.
   *
   * @param newState
   *          the state to transition to
   * @param versionNo
   *          the version number of this entity as previously
   *          retrieved
   * @throws InformationalException
   *           if validation errors occur during the transition
   */
  private void transitionTo(
      final State<MYLIFECYCLEENTITYSTATEEntry> newState,
      final Integer versionNo) throws InformationalException {

    // get the current state of this entity
    final State<MYLIFECYCLEENTITYSTATEEntry> oldState = getState();

    // set the field which stores the state value
    setState(newState);

    // validate the state transition
    oldState.transitionTo(newState);

    // update the database, bypassing any pre-modify validation in
    // this class
    super.modify(versionNo);
  }

  /**
   * Actively conducting business with the agency.
   */
  private final State<MYLIFECYCLEENTITYSTATEEntry> OPEN =
      new CodetableState<MYLIFECYCLEENTITYSTATEEntry>(states,
          MYLIFECYCLEENTITYSTATEEntry.OPEN, true, true) {
      };

  /**
   * Business has been suspended pending investigation.
   */
  private final State<MYLIFECYCLEENTITYSTATEEntry> SUSPENDED =
      new CodetableState<MYLIFECYCLEENTITYSTATEEntry>(states,
          MYLIFECYCLEENTITYSTATEEntry.SUSPENDED, true, false) {
      };

  /**
   * No longer conducting business with the agency.
   */
  private final State<MYLIFECYCLEENTITYSTATEEntry> CLOSED =
      new CodetableState<MYLIFECYCLEENTITYSTATEEntry>(states,
          MYLIFECYCLEENTITYSTATEEntry.CLOSED, false, false) {

        @Override
        protected void onEnter() {
          // whenever the entity is closed, send an email
          sendClosureEmail();
        }

      };

  private final Transition<MYLIFECYCLEENTITYSTATEEntry>
    OPEN2CLOSED =
      new Transition<MYLIFECYCLEENTITYSTATEEntry>(OPEN, CLOSED) {
      };

  private final Transition<MYLIFECYCLEENTITYSTATEEntry>
    OPEN2SUSPENDED =
      new Transition<MYLIFECYCLEENTITYSTATEEntry>(OPEN, SUSPENDED) {
      };

  private final Transition<MYLIFECYCLEENTITYSTATEEntry>
    SUSPENDED2OPEN =
      new Transition<MYLIFECYCLEENTITYSTATEEntry>(SUSPENDED, OPEN) {
      };

  private final Transition<MYLIFECYCLEENTITYSTATEEntry>
    SUSPENDED2CLOSED =
      new Transition<MYLIFECYCLEENTITYSTATEEntry>(SUSPENDED, CLOSED) {
      };

  /*
   * Validation
   */
  public void mandatoryFieldValidation() {
    throw new UnimplementedException();
  }

  public void crossFieldValidation() {
    throw new UnimplementedException();
  }

  public void crossEntityValidation() {
    throw new UnimplementedException();
  }

  /**
   * Defaults the state to
   * {@linkplain MYLIFECYCLEENTITYSTATEEntry#OPEN}.
   */
  public void setNewInstanceDefaults() {
    setState(OPEN);
  }

}