Kaynağa Gözat

Moved all aspects of the cargo delivery into the Delivery class

peter_backlund 17 yıl önce
ebeveyn
işleme
98533509e9

+ 2
- 2
dddsample/src/main/java/se/citerus/dddsample/application/impl/CargoInspectionServiceImpl.java Dosyayı Görüntüle

@@ -42,11 +42,11 @@ public class CargoInspectionServiceImpl implements CargoInspectionService {
42 42
 
43 43
     cargo.deriveDeliveryProgress(handlingHistory);
44 44
 
45
-    if (cargo.isMisdirected()) {
45
+    if (cargo.delivery().isMisdirected()) {
46 46
       applicationEvents.cargoWasMisdirected(cargo);
47 47
     }
48 48
 
49
-    if (cargo.isUnloadedAtDestination()) {
49
+    if (cargo.delivery().isUnloadedAtDestination()) {
50 50
       applicationEvents.cargoHasArrived(cargo);
51 51
     }
52 52
 

+ 9
- 9
dddsample/src/main/java/se/citerus/dddsample/application/util/SampleDataGenerator.java Dosyayı Görüntüle

@@ -132,16 +132,16 @@ public class SampleDataGenerator implements ServletContextListener {
132 132
 
133 133
   private static void loadCargoData(JdbcTemplate jdbcTemplate) {
134 134
     String cargoSql =
135
-      "insert into Cargo (id, tracking_id, origin_id, spec_origin_id, spec_destination_id, spec_arrival_deadline, transport_status, current_voyage_id, last_known_location_id, is_misdirected, routing_status) " +
136
-      "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
135
+      "insert into Cargo (id, tracking_id, origin_id, spec_origin_id, spec_destination_id, spec_arrival_deadline, transport_status, current_voyage_id, last_known_location_id, is_misdirected, routing_status, calculated_at, unloaded_at_dest) " +
136
+      "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
137 137
 
138 138
     Object[][] cargoArgs = {
139
-      {1, "XYZ", 1, 1, 2, ts(10), "IN_PORT", null, 1, false, "ROUTED"},
140
-      {2, "ABC", 1, 1, 5, ts(20), "IN_PORT", null, 1, false, "ROUTED"},
141
-      {3, "ZYX", 2, 2, 1, ts(30), "IN_PORT", null, 1, false, "NOT_ROUTED"},
142
-      {4, "CBA", 5, 5, 1, ts(40), "IN_PORT", null, 1, false, "MISROUTED"},
143
-      {5, "FGH", 1, 3, 5, ts(50), "IN_PORT", null, 1, false, "ROUTED"},  // Cargo origin differs from spec origin
144
-      {6, "JKL", 6, 6, 4, ts(60), "IN_PORT", null, 1, true, "ROUTED"}
139
+      {1, "XYZ", 1, 1, 2, ts(10), "IN_PORT", null, 1, false, "ROUTED", ts(100), false},
140
+      {2, "ABC", 1, 1, 5, ts(20), "IN_PORT", null, 1, false, "ROUTED", ts(100), false},
141
+      {3, "ZYX", 2, 2, 1, ts(30), "IN_PORT", null, 1, false, "NOT_ROUTED", ts(100), false},
142
+      {4, "CBA", 5, 5, 1, ts(40), "IN_PORT", null, 1, false, "MISROUTED", ts(100), false},
143
+      {5, "FGH", 1, 3, 5, ts(50), "IN_PORT", null, 1, false, "ROUTED", ts(100), false},  // Cargo origin differs from spec origin
144
+      {6, "JKL", 6, 6, 4, ts(60), "IN_PORT", null, 1, true, "ROUTED", ts(100), false}
145 145
     };
146 146
     executeUpdate(jdbcTemplate, cargoSql, cargoArgs);
147 147
   }
@@ -295,7 +295,7 @@ public class SampleDataGenerator implements ServletContextListener {
295 295
           throw new RuntimeException(e);
296 296
         }
297 297
 
298
-        HandlingHistory handlingHistory1 = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId);
298
+        HandlingHistory handlingHistory1 = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId1);
299 299
         jkl567.deriveDeliveryProgress(handlingHistory1);
300 300
 
301 301
         session.update(jkl567);

+ 17
- 167
dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Cargo.java Dosyayı Görüntüle

@@ -2,16 +2,11 @@ package se.citerus.dddsample.domain.model.cargo;
2 2
 
3 3
 import org.apache.commons.lang.Validate;
4 4
 import se.citerus.dddsample.domain.model.Entity;
5
-import static se.citerus.dddsample.domain.model.cargo.RoutingStatus.*;
6 5
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
7
-import static se.citerus.dddsample.domain.model.handling.HandlingEvent.Type.*;
8 6
 import se.citerus.dddsample.domain.model.handling.HandlingHistory;
9 7
 import se.citerus.dddsample.domain.model.location.Location;
10 8
 import se.citerus.dddsample.domain.shared.DomainObjectUtils;
11 9
 
12
-import java.util.Date;
13
-import java.util.Iterator;
14
-
15 10
 /**
16 11
  * A Cargo. This is the central class in the domain model,
17 12
  * and it is the root of the Cargo-Itinerary-Leg-Delivery-RouteSpecification aggregate.
@@ -32,11 +27,19 @@ import java.util.Iterator;
32 27
  * It may also happen that a cargo is accidentally misrouted, which should notify the proper
33 28
  * personnel and also trigger a re-routing procedure.
34 29
  *
30
+ * When a cargo is handled, the status of the delivery changes. Everything about the delivery
31
+ * of the cargo is contained in the Delivery value object, which is replaced whenever a cargo
32
+ * 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 specification
35
+ * changes, or the cargo is assigned to a new route. In that case, the delivery update is performed
36
+ * synchronously within the cargo aggregate.
37
+ *
35 38
  * The life cycle of a cargo ends when the cargo is claimed by the customer.
36 39
  *
37 40
  * The cargo aggregate, and the entre domain model, is built to solve the problem
38 41
  * of booking and tracking cargo. All important business rules for determining whether
39
- * or not a cargo is misrouted, what the current status of the cargo is (on board carrier,
42
+ * or not a cargo is misdirected, what the current status of the cargo is (on board carrier,
40 43
  * in port etc), are captured in this aggregate.
41 44
  *
42 45
  */
@@ -44,19 +47,12 @@ public class Cargo implements Entity<Cargo> {
44 47
 
45 48
   private TrackingId trackingId;
46 49
   private Location origin;
50
+  private RouteSpecification routeSpecification;
47 51
   private Itinerary itinerary;
48 52
   private Delivery delivery;
49
-  private RouteSpecification routeSpecification;
50
-  private RoutingStatus routingStatus;
51
-  private HandlingActivity nextExpectedActivity;
52
-  private boolean misdirected;
53
-  private Date eta;
54
-  
55
-  private static final Date ETA_UNKOWN = null;
56
-  private static final HandlingActivity NO_ACTIVITY = null;
57 53
 
58 54
   public Cargo(final TrackingId trackingId, final RouteSpecification routeSpecification) {
59
-    Validate.notNull(trackingId, "Tracking id is required");
55
+    Validate.notNull(trackingId, "Tracking ID is required");
60 56
     Validate.notNull(routeSpecification, "Route specification is required");
61 57
 
62 58
     this.trackingId = trackingId;
@@ -65,7 +61,7 @@ public class Cargo implements Entity<Cargo> {
65 61
     this.origin = routeSpecification.origin();
66 62
     this.routeSpecification = routeSpecification;
67 63
 
68
-    deriveDeliveryProgress(HandlingHistory.EMPTY);
64
+    this.delivery = Delivery.derivedFrom(this.routeSpecification, this.itinerary, HandlingHistory.EMPTY);
69 65
   }
70 66
 
71 67
   /**
@@ -74,7 +70,7 @@ public class Cargo implements Entity<Cargo> {
74 70
    * @return Tracking id.
75 71
    */
76 72
   public TrackingId trackingId() {
77
-    return this.trackingId;
73
+    return trackingId;
78 74
   }
79 75
 
80 76
   /**
@@ -88,7 +84,7 @@ public class Cargo implements Entity<Cargo> {
88 84
    * @return The delivery. Never null.
89 85
    */
90 86
   public Delivery delivery() {
91
-    return DomainObjectUtils.nullSafe(this.delivery, Delivery.EMPTY_DELIVERY);
87
+    return delivery;
92 88
   }
93 89
 
94 90
   /**
@@ -115,7 +111,7 @@ public class Cargo implements Entity<Cargo> {
115 111
 
116 112
     this.routeSpecification = routeSpecification;
117 113
     // Handling consistency within the Cargo aggregate synchronously
118
-    this.routingStatus = deriveRoutingStatus();
114
+    this.delivery = delivery.updateOnRouting(this.routeSpecification, this.itinerary);
119 115
   }
120 116
 
121 117
   /**
@@ -128,60 +124,7 @@ public class Cargo implements Entity<Cargo> {
128 124
 
129 125
     this.itinerary = itinerary;
130 126
     // Handling consistency within the Cargo aggregate synchronously
131
-    this.routingStatus = deriveRoutingStatus();
132
-    this.misdirected = deriveMisdirectionStatus();
133
-    this.nextExpectedActivity = deriveNextExpectedActivity();
134
-    this.eta = deriveEta();
135
-  }
136
-
137
-  /**
138
-   * Check if cargo is misdirected.
139
-   * <p/>
140
-   * <ul>
141
-   * <li>A cargo is misdirected if it is in a location that's not in the itinerary.
142
-   * <li>A cargo with no itinerary can not be misdirected.
143
-   * <li>A cargo that has received no handling events can not be misdirected.
144
-   * </ul>
145
-   *
146
-   * @return <code>true</code> if the cargo has been misdirected,
147
-   */
148
-  public boolean isMisdirected() {
149
-    return misdirected;
150
-  }
151
-
152
-  /**
153
-   * @return Routing status.
154
-   */
155
-  public RoutingStatus routingStatus() {
156
-    return routingStatus;
157
-  }
158
-
159
-  /**
160
-   * @return True if the cargo has been unloaded at the final destination.
161
-   */
162
-  public boolean isUnloadedAtDestination() {
163
-    final HandlingEvent lastEvent = delivery.lastEvent();
164
-    return lastEvent != null &&
165
-      UNLOAD.sameValueAs(lastEvent.type()) &&
166
-      routeSpecification.destination().sameIdentityAs(lastEvent.location());
167
-  }
168
-
169
-  /**
170
-   * @return estimated time of arrival
171
-   */
172
-  public Date estimatedTimeOfArrival() {
173
-    if (eta != ETA_UNKOWN) {
174
-      return new Date(eta.getTime());
175
-    } else {
176
-      return ETA_UNKOWN;
177
-    }
178
-  }
179
-
180
-  /**
181
-   * @return the next expected activity
182
-   */
183
-  public HandlingActivity nextExpectedActivity() {
184
-    return nextExpectedActivity;
127
+    this.delivery = delivery.updateOnRouting(this.routeSpecification, this.itinerary);
185 128
   }
186 129
 
187 130
   /**
@@ -202,102 +145,9 @@ public class Cargo implements Entity<Cargo> {
202 145
   public void deriveDeliveryProgress(final HandlingHistory handlingHistory) {
203 146
     // Delivery is a value object, so we can simply discard the old one
204 147
     // and replace it with a new
205
-    this.delivery = Delivery.derivedFrom(handlingHistory);
206
-    this.routingStatus = deriveRoutingStatus();
207
-    this.misdirected = deriveMisdirectionStatus();
208
-    this.eta = deriveEta();
209
-    this.nextExpectedActivity = deriveNextExpectedActivity();
148
+    this.delivery = Delivery.derivedFrom(routeSpecification(), itinerary(), handlingHistory);
210 149
   }
211 150
 
212
-  /**
213
-   *
214
-   * @return true if this cargo is misdirected.
215
-   */
216
-  private boolean deriveMisdirectionStatus() {
217
-    final HandlingEvent lastEvent = delivery().lastEvent();
218
-    if (lastEvent == null) {
219
-      return false;
220
-    } else {
221
-      return !itinerary().isExpected(lastEvent);
222
-    }
223
-  }
224
-
225
-  /**
226
-   * @return current routing status
227
-   */
228
-  private RoutingStatus deriveRoutingStatus() {
229
-    if (itinerary == null) {
230
-      return NOT_ROUTED;
231
-    } else {
232
-      if (routeSpecification.isSatisfiedBy(itinerary)) {
233
-        return ROUTED;
234
-      } else {
235
-        return MISROUTED;
236
-      }
237
-    }
238
-  }
239
-
240
-  /**
241
-   * @return estimated time of arrival, or null if unknown
242
-   */
243
-  private Date deriveEta() {
244
-    if (onTrack()) {
245
-      return itinerary().finalArrivalDate();
246
-    } else {
247
-      return ETA_UNKOWN;
248
-    }
249
-  }
250
-
251
-  private HandlingActivity deriveNextExpectedActivity() {
252
-    if (!onTrack()) return NO_ACTIVITY;
253
-
254
-    final HandlingEvent lastEvent = delivery().lastEvent();
255
-
256
-    if (lastEvent == null) return new HandlingActivity(RECEIVE, origin());
257
-
258
-    switch (lastEvent.type()) {
259
-
260
-      case LOAD:
261
-        for (Leg leg : itinerary().legs()) {
262
-          if (leg.loadLocation().sameIdentityAs(lastEvent.location())) {
263
-            return new HandlingActivity(UNLOAD, leg.unloadLocation(), leg.voyage());
264
-          }
265
-        }
266
-
267
-        return NO_ACTIVITY;
268
-
269
-      case UNLOAD:
270
-        for (Iterator<Leg> it = itinerary().legs().iterator(); it.hasNext();) {
271
-          final Leg leg = it.next();
272
-          if (leg.unloadLocation().sameIdentityAs(lastEvent.location())) {
273
-            if (it.hasNext()) {
274
-              final Leg nextLeg = it.next();
275
-              return new HandlingActivity(LOAD, nextLeg.loadLocation(), nextLeg.voyage());
276
-            } else {
277
-              return new HandlingActivity(CLAIM, leg.unloadLocation());
278
-            }
279
-          }
280
-        }
281
-
282
-        return NO_ACTIVITY;
283
-
284
-      case RECEIVE:
285
-        final Leg firstLeg = itinerary().legs().iterator().next();
286
-        return new HandlingActivity(LOAD, firstLeg.loadLocation(), firstLeg.voyage());
287
-
288
-      case CLAIM:
289
-      default:
290
-        return NO_ACTIVITY;
291
-    }
292
-  }
293
-
294
-  /**
295
-   * @return true if cargo is on track, i.e. routed and not misdirected
296
-   */
297
-  private boolean onTrack() {
298
-    return routingStatus.equals(ROUTED) && !misdirected;
299
-  }
300
-  
301 151
   @Override
302 152
   public boolean sameIdentityAs(final Cargo other) {
303 153
     return other != null && trackingId.sameValueAs(other.trackingId);

+ 221
- 39
dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Delivery.java Dosyayı Görüntüle

@@ -4,6 +4,7 @@ import org.apache.commons.lang.Validate;
4 4
 import org.apache.commons.lang.builder.EqualsBuilder;
5 5
 import org.apache.commons.lang.builder.HashCodeBuilder;
6 6
 import se.citerus.dddsample.domain.model.ValueObject;
7
+import static se.citerus.dddsample.domain.model.cargo.RoutingStatus.*;
7 8
 import static se.citerus.dddsample.domain.model.cargo.TransportStatus.*;
8 9
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
9 10
 import se.citerus.dddsample.domain.model.handling.HandlingHistory;
@@ -11,6 +12,9 @@ import se.citerus.dddsample.domain.model.location.Location;
11 12
 import se.citerus.dddsample.domain.model.voyage.Voyage;
12 13
 import se.citerus.dddsample.domain.shared.DomainObjectUtils;
13 14
 
15
+import java.util.Date;
16
+import java.util.Iterator;
17
+
14 18
 /**
15 19
  * The actual transportation of the cargo, as opposed to
16 20
  * the customer requirement (RouteSpecification) and the plan (Itinerary). 
@@ -18,15 +22,72 @@ import se.citerus.dddsample.domain.shared.DomainObjectUtils;
18 22
  */
19 23
 public class Delivery implements ValueObject<Delivery> {
20 24
 
21
-  public static final Delivery EMPTY_DELIVERY = Delivery.derivedFrom(HandlingHistory.EMPTY);
22
-
23 25
   private TransportStatus transportStatus;
24 26
   private Location lastKnownLocation;
25 27
   private Voyage currentVoyage;
28
+  private boolean misdirected;
29
+  private Date eta;
30
+  private HandlingActivity nextExpectedActivity;
31
+  private boolean isUnloadedAtDestination;
32
+  private RoutingStatus routingStatus;
33
+  private Date calculatedAt;
26 34
   private HandlingEvent lastEvent;
27 35
 
28
-  private Delivery(HandlingEvent lastEvent) {
36
+  private static final Date ETA_UNKOWN = null;
37
+  private static final HandlingActivity NO_ACTIVITY = null;
38
+
39
+  /**
40
+   * Creates a new delivery snapshot to reflect changes in routing, i.e.
41
+   * when the route specification or the itinerary has changed
42
+   * but no additional handling of the cargo has been performed.
43
+   *
44
+   * @param routeSpecification route specification
45
+   * @param itinerary itinerary
46
+   * @return An up to date delivery
47
+   */
48
+  Delivery updateOnRouting(RouteSpecification routeSpecification, Itinerary itinerary) {
49
+    Validate.notNull(routeSpecification, "Route specification is required");
50
+
51
+    return new Delivery(this.lastEvent, itinerary, routeSpecification);
52
+  }
53
+
54
+  /**
55
+   * Creates a new delivery snapshot based on the complete handling history of a cargo,
56
+   * as well as its route specification and itinerary.
57
+   *
58
+   * @param routeSpecification route specification
59
+   * @param itinerary itinerary
60
+   * @param handlingHistory delivery history
61
+   * @return An up to date delivery.
62
+   */
63
+  static Delivery derivedFrom(RouteSpecification routeSpecification, Itinerary itinerary, HandlingHistory handlingHistory) {
64
+    Validate.notNull(routeSpecification, "Route specification is required");
65
+    Validate.notNull(handlingHistory, "Delivery history is required");
66
+
67
+    final HandlingEvent lastEvent = handlingHistory.mostRecentlyCompletedEvent();
68
+
69
+    return new Delivery(lastEvent, itinerary, routeSpecification);
70
+  }
71
+
72
+  /**
73
+   * Internal constructor.
74
+   *
75
+   * @param lastEvent last event
76
+   * @param itinerary itinerary
77
+   * @param routeSpecification route specification
78
+   */
79
+  private Delivery(HandlingEvent lastEvent, Itinerary itinerary, RouteSpecification routeSpecification) {
80
+    this.calculatedAt = new Date();
29 81
     this.lastEvent = lastEvent;
82
+
83
+    this.misdirected = calculateMisdirectionStatus(itinerary);
84
+    this.routingStatus = calculateRoutingStatus(itinerary, routeSpecification);
85
+    this.transportStatus = calculateTransportStatus();
86
+    this.lastKnownLocation = calculateLastKnownLocation();
87
+    this.currentVoyage = calculateCurrentVoyage();
88
+    this.eta = calculateEta(itinerary);
89
+    this.nextExpectedActivity = calculateNextExpectedActivity(routeSpecification, itinerary);
90
+    this.isUnloadedAtDestination = calculateUnloadedAtDestination(routeSpecification);
30 91
   }
31 92
 
32 93
   /**
@@ -51,81 +112,197 @@ public class Delivery implements ValueObject<Delivery> {
51 112
   }
52 113
 
53 114
   /**
54
-   * @param handlingHistory delivery history
55
-   * @return An up to date Delivery derived from this collection of handling events.
115
+   * Check if cargo is misdirected.
116
+   * <p/>
117
+   * <ul>
118
+   * <li>A cargo is misdirected if it is in a location that's not in the itinerary.
119
+   * <li>A cargo with no itinerary can not be misdirected.
120
+   * <li>A cargo that has received no handling events can not be misdirected.
121
+   * </ul>
122
+   *
123
+   * @return <code>true</code> if the cargo has been misdirected,
56 124
    */
57
-  static Delivery derivedFrom(HandlingHistory handlingHistory) {
58
-    Validate.notNull(handlingHistory, "Delivery history is required");
59
-    
60
-    final Delivery delivery = new Delivery(
61
-      handlingHistory.mostRecentlyCompletedEvent()
62
-    );
63
-    delivery.calculateTransportStatus();
64
-    delivery.calculateLastKnownLocation();
65
-    delivery.calculateCurrentVoyage();
66
-    return delivery;
125
+  public boolean isMisdirected() {
126
+    return misdirected;
67 127
   }
68 128
 
69 129
   /**
70
-   * @return The last event of the delivery history, or null is history is empty.
130
+   * @return Estimated time of arrival
71 131
    */
72
-  HandlingEvent lastEvent() {
73
-    return lastEvent;
132
+  public Date estimatedTimeOfArrival() {
133
+    if (eta != ETA_UNKOWN) {
134
+      return new Date(eta.getTime());
135
+    } else {
136
+      return ETA_UNKOWN;
137
+    }
138
+  }
139
+
140
+  /**
141
+   * @return The next expected handling activity.
142
+   */
143
+  public HandlingActivity nextExpectedActivity() {
144
+    return nextExpectedActivity;
145
+  }
146
+
147
+  /**
148
+   * @return True if the cargo has been unloaded at the final destination.
149
+   */
150
+  public boolean isUnloadedAtDestination() {
151
+    return isUnloadedAtDestination;
74 152
   }
75 153
 
76
-  private void calculateTransportStatus() {
154
+  /**
155
+   * @return Routing status.
156
+   */
157
+  public RoutingStatus routingStatus() {
158
+    return routingStatus;
159
+  }
160
+
161
+  /**
162
+   * @return When this delivery was calculated.
163
+   */
164
+  public Date calculatedAt() {
165
+    return new Date(calculatedAt.getTime());
166
+  }
167
+
168
+  // TODO add currentCarrierMovement (?)
169
+
170
+
171
+  // --- Internal calculations below ---
172
+
173
+
174
+  private TransportStatus calculateTransportStatus() {
77 175
     if (lastEvent == null) {
78
-      transportStatus = NOT_RECEIVED;
79
-      return;
176
+      return NOT_RECEIVED;
80 177
     }
81 178
 
82 179
     switch (lastEvent.type()) {
83 180
       case LOAD:
84
-        transportStatus = ONBOARD_CARRIER;
85
-        break;
181
+        return ONBOARD_CARRIER;
86 182
       case UNLOAD:
87 183
       case RECEIVE:
88 184
       case CUSTOMS:
89
-        transportStatus = IN_PORT;
90
-        break;
185
+        return IN_PORT;
91 186
       case CLAIM:
92
-        transportStatus = CLAIMED;
93
-        break;
187
+        return CLAIMED;
94 188
       default:
95
-        transportStatus = UNKNOWN;
189
+        return UNKNOWN;
96 190
     }
97 191
   }
98 192
 
99
-  private void calculateLastKnownLocation() {
193
+  private Location calculateLastKnownLocation() {
100 194
     if (lastEvent != null) {
101
-      lastKnownLocation = lastEvent.location();
195
+      return lastEvent.location();
102 196
     } else {
103
-      lastKnownLocation = null;
197
+      return null;
104 198
     }
105 199
   }
106 200
 
107
-  // TODO add currentCarrierMovement (?)
108
-
109
-  private void calculateCurrentVoyage() {
201
+  private Voyage calculateCurrentVoyage() {
110 202
     if (transportStatus().equals(ONBOARD_CARRIER) && lastEvent != null) {
111
-      currentVoyage = lastEvent.voyage();
203
+      return lastEvent.voyage();
204
+    } else {
205
+      return null;
206
+    }
207
+  }
208
+
209
+  private boolean calculateMisdirectionStatus(Itinerary itinerary) {
210
+    if (lastEvent == null) {
211
+      return false;
212
+    } else {
213
+      return !itinerary.isExpected(lastEvent);
214
+    }
215
+  }
216
+
217
+  private Date calculateEta(Itinerary itinerary) {
218
+    if (onTrack()) {
219
+      return itinerary.finalArrivalDate();
220
+    } else {
221
+      return ETA_UNKOWN;
222
+    }
223
+  }
224
+
225
+  private HandlingActivity calculateNextExpectedActivity(RouteSpecification routeSpecification, Itinerary itinerary) {
226
+    if (!onTrack()) return NO_ACTIVITY;
227
+
228
+    if (lastEvent == null) return new HandlingActivity(HandlingEvent.Type.RECEIVE, routeSpecification.origin());
229
+
230
+    switch (lastEvent.type()) {
231
+
232
+      case LOAD:
233
+        for (Leg leg : itinerary.legs()) {
234
+          if (leg.loadLocation().sameIdentityAs(lastEvent.location())) {
235
+            return new HandlingActivity(HandlingEvent.Type.UNLOAD, leg.unloadLocation(), leg.voyage());
236
+          }
237
+        }
238
+
239
+        return NO_ACTIVITY;
240
+
241
+      case UNLOAD:
242
+        for (Iterator<Leg> it = itinerary.legs().iterator(); it.hasNext();) {
243
+          final Leg leg = it.next();
244
+          if (leg.unloadLocation().sameIdentityAs(lastEvent.location())) {
245
+            if (it.hasNext()) {
246
+              final Leg nextLeg = it.next();
247
+              return new HandlingActivity(HandlingEvent.Type.LOAD, nextLeg.loadLocation(), nextLeg.voyage());
248
+            } else {
249
+              return new HandlingActivity(HandlingEvent.Type.CLAIM, leg.unloadLocation());
250
+            }
251
+          }
252
+        }
253
+
254
+        return NO_ACTIVITY;
255
+
256
+      case RECEIVE:
257
+        final Leg firstLeg = itinerary.legs().iterator().next();
258
+        return new HandlingActivity(HandlingEvent.Type.LOAD, firstLeg.loadLocation(), firstLeg.voyage());
259
+
260
+      case CLAIM:
261
+      default:
262
+        return NO_ACTIVITY;
263
+    }
264
+  }
265
+
266
+  private RoutingStatus calculateRoutingStatus(Itinerary itinerary, RouteSpecification routeSpecification) {
267
+    if (itinerary == null) {
268
+      return NOT_ROUTED;
112 269
     } else {
113
-      currentVoyage = null;
270
+      if (routeSpecification.isSatisfiedBy(itinerary)) {
271
+        return ROUTED;
272
+      } else {
273
+        return MISROUTED;
274
+      }
114 275
     }
115 276
   }
116 277
 
278
+  private boolean calculateUnloadedAtDestination(RouteSpecification routeSpecification) {
279
+    return lastEvent != null &&
280
+      HandlingEvent.Type.UNLOAD.sameValueAs(lastEvent.type()) &&
281
+      routeSpecification.destination().sameIdentityAs(lastEvent.location());
282
+  }
283
+
284
+  private boolean onTrack() {
285
+    return routingStatus.equals(ROUTED) && !misdirected;
286
+  }
287
+
117 288
   @Override
118
-  public boolean sameValueAs(Delivery other) {
289
+  public boolean sameValueAs(final Delivery other) {
119 290
     return other != null && new EqualsBuilder().
120 291
       append(this.transportStatus, other.transportStatus).
121 292
       append(this.lastKnownLocation, other.lastKnownLocation).
122 293
       append(this.currentVoyage, other.currentVoyage).
294
+      append(this.misdirected, other.misdirected).
295
+      append(this.eta, other.eta).
296
+      append(this.nextExpectedActivity, other.nextExpectedActivity).
297
+      append(this.isUnloadedAtDestination, other.isUnloadedAtDestination).
298
+      append(this.routingStatus, other.routingStatus).
299
+      append(this.calculatedAt, other.calculatedAt).
123 300
       append(this.lastEvent, other.lastEvent).
124 301
       isEquals();
125 302
   }
126 303
 
127 304
   @Override
128
-  public boolean equals(Object o) {
305
+  public boolean equals(final Object o) {
129 306
     if (this == o) return true;
130 307
     if (o == null || getClass() != o.getClass()) return false;
131 308
 
@@ -140,6 +317,12 @@ public class Delivery implements ValueObject<Delivery> {
140 317
       append(transportStatus).
141 318
       append(lastKnownLocation).
142 319
       append(currentVoyage).
320
+      append(misdirected).
321
+      append(eta).
322
+      append(nextExpectedActivity).
323
+      append(isUnloadedAtDestination).
324
+      append(routingStatus).
325
+      append(calculatedAt).
143 326
       append(lastEvent).
144 327
       toHashCode();
145 328
   }
@@ -147,5 +330,4 @@ public class Delivery implements ValueObject<Delivery> {
147 330
   Delivery() {
148 331
     // Needed by Hibernate
149 332
   }
150
-
151 333
 }

+ 1
- 1
dddsample/src/main/java/se/citerus/dddsample/interfaces/booking/facade/internal/assembler/CargoRoutingDTOAssembler.java Dosyayı Görüntüle

@@ -16,7 +16,7 @@ public class CargoRoutingDTOAssembler {
16 16
       cargo.origin().unLocode().idString(),
17 17
       cargo.routeSpecification().destination().unLocode().idString(),
18 18
       cargo.routeSpecification().arrivalDeadline(),
19
-      cargo.routingStatus().equals(RoutingStatus.MISROUTED));
19
+      cargo.delivery().routingStatus().sameValueAs(RoutingStatus.MISROUTED));
20 20
     for (Leg leg : cargo.itinerary().legs()) {
21 21
       dto.addLeg(
22 22
         leg.voyage().voyageNumber().idString(),

+ 3
- 3
dddsample/src/main/java/se/citerus/dddsample/interfaces/tracking/CargoTrackingViewAdapter.java Dosyayı Görüntüle

@@ -104,14 +104,14 @@ public final class CargoTrackingViewAdapter {
104 104
   }
105 105
 
106 106
   public String getEta() {
107
-    Date eta = cargo.estimatedTimeOfArrival();
107
+    Date eta = cargo.delivery().estimatedTimeOfArrival();
108 108
 
109 109
     if (eta == null) return "?";
110 110
     else return new SimpleDateFormat(FORMAT).format(eta);
111 111
   }
112 112
 
113 113
   public String getNextExpectedActivity() {
114
-      HandlingActivity activity = cargo.nextExpectedActivity();
114
+      HandlingActivity activity = cargo.delivery().nextExpectedActivity();
115 115
       if (activity == null) {
116 116
         return "";
117 117
       }
@@ -135,7 +135,7 @@ public final class CargoTrackingViewAdapter {
135 135
    * @return True if cargo is misdirected.
136 136
    */
137 137
   public boolean isMisdirected() {
138
-    return cargo.isMisdirected();
138
+    return cargo.delivery().isMisdirected();
139 139
   }
140 140
 
141 141
   /**

+ 22
- 19
dddsample/src/main/resources/se/citerus/dddsample/infrastructure/persistence/hibernate/Cargo.hbm.xml Dosyayı Görüntüle

@@ -13,32 +13,34 @@
13 13
 
14 14
     <many-to-one name="origin" column="origin_id" not-null="false" cascade="none" update="false" foreign-key="origin_fk"/>
15 15
 
16
-    <property name="misdirected" column="is_misdirected" not-null="true"/>
17
-    <property name="eta" column="eta" not-null="false"/>
18
-    
19
-    <property name="routingStatus" column="routing_status" not-null="true">
20
-      <type name="org.hibernate.type.EnumType">
21
-        <param name="enumClass">se.citerus.dddsample.domain.model.cargo.RoutingStatus</param>
22
-        <param name="type">12</param><!-- 12 is java.sql.Types.VARCHAR -->
23
-      </type>
24
-    </property>
16
+    <component name="trackingId" unique="true" update="false">
17
+      <property name="id" column="tracking_id"/>
18
+    </component>
19
+
20
+    <component name="delivery" lazy="true">
21
+      <property name="misdirected" column="is_misdirected" not-null="true"/>
22
+      <property name="eta" column="eta" not-null="false"/>
23
+      <property name="calculatedAt" column="calculated_at" not-null="true"/>
24
+      <property name="isUnloadedAtDestination" column="unloaded_at_dest" not-null="true"/>
25 25
 
26
-    <component name="nextExpectedActivity" update="true">
27
-      <many-to-one name="location" column="next_expected_location_id" foreign-key="next_expected_location_fk"/>
28
-      <property name="type" column="next_expected_handling_event_type">
26
+      <property name="routingStatus" column="routing_status" not-null="true">
29 27
         <type name="org.hibernate.type.EnumType">
30
-          <param name="enumClass">se.citerus.dddsample.domain.model.handling.HandlingEvent$Type</param>
28
+          <param name="enumClass">se.citerus.dddsample.domain.model.cargo.RoutingStatus</param>
31 29
           <param name="type">12</param><!-- 12 is java.sql.Types.VARCHAR -->
32 30
         </type>
33 31
       </property>
34
-      <many-to-one name="voyage" column="next_expected_voyage_id" foreign-key="next_expected_voyage_fk"/>
35
-    </component>
36 32
 
37
-    <component name="trackingId" unique="true" update="false">
38
-      <property name="id" column="tracking_id"/>
39
-    </component>
33
+      <component name="nextExpectedActivity" update="true">
34
+        <many-to-one name="location" column="next_expected_location_id" foreign-key="next_expected_location_fk" cascade="none"/>
35
+        <property name="type" column="next_expected_handling_event_type">
36
+          <type name="org.hibernate.type.EnumType">
37
+            <param name="enumClass">se.citerus.dddsample.domain.model.handling.HandlingEvent$Type</param>
38
+            <param name="type">12</param><!-- 12 is java.sql.Types.VARCHAR -->
39
+          </type>
40
+        </property>
41
+        <many-to-one name="voyage" column="next_expected_voyage_id" foreign-key="next_expected_voyage_fk" cascade="none"/>
42
+      </component>
40 43
 
41
-    <component name="delivery" lazy="true">
42 44
       <property name="transportStatus" column="transport_status" not-null="true">
43 45
         <type name="org.hibernate.type.EnumType">
44 46
           <param name="enumClass">se.citerus.dddsample.domain.model.cargo.TransportStatus</param>
@@ -57,6 +59,7 @@
57 59
     </component>
58 60
 
59 61
     <component name="itinerary">
62
+      <!-- cascade=all-delete-orphan would be better, but it doesn't seem to work inside a component -->
60 63
       <list name="legs" lazy="true" cascade="all">
61 64
         <key column="cargo_id" foreign-key="itinerary_fk"/>
62 65
         <index column="leg_index"/>

+ 2
- 2
dddsample/src/main/resources/se/citerus/dddsample/infrastructure/persistence/hibernate/Voyage.hbm.xml Dosyayı Görüntüle

@@ -15,8 +15,8 @@
15 15
       <property name="number" column="voyage_number"/>
16 16
     </component>
17 17
 
18
-    <component name="schedule" lazy="false">
19
-      <list name="carrierMovements" lazy="false" cascade="all-delete-orphan">
18
+    <component name="schedule">
19
+      <list name="carrierMovements" cascade="all-delete-orphan">
20 20
         <key column="voyage_id" foreign-key="voyage_fk"/>
21 21
         <index column="cm_index"/>
22 22
         <one-to-many class="se.citerus.dddsample.domain.model.voyage.CarrierMovement"/>

+ 15
- 15
dddsample/src/test/java/se/citerus/dddsample/domain/model/cargo/CargoTest.java Dosyayı Görüntüle

@@ -40,7 +40,7 @@ public class CargoTest extends TestCase {
40 40
 
41 41
     final Cargo cargo = new Cargo(trackingId, routeSpecification);
42 42
 
43
-    assertEquals(NOT_ROUTED, cargo.routingStatus());
43
+    assertEquals(NOT_ROUTED, cargo.delivery().routingStatus());
44 44
     assertEquals(NOT_RECEIVED, cargo.delivery().transportStatus());
45 45
     assertEquals(Location.UNKNOWN, cargo.delivery().lastKnownLocation());
46 46
     assertEquals(Voyage.NONE, cargo.delivery().currentVoyage());    
@@ -59,13 +59,13 @@ public class CargoTest extends TestCase {
59 59
 
60 60
     cargo.specifyNewRoute(acceptOnlyGood);
61 61
 
62
-    assertEquals(NOT_ROUTED, cargo.routingStatus());
62
+    assertEquals(NOT_ROUTED, cargo.delivery().routingStatus());
63 63
     
64 64
     cargo.assignToRoute(bad);
65
-    assertEquals(MISROUTED, cargo.routingStatus());
65
+    assertEquals(MISROUTED, cargo.delivery().routingStatus());
66 66
 
67 67
     cargo.assignToRoute(good);
68
-    assertEquals(ROUTED, cargo.routingStatus());
68
+    assertEquals(ROUTED, cargo.delivery().routingStatus());
69 69
   }
70 70
 
71 71
   public void testlastKnownLocationUnknownWhenNoEvents() throws Exception {
@@ -114,13 +114,13 @@ public class CargoTest extends TestCase {
114 114
 
115 115
   public void testIsUnloadedAtFinalDestination() throws Exception {
116 116
     Cargo cargo = setUpCargoWithItinerary(HANGZOU, TOKYO, NEWYORK);
117
-    assertFalse(cargo.isUnloadedAtDestination());
117
+    assertFalse(cargo.delivery().isUnloadedAtDestination());
118 118
 
119 119
     // Adding an event unrelated to unloading at final destination
120 120
     events.add(
121 121
       new HandlingEvent(cargo, new Date(10), new Date(), HandlingEvent.Type.RECEIVE, HANGZOU));
122 122
     cargo.deriveDeliveryProgress(new HandlingHistory(events));
123
-    assertFalse(cargo.isUnloadedAtDestination());
123
+    assertFalse(cargo.delivery().isUnloadedAtDestination());
124 124
 
125 125
     Voyage voyage = new Voyage.Builder(new VoyageNumber("0123"), HANGZOU).
126 126
       addMovement(NEWYORK, new Date(), new Date()).
@@ -130,19 +130,19 @@ public class CargoTest extends TestCase {
130 130
     events.add(
131 131
       new HandlingEvent(cargo, new Date(20), new Date(), HandlingEvent.Type.UNLOAD, TOKYO, voyage));
132 132
     cargo.deriveDeliveryProgress(new HandlingHistory(events));
133
-    assertFalse(cargo.isUnloadedAtDestination());
133
+    assertFalse(cargo.delivery().isUnloadedAtDestination());
134 134
 
135 135
     // Adding an event in the final destination, but not unload
136 136
     events.add(
137 137
       new HandlingEvent(cargo, new Date(30), new Date(), HandlingEvent.Type.CUSTOMS, NEWYORK));
138 138
     cargo.deriveDeliveryProgress(new HandlingHistory(events));
139
-    assertFalse(cargo.isUnloadedAtDestination());
139
+    assertFalse(cargo.delivery().isUnloadedAtDestination());
140 140
 
141 141
     // Finally, cargo is unloaded at final destination
142 142
     events.add(
143 143
       new HandlingEvent(cargo, new Date(40), new Date(), HandlingEvent.Type.UNLOAD, NEWYORK, voyage));
144 144
     cargo.deriveDeliveryProgress(new HandlingHistory(events));
145
-    assertTrue(cargo.isUnloadedAtDestination());
145
+    assertTrue(cargo.delivery().isUnloadedAtDestination());
146 146
   }
147 147
 
148 148
   // TODO: Generate test data some better way
@@ -224,12 +224,12 @@ public class CargoTest extends TestCase {
224 224
   public void testIsMisdirected() throws Exception {
225 225
     //A cargo with no itinerary is not misdirected
226 226
     Cargo cargo = new Cargo(new TrackingId("TRKID"), new RouteSpecification(SHANGHAI, GOTHENBURG, new Date()));
227
-    assertFalse(cargo.isMisdirected());
227
+    assertFalse(cargo.delivery().isMisdirected());
228 228
 
229 229
     cargo = setUpCargoWithItinerary(SHANGHAI, ROTTERDAM, GOTHENBURG);
230 230
 
231 231
     //A cargo with no handling events is not misdirected
232
-    assertFalse(cargo.isMisdirected());
232
+    assertFalse(cargo.delivery().isMisdirected());
233 233
 
234 234
     Collection<HandlingEvent> handlingEvents = new ArrayList<HandlingEvent>();
235 235
 
@@ -244,7 +244,7 @@ public class CargoTest extends TestCase {
244 244
 
245 245
     events.addAll(handlingEvents);
246 246
     cargo.deriveDeliveryProgress(new HandlingHistory(events));
247
-    assertFalse(cargo.isMisdirected());
247
+    assertFalse(cargo.delivery().isMisdirected());
248 248
 
249 249
     //Try a couple of failing ones
250 250
 
@@ -255,7 +255,7 @@ public class CargoTest extends TestCase {
255 255
     events.addAll(handlingEvents);
256 256
     cargo.deriveDeliveryProgress(new HandlingHistory(events));
257 257
 
258
-    assertTrue(cargo.isMisdirected());
258
+    assertTrue(cargo.delivery().isMisdirected());
259 259
 
260 260
 
261 261
     cargo = setUpCargoWithItinerary(SHANGHAI, ROTTERDAM, GOTHENBURG);
@@ -269,7 +269,7 @@ public class CargoTest extends TestCase {
269 269
     events.addAll(handlingEvents);
270 270
     cargo.deriveDeliveryProgress(new HandlingHistory(events));
271 271
 
272
-    assertTrue(cargo.isMisdirected());
272
+    assertTrue(cargo.delivery().isMisdirected());
273 273
 
274 274
 
275 275
     cargo = setUpCargoWithItinerary(SHANGHAI, ROTTERDAM, GOTHENBURG);
@@ -283,7 +283,7 @@ public class CargoTest extends TestCase {
283 283
     events.addAll(handlingEvents);
284 284
     cargo.deriveDeliveryProgress(new HandlingHistory(events));
285 285
 
286
-    assertTrue(cargo.isMisdirected());
286
+    assertTrue(cargo.delivery().isMisdirected());
287 287
   }
288 288
 
289 289
   private Cargo setUpCargoWithItinerary(Location origin, Location midpoint, Location destination) {

+ 51
- 64
dddsample/src/test/java/se/citerus/dddsample/scenario/CargoLifecycleScenarioTest.java Dosyayı Görüntüle

@@ -8,14 +8,14 @@ import se.citerus.dddsample.application.HandlingEventService;
8 8
 import se.citerus.dddsample.application.impl.BookingServiceImpl;
9 9
 import se.citerus.dddsample.application.impl.CargoInspectionServiceImpl;
10 10
 import se.citerus.dddsample.application.impl.HandlingEventServiceImpl;
11
+import static se.citerus.dddsample.application.util.DateTestUtil.toDate;
11 12
 import se.citerus.dddsample.domain.model.cargo.*;
12 13
 import static se.citerus.dddsample.domain.model.cargo.RoutingStatus.*;
13 14
 import static se.citerus.dddsample.domain.model.cargo.TransportStatus.*;
14
-import se.citerus.dddsample.domain.model.handling.HandlingEvent;
15
+import se.citerus.dddsample.domain.model.handling.CannotCreateHandlingEventException;
15 16
 import static se.citerus.dddsample.domain.model.handling.HandlingEvent.Type.*;
16 17
 import se.citerus.dddsample.domain.model.handling.HandlingEventFactory;
17 18
 import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
18
-import se.citerus.dddsample.domain.model.handling.HandlingHistory;
19 19
 import se.citerus.dddsample.domain.model.location.Location;
20 20
 import se.citerus.dddsample.domain.model.location.LocationRepository;
21 21
 import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
@@ -27,10 +27,13 @@ import se.citerus.dddsample.domain.model.voyage.VoyageRepository;
27 27
 import se.citerus.dddsample.domain.service.RoutingService;
28 28
 import se.citerus.dddsample.infrastructure.messaging.stub.SynchronousApplicationEventsStub;
29 29
 import se.citerus.dddsample.infrastructure.persistence.inmemory.CargoRepositoryInMem;
30
+import se.citerus.dddsample.infrastructure.persistence.inmemory.HandlingEventRepositoryInMem;
30 31
 import se.citerus.dddsample.infrastructure.persistence.inmemory.LocationRepositoryInMem;
31 32
 import se.citerus.dddsample.infrastructure.persistence.inmemory.VoyageRepositoryInMem;
32 33
 
33
-import java.util.*;
34
+import java.util.Arrays;
35
+import java.util.Date;
36
+import java.util.List;
34 37
 
35 38
 public class CargoLifecycleScenarioTest extends TestCase {
36 39
 
@@ -85,7 +88,7 @@ public class CargoLifecycleScenarioTest extends TestCase {
85 88
        and it should arrive in no more than two weeks. */
86 89
     Location origin = HONGKONG;
87 90
     Location destination = STOCKHOLM;
88
-    Date arrivalDeadline = inTwoWeeks();
91
+    Date arrivalDeadline = toDate("2009-03-18");
89 92
 
90 93
     /* Use case 1: booking
91 94
 
@@ -105,10 +108,10 @@ public class CargoLifecycleScenarioTest extends TestCase {
105 108
     Cargo cargo = cargoRepository.find(trackingId);
106 109
     assertNotNull(cargo);
107 110
     assertEquals(NOT_RECEIVED, cargo.delivery().transportStatus());
108
-    assertEquals(NOT_ROUTED, cargo.routingStatus());
109
-    assertFalse(cargo.isMisdirected());
110
-    assertNull(cargo.estimatedTimeOfArrival());
111
-    assertNull(cargo.nextExpectedActivity());
111
+    assertEquals(NOT_ROUTED, cargo.delivery().routingStatus());
112
+    assertFalse(cargo.delivery().isMisdirected());
113
+    assertNull(cargo.delivery().estimatedTimeOfArrival());
114
+    assertNull(cargo.delivery().nextExpectedActivity());
112 115
 
113 116
     /* Use case 2: routing
114 117
 
@@ -123,9 +126,9 @@ public class CargoLifecycleScenarioTest extends TestCase {
123 126
     cargo.assignToRoute(itinerary);
124 127
 
125 128
     assertEquals(NOT_RECEIVED, cargo.delivery().transportStatus());
126
-    assertEquals(ROUTED, cargo.routingStatus());
127
-    assertNotNull(cargo.estimatedTimeOfArrival());
128
-    assertEquals(new HandlingActivity(RECEIVE, HONGKONG), cargo.nextExpectedActivity());
129
+    assertEquals(ROUTED, cargo.delivery().routingStatus());
130
+    assertNotNull(cargo.delivery().estimatedTimeOfArrival());
131
+    assertEquals(new HandlingActivity(RECEIVE, HONGKONG), cargo.delivery().nextExpectedActivity());
129 132
 
130 133
     /*
131 134
       Use case 3: handling
@@ -142,7 +145,7 @@ public class CargoLifecycleScenarioTest extends TestCase {
142 145
       Handling begins: cargo is received in Hongkong.
143 146
       */
144 147
     handlingEventService.registerHandlingEvent(
145
-      new Date(100), trackingId, null, HONGKONG.unLocode(), RECEIVE
148
+      toDate("2009-03-01"), trackingId, null, HONGKONG.unLocode(), RECEIVE
146 149
     );
147 150
 
148 151
     assertEquals(IN_PORT, cargo.delivery().transportStatus());
@@ -150,15 +153,15 @@ public class CargoLifecycleScenarioTest extends TestCase {
150 153
     
151 154
     // Next event: Load onto voyage CM003 in Hongkong
152 155
     handlingEventService.registerHandlingEvent(
153
-      new Date(200), trackingId, CM003.voyageNumber(), HONGKONG.unLocode(), LOAD
156
+      toDate("2009-03-03"), trackingId, CM003.voyageNumber(), HONGKONG.unLocode(), LOAD
154 157
     );
155 158
 
156 159
     // Check current state - should be ok
157 160
     assertEquals(CM003, cargo.delivery().currentVoyage());
158 161
     assertEquals(HONGKONG, cargo.delivery().lastKnownLocation());
159 162
     assertEquals(ONBOARD_CARRIER, cargo.delivery().transportStatus());
160
-    assertFalse(cargo.isMisdirected());
161
-    assertEquals(new HandlingActivity(UNLOAD, NEWYORK, CM003), cargo.nextExpectedActivity());
163
+    assertFalse(cargo.delivery().isMisdirected());
164
+    assertEquals(new HandlingActivity(UNLOAD, NEWYORK, CM003), cargo.delivery().nextExpectedActivity());
162 165
 
163 166
 
164 167
     /*
@@ -170,22 +173,26 @@ public class CargoLifecycleScenarioTest extends TestCase {
170 173
      */
171 174
     final VoyageNumber noSuchVoyageNumber = new VoyageNumber("XX000");
172 175
     final UnLocode noSuchUnLocode = new UnLocode("ZZZZZ");
173
-    handlingEventService.registerHandlingEvent(
174
-      new Date(300), trackingId, noSuchVoyageNumber, noSuchUnLocode, LOAD
175
-    );
176
+    try {
177
+      handlingEventService.registerHandlingEvent(
178
+      toDate("2009-03-05"), trackingId, noSuchVoyageNumber, noSuchUnLocode, LOAD
179
+      );
180
+      fail("Should not be able to register a handling event with invalid location and voyage");
181
+    } catch (CannotCreateHandlingEventException expected) {
182
+    }
176 183
 
177 184
 
178 185
     // Cargo is now (incorrectly) unloaded in Tokyo
179 186
     handlingEventService.registerHandlingEvent(
180
-      new Date(400), trackingId, CM003.voyageNumber(), TOKYO.unLocode(), UNLOAD
187
+      toDate("2009-03-05"), trackingId, CM003.voyageNumber(), TOKYO.unLocode(), UNLOAD
181 188
     );
182 189
 
183 190
     // Check current state - cargo is misdirected!
184 191
     assertEquals(NONE, cargo.delivery().currentVoyage());
185 192
     assertEquals(TOKYO, cargo.delivery().lastKnownLocation());
186 193
     assertEquals(IN_PORT, cargo.delivery().transportStatus());
187
-    assertTrue(cargo.isMisdirected());
188
-    assertNull(cargo.nextExpectedActivity());
194
+    assertTrue(cargo.delivery().isMisdirected());
195
+    assertNull(cargo.delivery().nextExpectedActivity());
189 196
 
190 197
 
191 198
     // -- Cargo needs to be rerouted --
@@ -196,8 +203,8 @@ public class CargoLifecycleScenarioTest extends TestCase {
196 203
     cargo.specifyNewRoute(fromTokyo);
197 204
 
198 205
     // The old itinerary does not satisfy the new specification
199
-    assertEquals(MISROUTED, cargo.routingStatus());
200
-    assertNull(cargo.nextExpectedActivity());
206
+    assertEquals(MISROUTED, cargo.delivery().routingStatus());
207
+    assertNull(cargo.delivery().nextExpectedActivity());
201 208
 
202 209
     // Repeat procedure of selecting one out of a number of possible routes satisfying the route spec
203 210
     List<Itinerary> newItineraries = bookingService.requestPossibleRoutesForCargo(cargo.trackingId());
@@ -205,7 +212,7 @@ public class CargoLifecycleScenarioTest extends TestCase {
205 212
     cargo.assignToRoute(newItinerary);
206 213
 
207 214
     // New itinerary should satisfy new route
208
-    assertEquals(ROUTED, cargo.routingStatus());
215
+    assertEquals(ROUTED, cargo.delivery().routingStatus());
209 216
 
210 217
     // TODO we can't handle the face that after a reroute, the cargo isn't misdirected anymore
211 218
     //assertFalse(cargo.isMisdirected());
@@ -217,65 +224,65 @@ public class CargoLifecycleScenarioTest extends TestCase {
217 224
 
218 225
     // Load in Tokyo
219 226
     handlingEventService.registerHandlingEvent(
220
-      new Date(500), trackingId, CM003.voyageNumber(), TOKYO.unLocode(), LOAD
227
+      toDate("2009-03-08"), trackingId, CM003.voyageNumber(), TOKYO.unLocode(), LOAD
221 228
     );
222 229
 
223 230
     // Check current state - should be ok
224 231
     assertEquals(CM003, cargo.delivery().currentVoyage());
225 232
     assertEquals(TOKYO, cargo.delivery().lastKnownLocation());
226 233
     assertEquals(ONBOARD_CARRIER, cargo.delivery().transportStatus());
227
-    assertFalse(cargo.isMisdirected());
228
-    assertEquals(new HandlingActivity(UNLOAD, HAMBURG, CM003), cargo.nextExpectedActivity());
234
+    assertFalse(cargo.delivery().isMisdirected());
235
+    assertEquals(new HandlingActivity(UNLOAD, HAMBURG, CM003), cargo.delivery().nextExpectedActivity());
229 236
 
230 237
     // Unload in Hamburg
231 238
     handlingEventService.registerHandlingEvent(
232
-      new Date(600), trackingId, CM003.voyageNumber(), HAMBURG.unLocode(), UNLOAD
239
+      toDate("2009-03-12"), trackingId, CM003.voyageNumber(), HAMBURG.unLocode(), UNLOAD
233 240
     );
234 241
 
235 242
     // Check current state - should be ok
236 243
     assertEquals(NONE, cargo.delivery().currentVoyage());
237 244
     assertEquals(HAMBURG, cargo.delivery().lastKnownLocation());
238 245
     assertEquals(IN_PORT, cargo.delivery().transportStatus());
239
-    assertFalse(cargo.isMisdirected());
240
-    assertEquals(new HandlingActivity(LOAD, HAMBURG, CM005), cargo.nextExpectedActivity());
246
+    assertFalse(cargo.delivery().isMisdirected());
247
+    assertEquals(new HandlingActivity(LOAD, HAMBURG, CM005), cargo.delivery().nextExpectedActivity());
241 248
 
242 249
 
243 250
     // Load in Hamburg
244 251
     handlingEventService.registerHandlingEvent(
245
-      new Date(700), trackingId, CM005.voyageNumber(), HAMBURG.unLocode(), LOAD
252
+      toDate("2009-03-14"), trackingId, CM005.voyageNumber(), HAMBURG.unLocode(), LOAD
246 253
     );
247 254
 
248 255
     // Check current state - should be ok
249 256
     assertEquals(CM005, cargo.delivery().currentVoyage());
250 257
     assertEquals(HAMBURG, cargo.delivery().lastKnownLocation());
251 258
     assertEquals(ONBOARD_CARRIER, cargo.delivery().transportStatus());
252
-    assertFalse(cargo.isMisdirected());
253
-    assertEquals(new HandlingActivity(UNLOAD, STOCKHOLM, CM005), cargo.nextExpectedActivity());
259
+    assertFalse(cargo.delivery().isMisdirected());
260
+    assertEquals(new HandlingActivity(UNLOAD, STOCKHOLM, CM005), cargo.delivery().nextExpectedActivity());
254 261
 
255 262
 
256 263
     // Unload in Stockholm
257 264
     handlingEventService.registerHandlingEvent(
258
-      new Date(800), trackingId, CM005.voyageNumber(), STOCKHOLM.unLocode(), UNLOAD
265
+      toDate("2009-03-15"), trackingId, CM005.voyageNumber(), STOCKHOLM.unLocode(), UNLOAD
259 266
     );
260 267
 
261 268
     // Check current state - should be ok
262 269
     assertEquals(NONE, cargo.delivery().currentVoyage());
263 270
     assertEquals(STOCKHOLM, cargo.delivery().lastKnownLocation());
264 271
     assertEquals(IN_PORT, cargo.delivery().transportStatus());
265
-    assertFalse(cargo.isMisdirected());
266
-    assertEquals(new HandlingActivity(CLAIM, STOCKHOLM), cargo.nextExpectedActivity());
272
+    assertFalse(cargo.delivery().isMisdirected());
273
+    assertEquals(new HandlingActivity(CLAIM, STOCKHOLM), cargo.delivery().nextExpectedActivity());
267 274
 
268 275
     // Finally, cargo is claimed in Stockholm. This ends the cargo lifecycle from our perspective.
269 276
     handlingEventService.registerHandlingEvent(
270
-      new Date(900), trackingId, null, STOCKHOLM.unLocode(), CLAIM
277
+      toDate("2009-03-16"), trackingId, null, STOCKHOLM.unLocode(), CLAIM
271 278
     );
272 279
 
273 280
     // Check current state - should be ok
274 281
     assertEquals(NONE, cargo.delivery().currentVoyage());
275 282
     assertEquals(STOCKHOLM, cargo.delivery().lastKnownLocation());
276 283
     assertEquals(CLAIMED, cargo.delivery().transportStatus());
277
-    assertFalse(cargo.isMisdirected());
278
-    assertNull(cargo.nextExpectedActivity());
284
+    assertFalse(cargo.delivery().isMisdirected());
285
+    assertNull(cargo.delivery().nextExpectedActivity());
279 286
   }
280 287
 
281 288
 
@@ -324,40 +331,20 @@ public class CargoLifecycleScenarioTest extends TestCase {
324 331
 
325 332
     applicationEvents = new SynchronousApplicationEventsStub();
326 333
 
327
-    // Stub
328
-    // TODO move functionality to in-mem impl
329
-    handlingEventRepository = new HandlingEventRepository() {
330
-      Map<TrackingId, List<HandlingEvent>> eventMap = new HashMap<TrackingId, List<HandlingEvent>>();
331
-
332
-      @Override
333
-      public void store(HandlingEvent event) {
334
-        final TrackingId trackingId = event.cargo().trackingId();
335
-        List<HandlingEvent> list = eventMap.get(trackingId);
336
-        if (list == null) {
337
-          list = new ArrayList<HandlingEvent>();
338
-          eventMap.put(trackingId, list);
339
-        }
340
-        list.add(event);
341
-      }
342
-
343
-      @Override
344
-      public HandlingHistory lookupHandlingHistoryOfCargo(TrackingId trackingId) {
345
-        return new HandlingHistory(eventMap.get(trackingId));
346
-      }
347
-    };
348
-
349
-    // In-memory implementations
334
+    // In-memory implementations of the repositories
335
+    handlingEventRepository = new HandlingEventRepositoryInMem();
350 336
     cargoRepository = new CargoRepositoryInMem();
351 337
     locationRepository = new LocationRepositoryInMem();
352 338
     voyageRepository = new VoyageRepositoryInMem();
353 339
 
354 340
     // Actual factories and application services, wired with stubbed or in-memory infrastructure
355
-    cargoInspectionService = new CargoInspectionServiceImpl(applicationEvents, cargoRepository, handlingEventRepository);
356 341
     handlingEventFactory = new HandlingEventFactory(cargoRepository, voyageRepository, locationRepository);
342
+
343
+    cargoInspectionService = new CargoInspectionServiceImpl(applicationEvents, cargoRepository, handlingEventRepository);
357 344
     handlingEventService = new HandlingEventServiceImpl(handlingEventRepository, applicationEvents, handlingEventFactory);
358 345
     bookingService = new BookingServiceImpl(cargoRepository, locationRepository, routingService);
359 346
 
360
-    // Stub
347
+    // Circular dependency when doing synchrounous calls
361 348
     ((SynchronousApplicationEventsStub) applicationEvents).setCargoInspectionService(cargoInspectionService);
362 349
   }
363 350