Просмотр исходного кода

Eliminated the need to keep track on "handled after routing".

Rerouting now works with complete, merged itineraries.

Scenario test is improved, using more realistic setup.
peter_backlund 16 лет назад
Родитель
Сommit
a4dff301cb
12 измененных файлов: 226 добавлений и 208 удалений
  1. 13
    12
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/application/util/SampleDataGenerator.java
  2. 35
    8
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Cargo.java
  3. 12
    29
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Delivery.java
  4. 34
    2
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Itinerary.java
  5. 25
    4
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Leg.java
  6. 1
    1
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/TransportStatus.java
  7. 31
    0
      dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/voyage/Voyage.java
  8. 0
    1
      dddsample/tracking/core/src/main/resources/se/citerus/dddsample/tracking/core/infrastructure/persistence/hibernate/Cargo.hbm.xml
  9. 18
    0
      dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/cargo/ItineraryTest.java
  10. 13
    3
      dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/cargo/LegTest.java
  11. 10
    8
      dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/voyage/VoyageTest.java
  12. 34
    140
      dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/scenario/CargoLifecycle.java

+ 13
- 12
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/application/util/SampleDataGenerator.java Просмотреть файл

@@ -10,14 +10,11 @@ import org.springframework.transaction.support.TransactionCallbackWithoutResult;
10 10
 import org.springframework.transaction.support.TransactionTemplate;
11 11
 import org.springframework.web.context.WebApplicationContext;
12 12
 import org.springframework.web.context.support.WebApplicationContextUtils;
13
-import static se.citerus.dddsample.tracking.core.application.util.DateTestUtil.toDate;
14 13
 import se.citerus.dddsample.tracking.core.domain.model.cargo.*;
15 14
 import se.citerus.dddsample.tracking.core.domain.model.handling.*;
16 15
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
17 16
 import se.citerus.dddsample.tracking.core.domain.model.location.LocationRepository;
18 17
 import se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations;
19
-import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
20
-import static se.citerus.dddsample.tracking.core.domain.model.voyage.SampleVoyages.*;
21 18
 import se.citerus.dddsample.tracking.core.domain.model.voyage.VoyageRepository;
22 19
 
23 20
 import javax.servlet.ServletContextEvent;
@@ -25,9 +22,13 @@ import javax.servlet.ServletContextListener;
25 22
 import java.sql.Timestamp;
26 23
 import java.text.ParseException;
27 24
 import java.text.SimpleDateFormat;
28
-import static java.util.Arrays.asList;
29 25
 import java.util.Date;
30 26
 
27
+import static java.util.Arrays.asList;
28
+import static se.citerus.dddsample.tracking.core.application.util.DateTestUtil.toDate;
29
+import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
30
+import static se.citerus.dddsample.tracking.core.domain.model.voyage.SampleVoyages.*;
31
+
31 32
 /**
32 33
  * Provides sample data.
33 34
  */
@@ -175,16 +176,16 @@ public class SampleDataGenerator implements ServletContextListener {
175 176
 
176 177
   private static void loadCargoData(JdbcTemplate jdbcTemplate) {
177 178
     String cargoSql =
178
-      "insert into Cargo (id, tracking_id, spec_origin_id, spec_destination_id, spec_arrival_deadline, last_update, routed_after_handling) " +
179
-        "values (?, ?, ?, ?, ?, ?, ?)";
179
+      "insert into Cargo (id, tracking_id, spec_origin_id, spec_destination_id, spec_arrival_deadline, last_update) " +
180
+        "values (?, ?, ?, ?, ?, ?)";
180 181
 
181 182
     Object[][] cargoArgs = {
182
-      {1, "XYZ", 1, 2, ts(10), ts(100), false},
183
-      {2, "ABC", 1, 5, ts(20), ts(100), false},
184
-      {3, "ZYX", 2, 1, ts(30), ts(100), false},
185
-      {4, "CBA", 5, 1, ts(40), ts(100), false},
186
-      {5, "FGH", 3, 5, ts(50), ts(100), false},
187
-      {6, "JKL", 6, 4, ts(60), ts(100), false}
183
+      {1, "XYZ", 1, 2, ts(10), ts(100)},
184
+      {2, "ABC", 1, 5, ts(20), ts(100)},
185
+      {3, "ZYX", 2, 1, ts(30), ts(100)},
186
+      {4, "CBA", 5, 1, ts(40), ts(100)},
187
+      {5, "FGH", 3, 5, ts(50), ts(100)},
188
+      {6, "JKL", 6, 4, ts(60), ts(100)}
188 189
     };
189 190
     executeUpdate(jdbcTemplate, cargoSql, cargoArgs);
190 191
   }

+ 35
- 8
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Cargo.java Просмотреть файл

@@ -1,18 +1,20 @@
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;
5 4
 import se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent;
6
-import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.UNLOAD;
7 5
 import se.citerus.dddsample.tracking.core.domain.model.location.CustomsZone;
8 6
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
9 7
 import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
10
-import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.customsIn;
11 8
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
12 9
 import se.citerus.dddsample.tracking.core.domain.patterns.entity.EntitySupport;
13 10
 
14 11
 import java.util.Date;
15 12
 
13
+import static se.citerus.dddsample.tracking.core.domain.model.cargo.RoutingStatus.NOT_ROUTED;
14
+import static se.citerus.dddsample.tracking.core.domain.model.cargo.TransportStatus.ONBOARD_CARRIER;
15
+import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.UNLOAD;
16
+import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.customsIn;
17
+
16 18
 /**
17 19
  * A Cargo. This is the central class in the domain model,
18 20
  * and it is the root of the Cargo-Itinerary-Leg-Delivery-RouteSpecification aggregate.
@@ -111,10 +113,6 @@ public class Cargo extends EntitySupport<Cargo,TrackingId> {
111 113
       return null;
112 114
     }
113 115
 
114
-    if (delivery.isRoutedAfterHandling()) {
115
-      return itinerary.firstLeg().deriveLoadActivity();
116
-    }
117
-
118 116
     if (unloadedInCustomsClearancePoint()) {
119 117
       return customsIn(customsClearancePoint());
120 118
     }
@@ -224,6 +222,18 @@ public class Cargo extends EntitySupport<Cargo,TrackingId> {
224 222
     return delivery.mostRecentHandlingActivity();
225 223
   }
226 224
 
225
+  public Location earliestReroutingLocation() {
226
+    if (isMisdirected()) {
227
+      if (transportStatus() == ONBOARD_CARRIER) {
228
+        return currentVoyage().nextArrivalLocation(lastKnownLocation());
229
+      } else {
230
+        return lastKnownLocation();
231
+      }
232
+    } else {
233
+      return itinerary.matchLeg(delivery.mostRecentPhysicalHandlingActivity()).leg().unloadLocation();
234
+    }
235
+  }
236
+
227 237
   /**
228 238
    * Updates all aspects of the cargo aggregate status
229 239
    * based on the current route specification, itinerary and handling of the cargo.
@@ -247,12 +257,29 @@ public class Cargo extends EntitySupport<Cargo,TrackingId> {
247 257
     }
248 258
   }
249 259
 
260
+  public Itinerary itineraryMergedWith(final Itinerary other) {
261
+    if (isMisdirected() && transportStatus() == ONBOARD_CARRIER) {
262
+      final Leg currentLeg = Leg.deriveLeg(
263
+        currentVoyage(), lastKnownLocation(), currentVoyage().nextArrivalLocation(lastKnownLocation())
264
+      );
265
+
266
+      return this.itinerary().
267
+        truncatedAfter(lastKnownLocation()).
268
+        withLeg(currentLeg).
269
+        appendBy(other);
270
+    } else {
271
+      return this.itinerary().
272
+        truncatedAfter(earliestReroutingLocation()).
273
+        appendBy(other);
274
+    }
275
+  }
276
+
250 277
   private boolean isSignificant(final HandlingActivity newHandlingActivity) {
251 278
     return succedsMostRecentActivity(newHandlingActivity);
252 279
   }
253 280
 
254 281
   private boolean succedsMostRecentActivity(final HandlingActivity newHandlingActivity) {
255
-    if (delivery.hasBeenHandledAfterRouting()) {
282
+    if (delivery.hasBeenHandled()) {
256 283
       final HandlingActivity priorActivity = itinerary.strictlyPriorOf(delivery.mostRecentPhysicalHandlingActivity(), newHandlingActivity);
257 284
       return !newHandlingActivity.sameValueAs(priorActivity);
258 285
     } else {

+ 12
- 29
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Delivery.java Просмотреть файл

@@ -1,9 +1,6 @@
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.*;
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.UNLOAD;
7 4
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
8 5
 import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
9 6
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
@@ -11,6 +8,10 @@ import se.citerus.dddsample.tracking.core.domain.patterns.valueobject.ValueObjec
11 8
 
12 9
 import java.util.Date;
13 10
 
11
+import static se.citerus.dddsample.tracking.core.domain.model.cargo.RoutingStatus.*;
12
+import static se.citerus.dddsample.tracking.core.domain.model.cargo.TransportStatus.ONBOARD_CARRIER;
13
+import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.UNLOAD;
14
+
14 15
 /**
15 16
  * Everything about the delivery of the cargo, i.e. where the cargo is
16 17
  * right now, whether or not it's routed, misdirected and so on.
@@ -20,7 +21,6 @@ class Delivery extends ValueObjectSupport<Delivery> {
20 21
   private final HandlingActivity mostRecentHandlingActivity;
21 22
   private final HandlingActivity mostRecentPhysicalHandlingActivity;
22 23
   private final Date lastUpdatedOn;
23
-  private final boolean routedAfterHandling;
24 24
 
25 25
   /**
26 26
    * Derives a new delivery when a cargo has been handled.
@@ -32,9 +32,9 @@ class Delivery extends ValueObjectSupport<Delivery> {
32 32
     Validate.notNull(newHandlingActivity, "Handling activity is required");
33 33
 
34 34
     if (newHandlingActivity.type().isPhysical()) {
35
-      return new Delivery(newHandlingActivity, newHandlingActivity, false);
35
+      return new Delivery(newHandlingActivity, newHandlingActivity);
36 36
     } else {
37
-      return new Delivery(newHandlingActivity, mostRecentPhysicalHandlingActivity, false);
37
+      return new Delivery(newHandlingActivity, mostRecentPhysicalHandlingActivity);
38 38
     }
39 39
   }
40 40
 
@@ -42,22 +42,20 @@ class Delivery extends ValueObjectSupport<Delivery> {
42 42
    * @return An up to date delivery
43 43
    */
44 44
   Delivery onRouting() {
45
-    return new Delivery(mostRecentHandlingActivity, mostRecentPhysicalHandlingActivity, true);
45
+    return new Delivery(mostRecentHandlingActivity, mostRecentPhysicalHandlingActivity);
46 46
   }
47 47
 
48 48
   /**
49 49
    * @return Initial delivery, before any handling has taken place
50 50
    */
51 51
   static Delivery beforeHandling() {
52
-    return new Delivery(null, null, false);
52
+    return new Delivery(null, null);
53 53
   }
54 54
 
55 55
   private Delivery(final HandlingActivity mostRecentHandlingActivity,
56
-                   final HandlingActivity mostRecentPhysicalHandlingActivity,
57
-                   final boolean routedAfterHandling) {
56
+                   final HandlingActivity mostRecentPhysicalHandlingActivity) {
58 57
     this.mostRecentHandlingActivity = mostRecentHandlingActivity;
59 58
     this.mostRecentPhysicalHandlingActivity = mostRecentPhysicalHandlingActivity;
60
-    this.routedAfterHandling = routedAfterHandling;
61 59
     this.lastUpdatedOn = new Date();
62 60
   }
63 61
 
@@ -91,7 +89,7 @@ class Delivery extends ValueObjectSupport<Delivery> {
91 89
    * @return Current voyage.
92 90
    */
93 91
   Voyage currentVoyage() {
94
-    if (hasBeenHandledAfterRouting() && transportStatus() == ONBOARD_CARRIER) {
92
+    if (hasBeenHandled() && transportStatus() == ONBOARD_CARRIER) {
95 93
       return mostRecentHandlingActivity.voyage();
96 94
     } else {
97 95
       return Voyage.NONE;
@@ -99,13 +97,6 @@ class Delivery extends ValueObjectSupport<Delivery> {
99 97
   }
100 98
 
101 99
   /**
102
-   * @return True if the cargo has been handled at least once since it was last routed
103
-   */
104
-  boolean hasBeenHandledAfterRouting() {
105
-    return hasBeenHandled() && !routedAfterHandling;
106
-  }
107
-
108
-  /**
109 100
    * @return True if the cargo has been handled at least once
110 101
    */
111 102
   boolean hasBeenHandled() {
@@ -125,7 +116,7 @@ class Delivery extends ValueObjectSupport<Delivery> {
125 116
    * @return <code>true</code> if the cargo has been misdirected.
126 117
    */
127 118
   boolean isMisdirected(final Itinerary itinerary) {
128
-    return hasBeenHandledAfterRouting() && !itinerary.isExpectedActivity(mostRecentPhysicalHandlingActivity);
119
+    return hasBeenHandled() && !itinerary.isExpectedActivity(mostRecentPhysicalHandlingActivity);
129 120
   }
130 121
 
131 122
   /**
@@ -133,7 +124,7 @@ class Delivery extends ValueObjectSupport<Delivery> {
133 124
    * @param routeSpecification route specification
134 125
    */
135 126
   boolean onTheGroundAtDestination(final RouteSpecification routeSpecification) {
136
-    return hasBeenHandledAfterRouting() &&
127
+    return hasBeenHandled() &&
137 128
            mostRecentHandlingActivity.type() == UNLOAD &&
138 129
            routeSpecification.destination().sameAs(mostRecentHandlingActivity.location());
139 130
   }
@@ -171,18 +162,10 @@ class Delivery extends ValueObjectSupport<Delivery> {
171 162
     return routingStatus(itinerary, routeSpecification) == ROUTED && !isMisdirected(itinerary);
172 163
   }
173 164
 
174
-  /**
175
-   * @return True if cargo has been routed after the most recent handling activity took place.
176
-   */
177
-  boolean isRoutedAfterHandling() {
178
-    return routedAfterHandling;
179
-  }
180
-
181 165
   Delivery() {
182 166
     // Needed by Hibernate
183 167
     lastUpdatedOn = null;
184 168
     mostRecentHandlingActivity = mostRecentPhysicalHandlingActivity = null;
185
-    routedAfterHandling = false;
186 169
   }
187 170
 
188 171
 }

+ 34
- 2
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Itinerary.java Просмотреть файл

@@ -19,8 +19,6 @@ import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingAct
19 19
 public class Itinerary extends ValueObjectSupport<Itinerary> {
20 20
 
21 21
   private final List<Leg> legs;
22
-  // Null object
23
-  static final Itinerary NONE = new Itinerary();
24 22
 
25 23
   /**
26 24
    * Constructor.
@@ -240,6 +238,40 @@ public class Itinerary extends ValueObjectSupport<Itinerary> {
240 238
     return legs.get(legs.size() - 1);
241 239
   }
242 240
 
241
+
242
+  Itinerary truncatedAfter(final Location location) {
243
+    final List<Leg> newLegs = new ArrayList<Leg>();
244
+
245
+    for (Leg leg : legs) {
246
+      if (leg.voyage().locations().contains(location)) {
247
+        newLegs.add(Leg.deriveLeg(leg.voyage(), leg.loadLocation(), location));
248
+        break;
249
+      } else {
250
+        newLegs.add(leg);
251
+        if (leg.unloadLocation().sameAs(location)) {
252
+          break;
253
+        }
254
+      }
255
+    }
256
+
257
+    return new Itinerary(newLegs);
258
+  }
259
+
260
+  Itinerary withLeg(final Leg leg) {
261
+    final List<Leg> newLegs = new ArrayList<Leg>(legs.size() + 1);
262
+    newLegs.add(leg);
263
+
264
+    return new Itinerary(newLegs);
265
+  }
266
+
267
+  Itinerary appendBy(final Itinerary other) {
268
+    final List<Leg> newLegs = new ArrayList<Leg>(this.legs.size() + other.legs.size());
269
+    newLegs.addAll(this.legs);
270
+    newLegs.addAll(other.legs);
271
+
272
+    return new Itinerary(newLegs);
273
+  }
274
+  
243 275
   private LegActivityMatch findLegMatchingActivity(final HandlingActivity handlingActivity) {
244 276
     for (Leg leg : legs) {
245 277
       if (leg.matchesActivity(handlingActivity)) {

+ 25
- 4
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Leg.java Просмотреть файл

@@ -1,16 +1,21 @@
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;
6 4
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
7 5
 import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
8
-import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.loadOnto;
9
-import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.unloadOff;
10 6
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
11 7
 import se.citerus.dddsample.tracking.core.domain.patterns.valueobject.ValueObjectSupport;
12 8
 
9
+import java.util.ArrayList;
13 10
 import java.util.Date;
11
+import java.util.Iterator;
12
+import java.util.List;
13
+
14
+import static java.util.Collections.unmodifiableList;
15
+import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.LOAD;
16
+import static se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent.Type.UNLOAD;
17
+import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.loadOnto;
18
+import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.unloadOff;
14 19
 
15 20
 /**
16 21
  * An itinerary consists of one or more legs.
@@ -134,6 +139,22 @@ public class Leg extends ValueObjectSupport<Leg> {
134 139
     return unloadOff(voyage).in(unloadLocation);
135 140
   }
136 141
 
142
+  public List<Location> intermediateLocations() {
143
+    final List<Location> locations = new ArrayList<Location>();
144
+    final Iterator<Location> it = voyage.locations().iterator();
145
+
146
+    Location location = it.next();
147
+    for (; it.hasNext() && !loadLocation.sameAs(location);) {}
148
+
149
+    location = it.next();
150
+    for (; it.hasNext() && !unloadLocation.sameAs(location);) {
151
+      locations.add(location);
152
+      location = it.next();
153
+    }
154
+
155
+    return unmodifiableList(locations);
156
+  }
157
+
137 158
   @Override
138 159
   public String toString() {
139 160
     return "Load in " + loadLocation + " at " + loadTime +

+ 1
- 1
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/TransportStatus.java Просмотреть файл

@@ -14,7 +14,7 @@ public enum TransportStatus implements ValueObject<TransportStatus> {
14 14
     return this.equals(other);
15 15
   }
16 16
 
17
-  public static TransportStatus derivedFrom(HandlingActivity handlingActivity) {
17
+  public static TransportStatus derivedFrom(final HandlingActivity handlingActivity) {
18 18
     if (handlingActivity == null) {
19 19
       return NOT_RECEIVED;
20 20
     }

+ 31
- 0
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/voyage/Voyage.java Просмотреть файл

@@ -9,6 +9,8 @@ import java.util.Date;
9 9
 import java.util.Iterator;
10 10
 import java.util.List;
11 11
 
12
+import static java.util.Collections.unmodifiableList;
13
+
12 14
 /**
13 15
  * A Voyage.
14 16
  */
@@ -75,6 +77,35 @@ public class Voyage extends EntitySupport<Voyage,VoyageNumber> {
75 77
     voyageNumber = null;
76 78
   }
77 79
 
80
+  public Location nextArrivalLocation(final Location location) {
81
+    for (Iterator<CarrierMovement> it = schedule.carrierMovements().iterator(); it.hasNext();) {
82
+      CarrierMovement carrierMovement = it.next();
83
+      if (carrierMovement.arrivalLocation().sameAs(location)) {
84
+        return location;
85
+      } else if (carrierMovement.departureLocation().sameAs(location)) {
86
+        return it.next().arrivalLocation();
87
+      }
88
+    }
89
+
90
+    return Location.NONE;
91
+  }
92
+
93
+  public List<Location> locations() {
94
+    final List<Location> locations = new ArrayList<Location>();
95
+    final Iterator<CarrierMovement> it = schedule.carrierMovements().iterator();
96
+
97
+    for (; it.hasNext(); ) {
98
+      final CarrierMovement carrierMovement = it.next();
99
+      locations.add(carrierMovement.departureLocation());
100
+
101
+      if (!it.hasNext()) {
102
+        locations.add(carrierMovement.arrivalLocation());
103
+      }
104
+    }
105
+
106
+    return unmodifiableList(locations);
107
+  }
108
+
78 109
   /**
79 110
    * Builder pattern is used for incremental construction
80 111
    * of a Voyage aggregate. This serves as an aggregate factory.

+ 0
- 1
dddsample/tracking/core/src/main/resources/se/citerus/dddsample/tracking/core/infrastructure/persistence/hibernate/Cargo.hbm.xml Просмотреть файл

@@ -16,7 +16,6 @@
16 16
     </component>
17 17
 
18 18
     <component name="delivery">
19
-      <property name="routedAfterHandling" column="routed_after_handling" not-null="true"/>
20 19
       <property name="lastUpdatedOn" column="last_update" not-null="true"/>
21 20
       <many-to-one name="mostRecentPhysicalHandlingActivity" column="most_recent_physical_act" not-null="false" update="false" cascade="all"/>
22 21
       <many-to-one name="mostRecentHandlingActivity" column="most_recent_act" not-null="false" update="false" cascade="all"/>

+ 18
- 0
dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/cargo/ItineraryTest.java Просмотреть файл

@@ -9,6 +9,7 @@ import java.util.ArrayList;
9 9
 import java.util.Date;
10 10
 import java.util.List;
11 11
 
12
+import static java.util.Arrays.asList;
12 13
 import static org.hamcrest.core.Is.is;
13 14
 import static org.hamcrest.core.IsEqual.equalTo;
14 15
 import static org.junit.Assert.assertThat;
@@ -150,6 +151,23 @@ public class ItineraryTest extends TestCase {
150 151
     assertNull(itinerary.strictlyPriorOf(unloadInLongbeach, unloadInLongbeach));
151 152
   }
152 153
 
154
+  public void testTruncatedAfter() throws Exception {
155
+    Leg shanghaiToLongBeach = Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH);
156
+    Leg longBeachToNewYork = Leg.deriveLeg(transcontinental, LONGBEACH, NEWYORK);
157
+    Leg newYorkToRotterdam = Leg.deriveLeg(atlantic, NEWYORK, ROTTERDAM);
158
+
159
+    Itinerary itinerary = new Itinerary(shanghaiToLongBeach, longBeachToNewYork, newYorkToRotterdam);
160
+
161
+    Itinerary toNewYork = itinerary.truncatedAfter(NEWYORK);
162
+    assertEquals(asList(shanghaiToLongBeach, longBeachToNewYork), toNewYork.legs());
163
+
164
+    Itinerary toChicago = itinerary.truncatedAfter(CHICAGO);
165
+    assertEquals(asList(shanghaiToLongBeach, Leg.deriveLeg(transcontinental, LONGBEACH, CHICAGO)), toChicago.legs());
166
+
167
+    Itinerary toRotterdam = itinerary.truncatedAfter(ROTTERDAM);
168
+    assertEquals(asList(shanghaiToLongBeach, longBeachToNewYork, Leg.deriveLeg(atlantic, NEWYORK, ROTTERDAM)), toRotterdam.legs());
169
+  }
170
+
153 171
   public void testCreateItinerary() throws Exception {
154 172
     try {
155 173
       new Itinerary(new ArrayList<Leg>());

+ 13
- 3
dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/cargo/LegTest.java Просмотреть файл

@@ -1,15 +1,18 @@
1 1
 package se.citerus.dddsample.tracking.core.domain.model.cargo;
2 2
 
3
+import org.junit.Test;
4
+import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
5
+import se.citerus.dddsample.tracking.core.domain.model.voyage.SampleVoyages;
6
+import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
7
+
8
+import static java.util.Arrays.asList;
3 9
 import static org.hamcrest.core.Is.is;
4 10
 import static org.junit.Assert.*;
5
-import org.junit.Test;
6 11
 import static se.citerus.dddsample.tracking.core.application.util.DateTestUtil.toDate;
7 12
 import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
8
-import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
9 13
 import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.loadOnto;
10 14
 import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.unloadOff;
11 15
 import static se.citerus.dddsample.tracking.core.domain.model.voyage.SampleVoyages.NEW_YORK_TO_DALLAS;
12
-import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
13 16
 
14 17
 public class LegTest {
15 18
 
@@ -72,4 +75,11 @@ public class LegTest {
72 75
     assertThat(newYorkToDallas.deriveLoadActivity(), is(HandlingActivity.loadOnto(voyage).in(NEWYORK)));
73 76
     assertThat(newYorkToDallas.deriveUnloadActivity(), is(HandlingActivity.unloadOff(voyage).in(DALLAS)));
74 77
   }
78
+
79
+  @Test
80
+  public void intermediateLocations() throws Exception {
81
+    Leg leg = Leg.deriveLeg(SampleVoyages.HONGKONG_TO_NEW_YORK, HONGKONG, NEWYORK);
82
+    assertEquals(asList(HANGZOU, TOKYO, MELBOURNE), leg.intermediateLocations());
83
+  }
84
+
75 85
 }

+ 10
- 8
dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/voyage/VoyageTest.java Просмотреть файл

@@ -1,16 +1,18 @@
1 1
 package se.citerus.dddsample.tracking.core.domain.model.voyage;
2 2
 
3
-import junit.framework.TestCase;
3
+import org.junit.Test;
4 4
 
5
-public class VoyageTest extends TestCase {
5
+import static java.util.Arrays.asList;
6
+import static org.junit.Assert.assertEquals;
7
+import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
8
+import static se.citerus.dddsample.tracking.core.domain.model.voyage.SampleVoyages.HONGKONG_TO_NEW_YORK;
6 9
 
10
+public class VoyageTest {
7 11
 
8
-  public void testSchedule() {
9
-    /*Voyage transcontinental = new Voyage.Builder(new VoyageNumber("4567"),
10
-      LONGBEACH).
11
-      addMovement(CHICAGO, new Date(1), new Date(2)).
12
-      addMovement(NEWYORK, new Date(3), new Date(4)).
13
-      build();*/
12
+  @Test
13
+  public void locations() {
14
+    Voyage voyage = HONGKONG_TO_NEW_YORK;
15
+    assertEquals(asList(HONGKONG, HANGZOU, TOKYO, MELBOURNE, NEWYORK), voyage.locations());
14 16
   }
15 17
 
16 18
 }

+ 34
- 140
dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/scenario/CargoLifecycle.java Просмотреть файл

@@ -1,22 +1,23 @@
1 1
 package se.citerus.dddsample.tracking.core.domain.scenario;
2 2
 
3
+import org.junit.Test;
4
+import se.citerus.dddsample.tracking.core.domain.model.cargo.*;
5
+import se.citerus.dddsample.tracking.core.domain.service.RoutingService;
6
+import se.citerus.dddsample.tracking.core.infrastructure.persistence.inmemory.TrackingIdFactoryInMem;
7
+
8
+import java.util.Date;
9
+import java.util.List;
10
+
11
+import static java.util.Arrays.asList;
3 12
 import static org.hamcrest.core.Is.is;
4 13
 import static org.junit.Assert.*;
5
-import org.junit.Test;
6 14
 import static se.citerus.dddsample.tracking.core.application.util.DateTestUtil.toDate;
7
-import se.citerus.dddsample.tracking.core.domain.model.cargo.*;
8 15
 import static se.citerus.dddsample.tracking.core.domain.model.cargo.RoutingStatus.*;
9 16
 import static se.citerus.dddsample.tracking.core.domain.model.cargo.TransportStatus.*;
10 17
 import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
11 18
 import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.*;
12 19
 import static se.citerus.dddsample.tracking.core.domain.model.voyage.SampleVoyages.*;
13 20
 import static se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage.NONE;
14
-import se.citerus.dddsample.tracking.core.domain.service.RoutingService;
15
-import se.citerus.dddsample.tracking.core.infrastructure.persistence.inmemory.TrackingIdFactoryInMem;
16
-
17
-import static java.util.Arrays.asList;
18
-import java.util.Date;
19
-import java.util.List;
20 21
 
21 22
 public class CargoLifecycle {
22 23
 
@@ -125,11 +126,8 @@ public class CargoLifecycle {
125 126
     assertNull(cargo.estimatedTimeOfArrival());
126 127
     assertNull(cargo.nextExpectedActivity());
127 128
 
128
-
129
-
130 129
     // Route: Hongkong - Long Beach - New York - Stockholm
131
-    List<Itinerary> itineraries =
132
-      routingService.fetchRoutesForSpecification(cargo.routeSpecification());
130
+    List<Itinerary> itineraries = routingService.fetchRoutesForSpecification(cargo.routeSpecification());
133 131
     Itinerary itinerary = selectAppropriateRoute(itineraries);
134 132
     cargo.assignToRoute(itinerary);
135 133
 
@@ -139,38 +137,12 @@ public class CargoLifecycle {
139 137
     assertThat(cargo.nextExpectedActivity(), is(receiveIn(HONGKONG)));
140 138
     assertNotNull(cargo.estimatedTimeOfArrival());
141 139
 
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152 140
     // Received
153 141
     cargo.handled(receiveIn(HONGKONG));
154 142
 
155 143
     assertThat(cargo.transportStatus(), is(IN_PORT));
156 144
     assertThat(cargo.lastKnownLocation(), is(HONGKONG));
157 145
 
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174 146
     // Loaded
175 147
     cargo.handled(loadOnto(pacific1).in(HONGKONG));
176 148
 
@@ -180,117 +152,38 @@ public class CargoLifecycle {
180 152
     assertThat(cargo.nextExpectedActivity(), is(unloadOff(pacific1).in(LONGBEACH)));
181 153
     assertFalse(cargo.isMisdirected());
182 154
 
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-    // Unloaded
197
-    cargo.handled(unloadOff(pacific1).in(LONGBEACH));
198
-
199
-    assertThat(cargo.currentVoyage(), is(NONE));
200
-    assertThat(cargo.lastKnownLocation(), is(LONGBEACH));
201
-    assertThat(cargo.transportStatus(), is(IN_PORT));
202
-    assertFalse(cargo.isMisdirected());
203
-    assertThat(cargo.nextExpectedActivity(), is(loadOnto(continental1).in(LONGBEACH)));
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-    // Unloaded in Rotterdam, wasn't supposed to happen
220
-    cargo.handled(unloadOff(pacific2).in(ROTTERDAM));
155
+    // Unloaded in Seattle, wasn't supposed to happen
156
+    cargo.handled(unloadOff(pacific1).in(SEATTLE));
221 157
 
222 158
     // Misdirected
223 159
     assertTrue(cargo.isMisdirected());
224
-    assertThat(cargo.lastKnownLocation(), is(ROTTERDAM));
160
+    assertThat(cargo.lastKnownLocation(), is(SEATTLE));
225 161
     assertThat(cargo.transportStatus(), is(IN_PORT));
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
162
+    assertNull(cargo.nextExpectedActivity());
163
+    assertNull(cargo.estimatedTimeOfArrival());
238 164
 
239 165
     // Reroute: specify new route
240
-    RouteSpecification currentRouteSpec = cargo.routeSpecification();
241
-    RouteSpecification newRouteSpec =
242
-        currentRouteSpec.withOrigin(cargo.lastKnownLocation());
243
-    cargo.specifyNewRoute(newRouteSpec);
244
-
245
-    assertThat(cargo.routingStatus(), is(MISROUTED));
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255 166
 
256 167
     // Assign to new route
257
-    List<Itinerary> available = routingService.fetchRoutesForSpecification(newRouteSpec);
168
+    List<Itinerary> available = routingService.fetchRoutesForSpecification(
169
+      cargo.routeSpecification().withOrigin(cargo.earliestReroutingLocation())
170
+    );
171
+
258 172
     Itinerary newItinerary = selectAppropriateRoute(available);
259
-    cargo.assignToRoute(newItinerary);
173
+    Itinerary mergedItinerary = cargo.itineraryMergedWith(newItinerary);
174
+    cargo.assignToRoute(mergedItinerary);
260 175
 
176
+    assertFalse(cargo.isMisdirected());
261 177
     assertThat(cargo.routingStatus(), is(ROUTED));
262
-    assertThat(cargo.nextExpectedActivity(), is(loadOnto(atlantic1).in(ROTTERDAM)));
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
178
+    assertThat(cargo.nextExpectedActivity(), is(loadOnto(continental3).in(SEATTLE)));
274 179
 
275 180
     // Loaded, back on track
276
-    cargo.handled(loadOnto(atlantic1).in(ROTTERDAM));
181
+    cargo.handled(loadOnto(continental3).in(SEATTLE));
277 182
     assertFalse(cargo.isMisdirected());
278
-    assertThat(cargo.lastKnownLocation(), is(ROTTERDAM));
183
+    assertThat(cargo.lastKnownLocation(), is(SEATTLE));
279 184
     assertThat(cargo.transportStatus(), is(ONBOARD_CARRIER));
280 185
 
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
186
+    // Etc
294 187
   }
295 188
 
296 189
   @Test
@@ -344,7 +237,6 @@ public class CargoLifecycle {
344 237
 
345 238
     // Customer wants cargo to go to Rotterdam instead of Stockholm
346 239
     RouteSpecification toRotterdam = cargo.routeSpecification().
347
-      withOrigin(cargo.lastKnownLocation()).
348 240
       withDestination(ROTTERDAM);
349 241
 
350 242
     cargo.specifyNewRoute(toRotterdam);
@@ -358,7 +250,9 @@ public class CargoLifecycle {
358 250
     // Assign to new route
359 251
     List<Itinerary> available = routingService.fetchRoutesForSpecification(cargo.routeSpecification());
360 252
     Itinerary newItinerary = selectAppropriateRoute(available);
361
-    cargo.assignToRoute(newItinerary);
253
+    Itinerary mergedItinerary = cargo.itineraryMergedWith(newItinerary);
254
+
255
+    cargo.assignToRoute(mergedItinerary);
362 256
 
363 257
     assertThat(cargo.routingStatus(), is(ROUTED));
364 258
     assertThat(cargo.nextExpectedActivity(), is(loadOnto(continental2).in(LONGBEACH)));
@@ -407,8 +301,8 @@ public class CargoLifecycle {
407 301
     );
408 302
 
409 303
     private static final Itinerary itinerary2 = new Itinerary(
410
-      Leg.deriveLeg(atlantic1, ROTTERDAM, HAMBURG),
411
-      Leg.deriveLeg(atlantic2, HAMBURG, STOCKHOLM)
304
+      Leg.deriveLeg(continental3, SEATTLE, NEWYORK),
305
+      Leg.deriveLeg(atlantic2, NEWYORK, STOCKHOLM)
412 306
     );
413 307
 
414 308
     private static final Itinerary itinerary3 = new Itinerary(
@@ -417,13 +311,13 @@ public class CargoLifecycle {
417 311
     );
418 312
 
419 313
     public List<Itinerary> fetchRoutesForSpecification(RouteSpecification routeSpecification) {
420
-      if (routeSpecification.origin().sameAs(HONGKONG)) {
314
+      if (routeSpecification.origin().sameAs(HONGKONG) && routeSpecification.destination().sameAs(STOCKHOLM)) {
421 315
         // Hongkong - Long Beach - New York - Stockholm, initial routing
422 316
         return asList(itinerary1);
423
-      } else if (routeSpecification.origin().sameAs(ROTTERDAM)) {
317
+      } else if (routeSpecification.origin().sameAs(SEATTLE) && routeSpecification.destination().sameAs(STOCKHOLM)) {
424 318
         // Rotterdam - Hamburg - Stockholm, rerouting misdirected cargo from Rotterdam
425 319
         return asList(itinerary2);
426
-      } else if (routeSpecification.origin().sameAs(LONGBEACH)) {
320
+      } else if (routeSpecification.origin().sameAs(HONGKONG) && routeSpecification.destination().sameAs(ROTTERDAM)) {
427 321
         // Customer requested change of destination
428 322
         return asList(itinerary3);
429 323
       } else {