Bladeren bron

Handling activities are now determined to be significant to the delivery of the cargo based on the logical order, not the reported completion time.

Introduced the LegMatch concept that describes how an activity matches a leg of an itinerary.

Moved scenario tests to inside the domain package (but still in the test source folder of course).
peter_backlund 17 jaren geleden
bovenliggende
commit
38fad0f01b
19 gewijzigde bestanden met toevoegingen van 1375 en 377 verwijderingen
  1. 1
    4
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/application/event/CargoUpdater.java
  2. 1
    0
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/application/event/ItineraryUpdater.java
  3. 2
    2
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/application/util/SampleDataGenerator.java
  4. 40
    122
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Cargo.java
  5. 59
    30
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Delivery.java
  6. 156
    98
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Itinerary.java
  7. 48
    0
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Leg.java
  8. 90
    0
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/LegMatch.java
  9. 2
    5
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/voyage/Voyage.java
  10. 1
    0
      dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/application/event/CargoUpdaterTest.java
  11. 86
    82
      dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/cargo/CargoTest.java
  12. 38
    12
      dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/cargo/DeliveryTest.java
  13. 74
    14
      dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/cargo/ItineraryTest.java
  14. 27
    6
      dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/cargo/LegTest.java
  15. 1
    1
      dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/location/CustomsZoneTest.java
  16. 228
    0
      dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/scenario/CargoLifecycle.java
  17. 418
    0
      dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/scenario/CargoLifecycleScenarioTest.java
  18. 102
    0
      dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/scenario/VoyageRescheduledScenarioTest.java
  19. 1
    1
      dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/infrastructure/reporting/ReportsUpdaterTest.java

+ 1
- 4
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/application/event/CargoUpdater.java Bestand weergeven

@@ -12,8 +12,6 @@ import se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent;
12 12
 import se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEventRepository;
13 13
 import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
14 14
 
15
-import java.util.Date;
16
-
17 15
 @Service
18 16
 public class CargoUpdater {
19 17
 
@@ -41,9 +39,8 @@ public class CargoUpdater {
41 39
 
42 40
     final HandlingActivity activity = handlingEvent.activity();
43 41
     final Cargo cargo = handlingEvent.cargo();
44
-    final Date completionTime = handlingEvent.completionTime();
45 42
 
46
-    cargo.handled(activity, completionTime);
43
+    cargo.handled(activity);
47 44
     cargoRepository.store(cargo);
48 45
 
49 46
     systemEvents.notifyOfCargoUpdate(cargo);

+ 1
- 0
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/application/event/ItineraryUpdater.java Bestand weergeven

@@ -27,6 +27,7 @@ public final class ItineraryUpdater {
27 27
   public void updateItineraries(final VoyageNumber voyageNumber) {
28 28
     final Voyage voyage = voyageRepository.find(voyageNumber);
29 29
     final List<Cargo> affectedCargos = cargoRepository.findCargosOnVoyage(voyage);
30
+
30 31
     for (final Cargo cargo : affectedCargos) {
31 32
       final Itinerary newItinerary = cargo.itinerary().withRescheduledVoyage(voyage);
32 33
       cargo.assignToRoute(newItinerary);

+ 2
- 2
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/application/util/SampleDataGenerator.java Bestand weergeven

@@ -250,7 +250,7 @@ public class SampleDataGenerator implements ServletContextListener {
250 250
         }
251 251
 
252 252
         final HandlingEvent handlingEvent = handlingEventRepository.mostRecentHandling(abc123);
253
-        abc123.handled(handlingEvent.activity(), new Date());
253
+        abc123.handled(handlingEvent.activity());
254 254
         session.update(abc123);
255 255
 
256 256
         // Cargo JKL567
@@ -294,7 +294,7 @@ public class SampleDataGenerator implements ServletContextListener {
294 294
         }
295 295
 
296 296
         HandlingEvent handlingEvent1 = handlingEventRepository.mostRecentHandling(jkl567);
297
-        jkl567.handled(handlingEvent1.activity(), new Date());
297
+        jkl567.handled(handlingEvent1.activity());
298 298
         session.update(jkl567);
299 299
       }
300 300
     });

+ 40
- 122
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Cargo.java Bestand weergeven

@@ -1,8 +1,8 @@
1 1
 package se.citerus.dddsample.tracking.core.domain.model.cargo;
2 2
 
3 3
 import org.apache.commons.lang.Validate;
4
+import static se.citerus.dddsample.tracking.core.domain.model.cargo.RoutingStatus.NOT_ROUTED;
4 5
 import se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent;
5
-import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.*;
6 6
 import se.citerus.dddsample.tracking.core.domain.model.location.CustomsZone;
7 7
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
8 8
 import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
@@ -10,7 +10,6 @@ import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
10 10
 import se.citerus.dddsample.tracking.core.domain.patterns.entity.EntitySupport;
11 11
 
12 12
 import java.util.Date;
13
-import java.util.Iterator;
14 13
 
15 14
 /**
16 15
  * A Cargo. This is the central class in the domain model,
@@ -60,7 +59,7 @@ public class Cargo extends EntitySupport<Cargo,TrackingId> {
60 59
 
61 60
     this.trackingId = trackingId;
62 61
     this.routeSpecification = routeSpecification;
63
-    this.delivery = Delivery.initial();
62
+    this.delivery = Delivery.beforeHandling();
64 63
   }
65 64
 
66 65
   @Override
@@ -78,7 +77,7 @@ public class Cargo extends EntitySupport<Cargo,TrackingId> {
78 77
   }
79 78
 
80 79
   /**
81
-   * @return The itinerary. Never null.
80
+   * @return The itinerary.
82 81
    */
83 82
   public Itinerary itinerary() {
84 83
     return itinerary;
@@ -106,112 +105,15 @@ public class Cargo extends EntitySupport<Cargo,TrackingId> {
106 105
    * @return Next expected activity.
107 106
    */
108 107
   public HandlingActivity nextExpectedActivity() {
109
-    /*
110
-     TODO Capture:
111
-
112
-     Cargo is misdirected but has been rerouted. Next expected acivity should be to load according to first leg
113
-     of new itinerary.
114
-
115
-     and
116
-
117
-     even if a cargo is misdirected, we expect it to be unloaded at next stop.
118
-
119
-    */
120
-    if (!onTrack()) {
121
-      return null;
122
-    }
123
-
124
-
125
-    
126
-    /*
127
-    if (delivery.transportStatus() == NOT_RECEIVED) {
128
-      return itinerary.firstLeg().receive();
129
-    }
130
-
131
-    Leg leg = itinerary.legMatching(delivery.mostRecentHandlingActivity());
132
-
133
-    if (leg == null) return null;
134
-
135
-    if (onboardCarrier()) return leg.unload();
136
-    else if (inPort())
137
-      if (leg.sameValueAs(itinerary.lastLeg())) return leg.claim();
138
-      else return leg.load();
139
-    else return null;
140
-
141
-  private boolean inPort() {
142
-    return delivery.transportStatus() == IN_PORT;
143
-  }
144
-
145
-  private boolean onboardCarrier() {
146
-    return delivery.transportStatus() == ONBOARD_CARRIER;
147
-  }
148
-
149
-
150
-    */
151
-
152
-  /*
153
-    public Date timeOfNextHandling() {
154
-      if (notReceived()) {
155
-        return null;
108
+    if (onTrack()) {
109
+      if (delivery.isRoutedAfterHandling()) {
110
+        return itinerary.firstLeg().deriveLoadActivity();
111
+      } else {
112
+        // Not yet able to determine when the next expected activity is non-pyhsical, i.e. customs
113
+        return itinerary.activitySucceding(delivery.mostRecentPhysicalHandlingActivity());
156 114
       }
157
-
158
-      final Leg leg = itinerary.legMatching(delivery.mostRecentHandlingActivity());
159
-
160
-      if (leg == null) return null;
161
-
162
-      if (onboardCarrier()) return leg.unloadTime();
163
-      else if (inPort())
164
-        if (leg.sameValueAs(itinerary.lastLeg())) return null;
165
-        else return leg.loadTime();
166
-      else return null;
167
-    }
168
-
169
-  private boolean notReceived() {
170
-    return delivery.transportStatus() == NOT_RECEIVED;
171
-  }
172
-  */
173
-
174
-
175
-
176
-  final Location lastKnownLocation = delivery.lastKnownLocation();
177
-
178
-    switch (delivery.transportStatus()) {
179
-      case IN_PORT:
180
-        if (itinerary.firstLeg().loadLocation().sameAs(lastKnownLocation)) {
181
-          final Leg firstLeg = itinerary.firstLeg();
182
-          return new HandlingActivity(LOAD, firstLeg.loadLocation(), firstLeg.voyage());
183
-        } else {
184
-          for (Iterator<Leg> it = itinerary.legs().iterator(); it.hasNext();) {
185
-            final Leg leg = it.next();
186
-            if (leg.unloadLocation().sameAs(lastKnownLocation)) {
187
-              if (it.hasNext()) {
188
-                final Leg nextLeg = it.next();
189
-                return new HandlingActivity(LOAD, nextLeg.loadLocation(), nextLeg.voyage());
190
-              } else {
191
-                return new HandlingActivity(CLAIM, leg.unloadLocation());
192
-              }
193
-            }
194
-          }
195
-
196
-          return null;
197
-        }
198
-
199
-      case NOT_RECEIVED:
200
-        final Leg leg = itinerary.firstLeg();
201
-        return new HandlingActivity(RECEIVE, leg.loadLocation());
202
-
203
-      case ONBOARD_CARRIER:
204
-        for (Leg leg1 : itinerary.legs()) {
205
-          if (leg1.loadLocation().sameAs(lastKnownLocation)) {
206
-            return new HandlingActivity(UNLOAD, leg1.unloadLocation(), leg1.voyage());
207
-          }
208
-        }
209
-
210
-        return null;
211
-
212
-      case CLAIMED:
213
-      default:
214
-        return null;
115
+    } else {
116
+      return null;
215 117
     }
216 118
   }
217 119
 
@@ -226,7 +128,8 @@ public class Cargo extends EntitySupport<Cargo,TrackingId> {
226 128
    * @return True if cargo is misdirected.
227 129
    */
228 130
   public boolean isMisdirected() {
229
-    return delivery.isMisdirected(itinerary, routeSpecification);
131
+    return delivery.hasBeenHandledAfterRouting() &&
132
+           !itinerary.isExpectedActivity(delivery.mostRecentPhysicalHandlingActivity());
230 133
   }
231 134
 
232 135
   /**
@@ -276,6 +179,9 @@ public class Cargo extends EntitySupport<Cargo,TrackingId> {
276 179
   public void assignToRoute(final Itinerary itinerary) {
277 180
     Validate.notNull(itinerary, "Itinerary is required");
278 181
 
182
+    if (routingStatus() != NOT_ROUTED) {
183
+      this.delivery = delivery.onRouting();
184
+    }
279 185
     this.itinerary = itinerary;
280 186
   }
281 187
 
@@ -291,14 +197,18 @@ public class Cargo extends EntitySupport<Cargo,TrackingId> {
291 197
    * @return Customs clearance point.
292 198
    */
293 199
   public Location customsClearancePoint() {
294
-    return customsZone().entryPoint(itinerary.locations());
200
+    if (itinerary == null) {
201
+      return Location.UNKNOWN;
202
+    } else {
203
+      return customsZone().entryPoint(itinerary.locations());
204
+    }
295 205
   }
296 206
 
297 207
   /**
298 208
    * @return True if the cargo is ready to be claimed.
299 209
    */
300 210
   public boolean isReadyToClaim() {
301
-    return delivery.isUnloadedAtDestination(routeSpecification);
211
+    return delivery.onTheGroundAtDestination(routeSpecification);
302 212
   }
303 213
 
304 214
   /**
@@ -322,22 +232,30 @@ public class Cargo extends EntitySupport<Cargo,TrackingId> {
322 232
    * since {@link HandlingEvent} is in a different aggregate.
323 233
    *
324 234
    * @param handlingActivity handling activity
325
-   * @param completionTime when the activity was completed
326 235
    */
327
-  public void handled(final HandlingActivity handlingActivity, final Date completionTime) {
236
+  public void handled(final HandlingActivity handlingActivity) {
328 237
     Validate.notNull(handlingActivity, "Handling activity is required");
329 238
 
330
-    if (delivery.lastTimestamp().after(completionTime)) {
331
-      return;
239
+    if (isSignificant(handlingActivity)) {
240
+      this.delivery = delivery.onHandling(handlingActivity);
332 241
     }
242
+  }
333 243
 
334
-    // TODO
335
-    // What if an activity that should logically happen later is entered with
336
-    // a completion time that's before some other activity?
337
-    // Who should win - logic or completion time?
244
+  private boolean isSignificant(final HandlingActivity newHandlingActivity) {
245
+    return succedsMostRecentActivity(newHandlingActivity);
246
+  }
338 247
 
339
-    // Delivery is a value object, so it's replaced with a new one
340
-    this.delivery = Delivery.cargoWasHandled(handlingActivity, completionTime);
248
+  /*
249
+  * Problem: CUSTOMS går inte att matcha mot ett Leg, så varje annan händelse som
250
+  * är förväntad kommer att trilla ut som strictly prior.
251
+  */
252
+  private boolean succedsMostRecentActivity(final HandlingActivity newHandlingActivity) {
253
+    if (delivery.hasBeenHandledAfterRouting()) {
254
+      final HandlingActivity priorActivity = itinerary.strictlyPriorOf(mostRecentHandlingActivity(), newHandlingActivity);
255
+      return !newHandlingActivity.sameValueAs(priorActivity);
256
+    } else {
257
+      return true;
258
+    }
341 259
   }
342 260
 
343 261
   @Override

+ 59
- 30
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Delivery.java Bestand weergeven

@@ -3,7 +3,8 @@ package se.citerus.dddsample.tracking.core.domain.model.cargo;
3 3
 import org.apache.commons.lang.Validate;
4 4
 import static se.citerus.dddsample.tracking.core.domain.model.cargo.RoutingStatus.*;
5 5
 import static se.citerus.dddsample.tracking.core.domain.model.cargo.TransportStatus.ONBOARD_CARRIER;
6
-import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.*;
6
+import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.CUSTOMS;
7
+import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.UNLOAD;
7 8
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
8 9
 import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
9 10
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
@@ -18,37 +19,57 @@ import java.util.Date;
18 19
 class Delivery extends ValueObjectSupport<Delivery> {
19 20
 
20 21
   private final HandlingActivity mostRecentHandlingActivity;
22
+  private final HandlingActivity mostRecentPhysicalHandlingActivity;
21 23
   private final Date calculatedAt;
24
+  private final boolean routedAfterHandling;
22 25
 
23 26
   /**
24 27
    * Derives a new delivery when a cargo has been handled.
25 28
    *
26
-   * @param handlingActivity  handling activity
27
-   * @param completionTime
29
+   * @param newHandlingActivity  handling activity
28 30
    * @return An up to date delivery
29 31
    */
30
-  static Delivery cargoWasHandled(final HandlingActivity handlingActivity, Date completionTime) {
31
-    Validate.notNull(handlingActivity, "Handling activity is required");
32
+  Delivery onHandling(final HandlingActivity newHandlingActivity) {
33
+    Validate.notNull(newHandlingActivity, "Handling activity is required");
32 34
 
33
-    return new Delivery(handlingActivity, completionTime);
35
+    if (newHandlingActivity.type().isPhysical()) {
36
+      return new Delivery(newHandlingActivity, newHandlingActivity, new Date(), false);
37
+    } else {
38
+      return new Delivery(newHandlingActivity, mostRecentPhysicalHandlingActivity, new Date(), false);
39
+    }
40
+  }
41
+
42
+  /**
43
+   * @return An up to date delivery
44
+   */
45
+  Delivery onRouting() {
46
+    return new Delivery(mostRecentHandlingActivity, mostRecentPhysicalHandlingActivity, calculatedAt, true);
34 47
   }
35 48
 
36 49
   /**
37 50
    * @return Initial delivery, before any handling has taken place
38 51
    */
39
-  static Delivery initial() {
40
-    return new Delivery(null, new Date(0L));
52
+  static Delivery beforeHandling() {
53
+    return new Delivery(null, null, new Date(0L), false);
41 54
   }
42 55
 
43
-  Delivery(final HandlingActivity mostRecentHandlingActivity, final Date completionTime) {
56
+  Delivery(final HandlingActivity mostRecentHandlingActivity,
57
+           final HandlingActivity mostRecentPhysicalHandlingActivity,
58
+           final Date completionTime, boolean routedAfterHandling) {
44 59
     this.mostRecentHandlingActivity = mostRecentHandlingActivity;
60
+    this.mostRecentPhysicalHandlingActivity = mostRecentPhysicalHandlingActivity;
45 61
     this.calculatedAt = completionTime;
62
+    this.routedAfterHandling = routedAfterHandling;
46 63
   }
47 64
 
48 65
   HandlingActivity mostRecentHandlingActivity() {
49 66
     return mostRecentHandlingActivity;
50 67
   }
51
-  
68
+
69
+  HandlingActivity mostRecentPhysicalHandlingActivity() {
70
+    return mostRecentPhysicalHandlingActivity;
71
+  }
72
+
52 73
   /**
53 74
    * @return Transport status
54 75
    */
@@ -60,7 +81,7 @@ class Delivery extends ValueObjectSupport<Delivery> {
60 81
    * @return Last known location of the cargo, or Location.UNKNOWN if the delivery history is empty.
61 82
    */
62 83
   Location lastKnownLocation() {
63
-    if (hasBeenHandled()) {
84
+    if (hasBeenHandledAfterRouting()) {
64 85
       return mostRecentHandlingActivity.location();
65 86
     } else {
66 87
       return Location.UNKNOWN;
@@ -71,15 +92,15 @@ class Delivery extends ValueObjectSupport<Delivery> {
71 92
    * @return Current voyage.
72 93
    */
73 94
   Voyage currentVoyage() {
74
-    if (hasBeenHandled() && transportStatus() == ONBOARD_CARRIER) {
95
+    if (hasBeenHandledAfterRouting() && transportStatus() == ONBOARD_CARRIER) {
75 96
       return mostRecentHandlingActivity.voyage();
76 97
     } else {
77 98
       return Voyage.NONE;
78 99
     }
79 100
   }
80 101
 
81
-  private boolean hasBeenHandled() {
82
-    return mostRecentHandlingActivity != null;
102
+  boolean hasBeenHandledAfterRouting() {
103
+    return !routedAfterHandling && mostRecentHandlingActivity != null;
83 104
   }
84 105
 
85 106
   /**
@@ -96,29 +117,29 @@ class Delivery extends ValueObjectSupport<Delivery> {
96 117
    * @param routeSpecification route specification
97 118
    */
98 119
   boolean isMisdirected(final Itinerary itinerary, final RouteSpecification routeSpecification) {
99
-    if (!hasBeenHandled()) {
100
-      return false;
120
+    if (hasBeenHandledAfterRouting()) {
121
+      if (mostRecentHandlingActivity.type() == CUSTOMS) {
122
+        return !handledAtDestination(routeSpecification);
123
+      } else {
124
+        return !itinerary.isExpectedActivity(mostRecentHandlingActivity);
125
+      }
101 126
     }
102 127
 
103
-    if (mostRecentHandlingActivity.type() == CUSTOMS) {
104
-      boolean handledAtDestination = routeSpecification.destination().sameAs(mostRecentHandlingActivity.location());
105
-      return !handledAtDestination;
106
-    } else {
107
-      return !itinerary.wasExpecting(mostRecentHandlingActivity);
108
-    }
128
+    return false;
129
+  }
130
+
131
+  private boolean handledAtDestination(RouteSpecification routeSpecification) {
132
+    return routeSpecification.destination().sameAs(mostRecentHandlingActivity.location());
109 133
   }
110 134
 
111 135
   /**
112 136
    * @return True if the cargo has been unloaded at the final destination.
113 137
    * @param routeSpecification route specification
114 138
    */
115
-  boolean isUnloadedAtDestination(final RouteSpecification routeSpecification) {
116
-    if (hasBeenHandled()) {
117
-      return (mostRecentHandlingActivity.type() == CLAIM ||
118
-              mostRecentHandlingActivity.type() == UNLOAD && routeSpecification.destination().sameAs(mostRecentHandlingActivity.location()));
119
-    } else {
120
-      return false;
121
-    }
139
+  boolean onTheGroundAtDestination(final RouteSpecification routeSpecification) {
140
+    return hasBeenHandledAfterRouting() &&
141
+           mostRecentHandlingActivity.type() == UNLOAD && 
142
+           routeSpecification.destination().sameAs(mostRecentHandlingActivity.location());
122 143
   }
123 144
 
124 145
   /**
@@ -154,10 +175,18 @@ class Delivery extends ValueObjectSupport<Delivery> {
154 175
     return routingStatus(itinerary, routeSpecification) == ROUTED && !isMisdirected(itinerary, routeSpecification);
155 176
   }
156 177
 
178
+  /**
179
+   * @return True if cargo has been routed after the most recent handling activity took place.
180
+   */
181
+  boolean isRoutedAfterHandling() {
182
+    return routedAfterHandling;
183
+  }
184
+
157 185
   Delivery() {
158 186
     // Needed by Hibernate
159 187
     calculatedAt = null;
160
-    mostRecentHandlingActivity = null;
188
+    mostRecentHandlingActivity = mostRecentPhysicalHandlingActivity = null;
189
+    routedAfterHandling = false;
161 190
   }
162 191
 
163 192
 }

+ 156
- 98
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Itinerary.java Bestand weergeven

@@ -5,6 +5,7 @@ import org.apache.commons.lang.Validate;
5 5
 import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.*;
6 6
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
7 7
 import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
8
+import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.*;
8 9
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
9 10
 import se.citerus.dddsample.tracking.core.domain.patterns.valueobject.ValueObjectSupport;
10 11
 
@@ -16,7 +17,6 @@ import java.util.*;
16 17
 public class Itinerary extends ValueObjectSupport<Itinerary> {
17 18
 
18 19
   private final List<Leg> legs;
19
-  private static final Date END_OF_DAYS = new Date(Long.MAX_VALUE);
20 20
 
21 21
   /**
22 22
    * Constructor.
@@ -27,6 +27,20 @@ public class Itinerary extends ValueObjectSupport<Itinerary> {
27 27
     Validate.notEmpty(legs);
28 28
     Validate.noNullElements(legs);
29 29
 
30
+    /*final Iterator<Leg> it = legs.iterator();
31
+    Leg leg = it.next();
32
+    while (it.hasNext()) {
33
+      final Leg nextLeg = it.next();
34
+      Validate.isTrue(leg.unloadTime().before(nextLeg.loadTime()));
35
+      leg = nextLeg;
36
+    }*/
37
+
38
+    /*final ListIterator<Leg> lit = legs.listIterator();
39
+    lit.next();
40
+    while (lit.hasNext()) {
41
+      Validate.isTrue(lit.previous().unloadTime().before(lit.next().loadTime()));
42
+    }*/
43
+
30 44
     // TODO
31 45
     // Validate that legs are in proper order, connected
32 46
     // and that load/unload times are doable?
@@ -46,133 +60,66 @@ public class Itinerary extends ValueObjectSupport<Itinerary> {
46 60
   }
47 61
 
48 62
   /**
49
-   * Test if the given handling event was expected when executing this itinerary.
50
-   *
51
-   * @param handlingActivity Event to test.
52
-   * @return <code>true</code> if the event is expected
63
+   * @param rescheduledVoyage the voyage that has been rescheduled
64
+   * @return A new itinerary which is a copy of the old one, adjusted for the delay of the given voyage.
53 65
    */
54
-  public boolean wasExpecting(final HandlingActivity handlingActivity) {
55
-    if (isEmpty()) {
56
-      return false;
57
-    }
58
-
59
-    if (handlingActivity.type() == RECEIVE) {
60
-      return firstLeg().loadLocation().sameAs(handlingActivity.location());
61
-    }
62
-
63
-    if (handlingActivity.type() == LOAD) {
64
-      //Check that the there is a leg with same load location and voyage
65
-      for (Leg leg : legs) {
66
-        if (leg.loadLocation().sameAs(handlingActivity.location()) && leg.voyage().sameAs(handlingActivity.voyage())) {
67
-          return true;
68
-        }
69
-      }
70
-      return false;
71
-    }
66
+  public Itinerary withRescheduledVoyage(final Voyage rescheduledVoyage) {
67
+    final List<Leg> newLegsList = new ArrayList<Leg>(this.legs.size());
72 68
 
73
-    if (handlingActivity.type() == UNLOAD) {
74
-      //Check that the there is a leg with same unload location and voyage
75
-      for (Leg leg : legs) {
76
-        if (leg.unloadLocation().sameAs(handlingActivity.location()) && leg.voyage().sameAs(handlingActivity.voyage())) {
77
-          return true;
69
+    Leg lastAdded = null;
70
+    for (Leg leg : legs) {
71
+      if (leg.voyage().sameAs(rescheduledVoyage)) {
72
+        Leg modifiedLeg = leg.withRescheduledVoyage(rescheduledVoyage);
73
+        // This truncates the itinerary if the voyage rescheduling makes
74
+        // it impossible to maintain the old unload-load chain.
75
+        if (lastAdded != null && modifiedLeg.loadTime().before(lastAdded.unloadTime())) {
76
+          break;
78 77
         }
78
+        newLegsList.add(modifiedLeg);
79
+      } else {
80
+        newLegsList.add(leg);
79 81
       }
80
-      return false;
81
-    }
82
-
83
-    if (handlingActivity.type() == CLAIM) {
84
-      //Check that the last leg's destination is from the handling activity's location
85
-      return lastLeg().unloadLocation().sameAs(handlingActivity.location());
82
+      lastAdded = leg;
86 83
     }
87 84
 
88
-    if (handlingActivity.type() == CUSTOMS) {
89
-      //Check that the customs location fits the rule of the customs zone
90
-      //TODO Answering this properly requires Cargo's destination. Can't be answered at itinerary level.
91
-    }
92
-    return false;
85
+    return new Itinerary(newLegsList);
86
+  }
93 87
 
88
+  /**
89
+   * Test if the given handling event was expected when executing this itinerary.
90
+   *
91
+   * @param handlingActivity Event to test.
92
+   * @return <code>true</code> if the event is expected
93
+   */
94
+  boolean isExpectedActivity(final HandlingActivity handlingActivity) {
95
+    return legMatchOf(handlingActivity).leg() != null;
94 96
   }
95 97
 
96 98
   /**
97 99
    * @return The initial departure location.
98 100
    */
99 101
   Location initialLoadLocation() {
100
-    if (isEmpty()) {
101
-      return Location.UNKNOWN;
102
-    } else {
103
-      return legs.get(0).loadLocation();
104
-    }
102
+    return firstLeg().loadLocation();
105 103
   }
106 104
 
107 105
   /**
108 106
    * @return The final arrival location.
109 107
    */
110 108
   Location finalUnloadLocation() {
111
-    if (isEmpty()) {
112
-      return Location.UNKNOWN;
113
-    } else {
114
-      return lastLeg().unloadLocation();
115
-    }
109
+    return lastLeg().unloadLocation();
116 110
   }
117 111
 
118 112
   /**
119 113
    * @return Date when cargo arrives at final destination.
120 114
    */
121 115
   Date finalUnloadTime() {
122
-    if (isEmpty()) return new Date(END_OF_DAYS.getTime());
123 116
     return new Date(lastLeg().unloadTime().getTime());
124 117
   }
125 118
 
126
-  private boolean isEmpty() {
127
-    return legs.isEmpty();
128
-  }
129
-
130
-  /**
131
-   * @return The first leg on the itinerary.
132
-   */
133
-  public Leg firstLeg() {
134
-    if (isEmpty()) return null;
135
-    return legs.get(0);
136
-  }
137
-
138
-  /**
139
-   * @return The last leg on the itinerary.
140
-   */
141
-  public Leg lastLeg() {
142
-    if (isEmpty()) return null;
143
-    return legs.get(legs.size() - 1);
144
-  }
145
-
146
-  /**
147
-   * @param rescheduledVoyage the voyage that has been rescheduled
148
-   * @return A new itinerary which is a copy of the old one, adjusted for the delay of the given voyage.
149
-   */
150
-  public Itinerary withRescheduledVoyage(final Voyage rescheduledVoyage) {
151
-    final List<Leg> newLegsList = new ArrayList<Leg>(this.legs.size());
152
-
153
-    Leg lastAdded = null;
154
-    for (Leg leg : this.legs) {
155
-      if (leg.voyage().sameAs(rescheduledVoyage)) {
156
-        Leg modifiedLeg = leg.withRescheduledVoyage(rescheduledVoyage);
157
-        // This truncates the itinerary if the voyage rescheduling makes
158
-        // it impossible to maintain the old unload-load chain.
159
-        if (lastAdded != null && modifiedLeg.loadTime().before(lastAdded.unloadTime())) {
160
-          break;
161
-        }
162
-        newLegsList.add(modifiedLeg);
163
-      } else {
164
-        newLegsList.add(leg);
165
-      }
166
-      lastAdded = leg;
167
-    }
168
-
169
-    return new Itinerary(newLegsList);
170
-  }
171
-
172 119
   /**
173 120
    * @return A list of all locations on this itinerary.
174 121
    */
175
-  public List<Location> locations() {
122
+  List<Location> locations() {
176 123
     final List<Location> result = new ArrayList<Location>(legs.size() + 1);
177 124
     result.add(firstLeg().loadLocation());
178 125
     for (Leg leg : legs) {
@@ -191,6 +138,7 @@ public class Itinerary extends ValueObjectSupport<Itinerary> {
191 138
         return leg.loadTime();
192 139
       }
193 140
     }
141
+
194 142
     return null;
195 143
   }
196 144
 
@@ -205,15 +153,124 @@ public class Itinerary extends ValueObjectSupport<Itinerary> {
205 153
    * @param location a location
206 154
    * @return Unload time at this location, or null if the location isn't on this itinerary.
207 155
    */
208
-  public Date unloadTimeAt(final Location location) {
156
+  Date unloadTimeAt(final Location location) {
209 157
     for (Leg leg : legs) {
210 158
       if (leg.unloadLocation().sameAs(location)) {
211 159
         return leg.unloadTime();
212 160
       }
213 161
     }
162
+
163
+    return null;
164
+  }
165
+
166
+  /**
167
+   * @param previousActivity previous handling activity
168
+   * @return Handling activity that succeds, or null if it can't be determined.
169
+   */
170
+  HandlingActivity activitySucceding(final HandlingActivity previousActivity) {
171
+    if (previousActivity == null) {
172
+      return receivedIn(firstLeg().loadLocation());
173
+    } else {
174
+      return deriveFromMatchingLeg(previousActivity, legMatchOf(previousActivity).leg());
175
+    }
176
+  }
177
+
178
+  /**
179
+   * @param handlingActivity1 handling activity
180
+   * @param handlingActivity2 handling activity
181
+   * @return The activity which is strictly prior to the other, according to the itinerary, or null if neither is strictly prior.
182
+   */
183
+  HandlingActivity strictlyPriorOf(final HandlingActivity handlingActivity1, final HandlingActivity handlingActivity2) {
184
+    final LegMatch match1 = legMatchOf(handlingActivity1);
185
+    final LegMatch match2 = legMatchOf(handlingActivity2);
186
+    final int compared = match1.compareTo(match2);
187
+
188
+    if (compared < 0) {
189
+      return match1.handlingActivity();
190
+    } else if (compared > 0) {
191
+      return match2.handlingActivity();
192
+    } else {
193
+      return null;
194
+    }
195
+  }
196
+
197
+  /**
198
+   * @param leg leg
199
+   * @return The next leg, or null if this is the last leg.
200
+   */
201
+  Leg nextLeg(final Leg leg) {
202
+    for (Iterator<Leg> it = legs.iterator(); it.hasNext();) {
203
+      if (it.next().sameValueAs(leg)) {
204
+        return it.hasNext() ? it.next() : null;
205
+      }
206
+    }
207
+
214 208
     return null;
215 209
   }
216 210
 
211
+  /**
212
+   * @param handlingActivity handling activity
213
+   * @return The leg match of this handling activity. Never null.
214
+   */
215
+  LegMatch legMatchOf(final HandlingActivity handlingActivity) {
216
+    if (handlingActivity == null) {
217
+      return LegMatch.noMatch(handlingActivity, this);
218
+    } else if (handlingActivity.type() == RECEIVE) {
219
+      return LegMatch.ifLoadLocationSame(firstLeg(), handlingActivity, this);
220
+    } else if (handlingActivity.type() == CLAIM) {
221
+      return LegMatch.ifUnloadLocationSame(lastLeg(), handlingActivity, this);
222
+    } else {
223
+      return findLegMatchingActivity(handlingActivity);
224
+    }
225
+  }
226
+
227
+  /**
228
+   * @return The first leg on the itinerary.
229
+   */
230
+  Leg firstLeg() {
231
+    return legs.get(0);
232
+  }
233
+
234
+  /**
235
+   * @return The last leg on the itinerary.
236
+   */
237
+  public Leg lastLeg() {
238
+    return legs.get(legs.size() - 1);
239
+  }
240
+
241
+  private LegMatch findLegMatchingActivity(final HandlingActivity handlingActivity) {
242
+    for (Leg leg : legs) {
243
+      if (leg.matchesActivity(handlingActivity)) {
244
+        return LegMatch.match(leg, handlingActivity, this);
245
+      }
246
+    }
247
+
248
+    return LegMatch.noMatch(handlingActivity, this);
249
+  }
250
+
251
+  private HandlingActivity deriveFromMatchingLeg(final HandlingActivity handlingActivity, final Leg matchingLeg) {
252
+    if (matchingLeg == null) {
253
+      return null;
254
+    } else {
255
+      if (handlingActivity.type() == LOAD) {
256
+        return unloadedOff(handlingActivity.voyage()).in(matchingLeg.unloadLocation());
257
+      } else if (handlingActivity.type() == UNLOAD) {
258
+        return deriveFromNextLeg(nextLeg(matchingLeg));
259
+      } else {
260
+        // Will only derive from load and unload within the itinerary context
261
+        return null;
262
+      }
263
+    }
264
+  }
265
+
266
+  private HandlingActivity deriveFromNextLeg(final Leg nextLeg) {
267
+    if (nextLeg == null) {
268
+      return claimedIn(lastLeg().unloadLocation());
269
+    } else {
270
+      return nextLeg.deriveLoadActivity();
271
+    }
272
+  }
273
+
217 274
   @Override
218 275
   public String toString() {
219 276
     return StringUtils.join(legs, "\n");
@@ -223,4 +280,5 @@ public class Itinerary extends ValueObjectSupport<Itinerary> {
223 280
     // Needed by Hibernate
224 281
     legs = null;
225 282
   }
283
+
226 284
 }

+ 48
- 0
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Leg.java Bestand weergeven

@@ -1,7 +1,12 @@
1 1
 package se.citerus.dddsample.tracking.core.domain.model.cargo;
2 2
 
3 3
 import org.apache.commons.lang.Validate;
4
+import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.LOAD;
5
+import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.UNLOAD;
4 6
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
7
+import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
8
+import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.loadedOnto;
9
+import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.unloadedOff;
5 10
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
6 11
 import se.citerus.dddsample.tracking.core.domain.patterns.valueobject.ValueObjectSupport;
7 12
 
@@ -83,6 +88,49 @@ public class Leg extends ValueObjectSupport<Leg> {
83 88
     return Leg.deriveLeg(voyage, loadLocation, unloadLocation);
84 89
   }
85 90
 
91
+
92
+  /**
93
+   *
94
+   * @param handlingActivity handling activity
95
+   * @return True if this legs matches the handling activity, i.e. the voyage and load location is the same in case of a load activity and so on.
96
+   */
97
+  boolean matchesActivity(final HandlingActivity handlingActivity) {
98
+    if (voyage.sameAs(handlingActivity.voyage())) {
99
+      if (handlingActivity.type() == LOAD) {
100
+        return loadLocation.sameAs(handlingActivity.location());
101
+      }
102
+      if (handlingActivity.type() == UNLOAD) {
103
+        return unloadLocation.sameAs(handlingActivity.location());
104
+      }
105
+    }
106
+
107
+    return false;
108
+  }
109
+
110
+  Leg ifLoadLocationSameAs(final HandlingActivity handlingActivity) {
111
+    if (loadLocation.sameAs(handlingActivity.location())) {
112
+      return this;
113
+    } else {
114
+      return null;
115
+    }
116
+  }
117
+
118
+  Leg ifUnloadLocationSameAs(final HandlingActivity handlingActivity) {
119
+    if (unloadLocation.sameAs(handlingActivity.location())) {
120
+      return this;
121
+    } else {
122
+      return null;
123
+    }
124
+  }
125
+
126
+  HandlingActivity deriveLoadActivity() {
127
+    return loadedOnto(voyage).in(loadLocation);
128
+  }
129
+
130
+  HandlingActivity deriveUnloadActivity() {
131
+    return unloadedOff(voyage).in(unloadLocation);
132
+  }
133
+
86 134
   @Override
87 135
   public String toString() {
88 136
     return "Load in " + loadLocation + " at " + loadTime +

+ 90
- 0
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/LegMatch.java Bestand weergeven

@@ -0,0 +1,90 @@
1
+package se.citerus.dddsample.tracking.core.domain.model.cargo;
2
+
3
+import static se.citerus.dddsample.tracking.core.domain.model.cargo.LegMatch.LegEnd.*;
4
+import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
5
+import se.citerus.dddsample.tracking.core.domain.patterns.valueobject.ValueObjectSupport;
6
+
7
+class LegMatch extends ValueObjectSupport<LegMatch> implements Comparable<LegMatch> {
8
+
9
+  private final Leg leg;
10
+  private final LegEnd legEnd;
11
+  private final HandlingActivity handlingActivity;
12
+  private final Itinerary itinerary;
13
+
14
+  enum LegEnd { LOAD_END, UNLOAD_END, NO_END }
15
+
16
+  private LegMatch(final Leg leg, final LegEnd legEnd, final HandlingActivity handlingActivity, final Itinerary itinerary) {
17
+    this.leg = leg;
18
+    this.legEnd = legEnd;
19
+    this.handlingActivity = handlingActivity;
20
+    this.itinerary = itinerary;
21
+  }
22
+
23
+  static LegMatch match(final Leg leg, final HandlingActivity handlingActivity, final Itinerary itinerary) {
24
+    switch (handlingActivity.type()) {
25
+      case RECEIVE:
26
+      case LOAD:
27
+        return new LegMatch(leg, LOAD_END, handlingActivity, itinerary);
28
+      case UNLOAD:
29
+      case CLAIM:
30
+      case CUSTOMS:
31
+        return new LegMatch(leg, UNLOAD_END, handlingActivity, itinerary);
32
+      default:
33
+        return noMatch(handlingActivity, itinerary);
34
+    }
35
+  }
36
+
37
+  static LegMatch ifLoadLocationSame(final Leg leg, final HandlingActivity handlingActivity, final Itinerary itinerary) {
38
+    if (leg.loadLocation().sameAs(handlingActivity.location())) {
39
+      return new LegMatch(leg, LOAD_END, handlingActivity, itinerary);
40
+    } else {
41
+      return noMatch(handlingActivity, itinerary);
42
+    }
43
+  }
44
+
45
+  static LegMatch ifUnloadLocationSame(final Leg leg, final HandlingActivity handlingActivity, final Itinerary itinerary) {
46
+    if (leg.unloadLocation().sameAs(handlingActivity.location())) {
47
+      return new LegMatch(leg, UNLOAD_END, handlingActivity, itinerary);
48
+    } else {
49
+      return noMatch(handlingActivity, itinerary);
50
+    }
51
+  }
52
+
53
+  static LegMatch noMatch(final HandlingActivity handlingActivity, final Itinerary itinerary) {
54
+    return new LegMatch(null, NO_END, handlingActivity, itinerary);
55
+  }
56
+
57
+  Leg leg() {
58
+    return leg;
59
+  }
60
+
61
+  HandlingActivity handlingActivity() {
62
+    return handlingActivity;
63
+  }
64
+
65
+  @Override
66
+  public int compareTo(final LegMatch other) {
67
+    final Integer thisLegIndex = itinerary.legs().indexOf(this.leg);
68
+    final Integer otherLegIndex = itinerary.legs().indexOf(other.leg);
69
+
70
+    if (thisLegIndex.equals(otherLegIndex)) {
71
+      return this.legEnd.compareTo(other.legEnd);
72
+    } else {
73
+      return toPositive(thisLegIndex).compareTo(toPositive(otherLegIndex));
74
+    }
75
+  }
76
+
77
+  private Integer toPositive(final Integer thisLegIndex) {
78
+    return thisLegIndex >= 0 ? thisLegIndex : Integer.MAX_VALUE;
79
+  }
80
+
81
+  @Override
82
+  public String toString() {
83
+    if (legEnd == NO_END) {
84
+      return "No match"; 
85
+    } else {            
86
+      return "Activity " + handlingActivity + " matches leg " + leg + " at " + legEnd;
87
+    }
88
+  }
89
+
90
+}

+ 2
- 5
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/voyage/Voyage.java Bestand weergeven

@@ -51,10 +51,7 @@ public class Voyage extends EntitySupport<Voyage,VoyageNumber> {
51 51
    * @param newDepartureTime new departure time
52 52
    */
53 53
   public void departureRescheduled(final Location location, final Date newDepartureTime) {
54
-    // TODO Change departure/arrival by diffs instead of absolute times
55
-
56
-    final int size = schedule.carrierMovements().size();
57
-    final List<CarrierMovement> carrierMovements = new ArrayList<CarrierMovement>(size);
54
+    final List<CarrierMovement> carrierMovements = new ArrayList<CarrierMovement>();
58 55
 
59 56
     for (CarrierMovement carrierMovement : schedule.carrierMovements()) {
60 57
       if (carrierMovement.departureLocation().sameAs(location)) {
@@ -69,7 +66,7 @@ public class Voyage extends EntitySupport<Voyage,VoyageNumber> {
69 66
 
70 67
   @Override
71 68
   public String toString() {
72
-    return "Voyage " + voyageNumber;
69
+    return voyageNumber.stringValue();
73 70
   }
74 71
 
75 72
   Voyage() {

+ 1
- 0
dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/application/event/CargoUpdaterTest.java Bestand weergeven

@@ -58,6 +58,7 @@ public class CargoUpdaterTest {
58 58
       HONGKONG.unLocode(),
59 59
       LOAD, new OperatorCode("ABCDE")
60 60
     );
61
+
61 62
     handlingEventRepository.store(handlingEvent);
62 63
 
63 64
     assertThat(handlingEvent.activity(), not(equalTo(cargo.mostRecentHandlingActivity())));

+ 86
- 82
dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/cargo/CargoTest.java Bestand weergeven

@@ -1,10 +1,11 @@
1 1
 package se.citerus.dddsample.tracking.core.domain.model.cargo;
2 2
 
3 3
 import junit.framework.TestCase;
4
+import static org.hamcrest.core.Is.is;
5
+import static org.junit.Assert.assertThat;
4 6
 import static se.citerus.dddsample.tracking.core.application.util.DateTestUtil.toDate;
5 7
 import static se.citerus.dddsample.tracking.core.domain.model.cargo.RoutingStatus.*;
6
-import static se.citerus.dddsample.tracking.core.domain.model.cargo.TransportStatus.IN_PORT;
7
-import static se.citerus.dddsample.tracking.core.domain.model.cargo.TransportStatus.NOT_RECEIVED;
8
+import static se.citerus.dddsample.tracking.core.domain.model.cargo.TransportStatus.*;
8 9
 import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.*;
9 10
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
10 11
 import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
@@ -18,35 +19,35 @@ import java.util.Date;
18 19
 public class CargoTest extends TestCase {
19 20
 
20 21
   private Voyage crazyVoyage = new Voyage.Builder(new VoyageNumber("0123"),
21
-    STOCKHOLM).
22
-    addMovement(HAMBURG, new Date(1), new Date(2)).
23
-    addMovement(HONGKONG, new Date(3), new Date(4)).
24
-    addMovement(MELBOURNE, new Date(5), new Date(6)).
25
-    build();
22
+      STOCKHOLM).
23
+      addMovement(HAMBURG, new Date(1), new Date(2)).
24
+      addMovement(HONGKONG, new Date(3), new Date(4)).
25
+      addMovement(MELBOURNE, new Date(5), new Date(6)).
26
+      build();
26 27
 
27 28
   private Voyage pacific = new Voyage.Builder(new VoyageNumber("4567"),
28
-    SHANGHAI).
29
-    addMovement(LONGBEACH, new Date(1), new Date(2)).
30
-    addMovement(SEATTLE, new Date(3), new Date(4)).
31
-    build();
29
+      SHANGHAI).
30
+      addMovement(LONGBEACH, new Date(1), new Date(2)).
31
+      addMovement(SEATTLE, new Date(3), new Date(4)).
32
+      build();
32 33
 
33 34
   private Voyage transcontinental = new Voyage.Builder(new VoyageNumber("4567"),
34
-    LONGBEACH).
35
-    addMovement(CHICAGO, new Date(1), new Date(2)).
36
-    addMovement(NEWYORK, new Date(3), new Date(4)).
37
-    build();
35
+      LONGBEACH).
36
+      addMovement(CHICAGO, new Date(1), new Date(2)).
37
+      addMovement(NEWYORK, new Date(3), new Date(4)).
38
+      build();
38 39
 
39 40
   private Voyage northernRail = new Voyage.Builder(new VoyageNumber("8901"),
40
-    SEATTLE).
41
-    addMovement(CHICAGO, new Date(1), new Date(2)).
42
-    addMovement(NEWYORK, new Date(3), new Date(4)).
43
-    build();
41
+      SEATTLE).
42
+      addMovement(CHICAGO, new Date(1), new Date(2)).
43
+      addMovement(NEWYORK, new Date(3), new Date(4)).
44
+      build();
44 45
 
45 46
   public void testConstruction() {
46 47
     TrackingId trackingId = new TrackingId("XYZ");
47 48
     Date arrivalDeadline = toDate("2009-03-13");
48 49
     RouteSpecification routeSpecification = new RouteSpecification(
49
-      STOCKHOLM, MELBOURNE, arrivalDeadline
50
+        STOCKHOLM, MELBOURNE, arrivalDeadline
50 51
     );
51 52
 
52 53
     Cargo cargo = new Cargo(trackingId, routeSpecification);
@@ -80,19 +81,24 @@ public class CargoTest extends TestCase {
80 81
   }
81 82
 
82 83
   public void testOutOrderHandling() throws Exception {
83
-    final Cargo cargo = setUpCargoWithItinerary(STOCKHOLM, HAMBURG, MELBOURNE);
84
+    Cargo cargo = setUpCargoWithItinerary(STOCKHOLM, HAMBURG, MELBOURNE);
85
+
86
+    cargo.handled(HandlingActivity.loadedOnto(crazyVoyage).in(STOCKHOLM));
87
+    assertThat(cargo.transportStatus(), is(ONBOARD_CARRIER));
88
+    assertThat(cargo.lastKnownLocation(), is(STOCKHOLM));
84 89
 
85
-    cargo.handled(HandlingActivity.loadedOnto(crazyVoyage).in(STOCKHOLM), toDate("2009-10-01"));
86
-    cargo.handled(new HandlingActivity(LOAD, STOCKHOLM, crazyVoyage), toDate("2009-10-01"));
87
-    cargo.handled(new HandlingActivity(UNLOAD, HAMBURG, crazyVoyage), toDate("2009-10-02"));
88
-    cargo.handled(new HandlingActivity(UNLOAD, HONGKONG, crazyVoyage), toDate("2009-10-04"));
89
-    assertEquals(cargo.transportStatus(), IN_PORT);
90
-    assertEquals(cargo.lastKnownLocation(), HONGKONG);
90
+    cargo.handled(HandlingActivity.unloadedOff(crazyVoyage).in(HAMBURG));
91
+    assertThat(cargo.transportStatus(), is(IN_PORT));
92
+    assertThat(cargo.lastKnownLocation(), is(HAMBURG));
93
+
94
+    cargo.handled(HandlingActivity.unloadedOff(crazyVoyage).in(MELBOURNE));
95
+    assertThat(cargo.transportStatus(), is(IN_PORT));
96
+    assertThat(cargo.lastKnownLocation(), is(MELBOURNE));
91 97
 
92 98
     // Out of order handling, does not affect state of cargo
93
-    cargo.handled(new HandlingActivity(LOAD, HAMBURG, crazyVoyage), toDate("2009-10-03"));
94
-    assertEquals(IN_PORT, cargo.transportStatus());
95
-    assertEquals(HONGKONG, cargo.lastKnownLocation());
99
+    cargo.handled(HandlingActivity.loadedOnto(crazyVoyage).in(HAMBURG));
100
+    assertThat(cargo.transportStatus(), is(IN_PORT));
101
+    assertThat(cargo.lastKnownLocation(), is(MELBOURNE));
96 102
   }
97 103
 
98 104
   public void testlastKnownLocationUnknownWhenNoEvents() throws Exception {
@@ -116,7 +122,7 @@ public class CargoTest extends TestCase {
116 122
   public void testlastKnownLocationUnloaded() throws Exception {
117 123
     Cargo cargo = populateCargoOffHongKong();
118 124
 
119
-    assertEquals(HONGKONG, cargo.lastKnownLocation());
125
+    assertEquals(MELBOURNE, cargo.lastKnownLocation());
120 126
   }
121 127
 
122 128
   public void testlastKnownLocationloaded() throws Exception {
@@ -139,72 +145,71 @@ public class CargoTest extends TestCase {
139 145
     assertFalse("Cargos are not equal when TrackingID differ", c1.equals(c2));
140 146
   }
141 147
 
142
-  public void testIsUnloadedAtFinalDestination() throws Exception {
148
+  public void testIsReadyToClaim() throws Exception {
143 149
     Cargo cargo = setUpCargoWithItinerary(HANGZOU, TOKYO, NEWYORK);
144 150
     assertFalse(cargo.isReadyToClaim());
145 151
 
146 152
     // Adding an event unrelated to unloading at final destination
147
-    cargo.handled(new HandlingActivity(RECEIVE, HANGZOU), new Date());
153
+    cargo.handled(new HandlingActivity(RECEIVE, HANGZOU));
148 154
     assertFalse(cargo.isReadyToClaim());
149 155
 
150 156
     Voyage voyage = new Voyage.Builder(new VoyageNumber("0123"), HANGZOU).
151
-      addMovement(NEWYORK, new Date(1), new Date(2)).
152
-      build();
157
+        addMovement(NEWYORK, new Date(1), new Date(2)).
158
+        build();
153 159
 
154 160
     // Adding an unload event, but not at the final destination
155
-    cargo.handled(new HandlingActivity(UNLOAD, TOKYO, voyage), new Date());
161
+    cargo.handled(new HandlingActivity(UNLOAD, TOKYO, voyage));
156 162
     assertFalse(cargo.isReadyToClaim());
157 163
 
158 164
     // Adding an event in the final destination, but not unload
159
-    cargo.handled(new HandlingActivity(CUSTOMS, NEWYORK), new Date());
165
+    /*cargo.handled(new HandlingActivity(CUSTOMS, NEWYORK));
160 166
     assertFalse(cargo.isReadyToClaim());
167
+    assertFalse(cargo.isMisdirected());*/
161 168
 
162 169
     // Finally, cargo is unloaded at final destination
163
-    cargo.handled(new HandlingActivity(UNLOAD, NEWYORK, voyage), new Date());
170
+    cargo.handled(new HandlingActivity(UNLOAD, NEWYORK, voyage));
164 171
     assertTrue(cargo.isReadyToClaim());
165 172
   }
166 173
 
167 174
   private Cargo populateCargoReceivedStockholm() throws Exception {
168 175
     final Cargo cargo = setUpCargoWithItinerary(STOCKHOLM, HAMBURG, MELBOURNE);
169
-    cargo.handled(new HandlingActivity(RECEIVE, STOCKHOLM), new Date());
176
+    cargo.handled(new HandlingActivity(RECEIVE, STOCKHOLM));
170 177
     return cargo;
171 178
   }
172 179
 
173 180
   private Cargo populateCargoClaimedMelbourne() throws Exception {
174 181
     final Cargo cargo = populateCargoOffMelbourne();
175 182
 
176
-    cargo.handled(new HandlingActivity(CLAIM, MELBOURNE), new Date());
183
+    cargo.handled(new HandlingActivity(CLAIM, MELBOURNE));
177 184
     return cargo;
178 185
   }
179 186
 
180 187
   private Cargo populateCargoOffHongKong() throws Exception {
181 188
     final Cargo cargo = setUpCargoWithItinerary(STOCKHOLM, HAMBURG, MELBOURNE);
182
-    
183
-    cargo.handled(new HandlingActivity(LOAD, STOCKHOLM, crazyVoyage), new Date());
184
-    cargo.handled(new HandlingActivity(UNLOAD, HAMBURG, crazyVoyage), new Date());
185
-    cargo.handled(new HandlingActivity(LOAD, HAMBURG, crazyVoyage), new Date());
186
-    cargo.handled(new HandlingActivity(UNLOAD, HONGKONG, crazyVoyage), new Date());
189
+
190
+    cargo.handled(new HandlingActivity(LOAD, STOCKHOLM, crazyVoyage));
191
+    cargo.handled(new HandlingActivity(UNLOAD, HAMBURG, crazyVoyage));
192
+    cargo.handled(new HandlingActivity(LOAD, HAMBURG, crazyVoyage));
193
+    cargo.handled(new HandlingActivity(UNLOAD, MELBOURNE, crazyVoyage));
187 194
     return cargo;
188 195
   }
189 196
 
190 197
   private Cargo populateCargoOnHamburg() throws Exception {
191 198
     final Cargo cargo = setUpCargoWithItinerary(STOCKHOLM, HAMBURG, MELBOURNE);
192
-    
193
-    cargo.handled(new HandlingActivity(LOAD, STOCKHOLM, crazyVoyage), new Date());
194
-    cargo.handled(new HandlingActivity(UNLOAD, HAMBURG, crazyVoyage), new Date());
195
-    cargo.handled(new HandlingActivity(LOAD, HAMBURG, crazyVoyage), new Date());
199
+
200
+    cargo.handled(new HandlingActivity(LOAD, STOCKHOLM, crazyVoyage));
201
+    cargo.handled(new HandlingActivity(UNLOAD, HAMBURG, crazyVoyage));
202
+    cargo.handled(new HandlingActivity(LOAD, HAMBURG, crazyVoyage));
196 203
     return cargo;
197 204
   }
198 205
 
199 206
   private Cargo populateCargoOffMelbourne() throws Exception {
200 207
     final Cargo cargo = setUpCargoWithItinerary(STOCKHOLM, HAMBURG, MELBOURNE);
201 208
 
202
-    cargo.handled(new HandlingActivity(LOAD, STOCKHOLM, crazyVoyage), new Date());
203
-    cargo.handled(new HandlingActivity(UNLOAD, HAMBURG, crazyVoyage), new Date());
204
-    cargo.handled(new HandlingActivity(LOAD, HAMBURG, crazyVoyage), new Date());
205
-    cargo.handled(new HandlingActivity(UNLOAD, HONGKONG, crazyVoyage), new Date());
206
-    cargo.handled(new HandlingActivity(LOAD, HONGKONG, crazyVoyage), new Date());
207
-    cargo.handled(new HandlingActivity(UNLOAD, MELBOURNE, crazyVoyage), new Date());
209
+    cargo.handled(new HandlingActivity(LOAD, STOCKHOLM, crazyVoyage));
210
+    cargo.handled(new HandlingActivity(UNLOAD, HAMBURG, crazyVoyage));
211
+    cargo.handled(new HandlingActivity(LOAD, HAMBURG, crazyVoyage));
212
+    cargo.handled(new HandlingActivity(UNLOAD, MELBOURNE, crazyVoyage));
208 213
 
209 214
     return cargo;
210 215
   }
@@ -220,30 +225,29 @@ public class CargoTest extends TestCase {
220 225
     assertFalse(cargo.isMisdirected());
221 226
 
222 227
     //Happy path
223
-    cargo.handled(new HandlingActivity(RECEIVE, SHANGHAI), new Date());
224
-    cargo.handled(new HandlingActivity(LOAD, SHANGHAI, crazyVoyage), new Date());
225
-    cargo.handled(new HandlingActivity(UNLOAD, ROTTERDAM, crazyVoyage), new Date());
226
-    cargo.handled(new HandlingActivity(LOAD, ROTTERDAM, crazyVoyage), new Date());
227
-    cargo.handled(new HandlingActivity(UNLOAD, GOTHENBURG, crazyVoyage), new Date());
228
-    cargo.handled(new HandlingActivity(CLAIM, GOTHENBURG), new Date());
229
-    cargo.handled(new HandlingActivity(CUSTOMS, GOTHENBURG), new Date());
228
+    cargo.handled(new HandlingActivity(RECEIVE, SHANGHAI));
229
+    cargo.handled(new HandlingActivity(LOAD, SHANGHAI, crazyVoyage));
230
+    cargo.handled(new HandlingActivity(UNLOAD, ROTTERDAM, crazyVoyage));
231
+    cargo.handled(new HandlingActivity(LOAD, ROTTERDAM, crazyVoyage));
232
+    cargo.handled(new HandlingActivity(UNLOAD, GOTHENBURG, crazyVoyage));
233
+    cargo.handled(new HandlingActivity(CLAIM, GOTHENBURG));
234
+    cargo.handled(new HandlingActivity(CUSTOMS, GOTHENBURG));
230 235
     assertFalse(cargo.isMisdirected());
231 236
 
232 237
     //Try a couple of failing ones
233 238
 
234 239
     cargo = setUpCargoWithItinerary(SHANGHAI, ROTTERDAM, GOTHENBURG);
235 240
 
236
-    cargo.handled(new HandlingActivity(RECEIVE, HANGZOU), new Date());
241
+    cargo.handled(new HandlingActivity(RECEIVE, HANGZOU));
237 242
     assertTrue(cargo.isMisdirected());
238 243
 
239 244
 
240
-
241 245
     cargo = setUpCargoWithItinerary(SHANGHAI, ROTTERDAM, GOTHENBURG);
242 246
 
243
-    cargo.handled(new HandlingActivity(RECEIVE, SHANGHAI), new Date());
244
-    cargo.handled(new HandlingActivity(LOAD, SHANGHAI, crazyVoyage), new Date());
245
-    cargo.handled(new HandlingActivity(UNLOAD, ROTTERDAM, crazyVoyage), new Date());
246
-    cargo.handled(new HandlingActivity(CLAIM, ROTTERDAM), new Date());
247
+    cargo.handled(new HandlingActivity(RECEIVE, SHANGHAI));
248
+    cargo.handled(new HandlingActivity(LOAD, SHANGHAI, crazyVoyage));
249
+    cargo.handled(new HandlingActivity(UNLOAD, ROTTERDAM, crazyVoyage));
250
+    cargo.handled(new HandlingActivity(CLAIM, ROTTERDAM));
247 251
 
248 252
     assertTrue(cargo.isMisdirected());
249 253
   }
@@ -251,25 +255,25 @@ public class CargoTest extends TestCase {
251 255
   public void testCustomsClearancePoint() {
252 256
     //cargo destination NYC
253 257
     final Cargo cargo = new Cargo(new TrackingId("XYZ"),
254
-      new RouteSpecification(SHANGHAI, NEWYORK, new Date()));
258
+        new RouteSpecification(SHANGHAI, NEWYORK, new Date()));
255 259
 
256 260
     //SHA-LGB-NYC
257 261
     cargo.assignToRoute(new Itinerary(
258
-      Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH),
259
-      Leg.deriveLeg(transcontinental, LONGBEACH, NEWYORK)));
262
+        Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH),
263
+        Leg.deriveLeg(transcontinental, LONGBEACH, NEWYORK)));
260 264
     assertEquals(LONGBEACH, cargo.customsClearancePoint());
261 265
 
262 266
     //SHA-SEA-NYC
263 267
     cargo.assignToRoute(new Itinerary(
264
-      Leg.deriveLeg(pacific, SHANGHAI, SEATTLE),
265
-      Leg.deriveLeg(northernRail, SEATTLE, NEWYORK)));
268
+        Leg.deriveLeg(pacific, SHANGHAI, SEATTLE),
269
+        Leg.deriveLeg(northernRail, SEATTLE, NEWYORK)));
266 270
     assertEquals(SEATTLE, cargo.customsClearancePoint());
267 271
 
268 272
     //cargo destination LGB
269 273
     //SHA-LGB
270 274
     cargo.specifyNewRoute(new RouteSpecification(SHANGHAI, LONGBEACH, new Date()));
271 275
     cargo.assignToRoute(new Itinerary(
272
-      Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH)));
276
+        Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH)));
273 277
     assertEquals(LONGBEACH, cargo.customsClearancePoint());
274 278
 
275 279
     //Cargo destination HAMBURG
@@ -277,8 +281,8 @@ public class CargoTest extends TestCase {
277 281
     // the cargo into its CustomsZone, so no clearancePoint.
278 282
     cargo.specifyNewRoute(new RouteSpecification(SHANGHAI, HAMBURG, new Date()));
279 283
     cargo.assignToRoute(new Itinerary(
280
-      Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH),
281
-      Leg.deriveLeg(transcontinental, LONGBEACH, NEWYORK)));
284
+        Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH),
285
+        Leg.deriveLeg(transcontinental, LONGBEACH, NEWYORK)));
282 286
     assertNull(cargo.customsClearancePoint());
283 287
 
284 288
     //Cargo destination NEWYORK on SHA-LGB-CHI
@@ -286,8 +290,8 @@ public class CargoTest extends TestCase {
286 290
     //but it does enter the CustomsZone, so it has a clearancePoint.
287 291
     cargo.specifyNewRoute(new RouteSpecification(SHANGHAI, NEWYORK, new Date()));
288 292
     cargo.assignToRoute(new Itinerary(
289
-      Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH),
290
-      Leg.deriveLeg(transcontinental, LONGBEACH, CHICAGO)));
293
+        Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH),
294
+        Leg.deriveLeg(transcontinental, LONGBEACH, CHICAGO)));
291 295
     assertEquals(LONGBEACH, cargo.customsClearancePoint());
292 296
 
293 297
   }
@@ -296,10 +300,10 @@ public class CargoTest extends TestCase {
296 300
     Cargo cargo = new Cargo(new TrackingId("CARGO1"), new RouteSpecification(origin, destination, new Date()));
297 301
 
298 302
     Itinerary itinerary = new Itinerary(
299
-      Arrays.asList(
300
-        new Leg(crazyVoyage, origin, midpoint, new Date(), new Date()),
301
-        new Leg(crazyVoyage, midpoint, destination, new Date(), new Date())
302
-      )
303
+        Arrays.asList(
304
+            new Leg(crazyVoyage, origin, midpoint, new Date(), new Date()),
305
+            new Leg(crazyVoyage, midpoint, destination, new Date(), new Date())
306
+        )
303 307
     );
304 308
 
305 309
     cargo.assignToRoute(itinerary);

+ 38
- 12
dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/cargo/DeliveryTest.java Bestand weergeven

@@ -1,6 +1,8 @@
1 1
 package se.citerus.dddsample.tracking.core.domain.model.cargo;
2 2
 
3 3
 import junit.framework.TestCase;
4
+import static org.hamcrest.core.Is.is;
5
+import static org.junit.Assert.assertThat;
4 6
 import static se.citerus.dddsample.tracking.core.application.util.DateTestUtil.toDate;
5 7
 import static se.citerus.dddsample.tracking.core.domain.model.cargo.RoutingStatus.MISROUTED;
6 8
 import static se.citerus.dddsample.tracking.core.domain.model.cargo.RoutingStatus.ROUTED;
@@ -9,6 +11,8 @@ import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingE
9 11
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
10 12
 import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
11 13
 import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
14
+import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.customsIn;
15
+import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.loadedOnto;
12 16
 import static se.citerus.dddsample.tracking.core.domain.model.voyage.SampleVoyages.*;
13 17
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
14 18
 
@@ -28,15 +32,37 @@ public class DeliveryTest extends TestCase {
28 32
       Leg.deriveLeg(NEW_YORK_TO_DALLAS, NEWYORK, DALLAS),
29 33
       Leg.deriveLeg(DALLAS_TO_HELSINKI, DALLAS, STOCKHOLM)
30 34
     );
31
-    delivery = Delivery.initial();
35
+    delivery = Delivery.beforeHandling();
32 36
     Thread.sleep(1);
33 37
   }
34 38
 
39
+  public void testOnHandling() {
40
+    Delivery delivery = Delivery.beforeHandling();
41
+
42
+    HandlingActivity load = loadedOnto(HONGKONG_TO_NEW_YORK).in(HONGKONG);
43
+    delivery = delivery.onHandling(load);
44
+
45
+    assertThat(delivery.mostRecentHandlingActivity(), is(load));
46
+    assertThat(delivery.mostRecentPhysicalHandlingActivity(), is(load));
47
+
48
+    HandlingActivity customs = customsIn(NEWYORK);
49
+    delivery = delivery.onHandling(customs);
50
+
51
+    assertThat(delivery.mostRecentHandlingActivity(), is(customs));
52
+    assertThat(delivery.mostRecentPhysicalHandlingActivity(), is(load));
53
+
54
+    HandlingActivity loadAgain = loadedOnto(NEW_YORK_TO_DALLAS).in(NEWYORK);
55
+    delivery = delivery.onHandling(loadAgain);
56
+
57
+    assertThat(delivery.mostRecentHandlingActivity(), is(loadAgain));
58
+    assertThat(delivery.mostRecentPhysicalHandlingActivity(), is(loadAgain));
59
+  }
60
+
35 61
   public void testDerivedFromRouteSpecificationAndItinerary() throws Exception {
36 62
     assertEquals(ROUTED, delivery.routingStatus(itinerary, routeSpecification));
37 63
     assertEquals(Voyage.NONE, delivery.currentVoyage());
38 64
     assertFalse(delivery.isMisdirected(itinerary, routeSpecification));
39
-    assertFalse(delivery.isUnloadedAtDestination(routeSpecification));
65
+    assertFalse(delivery.onTheGroundAtDestination(routeSpecification));
40 66
     assertEquals(Location.UNKNOWN, delivery.lastKnownLocation());
41 67
     assertEquals(NOT_RECEIVED, delivery.transportStatus());
42 68
     assertTrue(delivery.lastTimestamp().before(new Date()));
@@ -46,7 +72,7 @@ public class DeliveryTest extends TestCase {
46 72
     // 1. Receive
47 73
 
48 74
     HandlingActivity handlingActivity = new HandlingActivity(RECEIVE, HANGZOU);
49
-    Delivery newDelivery = Delivery.cargoWasHandled(handlingActivity, new Date());
75
+    Delivery newDelivery = delivery.onHandling(handlingActivity);
50 76
 
51 77
     // Changed on handling
52 78
     assertEquals(Voyage.NONE, newDelivery.currentVoyage());
@@ -55,7 +81,7 @@ public class DeliveryTest extends TestCase {
55 81
 
56 82
     // Changed on handling and/or (re-)routing
57 83
     assertFalse(newDelivery.isMisdirected(itinerary, routeSpecification));
58
-    assertFalse(newDelivery.isUnloadedAtDestination(routeSpecification));
84
+    assertFalse(newDelivery.onTheGroundAtDestination(routeSpecification));
59 85
 
60 86
     // Changed on (re-)routing
61 87
     assertEquals(ROUTED, newDelivery.routingStatus(itinerary, routeSpecification));
@@ -66,14 +92,14 @@ public class DeliveryTest extends TestCase {
66 92
     // 2. Load
67 93
 
68 94
     handlingActivity = new HandlingActivity(LOAD, HANGZOU, HONGKONG_TO_NEW_YORK);
69
-    newDelivery = Delivery.cargoWasHandled(handlingActivity, new Date());
95
+    newDelivery = newDelivery.onHandling(handlingActivity);
70 96
 
71 97
     assertEquals(HONGKONG_TO_NEW_YORK, newDelivery.currentVoyage());
72 98
     assertEquals(HANGZOU, newDelivery.lastKnownLocation());
73 99
     assertEquals(ONBOARD_CARRIER, newDelivery.transportStatus());
74 100
 
75 101
     assertFalse(newDelivery.isMisdirected(itinerary, routeSpecification));
76
-    assertFalse(newDelivery.isUnloadedAtDestination(routeSpecification));
102
+    assertFalse(newDelivery.onTheGroundAtDestination(routeSpecification));
77 103
 
78 104
     assertEquals(ROUTED, newDelivery.routingStatus(itinerary, routeSpecification));
79 105
 
@@ -84,14 +110,14 @@ public class DeliveryTest extends TestCase {
84 110
     // 3. Unload
85 111
 
86 112
     handlingActivity = new HandlingActivity(UNLOAD, STOCKHOLM, DALLAS_TO_HELSINKI);
87
-    newDelivery = Delivery.cargoWasHandled(handlingActivity, new Date());
113
+    newDelivery = newDelivery.onHandling(handlingActivity);
88 114
 
89 115
     assertEquals(Voyage.NONE, newDelivery.currentVoyage());
90 116
     assertEquals(STOCKHOLM, newDelivery.lastKnownLocation());
91 117
     assertEquals(IN_PORT, newDelivery.transportStatus());
92 118
 
93 119
     assertFalse(newDelivery.isMisdirected(itinerary, routeSpecification));
94
-    assertTrue(newDelivery.isUnloadedAtDestination(routeSpecification));
120
+    assertTrue(newDelivery.onTheGroundAtDestination(routeSpecification));
95 121
 
96 122
     assertEquals(ROUTED, newDelivery.routingStatus(itinerary, routeSpecification));
97 123
 
@@ -100,14 +126,14 @@ public class DeliveryTest extends TestCase {
100 126
     // 4. Claim
101 127
 
102 128
     handlingActivity = new HandlingActivity(CLAIM, STOCKHOLM);
103
-    newDelivery = Delivery.cargoWasHandled(handlingActivity, new Date());
129
+    newDelivery = newDelivery.onHandling(handlingActivity);
104 130
 
105 131
     assertEquals(Voyage.NONE, newDelivery.currentVoyage());
106 132
     assertEquals(STOCKHOLM, newDelivery.lastKnownLocation());
107 133
     assertEquals(CLAIMED, newDelivery.transportStatus());
108 134
 
109 135
     assertFalse(newDelivery.isMisdirected(itinerary, routeSpecification));
110
-    assertTrue(newDelivery.isUnloadedAtDestination(routeSpecification));
136
+    assertFalse(newDelivery.onTheGroundAtDestination(routeSpecification));
111 137
 
112 138
     assertEquals(ROUTED, newDelivery.routingStatus(itinerary, routeSpecification));
113 139
 
@@ -117,7 +143,7 @@ public class DeliveryTest extends TestCase {
117 143
   public void testUpdateOnHandlingWhenMisdirected() {
118 144
     // Unload in Hamburg, which is the wrong location
119 145
     HandlingActivity handlingActivity = new HandlingActivity(UNLOAD, HAMBURG, DALLAS_TO_HELSINKI);
120
-    Delivery newDelivery = Delivery.cargoWasHandled(handlingActivity, new Date());
146
+    Delivery newDelivery = delivery.onHandling(handlingActivity);
121 147
 
122 148
     assertEquals(Voyage.NONE, newDelivery.currentVoyage());
123 149
     assertEquals(HAMBURG, newDelivery.lastKnownLocation());
@@ -126,7 +152,7 @@ public class DeliveryTest extends TestCase {
126 152
     // Next handling activity is undefined. Need a new itinerary to know what to do.
127 153
 
128 154
     assertTrue(newDelivery.isMisdirected(itinerary, routeSpecification));
129
-    assertFalse(newDelivery.isUnloadedAtDestination(routeSpecification));
155
+    assertFalse(newDelivery.onTheGroundAtDestination(routeSpecification));
130 156
 
131 157
     assertEquals(ROUTED, newDelivery.routingStatus(itinerary, routeSpecification));
132 158
 

+ 74
- 14
dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/cargo/ItineraryTest.java Bestand weergeven

@@ -1,9 +1,13 @@
1 1
 package se.citerus.dddsample.tracking.core.domain.model.cargo;
2 2
 
3 3
 import junit.framework.TestCase;
4
+import static org.hamcrest.core.Is.is;
5
+import static org.hamcrest.core.IsEqual.equalTo;
6
+import static org.junit.Assert.assertThat;
4 7
 import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.*;
5 8
 import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
6 9
 import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
10
+import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.*;
7 11
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
8 12
 import se.citerus.dddsample.tracking.core.domain.model.voyage.VoyageNumber;
9 13
 
@@ -14,7 +18,7 @@ import java.util.List;
14 18
 
15 19
 public class ItineraryTest extends TestCase {
16 20
 
17
-  Voyage voyage, wrongVoyage, pacific, transcontinental;
21
+  Voyage voyage, wrongVoyage, pacific, transcontinental, atlantic;
18 22
 
19 23
   protected void setUp() throws Exception {
20 24
 
@@ -27,6 +31,11 @@ public class ItineraryTest extends TestCase {
27 31
       addMovement(NEWYORK, new Date(3), new Date(4)).
28 32
       build();
29 33
 
34
+    atlantic = new Voyage.Builder(new VoyageNumber("4556"), NEWYORK).
35
+      addMovement(ROTTERDAM, new Date(1), new Date(2)).
36
+      addMovement(GOTHENBURG, new Date(3), new Date(4)).
37
+      build();
38
+
30 39
     voyage = new Voyage.Builder(new VoyageNumber("0123"), SHANGHAI).
31 40
       addMovement(ROTTERDAM, new Date(1), new Date(2)).
32 41
       addMovement(GOTHENBURG, new Date(3), new Date(4)).
@@ -50,22 +59,22 @@ public class ItineraryTest extends TestCase {
50 59
     // HandlingActivity.Load(cargo, RECEIVE, SHANGHAI, toDate("2009-05-03"))
51 60
     //Happy path
52 61
     HandlingActivity receiveShanghai = new HandlingActivity(RECEIVE, SHANGHAI);
53
-    assertTrue(itinerary.wasExpecting(receiveShanghai));
62
+    assertTrue(itinerary.isExpectedActivity(receiveShanghai));
54 63
 
55 64
     HandlingActivity loadShanghai = new HandlingActivity(LOAD, SHANGHAI, voyage);
56
-    assertTrue(itinerary.wasExpecting(loadShanghai));
65
+    assertTrue(itinerary.isExpectedActivity(loadShanghai));
57 66
 
58 67
     HandlingActivity unloadRotterdam = new HandlingActivity(UNLOAD, ROTTERDAM, voyage);
59
-    assertTrue(itinerary.wasExpecting(unloadRotterdam));
68
+    assertTrue(itinerary.isExpectedActivity(unloadRotterdam));
60 69
 
61 70
     HandlingActivity loadRotterdam = new HandlingActivity(LOAD, ROTTERDAM, voyage);
62
-    assertTrue(itinerary.wasExpecting(loadRotterdam));
71
+    assertTrue(itinerary.isExpectedActivity(loadRotterdam));
63 72
 
64 73
     HandlingActivity unloadGothenburg = new HandlingActivity(UNLOAD, GOTHENBURG, voyage);
65
-    assertTrue(itinerary.wasExpecting(unloadGothenburg));
74
+    assertTrue(itinerary.isExpectedActivity(unloadGothenburg));
66 75
 
67 76
     HandlingActivity claimGothenburg = new HandlingActivity(CLAIM, GOTHENBURG);
68
-    assertTrue(itinerary.wasExpecting(claimGothenburg));
77
+    assertTrue(itinerary.isExpectedActivity(claimGothenburg));
69 78
 
70 79
     //TODO Customs event can only be interpreted properly by knowing the destination of the cargo.
71 80
     // This can be inferred from the Itinerary, but it isn't definitive. So, do we answer based on
@@ -73,22 +82,74 @@ public class ItineraryTest extends TestCase {
73 82
     // ignore this at itinerary level somehow and leave it purely as a cargo responsibility.
74 83
     // (See customsClearancePoint tests in CargoTest)
75 84
 //    HandlingActivity customsGothenburg = new HandlingActivity(CUSTOMS, GOTHENBURG);
76
-//    assertTrue(itinerary.wasExpecting(customsGothenburg));
85
+//    assertTrue(itinerary.isExpectedActivity(customsGothenburg));
77 86
 
78 87
     //Received at the wrong location
79 88
     HandlingActivity receiveHangzou = new HandlingActivity(RECEIVE, HANGZOU);
80
-    assertFalse(itinerary.wasExpecting(receiveHangzou));
89
+    assertFalse(itinerary.isExpectedActivity(receiveHangzou));
81 90
 
82 91
     //Loaded to onto the wrong ship, correct location
83 92
     HandlingActivity loadRotterdam666 = new HandlingActivity(LOAD, ROTTERDAM, wrongVoyage);
84
-    assertFalse(itinerary.wasExpecting(loadRotterdam666));
93
+    assertFalse(itinerary.isExpectedActivity(loadRotterdam666));
85 94
 
86 95
     //Unloaded from the wrong ship in the wrong location
87 96
     HandlingActivity unloadHelsinki = new HandlingActivity(UNLOAD, HELSINKI, wrongVoyage);
88
-    assertFalse(itinerary.wasExpecting(unloadHelsinki));
97
+    assertFalse(itinerary.isExpectedActivity(unloadHelsinki));
89 98
 
90 99
     HandlingActivity claimRotterdam = new HandlingActivity(CLAIM, ROTTERDAM);
91
-    assertFalse(itinerary.wasExpecting(claimRotterdam));
100
+    assertFalse(itinerary.isExpectedActivity(claimRotterdam));
101
+  }
102
+
103
+  public void testMatchingLeg() {
104
+    Leg shanghaiToRotterdam = Leg.deriveLeg(voyage, SHANGHAI, ROTTERDAM);
105
+    Leg rotterdamToGothenburg = Leg.deriveLeg(voyage, ROTTERDAM, GOTHENBURG);
106
+    Itinerary itinerary = new Itinerary(shanghaiToRotterdam, rotterdamToGothenburg);
107
+
108
+    assertThat(itinerary.legMatchOf(receivedIn(SHANGHAI)).leg(), is(shanghaiToRotterdam));
109
+    assertThat(itinerary.legMatchOf(loadedOnto(voyage).in(SHANGHAI)).leg(), is(shanghaiToRotterdam));
110
+    assertThat(itinerary.legMatchOf(unloadedOff(voyage).in(ROTTERDAM)).leg(), is(shanghaiToRotterdam));
111
+    assertThat(itinerary.legMatchOf(claimedIn(GOTHENBURG)).leg(), is(rotterdamToGothenburg));
112
+
113
+    assertNull(itinerary.legMatchOf(loadedOnto(wrongVoyage).in(SHANGHAI)).leg());
114
+    assertNull(itinerary.legMatchOf(loadedOnto(wrongVoyage).in(NEWYORK)).leg());
115
+
116
+    assertNull(itinerary.legMatchOf(unloadedOff(wrongVoyage).in(ROTTERDAM)).leg());
117
+    assertNull(itinerary.legMatchOf(unloadedOff(wrongVoyage).in(NEWYORK)).leg());
118
+
119
+    assertNull(itinerary.legMatchOf(receivedIn(NEWYORK)).leg());
120
+    assertNull(itinerary.legMatchOf(claimedIn(NEWYORK)).leg());
121
+  }
122
+
123
+  public void testNextLeg() {
124
+    Leg shanghaiToLongBeach = Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH);
125
+    Leg longBeachToNewYork = Leg.deriveLeg(transcontinental, LONGBEACH, NEWYORK);
126
+    Leg newYorkToRotterdam = Leg.deriveLeg(atlantic, NEWYORK, ROTTERDAM);
127
+
128
+    Itinerary itinerary = new Itinerary(shanghaiToLongBeach, longBeachToNewYork, newYorkToRotterdam);
129
+
130
+    assertThat(itinerary.nextLeg(shanghaiToLongBeach), equalTo(longBeachToNewYork));
131
+    assertThat(itinerary.nextLeg(longBeachToNewYork), equalTo(newYorkToRotterdam));
132
+    assertNull(itinerary.nextLeg(newYorkToRotterdam));
133
+  }
134
+
135
+  public void testLatestLeg() {
136
+    Leg shanghaiToLongBeach = Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH);
137
+    Leg longBeachToNewYork = Leg.deriveLeg(transcontinental, LONGBEACH, NEWYORK);
138
+    Leg newYorkToRotterdam = Leg.deriveLeg(atlantic, NEWYORK, ROTTERDAM);
139
+
140
+    Itinerary itinerary = new Itinerary(shanghaiToLongBeach, longBeachToNewYork, newYorkToRotterdam);
141
+
142
+    HandlingActivity notOnRoute = HandlingActivity.loadedOnto(pacific).in(STOCKHOLM);
143
+    HandlingActivity loadInShanghai = HandlingActivity.loadedOnto(pacific).in(SHANGHAI);
144
+    HandlingActivity unloadInLongbeach = HandlingActivity.unloadedOff(pacific).in(LONGBEACH);
145
+
146
+    assertThat(itinerary.strictlyPriorOf(loadInShanghai, unloadInLongbeach), is(loadInShanghai));
147
+    assertThat(itinerary.strictlyPriorOf(unloadInLongbeach, loadInShanghai), is(loadInShanghai));
148
+    
149
+    assertThat(itinerary.strictlyPriorOf(unloadInLongbeach, notOnRoute), is(unloadInLongbeach));
150
+    assertThat(itinerary.strictlyPriorOf(notOnRoute, loadInShanghai), is(loadInShanghai));
151
+    
152
+    assertNull(itinerary.strictlyPriorOf(unloadInLongbeach, unloadInLongbeach));
92 153
   }
93 154
 
94 155
   public void testCreateItinerary() throws Exception {
@@ -100,8 +161,7 @@ public class ItineraryTest extends TestCase {
100 161
     }
101 162
 
102 163
     try {
103
-      List<Leg> legs = null;
104
-      new Itinerary(legs);
164
+      new Itinerary((List<Leg>) null);
105 165
       fail("Null itinerary is not OK");
106 166
     } catch (IllegalArgumentException iae) {
107 167
       //Expected

+ 27
- 6
dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/cargo/LegTest.java Bestand weergeven

@@ -1,9 +1,13 @@
1 1
 package se.citerus.dddsample.tracking.core.domain.model.cargo;
2 2
 
3
-import static org.junit.Assert.assertEquals;
3
+import static org.hamcrest.core.Is.is;
4
+import static org.junit.Assert.*;
4 5
 import org.junit.Test;
5 6
 import static se.citerus.dddsample.tracking.core.application.util.DateTestUtil.toDate;
6 7
 import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
8
+import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
9
+import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.loadedOnto;
10
+import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.unloadedOff;
7 11
 import static se.citerus.dddsample.tracking.core.domain.model.voyage.SampleVoyages.NEW_YORK_TO_DALLAS;
8 12
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
9 13
 
@@ -28,12 +32,12 @@ public class LegTest {
28 32
 
29 33
   @Test
30 34
   public void legThatFollowsAnEntireVoyage() {
31
-    Leg chicagoToDallas = Leg.deriveLeg(voyage, NEWYORK, DALLAS);
35
+    Leg newYorkToDallas = Leg.deriveLeg(voyage, NEWYORK, DALLAS);
32 36
 
33
-    assertEquals(chicagoToDallas.loadTime(), toDate("2008-10-24", "07:00"));
34
-    assertEquals(chicagoToDallas.loadLocation(), NEWYORK);
35
-    assertEquals(chicagoToDallas.unloadTime(), toDate("2008-10-25", "19:30"));
36
-    assertEquals(chicagoToDallas.unloadLocation(), DALLAS);
37
+    assertEquals(newYorkToDallas.loadTime(), toDate("2008-10-24", "07:00"));
38
+    assertEquals(newYorkToDallas.loadLocation(), NEWYORK);
39
+    assertEquals(newYorkToDallas.unloadTime(), toDate("2008-10-25", "19:30"));
40
+    assertEquals(newYorkToDallas.unloadLocation(), DALLAS);
37 41
   }
38 42
 
39 43
   @Test(expected = IllegalArgumentException.class)
@@ -51,4 +55,21 @@ public class LegTest {
51 55
     Leg.deriveLeg(voyage, HONGKONG, DALLAS);
52 56
   }
53 57
 
58
+  @Test
59
+  public void matchActivity() {
60
+    Leg newYorkToDallas = Leg.deriveLeg(voyage, NEWYORK, DALLAS);
61
+
62
+    assertTrue(newYorkToDallas.matchesActivity(loadedOnto(voyage).in(NEWYORK)));
63
+    assertTrue(newYorkToDallas.matchesActivity(unloadedOff(voyage).in(DALLAS)));
64
+    assertFalse(newYorkToDallas.matchesActivity(loadedOnto(voyage).in(DALLAS)));
65
+    assertFalse(newYorkToDallas.matchesActivity(unloadedOff(voyage).in(NEWYORK)));
66
+  }
67
+
68
+  @Test
69
+  public void deriveActivities() {
70
+    Leg newYorkToDallas = Leg.deriveLeg(voyage, NEWYORK, DALLAS);
71
+    
72
+    assertThat(newYorkToDallas.deriveLoadActivity(), is(HandlingActivity.loadedOnto(voyage).in(NEWYORK)));
73
+    assertThat(newYorkToDallas.deriveUnloadActivity(), is(HandlingActivity.unloadedOff(voyage).in(DALLAS)));
74
+  }
54 75
 }

dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/cargo/CustomsZoneTest.java → dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/location/CustomsZoneTest.java Bestand weergeven

@@ -1,4 +1,4 @@
1
-package se.citerus.dddsample.tracking.core.domain.model.cargo;
1
+package se.citerus.dddsample.tracking.core.domain.model.location;
2 2
 
3 3
 import junit.framework.TestCase;
4 4
 import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;

+ 228
- 0
dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/scenario/CargoLifecycle.java Bestand weergeven

@@ -0,0 +1,228 @@
1
+package se.citerus.dddsample.tracking.core.domain.scenario;
2
+
3
+import static org.hamcrest.core.Is.is;
4
+import static org.junit.Assert.*;
5
+import org.junit.Test;
6
+import static se.citerus.dddsample.tracking.core.application.util.DateTestUtil.toDate;
7
+import se.citerus.dddsample.tracking.core.domain.model.cargo.*;
8
+import static se.citerus.dddsample.tracking.core.domain.model.cargo.RoutingStatus.*;
9
+import static se.citerus.dddsample.tracking.core.domain.model.cargo.TransportStatus.*;
10
+import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
11
+import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.*;
12
+import static se.citerus.dddsample.tracking.core.domain.model.voyage.SampleVoyages.*;
13
+import static se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage.NONE;
14
+import se.citerus.dddsample.tracking.core.domain.service.RoutingService;
15
+
16
+import static java.util.Arrays.asList;
17
+import static java.util.Collections.emptyList;
18
+import java.util.List;
19
+
20
+public class CargoLifecycle {
21
+
22
+  @Test
23
+  public void cargoIsMisdirectedAndRerouted() throws Exception {
24
+    
25
+    TrackingId trackingId = new TrackingId("ABC");
26
+    RouteSpecification routeSpecification =
27
+      new RouteSpecification(HONGKONG, STOCKHOLM, toDate("2009-03-18"));
28
+    Cargo cargo = new Cargo(trackingId, routeSpecification);
29
+
30
+    // Initial state, before routing
31
+    assertThat(cargo.transportStatus(), is(NOT_RECEIVED));
32
+    assertThat(cargo.routingStatus(), is(NOT_ROUTED));
33
+    assertFalse(cargo.isMisdirected());
34
+    assertNull(cargo.estimatedTimeOfArrival());
35
+    assertNull(cargo.nextExpectedActivity());
36
+
37
+
38
+
39
+
40
+
41
+
42
+    // Route: Hongkong - Long Beach - New York - Stockholm
43
+    List<Itinerary> itineraries =
44
+      routingService.fetchRoutesForSpecification(cargo.routeSpecification());
45
+    Itinerary itinerary = selectAppropriateRoute(itineraries);
46
+    cargo.assignToRoute(itinerary);
47
+
48
+    // Routed
49
+    assertThat(cargo.transportStatus(), is(NOT_RECEIVED));
50
+    assertThat(cargo.routingStatus(), is(ROUTED));
51
+    assertThat(cargo.nextExpectedActivity(), is(receivedIn(HONGKONG)));
52
+    assertNotNull(cargo.estimatedTimeOfArrival());
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+    // Received
62
+    cargo.handled(receivedIn(HONGKONG));
63
+
64
+    assertThat(cargo.transportStatus(), is(IN_PORT));
65
+    assertThat(cargo.lastKnownLocation(), is(HONGKONG));
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+    // Loaded
78
+    cargo.handled(loadedOnto(v100).in(HONGKONG));
79
+
80
+    assertThat(cargo.currentVoyage(), is(v100));
81
+    assertThat(cargo.lastKnownLocation(), is(HONGKONG));
82
+    assertThat(cargo.transportStatus(), is(ONBOARD_CARRIER));
83
+    assertThat(cargo.nextExpectedActivity(), is(unloadedOff(v100).in(LONGBEACH)));
84
+    assertFalse(cargo.isMisdirected());
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+
93
+
94
+
95
+    // Unloaded
96
+    cargo.handled(unloadedOff(v100).in(LONGBEACH));
97
+
98
+    assertThat(cargo.currentVoyage(), is(NONE));
99
+    assertThat(cargo.lastKnownLocation(), is(LONGBEACH));
100
+    assertThat(cargo.transportStatus(), is(IN_PORT));
101
+    assertFalse(cargo.isMisdirected());
102
+    assertThat(cargo.nextExpectedActivity(), is(loadedOnto(v250).in(LONGBEACH)));
103
+
104
+
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+    /*
113
+    loadInLongBeach();
114
+    checkDeliveryAfterLoadInLongBeach();
115
+
116
+    unloadInNewYork();
117
+    checkDeliveryAfterUnloadInNewYork();
118
+
119
+    loadInNewYork();
120
+    checkDeliveryAfterLoadInNewYork();
121
+    */
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+
131
+    // Unloaded in Rotterdam, wasn't supposed to happen
132
+    cargo.handled(unloadedOff(v200).in(ROTTERDAM));
133
+
134
+    // Misdirected
135
+    assertTrue(cargo.isMisdirected());
136
+    assertThat(cargo.lastKnownLocation(), is(ROTTERDAM));
137
+    assertThat(cargo.transportStatus(), is(IN_PORT));
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+    // Reroute: specify new route
149
+    RouteSpecification newRouteSpec =
150
+      cargo.routeSpecification().withOrigin(cargo.lastKnownLocation());
151
+    cargo.specifyNewRoute(newRouteSpec);
152
+    assertThat(cargo.routingStatus(), is(MISROUTED));
153
+
154
+    // Assign to new route
155
+    List<Itinerary> available =
156
+      routingService.fetchRoutesForSpecification(newRouteSpec);
157
+    Itinerary newItinerary = selectAppropriateRoute(available);
158
+    cargo.assignToRoute(newItinerary);
159
+    assertThat(cargo.routingStatus(), is(ROUTED));
160
+    assertThat(cargo.nextExpectedActivity(), is(loadedOnto(v300).in(ROTTERDAM)));
161
+
162
+
163
+
164
+
165
+
166
+
167
+
168
+
169
+
170
+
171
+    // Loaded, back on track
172
+    cargo.handled(loadedOnto(v300).in(ROTTERDAM));
173
+    assertFalse(cargo.isMisdirected());
174
+    assertThat(cargo.lastKnownLocation(), is(ROTTERDAM));
175
+    assertThat(cargo.transportStatus(), is(ONBOARD_CARRIER));
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
185
+
186
+
187
+    /*
188
+    checkDeliveryAfterUnloadInStockholm();
189
+
190
+    claimInStockholm();
191
+    checkDeliveryAfterClaimInStockholm();
192
+    */
193
+  }
194
+
195
+  private Itinerary selectAppropriateRoute(List<Itinerary> itineraries) {
196
+    return itineraries.get(0);
197
+  }
198
+
199
+  RoutingService routingService = new ScenarioStubRoutingService();
200
+
201
+  private static class ScenarioStubRoutingService implements RoutingService {
202
+
203
+    private static final Itinerary itinerary1 = new Itinerary(asList(
204
+      new Leg(v100, HONGKONG, LONGBEACH, toDate("2009-03-03"), toDate("2009-03-09")),
205
+      new Leg(v250, LONGBEACH, NEWYORK, toDate("2009-03-10"), toDate("2009-03-14")),
206
+      new Leg(v200, NEWYORK, STOCKHOLM, toDate("2009-03-07"), toDate("2009-03-11"))
207
+    ));
208
+
209
+    private static final Itinerary itinerary2 = new Itinerary(asList(
210
+      new Leg(v300, ROTTERDAM, HAMBURG, toDate("2009-03-10"), toDate("2009-03-12")),
211
+      new Leg(v400, HAMBURG, STOCKHOLM, toDate("2009-03-14"), toDate("2009-03-15"))
212
+    ));
213
+
214
+    public List<Itinerary> fetchRoutesForSpecification(RouteSpecification routeSpecification) {
215
+      if (routeSpecification.origin().sameAs(HONGKONG)) {
216
+        // Hongkong - Long Beach - New York - Stockholm, initial routing
217
+        return asList(itinerary1);
218
+      } else if (routeSpecification.origin().sameAs(ROTTERDAM)) {
219
+        // Rotterdam - Hamburg - Stockholm, rerouting misdirected cargo from Rotterdam
220
+        return asList(itinerary2);
221
+      } else {
222
+        return emptyList();
223
+      }
224
+    }
225
+
226
+  }
227
+
228
+}

+ 418
- 0
dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/scenario/CargoLifecycleScenarioTest.java Bestand weergeven

@@ -0,0 +1,418 @@
1
+package se.citerus.dddsample.tracking.core.domain.scenario;
2
+
3
+import static org.hamcrest.core.Is.is;
4
+import static org.junit.Assert.*;
5
+import org.junit.Test;
6
+import static se.citerus.dddsample.tracking.core.application.util.DateTestUtil.toDate;
7
+import se.citerus.dddsample.tracking.core.domain.model.cargo.*;
8
+import static se.citerus.dddsample.tracking.core.domain.model.cargo.RoutingStatus.*;
9
+import static se.citerus.dddsample.tracking.core.domain.model.cargo.TransportStatus.*;
10
+import se.citerus.dddsample.tracking.core.domain.model.handling.*;
11
+import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.*;
12
+import se.citerus.dddsample.tracking.core.domain.model.location.Location;
13
+import se.citerus.dddsample.tracking.core.domain.model.location.LocationRepository;
14
+import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
15
+import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
16
+import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.loadedOnto;
17
+import static se.citerus.dddsample.tracking.core.domain.model.voyage.SampleVoyages.*;
18
+import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
19
+import static se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage.NONE;
20
+import se.citerus.dddsample.tracking.core.domain.model.voyage.VoyageNumber;
21
+import se.citerus.dddsample.tracking.core.domain.model.voyage.VoyageRepository;
22
+import se.citerus.dddsample.tracking.core.domain.service.RoutingService;
23
+import se.citerus.dddsample.tracking.core.infrastructure.persistence.inmemory.*;
24
+
25
+import java.util.Arrays;
26
+import java.util.Date;
27
+import java.util.List;
28
+
29
+public class CargoLifecycleScenarioTest {
30
+
31
+  /**
32
+   * Repository implementations are part of the infrastructure layer,
33
+   * which in this test is stubbed out by in-memory replacements.
34
+   */
35
+  HandlingEventRepository handlingEventRepository = new HandlingEventRepositoryInMem();
36
+  CargoRepository cargoRepository = new CargoRepositoryInMem();
37
+  LocationRepository locationRepository = new LocationRepositoryInMem();
38
+  VoyageRepository voyageRepository = new VoyageRepositoryInMem();
39
+
40
+  HandlingEventFactory handlingEventFactory = new HandlingEventFactory(cargoRepository, voyageRepository, locationRepository);
41
+  TrackingIdFactory trackingIdFactory = new TrackingIdFactoryInMem();
42
+
43
+  /**
44
+   * This is a domain service interface, whose implementation
45
+   * is part of the infrastructure layer (re mote call to external system).
46
+   * <p/>
47
+   * It is stubbed in this test.
48
+   */
49
+  RoutingService routingService = new ScenarioStubRoutingService();
50
+
51
+  TrackingId trackingId;
52
+
53
+  @Test
54
+  public void cargoIsCorrectlyDelivered() throws Exception {
55
+    bookCargoFromHongkongToStockholm();
56
+    checkDeliveryAfterBooking();
57
+
58
+    // Route: Hongkong - Long Beach - New York - Stockholm
59
+    routeCargoFromHongkongToStockholm();
60
+    checkDeliveryAfterRouting();
61
+
62
+    receiveInHongkong();
63
+    checkDeliveryAfterReceiveInHongkong();
64
+
65
+    loadInHongkong();
66
+    checkDeliveryAfterLoadInHongKong();
67
+
68
+    unloadInLongBeach();
69
+    checkDeliveryAfterUnloadInLongBeach();
70
+
71
+    loadInLongBeach();
72
+    checkDeliveryAfterLoadInLongBeach();
73
+
74
+    unloadInNewYork();
75
+    checkDeliveryAfterUnloadInNewYork();
76
+
77
+    loadInNewYork();
78
+    checkDeliveryAfterLoadInNewYork();
79
+
80
+    unloadInStockholmOffOf(v200);
81
+    checkDeliveryAfterUnloadInStockholm();
82
+
83
+    claimInStockholm();
84
+    checkDeliveryAfterClaimInStockholm();
85
+  }
86
+
87
+  private void unloadInLongBeach() throws CannotCreateHandlingEventException {
88
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-06"), v100, LONGBEACH, UNLOAD);
89
+  }
90
+
91
+  private void checkDeliveryAfterUnloadInLongBeach() {
92
+    Cargo cargo = cargoRepository.find(trackingId);
93
+
94
+    assertThat(cargo.currentVoyage(), is(NONE));
95
+    assertThat(cargo.lastKnownLocation(), is(LONGBEACH));
96
+    assertThat(cargo.transportStatus(), is(IN_PORT));
97
+    assertFalse(cargo.isMisdirected());
98
+    assertThat(cargo.nextExpectedActivity(), is(new HandlingActivity(LOAD, LONGBEACH, v250)));
99
+  }
100
+
101
+  private void loadInLongBeach() throws CannotCreateHandlingEventException {
102
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-07"), v250, LONGBEACH, LOAD);
103
+  }
104
+
105
+  private void checkDeliveryAfterLoadInLongBeach() {
106
+    Cargo cargo = cargoRepository.find(trackingId);
107
+
108
+    assertThat(cargo.currentVoyage(), is(v250));
109
+    assertThat(cargo.lastKnownLocation(), is(LONGBEACH));
110
+    assertThat(cargo.transportStatus(), is(ONBOARD_CARRIER));
111
+    assertFalse(cargo.isMisdirected());
112
+    assertThat(cargo.nextExpectedActivity(), is(new HandlingActivity(UNLOAD, NEWYORK, v250)));
113
+  }
114
+
115
+  private void unloadInNewYork() throws CannotCreateHandlingEventException {
116
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-08"), v250, NEWYORK, UNLOAD);
117
+  }
118
+
119
+  private void checkDeliveryAfterUnloadInNewYork() {
120
+    Cargo cargo = cargoRepository.find(trackingId);
121
+
122
+    assertThat(cargo.currentVoyage(), is(NONE));
123
+    assertThat(cargo.lastKnownLocation(), is(NEWYORK));
124
+    assertThat(cargo.transportStatus(), is(IN_PORT));
125
+    assertThat(cargo.nextExpectedActivity(), is(new HandlingActivity(LOAD, NEWYORK, v200)));
126
+    assertFalse(cargo.isMisdirected());
127
+  }
128
+
129
+  private void loadInNewYork() throws CannotCreateHandlingEventException {
130
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-10"), v200, NEWYORK, LOAD);
131
+  }
132
+
133
+  private void checkDeliveryAfterLoadInNewYork() {
134
+    Cargo cargo = cargoRepository.find(trackingId);
135
+
136
+    assertThat(cargo.currentVoyage(), is(v200));
137
+    assertThat(cargo.lastKnownLocation(), is(NEWYORK));
138
+    assertThat(cargo.transportStatus(), is(ONBOARD_CARRIER));
139
+    assertFalse(cargo.isMisdirected());
140
+    assertThat(cargo.nextExpectedActivity(), is(new HandlingActivity(UNLOAD, STOCKHOLM, v200)));
141
+  }
142
+
143
+  @Test
144
+  public void cargoIsMisdirectedAndRerouted() throws Exception {
145
+    bookCargoFromHongkongToStockholm();
146
+    checkDeliveryAfterBooking();
147
+
148
+
149
+    // Initial route: Hongkong - Long Beach - New York - Stockholm
150
+    routeCargoFromHongkongToStockholm();
151
+    checkDeliveryAfterRouting();
152
+
153
+    receiveInHongkong();
154
+    checkDeliveryAfterReceiveInHongkong();
155
+
156
+    loadInHongkong();
157
+    checkDeliveryAfterLoadInHongKong();
158
+
159
+    unloadInTokyo();
160
+    checkDeliveryAfterUnloadInTokyo();
161
+
162
+
163
+    // Reroute: Tokyo - Hamburg - Stockholm
164
+    specifyNewRouteFromTokyoToStockholm();
165
+    checkDeliveryAfterNewRouteSpecified();
166
+
167
+    assignToNewRouteFromTokyoToStockholm();
168
+    checkDeliveryAfterAssignedToNewRoute();
169
+
170
+
171
+    loadInTokyo();
172
+    checkDeliveryAfterLoadInTokyo();
173
+
174
+    unloadInHamburg();
175
+    checkDeliveryAfterUnloadInHamburg();
176
+
177
+    loadInHamburg();
178
+    checkDeliveryAfterLoadInHamburg();
179
+
180
+    unloadInStockholmOffOf(v400);
181
+    checkDeliveryAfterUnloadInStockholm();
182
+
183
+    claimInStockholm();
184
+    checkDeliveryAfterClaimInStockholm();
185
+  }
186
+
187
+  private void bookCargoFromHongkongToStockholm() {
188
+    Date arrivalDeadline = toDate("2009-03-18");
189
+    final TrackingId trackingId1 = trackingIdFactory.nextTrackingId();
190
+    final RouteSpecification routeSpecification = new RouteSpecification(HONGKONG, STOCKHOLM, arrivalDeadline);
191
+
192
+    Cargo cargo = new Cargo(trackingId1, routeSpecification);
193
+    cargoRepository.store(cargo);
194
+
195
+    trackingId = cargo.trackingId();
196
+  }
197
+
198
+  private void routeCargoFromHongkongToStockholm() {
199
+    Cargo cargo = cargoRepository.find(trackingId);
200
+    List<Itinerary> itineraries = routingService.fetchRoutesForSpecification(cargo.routeSpecification());
201
+    Itinerary itinerary = itineraries.get(0);
202
+    cargo.assignToRoute(itinerary);
203
+    cargoRepository.store(cargo);
204
+  }
205
+
206
+  public void checkDeliveryAfterBooking() throws Exception {
207
+    Cargo cargo = cargoRepository.find(trackingId);
208
+
209
+    assertThat(cargo.transportStatus(), is(NOT_RECEIVED));
210
+    assertThat(cargo.routingStatus(), is(NOT_ROUTED));
211
+    assertFalse(cargo.isMisdirected());
212
+    assertNull(cargo.estimatedTimeOfArrival());
213
+    assertNull(cargo.nextExpectedActivity());
214
+  }
215
+
216
+  public void checkDeliveryAfterRouting() throws Exception {
217
+    Cargo cargo = cargoRepository.find(trackingId);
218
+
219
+    assertThat(cargo.transportStatus(), is(NOT_RECEIVED));
220
+    assertThat(cargo.routingStatus(), is(ROUTED));
221
+    assertThat(cargo.nextExpectedActivity(), is(new HandlingActivity(RECEIVE, HONGKONG)));
222
+    assertNotNull(cargo.estimatedTimeOfArrival());
223
+  }
224
+
225
+  public void receiveInHongkong() throws CannotCreateHandlingEventException {
226
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-01"), null, HONGKONG, RECEIVE);
227
+  }
228
+
229
+  private void checkDeliveryAfterReceiveInHongkong() {
230
+    Cargo cargo = cargoRepository.find(trackingId);
231
+    assertThat(cargo.transportStatus(), is(IN_PORT));
232
+    assertThat(cargo.lastKnownLocation(), is(HONGKONG));
233
+  }
234
+
235
+  public void loadInHongkong() throws CannotCreateHandlingEventException {
236
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-03"), v100, HONGKONG, LOAD);
237
+  }
238
+
239
+  private void checkDeliveryAfterLoadInHongKong() {
240
+    // Check current state - should be ok
241
+    Cargo cargo = cargoRepository.find(trackingId);
242
+
243
+    assertThat(cargo.currentVoyage(), is(v100));
244
+    assertThat(cargo.lastKnownLocation(), is(HONGKONG));
245
+    assertThat(cargo.transportStatus(), is(ONBOARD_CARRIER));
246
+    assertThat(cargo.nextExpectedActivity(), is(new HandlingActivity(UNLOAD, LONGBEACH, v100)));
247
+    assertFalse(cargo.isMisdirected());
248
+  }
249
+
250
+  public void unloadInTokyo() throws CannotCreateHandlingEventException {
251
+    // Cargo is now (incorrectly) unloaded in Tokyo
252
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-05"), v100, TOKYO, UNLOAD);
253
+  }
254
+
255
+  private void checkDeliveryAfterUnloadInTokyo() {
256
+    Cargo cargo = cargoRepository.find(trackingId);
257
+    // Check current state - cargo is misdirected!
258
+    assertThat(cargo.currentVoyage(), is(NONE));
259
+    assertThat(cargo.lastKnownLocation(), is(TOKYO));
260
+    assertThat(cargo.transportStatus(), is(IN_PORT));
261
+    assertTrue(cargo.isMisdirected());
262
+    assertNull(cargo.nextExpectedActivity());
263
+  }
264
+
265
+  public void specifyNewRouteFromTokyoToStockholm() {
266
+    // TODO cleaner reroute from "earliest location from where the new route originates"
267
+    Cargo cargo = cargoRepository.find(trackingId);
268
+
269
+    // Specify a new route, this time from Tokyo (where it was incorrectly unloaded) to Stockholm
270
+    RouteSpecification fromTokyo = cargo.routeSpecification().withOrigin(TOKYO);
271
+    cargo.specifyNewRoute(fromTokyo);
272
+    cargoRepository.store(cargo);
273
+  }
274
+
275
+  public void checkDeliveryAfterNewRouteSpecified() {
276
+    Cargo cargo = cargoRepository.find(trackingId);
277
+
278
+    // The old itinerary does not satisfy the new specification
279
+    assertThat(cargo.routingStatus(), is(MISROUTED));
280
+    assertNull(cargo.nextExpectedActivity());
281
+  }
282
+
283
+  public void assignToNewRouteFromTokyoToStockholm() {
284
+    Cargo cargo = cargoRepository.find(trackingId);
285
+
286
+    // Repeat procedure of selecting one out of a number of possible routes satisfying the route spec
287
+    List<Itinerary> newItineraries = routingService.fetchRoutesForSpecification(cargo.routeSpecification());
288
+    Itinerary newItinerary = newItineraries.get(0);
289
+
290
+    cargo.assignToRoute(newItinerary);
291
+    cargoRepository.store(cargo);
292
+  }
293
+
294
+  public void checkDeliveryAfterAssignedToNewRoute() {
295
+    Cargo cargo = cargoRepository.find(trackingId);
296
+
297
+    // New itinerary should satisfy new route
298
+    assertThat(cargo.routingStatus(), is(ROUTED));
299
+    assertFalse(cargo.isMisdirected());
300
+    assertThat(cargo.nextExpectedActivity(), is(loadedOnto(v300).in(TOKYO)));
301
+  }
302
+
303
+  public void loadInTokyo() throws CannotCreateHandlingEventException {
304
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-08"), v300, TOKYO, LOAD);
305
+  }
306
+
307
+  private void checkDeliveryAfterLoadInTokyo() {
308
+    Cargo cargo = cargoRepository.find(trackingId);
309
+    // Check current state - should be ok
310
+    assertThat(cargo.currentVoyage(), is(v300));
311
+    assertThat(cargo.lastKnownLocation(), is(TOKYO));
312
+    assertThat(cargo.transportStatus(), is(ONBOARD_CARRIER));
313
+    assertThat(cargo.nextExpectedActivity(), is(new HandlingActivity(UNLOAD, HAMBURG, v300)));
314
+    assertFalse(cargo.isMisdirected());
315
+  }
316
+
317
+  public void unloadInHamburg() throws CannotCreateHandlingEventException {
318
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-12"), v300, HAMBURG, UNLOAD);
319
+  }
320
+
321
+  private void checkDeliveryAfterUnloadInHamburg() {
322
+    Cargo cargo = cargoRepository.find(trackingId);
323
+    // Check current state - should be ok
324
+
325
+    assertThat(cargo.currentVoyage(), is(NONE));
326
+    assertThat(cargo.lastKnownLocation(), is(HAMBURG));
327
+    assertThat(cargo.transportStatus(), is(IN_PORT));
328
+    assertThat(cargo.nextExpectedActivity(), is(new HandlingActivity(LOAD, HAMBURG, v400)));
329
+    assertFalse(cargo.isMisdirected());
330
+  }
331
+
332
+  public void loadInHamburg() throws CannotCreateHandlingEventException {
333
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-14"), v400, HAMBURG, LOAD);
334
+  }
335
+
336
+  private void checkDeliveryAfterLoadInHamburg() {
337
+    Cargo cargo = cargoRepository.find(trackingId);
338
+    // Check current state - should be ok
339
+
340
+    assertThat(cargo.currentVoyage(), is(v400));
341
+    assertThat(cargo.lastKnownLocation(), is(HAMBURG));
342
+    assertThat(cargo.transportStatus(), is(ONBOARD_CARRIER));
343
+    assertThat(cargo.nextExpectedActivity(), is(new HandlingActivity(UNLOAD, STOCKHOLM, v400)));
344
+    assertFalse(cargo.isMisdirected());
345
+  }
346
+
347
+  public void unloadInStockholmOffOf(Voyage voyage) throws CannotCreateHandlingEventException {
348
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-15"), voyage, STOCKHOLM, UNLOAD);
349
+  }
350
+
351
+  private void checkDeliveryAfterUnloadInStockholm() {
352
+    Cargo cargo = cargoRepository.find(trackingId);
353
+    // Check current state - should be ok
354
+    assertThat(cargo.currentVoyage(), is(NONE));
355
+    assertThat(cargo.lastKnownLocation(), is(STOCKHOLM));
356
+    assertThat(cargo.transportStatus(), is(IN_PORT));
357
+    assertThat(cargo.nextExpectedActivity(), is(new HandlingActivity(CLAIM, STOCKHOLM)));
358
+    assertFalse(cargo.isMisdirected());
359
+  }
360
+
361
+  private void claimInStockholm() throws CannotCreateHandlingEventException {
362
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-16"), null, STOCKHOLM, CLAIM);
363
+  }
364
+
365
+  private void checkDeliveryAfterClaimInStockholm() {
366
+    Cargo cargo = cargoRepository.find(trackingId);
367
+    // Check current state - should be ok
368
+    assertThat(cargo.currentVoyage(), is(NONE));
369
+    assertThat(cargo.lastKnownLocation(), is(STOCKHOLM));
370
+    assertThat(cargo.transportStatus(), is(CLAIMED));
371
+    assertFalse(cargo.isMisdirected());
372
+    assertNull(cargo.nextExpectedActivity());
373
+  }
374
+
375
+  private void createHandlingEventAndUpdateAggregates(Date completionTime, Voyage voyage, Location location, HandlingEvent.Type type) throws CannotCreateHandlingEventException {
376
+    updateHandlingEventAggregate(completionTime, voyage, location, type);
377
+    updateCargoAggregate();
378
+  }
379
+
380
+  private void updateHandlingEventAggregate(Date completionTime, Voyage voyage, Location location, HandlingEvent.Type type) throws CannotCreateHandlingEventException {
381
+    VoyageNumber voyageNumber = voyage != null ? voyage.voyageNumber() : null;
382
+    HandlingEvent handlingEvent = handlingEventFactory.createHandlingEvent(completionTime, trackingId, voyageNumber, location.unLocode(), type, new OperatorCode("ABCDE"));
383
+    handlingEventRepository.store(handlingEvent);
384
+  }
385
+
386
+  private void updateCargoAggregate() {
387
+    Cargo cargo = cargoRepository.find(trackingId);
388
+    HandlingEvent handlingEvent = handlingEventRepository.mostRecentHandling(cargo);
389
+    cargo.handled(handlingEvent.activity());
390
+    cargoRepository.store(cargo);
391
+  }
392
+
393
+  private static class ScenarioStubRoutingService implements RoutingService {
394
+
395
+    public List<Itinerary> fetchRoutesForSpecification(RouteSpecification routeSpecification) {
396
+      if (routeSpecification.origin().equals(HONGKONG)) {
397
+        // Hongkong - NYC - Chicago - Stockholm, initial routing
398
+        return Arrays.asList(
399
+          new Itinerary(Arrays.asList(
400
+            new Leg(v100, HONGKONG, LONGBEACH, toDate("2009-03-03"), toDate("2009-03-09")),
401
+            new Leg(v250, LONGBEACH, NEWYORK, toDate("2009-03-10"), toDate("2009-03-14")),
402
+            new Leg(v200, NEWYORK, STOCKHOLM, toDate("2009-03-07"), toDate("2009-03-11"))
403
+          ))
404
+        );
405
+      } else {
406
+        // Tokyo - Hamburg - Stockholm, rerouting misdirected cargo from Tokyo
407
+        return Arrays.asList(
408
+          new Itinerary(Arrays.asList(
409
+            new Leg(v300, TOKYO, HAMBURG, toDate("2009-03-08"), toDate("2009-03-12")),
410
+            new Leg(v400, HAMBURG, STOCKHOLM, toDate("2009-03-14"), toDate("2009-03-15"))
411
+          ))
412
+        );
413
+      }
414
+    }
415
+
416
+  }
417
+
418
+}

+ 102
- 0
dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/scenario/VoyageRescheduledScenarioTest.java Bestand weergeven

@@ -0,0 +1,102 @@
1
+package se.citerus.dddsample.tracking.core.domain.scenario;
2
+
3
+import static org.hamcrest.core.Is.is;
4
+import static org.junit.Assert.assertThat;
5
+import org.junit.Before;
6
+import org.junit.Test;
7
+import static se.citerus.dddsample.tracking.core.application.util.DateTestUtil.toDate;
8
+import se.citerus.dddsample.tracking.core.domain.model.cargo.*;
9
+import static se.citerus.dddsample.tracking.core.domain.model.cargo.RoutingStatus.MISROUTED;
10
+import static se.citerus.dddsample.tracking.core.domain.model.cargo.RoutingStatus.ROUTED;
11
+import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
12
+import static se.citerus.dddsample.tracking.core.domain.model.voyage.SampleVoyages.*;
13
+import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
14
+import se.citerus.dddsample.tracking.core.domain.model.voyage.VoyageNumber;
15
+import se.citerus.dddsample.tracking.core.infrastructure.persistence.inmemory.TrackingIdFactoryInMem;
16
+
17
+import java.util.Date;
18
+
19
+public class VoyageRescheduledScenarioTest {
20
+
21
+  Cargo cargo;
22
+  Voyage voyage1;
23
+  Voyage voyage2;
24
+  Voyage voyage3;
25
+
26
+  @Before
27
+  public void setupCargo() {
28
+    TrackingIdFactoryInMem trackingIdFactory = new TrackingIdFactoryInMem();
29
+
30
+    // Creating new voyages to avoid rescheduling shared ones, breaking other tests
31
+    voyage1 = new Voyage(new VoyageNumber("V1"), HONGKONG_TO_NEW_YORK.schedule());
32
+    voyage2 = new Voyage(new VoyageNumber("V2"), NEW_YORK_TO_DALLAS.schedule());
33
+    voyage3 = new Voyage(new VoyageNumber("V3"), DALLAS_TO_HELSINKI.schedule());
34
+
35
+    TrackingId trackingId = trackingIdFactory.nextTrackingId();
36
+    RouteSpecification routeSpecification = new RouteSpecification(HANGZOU, STOCKHOLM, toDate("2008-12-23"));
37
+
38
+    cargo = new Cargo(trackingId, routeSpecification);
39
+    Itinerary itinerary = new Itinerary(
40
+      Leg.deriveLeg(voyage1, HANGZOU, NEWYORK),
41
+      Leg.deriveLeg(voyage2, NEWYORK, DALLAS),
42
+      Leg.deriveLeg(voyage3, DALLAS, STOCKHOLM)
43
+    );
44
+    cargo.assignToRoute(itinerary);
45
+  }
46
+
47
+  @Test
48
+  public void voyageIsRescheduledWithMaintainableRoute() {
49
+    assertThat(cargo.routingStatus(), is(ROUTED));
50
+
51
+    Date oldDepartureTime = toDate("2008-10-24", "07:00");
52
+
53
+    assertThat(voyage2.schedule().departureTimeAt(NEWYORK), is(oldDepartureTime));
54
+    assertThat(cargo.itinerary().loadTimeAt(NEWYORK), is(oldDepartureTime));
55
+
56
+    // Now voyage2 is rescheduled, the departure from NYC is delayed a few hours.
57
+    Date newDepartureTime = toDate("2008-10-24", "17:00");
58
+    voyage2.departureRescheduled(NEWYORK, newDepartureTime);
59
+
60
+    // The schedule of voyage2 is updated
61
+    assertThat(voyage2.schedule().departureTimeAt(NEWYORK), is(newDepartureTime));
62
+    // ...but the cargo itinerary still has the old departure time
63
+    assertThat(cargo.itinerary().loadTimeAt(NEWYORK), is(oldDepartureTime));
64
+
65
+    // Generate a new itinerary from the old one and assign the cargo to this route
66
+    Itinerary newItinerary = cargo.itinerary().withRescheduledVoyage(voyage2);
67
+    cargo.assignToRoute(newItinerary);
68
+
69
+    // Now the cargo aggregate is updated to reflect the scheduling change!
70
+    assertThat(cargo.itinerary().loadTimeAt(NEWYORK), is(newDepartureTime));
71
+    assertThat(cargo.routingStatus(), is(ROUTED));
72
+  }
73
+
74
+  @Test
75
+  public void voyageIsRescheduledWithUnmaintainableRoute() {
76
+    assertThat(cargo.routingStatus(), is(ROUTED));
77
+
78
+    // Voyage1 arrives in NYC at 2008-10-23 23:10
79
+    // Now rescheduling the departure of voyage2 to BEFORE
80
+    // voyage1 arrives in NYC. This makes it impossible to
81
+    // keep the latter part of the old itinerary, and the new itinerary
82
+    // is therefore truncated after unload in NYC.
83
+
84
+    // TODO delay the arrival instead of advancing the departure
85
+
86
+    Date newDepartureTime = toDate("2008-10-23", "18:30");
87
+    voyage2.departureRescheduled(NEWYORK, newDepartureTime);
88
+
89
+    // Only the part of the itinerary up to and including NYC is maintainable, the rest is truncated
90
+    Itinerary truncatedItinerary = cargo.itinerary().withRescheduledVoyage(voyage2);
91
+    assertThat(truncatedItinerary.lastLeg().unloadLocation(), is(NEWYORK));
92
+
93
+    //Or... The Itinerary is created with an 'Illegal Connection' based on a coomparison of
94
+    //each transfer with a Location.minimumAllowedConnectionTime(). Since Loation is an entity
95
+    //we don't allow Itinerary to dynamically use the property directly because it is not immutable.
96
+    
97
+    // The cargo enters MISROUTED state
98
+    cargo.assignToRoute(truncatedItinerary);
99
+    assertThat(cargo.routingStatus(), is(MISROUTED));
100
+  }
101
+
102
+}

+ 1
- 1
dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/infrastructure/reporting/ReportsUpdaterTest.java Bestand weergeven

@@ -43,7 +43,7 @@ public class ReportsUpdaterTest {
43 43
     );
44 44
     handlingEventRepository.store(handlingEvent);
45 45
 
46
-    cargo.handled(handlingEvent.activity(), handlingEvent.completionTime());
46
+    cargo.handled(handlingEvent.activity());
47 47
 
48 48
     reportsUpdater = new ReportsUpdater(client, cargoRepository, handlingEventRepository, "/handling", "/cargo");
49 49
     eventSequenceNumber = handlingEvent.sequenceNumber();