Ver código fonte

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 anos atrás
pai
commit
a4dff301cb
12 arquivos alterados com 226 adições e 208 exclusões
  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 Ver arquivo

10
 import org.springframework.transaction.support.TransactionTemplate;
10
 import org.springframework.transaction.support.TransactionTemplate;
11
 import org.springframework.web.context.WebApplicationContext;
11
 import org.springframework.web.context.WebApplicationContext;
12
 import org.springframework.web.context.support.WebApplicationContextUtils;
12
 import org.springframework.web.context.support.WebApplicationContextUtils;
13
-import static se.citerus.dddsample.tracking.core.application.util.DateTestUtil.toDate;
14
 import se.citerus.dddsample.tracking.core.domain.model.cargo.*;
13
 import se.citerus.dddsample.tracking.core.domain.model.cargo.*;
15
 import se.citerus.dddsample.tracking.core.domain.model.handling.*;
14
 import se.citerus.dddsample.tracking.core.domain.model.handling.*;
16
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
15
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
17
 import se.citerus.dddsample.tracking.core.domain.model.location.LocationRepository;
16
 import se.citerus.dddsample.tracking.core.domain.model.location.LocationRepository;
18
 import se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations;
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
 import se.citerus.dddsample.tracking.core.domain.model.voyage.VoyageRepository;
18
 import se.citerus.dddsample.tracking.core.domain.model.voyage.VoyageRepository;
22
 
19
 
23
 import javax.servlet.ServletContextEvent;
20
 import javax.servlet.ServletContextEvent;
25
 import java.sql.Timestamp;
22
 import java.sql.Timestamp;
26
 import java.text.ParseException;
23
 import java.text.ParseException;
27
 import java.text.SimpleDateFormat;
24
 import java.text.SimpleDateFormat;
28
-import static java.util.Arrays.asList;
29
 import java.util.Date;
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
  * Provides sample data.
33
  * Provides sample data.
33
  */
34
  */
175
 
176
 
176
   private static void loadCargoData(JdbcTemplate jdbcTemplate) {
177
   private static void loadCargoData(JdbcTemplate jdbcTemplate) {
177
     String cargoSql =
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
     Object[][] cargoArgs = {
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
     executeUpdate(jdbcTemplate, cargoSql, cargoArgs);
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 Ver arquivo

1
 package se.citerus.dddsample.tracking.core.domain.model.cargo;
1
 package se.citerus.dddsample.tracking.core.domain.model.cargo;
2
 
2
 
3
 import org.apache.commons.lang.Validate;
3
 import org.apache.commons.lang.Validate;
4
-import static se.citerus.dddsample.tracking.core.domain.model.cargo.RoutingStatus.NOT_ROUTED;
5
 import se.citerus.dddsample.tracking.core.domain.model.handling.HandlingEvent;
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
 import se.citerus.dddsample.tracking.core.domain.model.location.CustomsZone;
5
 import se.citerus.dddsample.tracking.core.domain.model.location.CustomsZone;
8
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
6
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
9
 import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
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
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
8
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
12
 import se.citerus.dddsample.tracking.core.domain.patterns.entity.EntitySupport;
9
 import se.citerus.dddsample.tracking.core.domain.patterns.entity.EntitySupport;
13
 
10
 
14
 import java.util.Date;
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
  * A Cargo. This is the central class in the domain model,
19
  * A Cargo. This is the central class in the domain model,
18
  * and it is the root of the Cargo-Itinerary-Leg-Delivery-RouteSpecification aggregate.
20
  * and it is the root of the Cargo-Itinerary-Leg-Delivery-RouteSpecification aggregate.
111
       return null;
113
       return null;
112
     }
114
     }
113
 
115
 
114
-    if (delivery.isRoutedAfterHandling()) {
115
-      return itinerary.firstLeg().deriveLoadActivity();
116
-    }
117
-
118
     if (unloadedInCustomsClearancePoint()) {
116
     if (unloadedInCustomsClearancePoint()) {
119
       return customsIn(customsClearancePoint());
117
       return customsIn(customsClearancePoint());
120
     }
118
     }
224
     return delivery.mostRecentHandlingActivity();
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
    * Updates all aspects of the cargo aggregate status
238
    * Updates all aspects of the cargo aggregate status
229
    * based on the current route specification, itinerary and handling of the cargo.
239
    * based on the current route specification, itinerary and handling of the cargo.
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
   private boolean isSignificant(final HandlingActivity newHandlingActivity) {
277
   private boolean isSignificant(final HandlingActivity newHandlingActivity) {
251
     return succedsMostRecentActivity(newHandlingActivity);
278
     return succedsMostRecentActivity(newHandlingActivity);
252
   }
279
   }
253
 
280
 
254
   private boolean succedsMostRecentActivity(final HandlingActivity newHandlingActivity) {
281
   private boolean succedsMostRecentActivity(final HandlingActivity newHandlingActivity) {
255
-    if (delivery.hasBeenHandledAfterRouting()) {
282
+    if (delivery.hasBeenHandled()) {
256
       final HandlingActivity priorActivity = itinerary.strictlyPriorOf(delivery.mostRecentPhysicalHandlingActivity(), newHandlingActivity);
283
       final HandlingActivity priorActivity = itinerary.strictlyPriorOf(delivery.mostRecentPhysicalHandlingActivity(), newHandlingActivity);
257
       return !newHandlingActivity.sameValueAs(priorActivity);
284
       return !newHandlingActivity.sameValueAs(priorActivity);
258
     } else {
285
     } else {

+ 12
- 29
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Delivery.java Ver arquivo

1
 package se.citerus.dddsample.tracking.core.domain.model.cargo;
1
 package se.citerus.dddsample.tracking.core.domain.model.cargo;
2
 
2
 
3
 import org.apache.commons.lang.Validate;
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
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
4
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
8
 import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
5
 import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
9
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
6
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
11
 
8
 
12
 import java.util.Date;
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
  * Everything about the delivery of the cargo, i.e. where the cargo is
16
  * Everything about the delivery of the cargo, i.e. where the cargo is
16
  * right now, whether or not it's routed, misdirected and so on.
17
  * right now, whether or not it's routed, misdirected and so on.
20
   private final HandlingActivity mostRecentHandlingActivity;
21
   private final HandlingActivity mostRecentHandlingActivity;
21
   private final HandlingActivity mostRecentPhysicalHandlingActivity;
22
   private final HandlingActivity mostRecentPhysicalHandlingActivity;
22
   private final Date lastUpdatedOn;
23
   private final Date lastUpdatedOn;
23
-  private final boolean routedAfterHandling;
24
 
24
 
25
   /**
25
   /**
26
    * Derives a new delivery when a cargo has been handled.
26
    * Derives a new delivery when a cargo has been handled.
32
     Validate.notNull(newHandlingActivity, "Handling activity is required");
32
     Validate.notNull(newHandlingActivity, "Handling activity is required");
33
 
33
 
34
     if (newHandlingActivity.type().isPhysical()) {
34
     if (newHandlingActivity.type().isPhysical()) {
35
-      return new Delivery(newHandlingActivity, newHandlingActivity, false);
35
+      return new Delivery(newHandlingActivity, newHandlingActivity);
36
     } else {
36
     } else {
37
-      return new Delivery(newHandlingActivity, mostRecentPhysicalHandlingActivity, false);
37
+      return new Delivery(newHandlingActivity, mostRecentPhysicalHandlingActivity);
38
     }
38
     }
39
   }
39
   }
40
 
40
 
42
    * @return An up to date delivery
42
    * @return An up to date delivery
43
    */
43
    */
44
   Delivery onRouting() {
44
   Delivery onRouting() {
45
-    return new Delivery(mostRecentHandlingActivity, mostRecentPhysicalHandlingActivity, true);
45
+    return new Delivery(mostRecentHandlingActivity, mostRecentPhysicalHandlingActivity);
46
   }
46
   }
47
 
47
 
48
   /**
48
   /**
49
    * @return Initial delivery, before any handling has taken place
49
    * @return Initial delivery, before any handling has taken place
50
    */
50
    */
51
   static Delivery beforeHandling() {
51
   static Delivery beforeHandling() {
52
-    return new Delivery(null, null, false);
52
+    return new Delivery(null, null);
53
   }
53
   }
54
 
54
 
55
   private Delivery(final HandlingActivity mostRecentHandlingActivity,
55
   private Delivery(final HandlingActivity mostRecentHandlingActivity,
56
-                   final HandlingActivity mostRecentPhysicalHandlingActivity,
57
-                   final boolean routedAfterHandling) {
56
+                   final HandlingActivity mostRecentPhysicalHandlingActivity) {
58
     this.mostRecentHandlingActivity = mostRecentHandlingActivity;
57
     this.mostRecentHandlingActivity = mostRecentHandlingActivity;
59
     this.mostRecentPhysicalHandlingActivity = mostRecentPhysicalHandlingActivity;
58
     this.mostRecentPhysicalHandlingActivity = mostRecentPhysicalHandlingActivity;
60
-    this.routedAfterHandling = routedAfterHandling;
61
     this.lastUpdatedOn = new Date();
59
     this.lastUpdatedOn = new Date();
62
   }
60
   }
63
 
61
 
91
    * @return Current voyage.
89
    * @return Current voyage.
92
    */
90
    */
93
   Voyage currentVoyage() {
91
   Voyage currentVoyage() {
94
-    if (hasBeenHandledAfterRouting() && transportStatus() == ONBOARD_CARRIER) {
92
+    if (hasBeenHandled() && transportStatus() == ONBOARD_CARRIER) {
95
       return mostRecentHandlingActivity.voyage();
93
       return mostRecentHandlingActivity.voyage();
96
     } else {
94
     } else {
97
       return Voyage.NONE;
95
       return Voyage.NONE;
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
    * @return True if the cargo has been handled at least once
100
    * @return True if the cargo has been handled at least once
110
    */
101
    */
111
   boolean hasBeenHandled() {
102
   boolean hasBeenHandled() {
125
    * @return <code>true</code> if the cargo has been misdirected.
116
    * @return <code>true</code> if the cargo has been misdirected.
126
    */
117
    */
127
   boolean isMisdirected(final Itinerary itinerary) {
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
    * @param routeSpecification route specification
124
    * @param routeSpecification route specification
134
    */
125
    */
135
   boolean onTheGroundAtDestination(final RouteSpecification routeSpecification) {
126
   boolean onTheGroundAtDestination(final RouteSpecification routeSpecification) {
136
-    return hasBeenHandledAfterRouting() &&
127
+    return hasBeenHandled() &&
137
            mostRecentHandlingActivity.type() == UNLOAD &&
128
            mostRecentHandlingActivity.type() == UNLOAD &&
138
            routeSpecification.destination().sameAs(mostRecentHandlingActivity.location());
129
            routeSpecification.destination().sameAs(mostRecentHandlingActivity.location());
139
   }
130
   }
171
     return routingStatus(itinerary, routeSpecification) == ROUTED && !isMisdirected(itinerary);
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
   Delivery() {
165
   Delivery() {
182
     // Needed by Hibernate
166
     // Needed by Hibernate
183
     lastUpdatedOn = null;
167
     lastUpdatedOn = null;
184
     mostRecentHandlingActivity = mostRecentPhysicalHandlingActivity = null;
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 Ver arquivo

19
 public class Itinerary extends ValueObjectSupport<Itinerary> {
19
 public class Itinerary extends ValueObjectSupport<Itinerary> {
20
 
20
 
21
   private final List<Leg> legs;
21
   private final List<Leg> legs;
22
-  // Null object
23
-  static final Itinerary NONE = new Itinerary();
24
 
22
 
25
   /**
23
   /**
26
    * Constructor.
24
    * Constructor.
240
     return legs.get(legs.size() - 1);
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
   private LegActivityMatch findLegMatchingActivity(final HandlingActivity handlingActivity) {
275
   private LegActivityMatch findLegMatchingActivity(final HandlingActivity handlingActivity) {
244
     for (Leg leg : legs) {
276
     for (Leg leg : legs) {
245
       if (leg.matchesActivity(handlingActivity)) {
277
       if (leg.matchesActivity(handlingActivity)) {

+ 25
- 4
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/cargo/Leg.java Ver arquivo

1
 package se.citerus.dddsample.tracking.core.domain.model.cargo;
1
 package se.citerus.dddsample.tracking.core.domain.model.cargo;
2
 
2
 
3
 import org.apache.commons.lang.Validate;
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
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
4
 import se.citerus.dddsample.tracking.core.domain.model.location.Location;
7
 import se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity;
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
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
6
 import se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage;
11
 import se.citerus.dddsample.tracking.core.domain.patterns.valueobject.ValueObjectSupport;
7
 import se.citerus.dddsample.tracking.core.domain.patterns.valueobject.ValueObjectSupport;
12
 
8
 
9
+import java.util.ArrayList;
13
 import java.util.Date;
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
  * An itinerary consists of one or more legs.
21
  * An itinerary consists of one or more legs.
134
     return unloadOff(voyage).in(unloadLocation);
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
   @Override
158
   @Override
138
   public String toString() {
159
   public String toString() {
139
     return "Load in " + loadLocation + " at " + loadTime +
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 Ver arquivo

14
     return this.equals(other);
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
     if (handlingActivity == null) {
18
     if (handlingActivity == null) {
19
       return NOT_RECEIVED;
19
       return NOT_RECEIVED;
20
     }
20
     }

+ 31
- 0
dddsample/tracking/core/src/main/java/se/citerus/dddsample/tracking/core/domain/model/voyage/Voyage.java Ver arquivo

9
 import java.util.Iterator;
9
 import java.util.Iterator;
10
 import java.util.List;
10
 import java.util.List;
11
 
11
 
12
+import static java.util.Collections.unmodifiableList;
13
+
12
 /**
14
 /**
13
  * A Voyage.
15
  * A Voyage.
14
  */
16
  */
75
     voyageNumber = null;
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
    * Builder pattern is used for incremental construction
110
    * Builder pattern is used for incremental construction
80
    * of a Voyage aggregate. This serves as an aggregate factory.
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 Ver arquivo

16
     </component>
16
     </component>
17
 
17
 
18
     <component name="delivery">
18
     <component name="delivery">
19
-      <property name="routedAfterHandling" column="routed_after_handling" not-null="true"/>
20
       <property name="lastUpdatedOn" column="last_update" not-null="true"/>
19
       <property name="lastUpdatedOn" column="last_update" not-null="true"/>
21
       <many-to-one name="mostRecentPhysicalHandlingActivity" column="most_recent_physical_act" not-null="false" update="false" cascade="all"/>
20
       <many-to-one name="mostRecentPhysicalHandlingActivity" column="most_recent_physical_act" not-null="false" update="false" cascade="all"/>
22
       <many-to-one name="mostRecentHandlingActivity" column="most_recent_act" not-null="false" update="false" cascade="all"/>
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 Ver arquivo

9
 import java.util.Date;
9
 import java.util.Date;
10
 import java.util.List;
10
 import java.util.List;
11
 
11
 
12
+import static java.util.Arrays.asList;
12
 import static org.hamcrest.core.Is.is;
13
 import static org.hamcrest.core.Is.is;
13
 import static org.hamcrest.core.IsEqual.equalTo;
14
 import static org.hamcrest.core.IsEqual.equalTo;
14
 import static org.junit.Assert.assertThat;
15
 import static org.junit.Assert.assertThat;
150
     assertNull(itinerary.strictlyPriorOf(unloadInLongbeach, unloadInLongbeach));
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
   public void testCreateItinerary() throws Exception {
171
   public void testCreateItinerary() throws Exception {
154
     try {
172
     try {
155
       new Itinerary(new ArrayList<Leg>());
173
       new Itinerary(new ArrayList<Leg>());

+ 13
- 3
dddsample/tracking/core/src/test/java/se/citerus/dddsample/tracking/core/domain/model/cargo/LegTest.java Ver arquivo

1
 package se.citerus.dddsample.tracking.core.domain.model.cargo;
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
 import static org.hamcrest.core.Is.is;
9
 import static org.hamcrest.core.Is.is;
4
 import static org.junit.Assert.*;
10
 import static org.junit.Assert.*;
5
-import org.junit.Test;
6
 import static se.citerus.dddsample.tracking.core.application.util.DateTestUtil.toDate;
11
 import static se.citerus.dddsample.tracking.core.application.util.DateTestUtil.toDate;
7
 import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
12
 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.loadOnto;
13
 import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.loadOnto;
10
 import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.unloadOff;
14
 import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.unloadOff;
11
 import static se.citerus.dddsample.tracking.core.domain.model.voyage.SampleVoyages.NEW_YORK_TO_DALLAS;
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
 public class LegTest {
17
 public class LegTest {
15
 
18
 
72
     assertThat(newYorkToDallas.deriveLoadActivity(), is(HandlingActivity.loadOnto(voyage).in(NEWYORK)));
75
     assertThat(newYorkToDallas.deriveLoadActivity(), is(HandlingActivity.loadOnto(voyage).in(NEWYORK)));
73
     assertThat(newYorkToDallas.deriveUnloadActivity(), is(HandlingActivity.unloadOff(voyage).in(DALLAS)));
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 Ver arquivo

1
 package se.citerus.dddsample.tracking.core.domain.model.voyage;
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 Ver arquivo

1
 package se.citerus.dddsample.tracking.core.domain.scenario;
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
 import static org.hamcrest.core.Is.is;
12
 import static org.hamcrest.core.Is.is;
4
 import static org.junit.Assert.*;
13
 import static org.junit.Assert.*;
5
-import org.junit.Test;
6
 import static se.citerus.dddsample.tracking.core.application.util.DateTestUtil.toDate;
14
 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.*;
15
 import static se.citerus.dddsample.tracking.core.domain.model.cargo.RoutingStatus.*;
9
 import static se.citerus.dddsample.tracking.core.domain.model.cargo.TransportStatus.*;
16
 import static se.citerus.dddsample.tracking.core.domain.model.cargo.TransportStatus.*;
10
 import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
17
 import static se.citerus.dddsample.tracking.core.domain.model.location.SampleLocations.*;
11
 import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.*;
18
 import static se.citerus.dddsample.tracking.core.domain.model.shared.HandlingActivity.*;
12
 import static se.citerus.dddsample.tracking.core.domain.model.voyage.SampleVoyages.*;
19
 import static se.citerus.dddsample.tracking.core.domain.model.voyage.SampleVoyages.*;
13
 import static se.citerus.dddsample.tracking.core.domain.model.voyage.Voyage.NONE;
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
 public class CargoLifecycle {
22
 public class CargoLifecycle {
22
 
23
 
125
     assertNull(cargo.estimatedTimeOfArrival());
126
     assertNull(cargo.estimatedTimeOfArrival());
126
     assertNull(cargo.nextExpectedActivity());
127
     assertNull(cargo.nextExpectedActivity());
127
 
128
 
128
-
129
-
130
     // Route: Hongkong - Long Beach - New York - Stockholm
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
     Itinerary itinerary = selectAppropriateRoute(itineraries);
131
     Itinerary itinerary = selectAppropriateRoute(itineraries);
134
     cargo.assignToRoute(itinerary);
132
     cargo.assignToRoute(itinerary);
135
 
133
 
139
     assertThat(cargo.nextExpectedActivity(), is(receiveIn(HONGKONG)));
137
     assertThat(cargo.nextExpectedActivity(), is(receiveIn(HONGKONG)));
140
     assertNotNull(cargo.estimatedTimeOfArrival());
138
     assertNotNull(cargo.estimatedTimeOfArrival());
141
 
139
 
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
     // Received
140
     // Received
153
     cargo.handled(receiveIn(HONGKONG));
141
     cargo.handled(receiveIn(HONGKONG));
154
 
142
 
155
     assertThat(cargo.transportStatus(), is(IN_PORT));
143
     assertThat(cargo.transportStatus(), is(IN_PORT));
156
     assertThat(cargo.lastKnownLocation(), is(HONGKONG));
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
     // Loaded
146
     // Loaded
175
     cargo.handled(loadOnto(pacific1).in(HONGKONG));
147
     cargo.handled(loadOnto(pacific1).in(HONGKONG));
176
 
148
 
180
     assertThat(cargo.nextExpectedActivity(), is(unloadOff(pacific1).in(LONGBEACH)));
152
     assertThat(cargo.nextExpectedActivity(), is(unloadOff(pacific1).in(LONGBEACH)));
181
     assertFalse(cargo.isMisdirected());
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
     // Misdirected
158
     // Misdirected
223
     assertTrue(cargo.isMisdirected());
159
     assertTrue(cargo.isMisdirected());
224
-    assertThat(cargo.lastKnownLocation(), is(ROTTERDAM));
160
+    assertThat(cargo.lastKnownLocation(), is(SEATTLE));
225
     assertThat(cargo.transportStatus(), is(IN_PORT));
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
     // Reroute: specify new route
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
     // Assign to new route
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
     Itinerary newItinerary = selectAppropriateRoute(available);
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
     assertThat(cargo.routingStatus(), is(ROUTED));
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
     // Loaded, back on track
180
     // Loaded, back on track
276
-    cargo.handled(loadOnto(atlantic1).in(ROTTERDAM));
181
+    cargo.handled(loadOnto(continental3).in(SEATTLE));
277
     assertFalse(cargo.isMisdirected());
182
     assertFalse(cargo.isMisdirected());
278
-    assertThat(cargo.lastKnownLocation(), is(ROTTERDAM));
183
+    assertThat(cargo.lastKnownLocation(), is(SEATTLE));
279
     assertThat(cargo.transportStatus(), is(ONBOARD_CARRIER));
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
   @Test
189
   @Test
344
 
237
 
345
     // Customer wants cargo to go to Rotterdam instead of Stockholm
238
     // Customer wants cargo to go to Rotterdam instead of Stockholm
346
     RouteSpecification toRotterdam = cargo.routeSpecification().
239
     RouteSpecification toRotterdam = cargo.routeSpecification().
347
-      withOrigin(cargo.lastKnownLocation()).
348
       withDestination(ROTTERDAM);
240
       withDestination(ROTTERDAM);
349
 
241
 
350
     cargo.specifyNewRoute(toRotterdam);
242
     cargo.specifyNewRoute(toRotterdam);
358
     // Assign to new route
250
     // Assign to new route
359
     List<Itinerary> available = routingService.fetchRoutesForSpecification(cargo.routeSpecification());
251
     List<Itinerary> available = routingService.fetchRoutesForSpecification(cargo.routeSpecification());
360
     Itinerary newItinerary = selectAppropriateRoute(available);
252
     Itinerary newItinerary = selectAppropriateRoute(available);
361
-    cargo.assignToRoute(newItinerary);
253
+    Itinerary mergedItinerary = cargo.itineraryMergedWith(newItinerary);
254
+
255
+    cargo.assignToRoute(mergedItinerary);
362
 
256
 
363
     assertThat(cargo.routingStatus(), is(ROUTED));
257
     assertThat(cargo.routingStatus(), is(ROUTED));
364
     assertThat(cargo.nextExpectedActivity(), is(loadOnto(continental2).in(LONGBEACH)));
258
     assertThat(cargo.nextExpectedActivity(), is(loadOnto(continental2).in(LONGBEACH)));
407
     );
301
     );
408
 
302
 
409
     private static final Itinerary itinerary2 = new Itinerary(
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
     private static final Itinerary itinerary3 = new Itinerary(
308
     private static final Itinerary itinerary3 = new Itinerary(
417
     );
311
     );
418
 
312
 
419
     public List<Itinerary> fetchRoutesForSpecification(RouteSpecification routeSpecification) {
313
     public List<Itinerary> fetchRoutesForSpecification(RouteSpecification routeSpecification) {
420
-      if (routeSpecification.origin().sameAs(HONGKONG)) {
314
+      if (routeSpecification.origin().sameAs(HONGKONG) && routeSpecification.destination().sameAs(STOCKHOLM)) {
421
         // Hongkong - Long Beach - New York - Stockholm, initial routing
315
         // Hongkong - Long Beach - New York - Stockholm, initial routing
422
         return asList(itinerary1);
316
         return asList(itinerary1);
423
-      } else if (routeSpecification.origin().sameAs(ROTTERDAM)) {
317
+      } else if (routeSpecification.origin().sameAs(SEATTLE) && routeSpecification.destination().sameAs(STOCKHOLM)) {
424
         // Rotterdam - Hamburg - Stockholm, rerouting misdirected cargo from Rotterdam
318
         // Rotterdam - Hamburg - Stockholm, rerouting misdirected cargo from Rotterdam
425
         return asList(itinerary2);
319
         return asList(itinerary2);
426
-      } else if (routeSpecification.origin().sameAs(LONGBEACH)) {
320
+      } else if (routeSpecification.origin().sameAs(HONGKONG) && routeSpecification.destination().sameAs(ROTTERDAM)) {
427
         // Customer requested change of destination
321
         // Customer requested change of destination
428
         return asList(itinerary3);
322
         return asList(itinerary3);
429
       } else {
323
       } else {