1package se.citerus.dddsample.domain.model.cargo;
23import org.apache.commons.lang.Validate;
4import se.citerus.dddsample.domain.model.handling.HandlingEvent;
5import se.citerus.dddsample.domain.model.handling.HandlingHistory;
6import se.citerus.dddsample.domain.model.location.Location;
7import se.citerus.dddsample.domain.shared.DomainObjectUtils;
8import se.citerus.dddsample.domain.shared.Entity;
910/**11 * A Cargo. This is the central class in the domain model,12 * and it is the root of the Cargo-Itinerary-Leg-Delivery-RouteSpecification aggregate.13 *14 * A cargo is identified by a unique tracking id, and it always has an origin15 * and a route specification. The life cycle of a cargo begins with the booking procedure,16 * when the tracking id is assigned. During a (short) period of time, between booking17 * and initial routing, the cargo has no itinerary.18 *19 * The booking clerk requests a list of possible routes, matching the route specification,20 * and assigns the cargo to one route. The route to which a cargo is assigned is described21 * by an itinerary.22 *23 * A cargo can be re-routed during transport, on demand of the customer, in which case24 * a new route is specified for the cargo and a new route is requested. The old itinerary,25 * being a value object, is discarded and a new one is attached.26 *27 * It may also happen that a cargo is accidentally misrouted, which should notify the proper28 * personnel and also trigger a re-routing procedure.29 *30 * When a cargo is handled, the status of the delivery changes. Everything about the delivery31 * of the cargo is contained in the Delivery value object, which is replaced whenever a cargo32 * is handled by an asynchronous event triggered by the registration of the handling event.33 *34 * The delivery can also be affected by routing changes, i.e. when a the route specification35 * changes, or the cargo is assigned to a new route. In that case, the delivery update is performed36 * synchronously within the cargo aggregate.37 *38 * The life cycle of a cargo ends when the cargo is claimed by the customer.39 *40 * The cargo aggregate, and the entre domain model, is built to solve the problem41 * of booking and tracking cargo. All important business rules for determining whether42 * or not a cargo is misdirected, what the current status of the cargo is (on board carrier,43 * in port etc), are captured in this aggregate.44 *45 */46publicclassCargo implements Entity<Cargo> {
4748privateTrackingId trackingId;
49privateLocation origin;
50privateRouteSpecification routeSpecification;
51privateItinerary itinerary;
52privateDelivery delivery;
5354publicCargo(finalTrackingId trackingId, finalRouteSpecification routeSpecification) {
55 Validate.notNull(trackingId, "Tracking ID is required");
56 Validate.notNull(routeSpecification, "Route specification is required");
5758this.trackingId = trackingId;
59// Cargo origin never changes, even if the route specification changes.60// However, at creation, cargo orgin can be derived from the initial route specification.61this.origin = routeSpecification.origin();
62this.routeSpecification = routeSpecification;
6364this.delivery = Delivery.derivedFrom(
65this.routeSpecification, this.itinerary, HandlingHistory.EMPTY
66 );
67 }
6869/**70 * The tracking id is the identity of this entity, and is unique.71 * 72 * @return Tracking id.73 */74publicTrackingId trackingId() {
75return trackingId;
76 }
7778/**79 * @return Origin location.80 */81publicLocation origin() {
82return origin;
83 }
8485/**86 * @return The delivery. Never null.87 */88publicDelivery delivery() {
89return delivery;
90 }
9192/**93 * @return The itinerary. Never null.94 */95publicItinerary itinerary() {
96return DomainObjectUtils.nullSafe(this.itinerary, Itinerary.EMPTY_ITINERARY);
97 }
9899/**100 * @return The route specification.101 */102publicRouteSpecification routeSpecification() {
103return routeSpecification;
104 }
105106/**107 * Specifies a new route for this cargo.108 *109 * @param routeSpecification route specification.110 */111publicvoid specifyNewRoute(finalRouteSpecification routeSpecification) {
112 Validate.notNull(routeSpecification, "Route specification is required");
113114this.routeSpecification = routeSpecification;
115// Handling consistency within the Cargo aggregate synchronously116this.delivery = delivery.updateOnRouting(this.routeSpecification, this.itinerary);
117 }
118119/**120 * Attach a new itinerary to this cargo.121 *122 * @param itinerary an itinerary. May not be null.123 */124publicvoid assignToRoute(finalItinerary itinerary) {
125 Validate.notNull(itinerary, "Itinerary is required for assignment");
126127this.itinerary = itinerary;
128// Handling consistency within the Cargo aggregate synchronously129this.delivery = delivery.updateOnRouting(this.routeSpecification, this.itinerary);
130 }
131132/**133 * Updates all aspects of the cargo aggregate status134 * based on the current route specification, itinerary and handling of the cargo.135 * <p/>136 * When either of those three changes, i.e. when a new route is specified for the cargo,137 * the cargo is assigned to a route or when the cargo is handled, the status must be138 * re-calculated.139 * <p/>140 * {@link RouteSpecification} and {@link Itinerary} are both inside the Cargo141 * aggregate, so changes to them cause the status to be updated <b>synchronously</b>,142 * but changes to the delivery history (when a cargo is handled) cause the status update143 * to happen <b>asynchronously</b> since {@link HandlingEvent} is in a different aggregate.144 *145 * @param handlingHistory handling history146 */147publicvoid deriveDeliveryProgress(finalHandlingHistory handlingHistory) {
148// TODO filter events on cargo (must be same as this cargo)149150// Delivery is a value object, so we can simply discard the old one151// and replace it with a new152this.delivery = Delivery.derivedFrom(routeSpecification(), itinerary(), handlingHistory);
153 }
154155 @Override
156publicboolean sameIdentityAs(finalCargo other) {
157return other != null && trackingId.sameValueAs(other.trackingId);
158 }
159160/**161 * @param object to compare162 * @return True if they have the same identity163 * @see #sameIdentityAs(Cargo)164 */165 @Override
166publicboolean equals(final Object object) {
167if (this == object) returntrue;
168if (object == null || getClass() != object.getClass()) return false;
169170finalCargo other = (Cargo) object;
171return sameIdentityAs(other);
172 }
173174/**175 * @return Hash code of tracking id.176 */177 @Override
178publicint hashCode() {
179return trackingId.hashCode();
180 }
181182 @Override
183public String toString() {
184return trackingId.toString();
185 }
186187Cargo() {
188// Needed by Hibernate189 }
190191// Auto-generated surrogate key192private Long id;
193194 }