Explorar el Código

Merged patch from Eric that has started exploration of the customs aspect (zones, handling etc) and made sample voyages and locations more realistic.

peter_backlund hace 17 años
padre
commit
24ccb04db6
Se han modificado 19 ficheros con 586 adiciones y 307 borrados
  1. 21
    12
      dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Cargo.java
  2. 11
    15
      dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Delivery.java
  3. 45
    41
      dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Itinerary.java
  4. 22
    25
      dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Leg.java
  5. 27
    2
      dddsample/src/main/java/se/citerus/dddsample/domain/model/handling/HandlingHistory.java
  6. 124
    0
      dddsample/src/main/java/se/citerus/dddsample/domain/model/location/CustomsZone.java
  7. 25
    15
      dddsample/src/main/java/se/citerus/dddsample/domain/model/location/Location.java
  8. 30
    23
      dddsample/src/main/java/se/citerus/dddsample/domain/model/location/SampleLocations.java
  9. 26
    27
      dddsample/src/main/java/se/citerus/dddsample/domain/model/voyage/SampleVoyages.java
  10. 29
    3
      dddsample/src/main/java/se/citerus/dddsample/domain/model/voyage/Schedule.java
  11. 105
    44
      dddsample/src/test/java/se/citerus/dddsample/domain/model/cargo/CargoTest.java
  12. 21
    0
      dddsample/src/test/java/se/citerus/dddsample/domain/model/cargo/CustomsZoneTest.java
  13. 44
    32
      dddsample/src/test/java/se/citerus/dddsample/domain/model/cargo/ItineraryTest.java
  14. 6
    6
      dddsample/src/test/java/se/citerus/dddsample/domain/model/cargo/LegTest.java
  15. 12
    3
      dddsample/src/test/java/se/citerus/dddsample/domain/model/handling/HandlingHistoryTest.java
  16. 13
    12
      dddsample/src/test/java/se/citerus/dddsample/domain/model/location/LocationTest.java
  17. 10
    31
      dddsample/src/test/java/se/citerus/dddsample/domain/model/voyage/VoyageTest.java
  18. 10
    10
      dddsample/src/test/java/se/citerus/dddsample/scenario/CargoLifecycleScenarioTest.java
  19. 5
    6
      dddsample/src/test/java/se/citerus/dddsample/scenario/VoyageRescheduledScenarioTest.java

+ 21
- 12
dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Cargo.java Ver fichero

@@ -3,6 +3,7 @@ package se.citerus.dddsample.domain.model.cargo;
3 3
 import org.apache.commons.lang.Validate;
4 4
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
5 5
 import se.citerus.dddsample.domain.model.handling.HandlingHistory;
6
+import se.citerus.dddsample.domain.model.location.CustomsZone;
6 7
 import se.citerus.dddsample.domain.model.location.Location;
7 8
 import se.citerus.dddsample.domain.shared.DomainObjectUtils;
8 9
 import se.citerus.dddsample.domain.shared.Entity;
@@ -10,38 +11,37 @@ import se.citerus.dddsample.domain.shared.Entity;
10 11
 /**
11 12
  * A Cargo. This is the central class in the domain model,
12 13
  * and it is the root of the Cargo-Itinerary-Leg-Delivery-RouteSpecification aggregate.
13
- *
14
+ * <p/>
14 15
  * A cargo is identified by a unique tracking id, and it always has an origin
15 16
  * and a route specification. The life cycle of a cargo begins with the booking procedure,
16 17
  * when the tracking id is assigned. During a (short) period of time, between booking
17 18
  * and initial routing, the cargo has no itinerary.
18
- *
19
+ * <p/>
19 20
  * The booking clerk requests a list of possible routes, matching the route specification,
20 21
  * and assigns the cargo to one route. The route to which a cargo is assigned is described
21 22
  * by an itinerary.
22
- *
23
+ * <p/>
23 24
  * A cargo can be re-routed during transport, on demand of the customer, in which case
24 25
  * a new route is specified for the cargo and a new route is requested. The old itinerary,
25 26
  * being a value object, is discarded and a new one is attached.
26
- *
27
+ * <p/>
27 28
  * It may also happen that a cargo is accidentally misrouted, which should notify the proper
28 29
  * personnel and also trigger a re-routing procedure.
29
- *
30
+ * <p/>
30 31
  * When a cargo is handled, the status of the delivery changes. Everything about the delivery
31 32
  * of the cargo is contained in the Delivery value object, which is replaced whenever a cargo
32 33
  * is handled by an asynchronous event triggered by the registration of the handling event.
33
- *
34
+ * <p/>
34 35
  * The delivery can also be affected by routing changes, i.e. when a the route specification
35 36
  * changes, or the cargo is assigned to a new route. In that case, the delivery update is performed
36 37
  * synchronously within the cargo aggregate.
37
- *
38
+ * <p/>
38 39
  * The life cycle of a cargo ends when the cargo is claimed by the customer.
39
- *
40
+ * <p/>
40 41
  * The cargo aggregate, and the entre domain model, is built to solve the problem
41 42
  * of booking and tracking cargo. All important business rules for determining whether
42 43
  * or not a cargo is misdirected, what the current status of the cargo is (on board carrier,
43 44
  * in port etc), are captured in this aggregate.
44
- *
45 45
  */
46 46
 public class Cargo implements Entity<Cargo> {
47 47
 
@@ -64,7 +64,7 @@ public class Cargo implements Entity<Cargo> {
64 64
 
65 65
   /**
66 66
    * The tracking id is the identity of this entity, and is unique.
67
-   * 
67
+   *
68 68
    * @return Tracking id.
69 69
    */
70 70
   public TrackingId trackingId() {
@@ -91,7 +91,7 @@ public class Cargo implements Entity<Cargo> {
91 91
   public RouteSpecification routeSpecification() {
92 92
     return routeSpecification;
93 93
   }
94
-  
94
+
95 95
   /**
96 96
    * Specifies a new route for this cargo.
97 97
    *
@@ -118,6 +118,15 @@ public class Cargo implements Entity<Cargo> {
118 118
     this.delivery = delivery.updateOnRouting(this.routeSpecification, this.itinerary);
119 119
   }
120 120
 
121
+  public CustomsZone customsZone() {
122
+    return routeSpecification.destination().customsZone();
123
+  }
124
+
125
+  public Location customsClearancePoint() {
126
+    return customsZone().entryPoint(itinerary.locations());
127
+  }
128
+
129
+
121 130
   /**
122 131
    * Updates all aspects of the cargo aggregate status
123 132
    * based on the current route specification, itinerary and handling of the cargo.
@@ -136,7 +145,7 @@ public class Cargo implements Entity<Cargo> {
136 145
   public void deriveDeliveryProgress(final HandlingHistory handlingHistory) {
137 146
     Validate.isTrue(this.sameIdentityAs(handlingHistory.cargo()),
138 147
       "Handling history must refer to this cargo, " + this + ". " +
139
-      "Given handlig history refers to cargo " + handlingHistory.cargo());
148
+        "Given handlig history refers to cargo " + handlingHistory.cargo());
140 149
 
141 150
     // Delivery is a value object, so we can simply discard the old one
142 151
     // and replace it with a new

+ 11
- 15
dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Delivery.java Ver fichero

@@ -17,8 +17,7 @@ import java.util.Iterator;
17 17
 
18 18
 /**
19 19
  * The actual transportation of the cargo, as opposed to
20
- * the customer requirement (RouteSpecification) and the plan (Itinerary). 
21
- *
20
+ * the customer requirement (RouteSpecification) and the plan (Itinerary).
22 21
  */
23 22
 public class Delivery implements ValueObject<Delivery> {
24 23
 
@@ -31,7 +30,7 @@ public class Delivery implements ValueObject<Delivery> {
31 30
   private boolean isUnloadedAtDestination;
32 31
   private RoutingStatus routingStatus;
33 32
   private Date calculatedAt;
34
-  private HandlingEvent lastEvent;
33
+  private HandlingEvent lastEvent; //TODO This field would be better named lastPhysicalHandling
35 34
 
36 35
   private static final Date ETA_UNKOWN = null;
37 36
   private static final HandlingActivity NO_ACTIVITY = null;
@@ -42,7 +41,7 @@ public class Delivery implements ValueObject<Delivery> {
42 41
    * but no additional handling of the cargo has been performed.
43 42
    *
44 43
    * @param routeSpecification route specification
45
-   * @param itinerary itinerary
44
+   * @param itinerary          itinerary
46 45
    * @return An up to date delivery
47 46
    */
48 47
   Delivery updateOnRouting(RouteSpecification routeSpecification, Itinerary itinerary) {
@@ -59,29 +58,27 @@ public class Delivery implements ValueObject<Delivery> {
59 58
    * as well as its route specification and itinerary.
60 59
    *
61 60
    * @param routeSpecification route specification
62
-   * @param itinerary itinerary
63
-   * @param handlingHistory delivery history
61
+   * @param itinerary          itinerary
62
+   * @param handlingHistory    delivery history
64 63
    * @return An up to date delivery.
65 64
    */
66 65
   static Delivery derivedFrom(RouteSpecification routeSpecification, Itinerary itinerary, HandlingHistory handlingHistory) {
67 66
     Validate.notNull(routeSpecification, "Route specification is required");
68 67
     Validate.notNull(handlingHistory, "Delivery history is required");
69 68
 
70
-    final HandlingEvent lastEvent = handlingHistory.mostRecentlyCompletedEvent();
71
-
72
-    return new Delivery(lastEvent, itinerary, routeSpecification);
69
+    return new Delivery(handlingHistory.mostRecentPhysicalHandling(), itinerary, routeSpecification);
73 70
   }
74 71
 
75 72
   /**
76 73
    * Internal constructor.
77 74
    *
78
-   * @param lastEvent last event
79
-   * @param itinerary itinerary
80
-   * @param routeSpecification route specification
75
+   * @param lastPhysicalHandling last event
76
+   * @param itinerary            itinerary
77
+   * @param routeSpecification   route specification
81 78
    */
82
-  private Delivery(HandlingEvent lastEvent, Itinerary itinerary, RouteSpecification routeSpecification) {
79
+  private Delivery(HandlingEvent lastPhysicalHandling, Itinerary itinerary, RouteSpecification routeSpecification) {
83 80
     this.calculatedAt = new Date();
84
-    this.lastEvent = lastEvent;
81
+    this.lastEvent = lastPhysicalHandling;
85 82
 
86 83
     this.misdirected = calculateMisdirectionStatus(itinerary);
87 84
     this.routingStatus = calculateRoutingStatus(itinerary, routeSpecification);
@@ -184,7 +181,6 @@ public class Delivery implements ValueObject<Delivery> {
184 181
         return ONBOARD_CARRIER;
185 182
       case UNLOAD:
186 183
       case RECEIVE:
187
-      case CUSTOMS:
188 184
         return IN_PORT;
189 185
       case CLAIM:
190 186
         return CLAIMED;

+ 45
- 41
dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Itinerary.java Ver fichero

@@ -6,14 +6,10 @@ import se.citerus.dddsample.domain.model.location.Location;
6 6
 import se.citerus.dddsample.domain.model.voyage.Voyage;
7 7
 import se.citerus.dddsample.domain.shared.ValueObject;
8 8
 
9
-import java.util.ArrayList;
10
-import java.util.Collections;
11
-import java.util.Date;
12
-import java.util.List;
9
+import java.util.*;
13 10
 
14 11
 /**
15 12
  * An itinerary.
16
- *
17 13
  */
18 14
 public class Itinerary implements ValueObject<Itinerary> {
19 15
 
@@ -38,6 +34,10 @@ public class Itinerary implements ValueObject<Itinerary> {
38 34
     this.legs = legs;
39 35
   }
40 36
 
37
+  public Itinerary(Leg... legs) {
38
+    this(Arrays.asList(legs));
39
+  }
40
+
41 41
   /**
42 42
    * @return the legs of this itinerary, as an <b>immutable</b> list.
43 43
    */
@@ -52,31 +52,29 @@ public class Itinerary implements ValueObject<Itinerary> {
52 52
    * @return <code>true</code> if the event is expected
53 53
    */
54 54
   public boolean isExpected(final HandlingEvent event) {
55
-    if (legs.isEmpty()) {
55
+    if (isEmpty()) {
56 56
       return false;
57 57
     }
58 58
 
59 59
     if (event.type() == HandlingEvent.Type.RECEIVE) {
60
-      //Check that the first leg's origin is the event's location
61
-      final Leg leg = legs.get(0);
62
-      return (leg.loadLocation().equals(event.location()));
60
+      return (firstLeg().loadLocation().equals(event.location()));
63 61
     }
64 62
 
65 63
     if (event.type() == HandlingEvent.Type.LOAD) {
66
-      //Check that the there is one leg with same load location and voyage
64
+      //Check that the there is a leg with same load location and voyage
67 65
       for (Leg leg : legs) {
68 66
         if (leg.loadLocation().sameIdentityAs(event.location()) &&
69
-            leg.voyage().sameIdentityAs(event.voyage()))
67
+          leg.voyage().sameIdentityAs(event.voyage()))
70 68
           return true;
71 69
       }
72 70
       return false;
73 71
     }
74 72
 
75 73
     if (event.type() == HandlingEvent.Type.UNLOAD) {
76
-      //Check that the there is one leg with same unload location and voyage
74
+      //Check that the there is a leg with same unload location and voyage
77 75
       for (Leg leg : legs) {
78
-        if (leg.unloadLocation().equals(event.location()) &&
79
-            leg.voyage().equals(event.voyage()))
76
+        if (leg.unloadLocation().sameIdentityAs(event.location()) &&
77
+          leg.voyage().sameIdentityAs(event.voyage()))
80 78
           return true;
81 79
       }
82 80
       return false;
@@ -88,19 +86,23 @@ public class Itinerary implements ValueObject<Itinerary> {
88 86
       return (leg.unloadLocation().equals(event.location()));
89 87
     }
90 88
 
91
-    //HandlingEvent.Type.CUSTOMS;
92
-    return true;
89
+    if (event.type() == HandlingEvent.Type.CUSTOMS) {
90
+      //Check that the customs location fits the rule of the customs zone
91
+      //TODO Answering this properly requires Cargo's destination. Can't be answered at itinerary level.
92
+    }
93
+    return false;
94
+
93 95
   }
94 96
 
95 97
   /**
96 98
    * @return The initial departure location.
97 99
    */
98 100
   Location initialDepartureLocation() {
99
-     if (legs.isEmpty()) {
100
-       return Location.UNKNOWN;
101
-     } else {
102
-       return legs.get(0).loadLocation();
103
-     }
101
+    if (legs.isEmpty()) {
102
+      return Location.UNKNOWN;
103
+    } else {
104
+      return legs.get(0).loadLocation();
105
+    }
104 106
   }
105 107
 
106 108
   /**
@@ -118,35 +120,28 @@ public class Itinerary implements ValueObject<Itinerary> {
118 120
    * @return Date when cargo arrives at final destination.
119 121
    */
120 122
   Date finalArrivalDate() {
121
-    final Leg lastLeg = lastLeg();
123
+    if (isEmpty()) return new Date(END_OF_DAYS.getTime());
124
+    return new Date(lastLeg().unloadTime().getTime());
125
+  }
122 126
 
123
-    if (lastLeg == null) {
124
-      return new Date(END_OF_DAYS.getTime());
125
-    } else {
126
-      return new Date(lastLeg.unloadTime().getTime());
127
-    }
127
+  private boolean isEmpty() {
128
+    return legs.isEmpty();
128 129
   }
129 130
 
130 131
   /**
131
-   * @return The last leg on the itinerary.
132
+   * @return The first leg on the itinerary.
132 133
    */
133
-  Leg lastLeg() {
134
-    if (legs.isEmpty()) {
135
-      return null;
136
-    } else {
137
-      return legs.get(legs.size() - 1);
138
-    }
134
+  Leg firstLeg() {
135
+    if (isEmpty()) return null;
136
+    return legs.get(0);
139 137
   }
140 138
 
141 139
   /**
142
-   * @return The first leg on the itinerary.
140
+   * @return The last leg on the itinerary.
143 141
    */
144
-  Leg firstLeg() {
145
-    if (legs.isEmpty()) {
146
-      return null;
147
-    } else {
148
-      return legs.get(0);
149
-    }
142
+  Leg lastLeg() {
143
+    if (isEmpty()) return null;
144
+    return legs.get(legs.size() - 1);
150 145
   }
151 146
 
152 147
   /**
@@ -167,6 +162,15 @@ public class Itinerary implements ValueObject<Itinerary> {
167 162
     return new Itinerary(newLegsList);
168 163
   }
169 164
 
165
+  public List<Location> locations() {
166
+    List<Location> result = new ArrayList<Location>();
167
+    result.add(firstLeg().loadLocation());
168
+    for (Leg leg : legs) {
169
+      result.add(leg.unloadLocation());
170
+    }
171
+    return result;
172
+  }
173
+
170 174
   /**
171 175
    * @param other itinerary to compare
172 176
    * @return <code>true</code> if the legs in this and the other itinerary are all equal.

+ 22
- 25
dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Leg.java Ver fichero

@@ -4,7 +4,6 @@ import org.apache.commons.lang.Validate;
4 4
 import org.apache.commons.lang.builder.EqualsBuilder;
5 5
 import org.apache.commons.lang.builder.HashCodeBuilder;
6 6
 import se.citerus.dddsample.domain.model.location.Location;
7
-import se.citerus.dddsample.domain.model.voyage.CarrierMovement;
8 7
 import se.citerus.dddsample.domain.model.voyage.Voyage;
9 8
 import se.citerus.dddsample.domain.shared.ValueObject;
10 9
 
@@ -21,9 +20,10 @@ public class Leg implements ValueObject<Leg> {
21 20
   private Date loadTime;
22 21
   private Date unloadTime;
23 22
 
23
+  // TODO remove this ctor
24 24
   public Leg(Voyage voyage, Location loadLocation, Location unloadLocation, Date loadTime, Date unloadTime) {
25
-    Validate.noNullElements(new Object[] {voyage, loadLocation, unloadLocation, loadTime, unloadTime});
26
-    
25
+    Validate.noNullElements(new Object[]{voyage, loadLocation, unloadLocation, loadTime, unloadTime});
26
+
27 27
     this.voyage = voyage;
28 28
     this.loadLocation = loadLocation;
29 29
     this.unloadLocation = unloadLocation;
@@ -31,30 +31,27 @@ public class Leg implements ValueObject<Leg> {
31 31
     this.unloadTime = unloadTime;
32 32
   }
33 33
 
34
-  public Leg(final Voyage voyage, final Location loadLocation, final Location unloadLocation) {
34
+  /**
35
+   * This simple factory takes the Leg's times from the state of the
36
+   * Voyage as of the time of construction.
37
+   * A fuller version might also factor operational time
38
+   * in the port. For example, average unload time of the
39
+   * unloadLocation could be added to the eta of the vessel
40
+   * schedule, providing an estimated unload time.
41
+   * In a real system, the estimation of the unload time
42
+   * might be more complex.
43
+   *
44
+   * @param voyage         voyage
45
+   * @param loadLocation   load location
46
+   * @param unloadLocation unload location
47
+   * @return A leg on this voyage between the given locations.
48
+   */
49
+  public static Leg deriveLeg(Voyage voyage, Location loadLocation, Location unloadLocation) {
35 50
     Validate.notNull(voyage, "Voyage is required");
36 51
     Validate.notNull(loadLocation, "Load location is required");
37 52
     Validate.notNull(unloadLocation, "Unload location is required");
38 53
     Validate.isTrue(!loadLocation.sameIdentityAs(unloadLocation));
39
-
40
-    CarrierMovement loadCm = null, unloadCm = null;
41
-    for (CarrierMovement carrierMovement : voyage.schedule().carrierMovements()) {
42
-      if (unloadCm == null && carrierMovement.departureLocation().sameIdentityAs(loadLocation)) {
43
-        loadCm = carrierMovement;
44
-      }
45
-      if (loadCm != null && carrierMovement.arrivalLocation().sameIdentityAs(unloadLocation)) {
46
-        unloadCm = carrierMovement;
47
-      }
48
-    }
49
-
50
-    Validate.notNull(loadCm, "Load location is not valid for this voyage");
51
-    Validate.notNull(unloadCm, "Unload location is not valid for this voyage");
52
-
53
-    this.voyage = voyage;
54
-    this.loadLocation = loadCm.departureLocation();
55
-    this.loadTime = loadCm.departureTime();
56
-    this.unloadLocation = unloadCm.arrivalLocation();
57
-    this.unloadTime = unloadCm.arrivalTime();
54
+    return new Leg(voyage, loadLocation, unloadLocation, voyage.schedule().departureTimeAt(loadLocation), voyage.schedule().arrivalTimeAt(unloadLocation));
58 55
   }
59 56
 
60 57
   public Voyage voyage() {
@@ -82,7 +79,7 @@ public class Leg implements ValueObject<Leg> {
82 79
    * @return A new leg with the same load and unload locations, but with updated load/unload times.
83 80
    */
84 81
   Leg withRescheduledVoyage(final Voyage voyage) {
85
-    return new Leg(voyage, loadLocation, unloadLocation);
82
+    return Leg.deriveLeg(voyage, loadLocation, unloadLocation);
86 83
   }
87 84
 
88 85
   @Override
@@ -120,7 +117,7 @@ public class Leg implements ValueObject<Leg> {
120 117
   @Override
121 118
   public String toString() {
122 119
     return "Load in " + loadLocation + " at " + loadTime +
123
-           ", unload in " + unloadLocation + " at " + unloadTime;
120
+      ", unload in " + unloadLocation + " at " + unloadTime;
124 121
   }
125 122
 
126 123
   Leg() {

+ 27
- 2
dddsample/src/main/java/se/citerus/dddsample/domain/model/handling/HandlingHistory.java Ver fichero

@@ -9,7 +9,6 @@ import static java.util.Collections.sort;
9 9
 
10 10
 /**
11 11
  * The handling history of a cargo.
12
- *
13 12
  */
14 13
 public class HandlingHistory implements ValueObject<HandlingHistory> {
15 14
 
@@ -46,7 +45,7 @@ public class HandlingHistory implements ValueObject<HandlingHistory> {
46 45
       final Cargo nextCargo = it.next().cargo();
47 46
       Validate.isTrue(firstCargo.sameIdentityAs(nextCargo),
48 47
         "A handling history can only contain handling events for a unique cargo. " +
49
-        "First event is for cargo " + firstCargo + ", also discovered cargo " + nextCargo
48
+          "First event is for cargo " + firstCargo + ", also discovered cargo " + nextCargo
50 49
       );
51 50
     }
52 51
 
@@ -65,6 +64,20 @@ public class HandlingHistory implements ValueObject<HandlingHistory> {
65 64
   }
66 65
 
67 66
   /**
67
+   * @return Filter a list of handling events, returning only the LOAD and UNLOAD events.
68
+   */
69
+  public List<HandlingEvent> physicalHandlingEvents(List<HandlingEvent> unfilteredEvents) {
70
+    final List<HandlingEvent> filtered = new ArrayList<HandlingEvent>();
71
+    for (HandlingEvent event : unfilteredEvents) {
72
+      if (event.type().equals(HandlingEvent.Type.RECEIVE)) filtered.add(event);
73
+      if (event.type().equals(HandlingEvent.Type.LOAD)) filtered.add(event);
74
+      if (event.type().equals(HandlingEvent.Type.UNLOAD)) filtered.add(event);
75
+      if (event.type().equals(HandlingEvent.Type.CLAIM)) filtered.add(event);
76
+    }
77
+    return Collections.unmodifiableList(filtered);
78
+  }
79
+
80
+  /**
68 81
    * @return Most recently completed event, or null if the handling history is empty.
69 82
    */
70 83
   public HandlingEvent mostRecentlyCompletedEvent() {
@@ -77,6 +90,18 @@ public class HandlingHistory implements ValueObject<HandlingHistory> {
77 90
   }
78 91
 
79 92
   /**
93
+   * @return Most recently completed load or unload, or null if there have been none.
94
+   */
95
+  public HandlingEvent mostRecentPhysicalHandling() {
96
+    final List<HandlingEvent> loadsAndUnloads = physicalHandlingEvents(distinctEventsByCompletionTime());
97
+    if (loadsAndUnloads.isEmpty()) {
98
+      return null;
99
+    } else {
100
+      return loadsAndUnloads.get(loadsAndUnloads.size() - 1);
101
+    }
102
+  }
103
+
104
+  /**
80 105
    * @return The cargo to which this handling history refers.
81 106
    */
82 107
   public Cargo cargo() {

+ 124
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/model/location/CustomsZone.java Ver fichero

@@ -0,0 +1,124 @@
1
+package se.citerus.dddsample.domain.model.location;
2
+
3
+import org.apache.commons.lang.Validate;
4
+import se.citerus.dddsample.domain.shared.Entity;
5
+
6
+import java.util.Arrays;
7
+import java.util.List;
8
+import java.util.regex.Pattern;
9
+
10
+/**
11
+ * A geographical zone within which there are no customs restrictions or checks.
12
+ */
13
+public class CustomsZone implements Entity<CustomsZone> {
14
+
15
+  private String code;
16
+  private String name;
17
+
18
+  // TODO: Find out what the standards are for this, if any. For now:
19
+  // For CustomsZone code, we are using the "country code" portion of the UnLocode,
20
+  // except within economic areas that are not countries, such as the EU. Then we make one up.
21
+  // Country code is exactly two letters, so we'll use 2 letters.
22
+  private static final Pattern VALID_PATTERN = Pattern.compile("[a-zA-Z]{2}");
23
+
24
+  /**
25
+   * Constructor.
26
+   *
27
+   * @param code code
28
+   * @param name name
29
+   */
30
+  public CustomsZone(final String code, final String name) {
31
+    Validate.notNull(code, "Code is required");
32
+    Validate.isTrue(VALID_PATTERN.matcher(code).matches(),
33
+      code + " is not a valid CustomsZone code (does not match pattern)");
34
+    Validate.notNull(name, "Name is required");
35
+    this.code = code.toUpperCase();
36
+    this.name = name;
37
+  }
38
+
39
+  /**
40
+   * Where would a Cargo enter this CustomsZone if it were
41
+   * following this route. Note that specific voyages, etc do not
42
+   * matter, only the sequence of Locations.
43
+   *
44
+   * @param route
45
+   * @return
46
+   */
47
+  public Location entryPoint(List<Location> route) {
48
+    for (Location location : route) {
49
+      if (this.includes(location)) return location;
50
+    }
51
+    return null; //The route does not enter this CustomsZone
52
+  }
53
+
54
+  /**
55
+   * Convenience method for testing. Usage in application would be
56
+   * entryPoint(List<Location) route)
57
+   *
58
+   * @param route
59
+   * @return
60
+   */
61
+  public Location entryPoint(Location... route) {
62
+    return entryPoint(Arrays.asList(route));
63
+  }
64
+
65
+  /**
66
+   * If the rules of the CustomsZone were different or more complex,
67
+   * this is where that rule would be expressed. For the example, we'll
68
+   * just use the basic rule -- customs clearance is at the entry point.
69
+   *
70
+   * @param route
71
+   * @return
72
+   */
73
+//   public Location clearancePoint(List<Location> route) {
74
+//     return entryPoint(route);
75
+//   }
76
+
77
+  /**
78
+   * Convenience method for testing. Usage in application would be
79
+   * clearancePoint(List<Location) route)
80
+   *
81
+   * @param route
82
+   * @return
83
+   */
84
+//   @SuppressWarnings("unchecked")
85
+//   public Location clearancePoint(Location ... route) {
86
+//     return clearancePoint(Arrays.asList(route));
87
+//   }
88
+//
89
+
90
+
91
+  /**
92
+   * @return Code, always upper case.
93
+   */
94
+  public String code() {
95
+    return code;
96
+  }
97
+
98
+  /**
99
+   * @return The name.
100
+   */
101
+  public String name() {
102
+    return name;
103
+  }
104
+
105
+  @Override
106
+  public String toString() {
107
+    return code + "[" + name + "]";
108
+  }
109
+
110
+  @Override
111
+  public boolean sameIdentityAs(CustomsZone other) {
112
+    return code.equals(other.code);
113
+  }
114
+
115
+  CustomsZone() {
116
+    // Needed by Hibernate
117
+  }
118
+
119
+  public boolean includes(Location location) {
120
+    return this.sameIdentityAs(location.customsZone());
121
+  }
122
+
123
+
124
+}

+ 25
- 15
dddsample/src/main/java/se/citerus/dddsample/domain/model/location/Location.java Ver fichero

@@ -1,46 +1,49 @@
1 1
 package se.citerus.dddsample.domain.model.location;
2 2
 
3
-import java.util.TimeZone;
4
-
5 3
 import org.apache.commons.lang.Validate;
6 4
 import se.citerus.dddsample.domain.shared.Entity;
7 5
 
6
+import java.util.TimeZone;
7
+
8 8
 /**
9 9
  * A location is our model is stops on a journey, such as cargo
10 10
  * origin or destination, or carrier movement endpoints.
11
- *
11
+ * <p/>
12 12
  * It is uniquely identified by a UN Locode.
13
- *
14 13
  */
15 14
 public final class Location implements Entity<Location> {
16 15
 
17 16
   private UnLocode unLocode;
18 17
   private String name;
19 18
   private TimeZone timeZone;
19
+  private CustomsZone customsZone;
20 20
 
21 21
   /**
22 22
    * Special Location object that marks an unknown location.
23 23
    */
24 24
   public static final Location UNKNOWN = new Location(
25
-    new UnLocode("XXXXX"), "Unknown location", TimeZone.getTimeZone("Zulu")
25
+    new UnLocode("XXXXX"), "Unknown location", TimeZone.getTimeZone("Zulu"), null
26 26
   );
27 27
 
28 28
   /**
29 29
    * Package-level constructor, visible for test only.
30 30
    *
31
-   * @param unLocode UN Locode
32
- * @param name     location name
33
- * @param timeZone TODO
31
+   * @param unLocode    UN Locode
32
+   * @param name        location name
33
+   * @param timeZone
34
+   * @param customsZone
34 35
    * @throws IllegalArgumentException if the UN Locode or name is null
35 36
    */
36
-  Location(final UnLocode unLocode, final String name, TimeZone timeZone) {
37
-	Validate.notNull(unLocode);
37
+  Location(final UnLocode unLocode, final String name, final TimeZone timeZone, final CustomsZone customsZone) {
38
+    Validate.notNull(unLocode);
38 39
     Validate.notNull(name);
39 40
     Validate.notNull(timeZone);
40
-    
41
+//    Validate.notNull(customsZone);
42
+
41 43
     this.unLocode = unLocode;
42 44
     this.name = name;
43 45
     this.timeZone = timeZone;
46
+    this.customsZone = customsZone;
44 47
   }
45 48
 
46 49
   /**
@@ -58,8 +61,15 @@ public final class Location implements Entity<Location> {
58 61
   }
59 62
 
60 63
   /**
64
+   * @return Customs zone of this location.
65
+   */
66
+  public CustomsZone customsZone() {
67
+    return customsZone;
68
+  }
69
+
70
+  /**
61 71
    * @param object to compare
62
-   * @return Since this is an entiy this will be true iff UN locodes are equal.
72
+   * @return Since this is an entity this will be true iff UN locodes are equal.
63 73
    */
64 74
   @Override
65 75
   public boolean equals(final Object object) {
@@ -100,8 +110,8 @@ public final class Location implements Entity<Location> {
100 110
 
101 111
   private Long id;
102 112
 
103
-public TimeZone timeZone() {
104
-	return timeZone;
105
-}
113
+  public TimeZone timeZone() {
114
+    return timeZone;
115
+  }
106 116
 
107 117
 }

+ 30
- 23
dddsample/src/main/java/se/citerus/dddsample/domain/model/location/SampleLocations.java Ver fichero

@@ -5,31 +5,38 @@ import java.util.*;
5 5
 
6 6
 /**
7 7
  * Sample locations, for test purposes.
8
- * 
9 8
  */
10 9
 public class SampleLocations {
11
-  private static final TimeZone CHINA =  TimeZone.getTimeZone("Asia/Shanghai");
12
-  private static final TimeZone CENTRAL_EUROPE =  TimeZone.getTimeZone("Europe/Stockholm");
13
-  private static final TimeZone JAPAN =  TimeZone.getTimeZone("Asia/Tokyo");
14
-  private static final TimeZone EASTERN =  TimeZone.getTimeZone("America/New_York");
15
-  private static final TimeZone PACIFIC =  TimeZone.getTimeZone("America/Los Angeles");
16
-  private static final TimeZone CENTRAL =  TimeZone.getTimeZone("America/Chicago");
17
-  private static final TimeZone EASTERN_AUSTRALIA =  TimeZone.getTimeZone("Australia/Melbourne");
18
-
19
-  public static final Location HONGKONG = new Location(new UnLocode("CNHKG"), "Hongkong",  CHINA);
20
-  public static final Location MELBOURNE = new Location(new UnLocode("AUMEL"), "Melbourne", EASTERN_AUSTRALIA);
21
-  public static final Location STOCKHOLM = new Location(new UnLocode("SESTO"), "Stockholm",  CENTRAL_EUROPE);
22
-  public static final Location HELSINKI = new Location(new UnLocode("FIHEL"), "Helsinki",  CENTRAL_EUROPE);
23
-  public static final Location CHICAGO = new Location(new UnLocode("USCHI"), "Chicago", CENTRAL);
24
-  public static final Location TOKYO = new Location(new UnLocode("JNTKO"), "Tokyo", JAPAN);
25
-  public static final Location HAMBURG = new Location(new UnLocode("DEHAM"), "Hamburg", CENTRAL_EUROPE);
26
-  public static final Location SHANGHAI = new Location(new UnLocode("CNSHA"), "Shanghai", CHINA);
27
-  public static final Location ROTTERDAM = new Location(new UnLocode("NLRTM"), "Rotterdam", CENTRAL_EUROPE);
28
-  public static final Location GOTHENBURG = new Location(new UnLocode("SEGOT"), "Göteborg", CENTRAL_EUROPE);
29
-  public static final Location HANGZOU = new Location(new UnLocode("CNHGH"), "Hangzhou", CHINA);
30
-  public static final Location NEWYORK = new Location(new UnLocode("USNYC"), "New York", EASTERN);
31
-  public static final Location DALLAS = new Location(new UnLocode("USDAL"), "Dallas", CENTRAL);
32
-  public static final Location LONG_BEACH = new Location(new UnLocode("USLBC"), "Long Beach", PACIFIC);
10
+  private static final TimeZone CHINA = TimeZone.getTimeZone("Asia/Shanghai");
11
+  private static final TimeZone CENTRAL_EUROPE = TimeZone.getTimeZone("Europe/Stockholm");
12
+  private static final TimeZone JAPAN = TimeZone.getTimeZone("Asia/Tokyo");
13
+  private static final TimeZone EASTERN = TimeZone.getTimeZone("America/New_York");
14
+  private static final TimeZone CENTRAL = TimeZone.getTimeZone("America/Chicago");
15
+  private static final TimeZone PACIFIC = TimeZone.getTimeZone("America/Los_Angeles");
16
+  private static final TimeZone EASTERN_AUSTRALIA = TimeZone.getTimeZone("Australia/Melbourne");
17
+
18
+  public static final CustomsZone US = new CustomsZone("US", "United States");
19
+  public static final CustomsZone EU = new CustomsZone("EU", "European Union");
20
+  public static final CustomsZone CN = new CustomsZone("CN", "United States");
21
+  public static final CustomsZone AU = new CustomsZone("AU", "Australia");
22
+  public static final CustomsZone JN = new CustomsZone("JN", "Japan");
23
+
24
+  public static final Location HONGKONG = new Location(new UnLocode("CNHKG"), "Hongkong", CHINA, CN);
25
+  public static final Location MELBOURNE = new Location(new UnLocode("AUMEL"), "Melbourne", EASTERN_AUSTRALIA, AU);
26
+  public static final Location STOCKHOLM = new Location(new UnLocode("SESTO"), "Stockholm", CENTRAL_EUROPE, EU);
27
+  public static final Location HELSINKI = new Location(new UnLocode("FIHEL"), "Helsinki", CENTRAL_EUROPE, EU);
28
+  public static final Location CHICAGO = new Location(new UnLocode("USCHI"), "Chicago", CENTRAL, US);
29
+  public static final Location LONGBEACH = new Location(new UnLocode("USLBG"), "Long Beach", PACIFIC, US);
30
+  public static final Location OAKLAND = new Location(new UnLocode("USOAK"), "Oakland", PACIFIC, US);
31
+  public static final Location SEATTLE = new Location(new UnLocode("USSEA"), "Seattle", PACIFIC, US);
32
+  public static final Location TOKYO = new Location(new UnLocode("JNTKO"), "Tokyo", JAPAN, JN);
33
+  public static final Location HAMBURG = new Location(new UnLocode("DEHAM"), "Hamburg", CENTRAL_EUROPE, EU);
34
+  public static final Location SHANGHAI = new Location(new UnLocode("CNSHA"), "Shanghai", CHINA, CN);
35
+  public static final Location ROTTERDAM = new Location(new UnLocode("NLRTM"), "Rotterdam", CENTRAL_EUROPE, EU);
36
+  public static final Location GOTHENBURG = new Location(new UnLocode("SEGOT"), "Göteborg", CENTRAL_EUROPE, EU);
37
+  public static final Location HANGZOU = new Location(new UnLocode("CNHGH"), "Hangzhou", CHINA, CN);
38
+  public static final Location NEWYORK = new Location(new UnLocode("USNYC"), "New York", EASTERN, US);
39
+  public static final Location DALLAS = new Location(new UnLocode("USDAL"), "Dallas", CENTRAL, US);
33 40
 
34 41
   public static final Map<UnLocode, Location> ALL = new HashMap<UnLocode, Location>();
35 42
 

+ 26
- 27
dddsample/src/main/java/se/citerus/dddsample/domain/model/voyage/SampleVoyages.java Ver fichero

@@ -9,7 +9,6 @@ import java.util.*;
9 9
 
10 10
 /**
11 11
  * Sample carrier movements, for test purposes.
12
- *
13 12
  */
14 13
 public class SampleVoyages {
15 14
 
@@ -19,6 +18,7 @@ public class SampleVoyages {
19 18
   public static final Voyage CM004 = createVoyage("CM004", NEWYORK, CHICAGO);
20 19
   public static final Voyage CM005 = createVoyage("CM005", CHICAGO, HAMBURG);
21 20
   public static final Voyage CM006 = createVoyage("CM006", HAMBURG, HANGZOU);
21
+
22 22
   private static Voyage createVoyage(String id, Location from, Location to) {
23 23
     return new Voyage(new VoyageNumber(id), new Schedule(Arrays.asList(
24 24
       new CarrierMovement(from, to, new Date(), new Date())
@@ -29,32 +29,32 @@ public class SampleVoyages {
29 29
 
30 30
   public final static Voyage v100 = new Voyage.Builder(new VoyageNumber("V100"), HONGKONG).
31 31
     addMovement(TOKYO, toDate("2009-03-03"), toDate("2009-03-05")).
32
-    addMovement(LONG_BEACH, toDate("2009-03-06"), toDate("2009-03-09")).
32
+    addMovement(LONGBEACH, toDate("2009-03-06"), toDate("2009-03-09")).
33 33
     build();
34 34
   public final static Voyage v200 = new Voyage.Builder(new VoyageNumber("V200"), SHANGHAI).
35
-      addMovement(NEWYORK, toDate("2009-03-06"), toDate("2009-03-08")).
36
-      addMovement(ROTTERDAM, toDate("2009-03-10"), toDate("2009-03-14")).
37
-      addMovement(STOCKHOLM, toDate("2009-03-14"), toDate("2009-03-16")).
38
-      build();
39
-  public final static Voyage v250 = new Voyage.Builder(new VoyageNumber("V250"), LONG_BEACH).
40
-      addMovement(DALLAS, toDate("2009-03-06"), toDate("2009-03-08")).
41
-      addMovement(NEWYORK, toDate("2009-03-10"), toDate("2009-03-14")).
42
-      build();
35
+    addMovement(NEWYORK, toDate("2009-03-06"), toDate("2009-03-08")).
36
+    addMovement(ROTTERDAM, toDate("2009-03-10"), toDate("2009-03-14")).
37
+    addMovement(STOCKHOLM, toDate("2009-03-14"), toDate("2009-03-16")).
38
+    build();
39
+  public final static Voyage v250 = new Voyage.Builder(new VoyageNumber("V250"), LONGBEACH).
40
+    addMovement(DALLAS, toDate("2009-03-06"), toDate("2009-03-08")).
41
+    addMovement(NEWYORK, toDate("2009-03-10"), toDate("2009-03-14")).
42
+    build();
43 43
   public final static Voyage v300 = new Voyage.Builder(new VoyageNumber("V300"), TOKYO).
44
-        addMovement(ROTTERDAM, toDate("2009-03-08"), toDate("2009-03-11")).
45
-        addMovement(HAMBURG, toDate("2009-03-11"), toDate("2009-03-12")).
46
-        addMovement(MELBOURNE, toDate("2009-03-14"), toDate("2009-03-18")).
47
-        addMovement(TOKYO, toDate("2009-03-19"), toDate("2009-03-21")).
48
-        build();
44
+    addMovement(ROTTERDAM, toDate("2009-03-08"), toDate("2009-03-11")).
45
+    addMovement(HAMBURG, toDate("2009-03-11"), toDate("2009-03-12")).
46
+    addMovement(MELBOURNE, toDate("2009-03-14"), toDate("2009-03-18")).
47
+    addMovement(TOKYO, toDate("2009-03-19"), toDate("2009-03-21")).
48
+    build();
49 49
   public final static Voyage v400 = new Voyage.Builder(new VoyageNumber("V400"), HAMBURG).
50
-          addMovement(STOCKHOLM, toDate("2009-03-14"), toDate("2009-03-15")).
51
-          addMovement(HELSINKI, toDate("2009-03-15"), toDate("2009-03-16")).
52
-          addMovement(HAMBURG, toDate("2009-03-20"), toDate("2009-03-22")).
53
-          build();
50
+    addMovement(STOCKHOLM, toDate("2009-03-14"), toDate("2009-03-15")).
51
+    addMovement(HELSINKI, toDate("2009-03-15"), toDate("2009-03-16")).
52
+    addMovement(HAMBURG, toDate("2009-03-20"), toDate("2009-03-22")).
53
+    build();
54 54
 
55 55
   /**
56 56
    * Voyage number 0100S (by ship)
57
-   *
57
+   * <p/>
58 58
    * Hongkong - Hangzou - Tokyo - Melbourne - New York
59 59
    */
60 60
   public static final Voyage HONGKONG_TO_NEW_YORK =
@@ -68,7 +68,7 @@ public class SampleVoyages {
68 68
 
69 69
   /**
70 70
    * Voyage number 0200T (by train)
71
-   *
71
+   * <p/>
72 72
    * New York - Chicago - Dallas
73 73
    */
74 74
   public static final Voyage NEW_YORK_TO_DALLAS =
@@ -79,7 +79,7 @@ public class SampleVoyages {
79 79
 
80 80
   /**
81 81
    * Voyage number 0300A (by airplane)
82
-   *
82
+   * <p/>
83 83
    * Dallas - Hamburg - Stockholm - Helsinki
84 84
    */
85 85
   public static final Voyage DALLAS_TO_HELSINKI =
@@ -91,7 +91,7 @@ public class SampleVoyages {
91 91
 
92 92
   /**
93 93
    * Voyage number 0301S (by ship)
94
-   *
94
+   * <p/>
95 95
    * Dallas - Hamburg - Stockholm - Helsinki, alternate route
96 96
    */
97 97
   public static final Voyage DALLAS_TO_HELSINKI_ALT =
@@ -101,9 +101,8 @@ public class SampleVoyages {
101 101
 
102 102
   /**
103 103
    * Voyage number 0400S (by ship)
104
-   *
104
+   * <p/>
105 105
    * Helsinki - Rotterdam - Shanghai - Hongkong
106
-   *
107 106
    */
108 107
   public static final Voyage HELSINKI_TO_HONGKONG =
109 108
     new Voyage.Builder(new VoyageNumber("0400S"), HELSINKI).
@@ -128,11 +127,11 @@ public class SampleVoyages {
128 127
   }
129 128
 
130 129
   public static List<Voyage> getAll() {
131
-    return new ArrayList(ALL.values());
130
+    return new ArrayList<Voyage>(ALL.values());
132 131
   }
133 132
 
134 133
   public static Voyage lookup(VoyageNumber voyageNumber) {
135 134
     return ALL.get(voyageNumber);
136 135
   }
137
-  
136
+
138 137
 }

+ 29
- 3
dddsample/src/main/java/se/citerus/dddsample/domain/model/voyage/Schedule.java Ver fichero

@@ -2,18 +2,19 @@ package se.citerus.dddsample.domain.model.voyage;
2 2
 
3 3
 import org.apache.commons.lang.Validate;
4 4
 import org.apache.commons.lang.builder.HashCodeBuilder;
5
+import se.citerus.dddsample.domain.model.location.Location;
5 6
 import se.citerus.dddsample.domain.shared.ValueObject;
6 7
 
7 8
 import java.util.Collections;
9
+import java.util.Date;
8 10
 import java.util.List;
9 11
 
10 12
 /**
11 13
  * A voyage schedule.
12
- * 
13 14
  */
14 15
 public class Schedule implements ValueObject<Schedule> {
15 16
 
16
-  private List<CarrierMovement> carrierMovements = Collections.EMPTY_LIST;
17
+  private List<CarrierMovement> carrierMovements;
17 18
 
18 19
   public static final Schedule EMPTY = new Schedule();
19 20
 
@@ -32,6 +33,32 @@ public class Schedule implements ValueObject<Schedule> {
32 33
     return Collections.unmodifiableList(carrierMovements);
33 34
   }
34 35
 
36
+  /**
37
+   * @param location location of departure
38
+   * @return Date of departure from this location, or null if it's not part of the voyage.
39
+   */
40
+  public Date departureTimeAt(final Location location) {
41
+    for (CarrierMovement movement : carrierMovements) {
42
+      if (movement.departureLocation().sameIdentityAs(location)) {
43
+        return movement.departureTime();
44
+      }
45
+    }
46
+    return null;
47
+  }
48
+
49
+  /**
50
+   * @param location location of arrival
51
+   * @return Date of arrival at this location, or null if it's not part of the voyage.
52
+   */
53
+  public Date arrivalTimeAt(final Location location) {
54
+    for (CarrierMovement movement : carrierMovements) {
55
+      if (movement.arrivalLocation().sameIdentityAs(location)) {
56
+        return movement.arrivalTime();
57
+      }
58
+    }
59
+    return null;
60
+  }
61
+
35 62
   @Override
36 63
   public boolean sameValueAs(final Schedule other) {
37 64
     return other != null && this.carrierMovements.equals(other.carrierMovements);
@@ -55,5 +82,4 @@ public class Schedule implements ValueObject<Schedule> {
55 82
   Schedule() {
56 83
     // Needed by Hibernate
57 84
   }
58
-
59 85
 }

+ 105
- 44
dddsample/src/test/java/se/citerus/dddsample/domain/model/cargo/CargoTest.java Ver fichero

@@ -11,20 +11,37 @@ import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
11 11
 import se.citerus.dddsample.domain.model.voyage.Voyage;
12 12
 import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
13 13
 
14
-import java.util.*;
14
+import java.util.ArrayList;
15
+import java.util.Arrays;
16
+import java.util.Date;
17
+import java.util.List;
15 18
 
16 19
 public class CargoTest extends TestCase {
17 20
 
18
-  private Voyage voyage;
19
-
20
-  protected void setUp() throws Exception {
21
-
22
-    voyage = new Voyage.Builder(new VoyageNumber("0123"), STOCKHOLM).
23
-      addMovement(HAMBURG, new Date(), new Date()).
24
-      addMovement(HONGKONG, new Date(), new Date()).
25
-      addMovement(MELBOURNE, new Date(), new Date()).
26
-      build();
27
-  }
21
+  private Voyage crazyVoyage = new Voyage.Builder(new VoyageNumber("0123"),
22
+    STOCKHOLM).
23
+    addMovement(HAMBURG, new Date(), new Date()).
24
+    addMovement(HONGKONG, new Date(), new Date()).
25
+    addMovement(MELBOURNE, new Date(), new Date()).
26
+    build();
27
+
28
+  private Voyage pacific = new Voyage.Builder(new VoyageNumber("4567"),
29
+    SHANGHAI).
30
+    addMovement(LONGBEACH, new Date(), new Date()).
31
+    addMovement(SEATTLE, new Date(), new Date()).
32
+    build();
33
+
34
+  private Voyage transcontinental = new Voyage.Builder(new VoyageNumber("4567"),
35
+    LONGBEACH).
36
+    addMovement(CHICAGO, new Date(), new Date()).
37
+    addMovement(NEWYORK, new Date(), new Date()).
38
+    build();
39
+
40
+  private Voyage northernRail = new Voyage.Builder(new VoyageNumber("8901"),
41
+    SEATTLE).
42
+    addMovement(CHICAGO, new Date(), new Date()).
43
+    addMovement(NEWYORK, new Date(), new Date()).
44
+    build();
28 45
 
29 46
   public void testConstruction() {
30 47
     TrackingId trackingId = new TrackingId("XYZ");
@@ -38,7 +55,7 @@ public class CargoTest extends TestCase {
38 55
     assertEquals(NOT_ROUTED, cargo.delivery().routingStatus());
39 56
     assertEquals(NOT_RECEIVED, cargo.delivery().transportStatus());
40 57
     assertEquals(Location.UNKNOWN, cargo.delivery().lastKnownLocation());
41
-    assertEquals(Voyage.NONE, cargo.delivery().currentVoyage());    
58
+    assertEquals(Voyage.NONE, cargo.delivery().currentVoyage());
42 59
   }
43 60
 
44 61
   public void testRoutingStatus() throws Exception {
@@ -55,7 +72,7 @@ public class CargoTest extends TestCase {
55 72
     cargo.specifyNewRoute(acceptOnlyGood);
56 73
 
57 74
     assertEquals(NOT_ROUTED, cargo.delivery().routingStatus());
58
-    
75
+
59 76
     cargo.assignToRoute(bad);
60 77
     assertEquals(MISROUTED, cargo.delivery().routingStatus());
61 78
 
@@ -112,7 +129,7 @@ public class CargoTest extends TestCase {
112 129
     assertFalse(cargo.delivery().isUnloadedAtDestination());
113 130
 
114 131
     // Adding an event unrelated to unloading at final destination
115
-    
132
+
116 133
     List<HandlingEvent> events = new ArrayList<HandlingEvent>();
117 134
     events.add(
118 135
       new HandlingEvent(cargo, new Date(10), new Date(), HandlingEvent.Type.RECEIVE, HANGZOU));
@@ -147,7 +164,7 @@ public class CargoTest extends TestCase {
147 164
     Cargo cargo1 = new Cargo(new TrackingId("ABC"), sharedRouteSpec);
148 165
     Cargo cargo2 = new Cargo(new TrackingId("DEF"), sharedRouteSpec);
149 166
     assertFalse(cargo1.sameIdentityAs(cargo2));
150
-    
167
+
151 168
     HandlingHistory handlingHistoryOfCargo1 = HandlingHistory.fromEvents(Arrays.asList(
152 169
       new HandlingEvent(cargo1, toDate("2009-03-10"), toDate("2009-03-12"), HandlingEvent.Type.RECEIVE, HANGZOU)
153 170
     ));
@@ -187,11 +204,11 @@ public class CargoTest extends TestCase {
187 204
     final Cargo cargo = new Cargo(new TrackingId("XYZ"), new RouteSpecification(STOCKHOLM, MELBOURNE, new Date()));
188 205
 
189 206
     List<HandlingEvent> events = new ArrayList<HandlingEvent>();
190
-    events.add(new HandlingEvent(cargo, toDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, voyage));
191
-    events.add(new HandlingEvent(cargo, toDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, voyage));
207
+    events.add(new HandlingEvent(cargo, toDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, crazyVoyage));
208
+    events.add(new HandlingEvent(cargo, toDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, crazyVoyage));
192 209
 
193
-    events.add(new HandlingEvent(cargo, toDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
194
-    events.add(new HandlingEvent(cargo, toDate("2007-12-04"), new Date(), HandlingEvent.Type.UNLOAD, HONGKONG, voyage));
210
+    events.add(new HandlingEvent(cargo, toDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, crazyVoyage));
211
+    events.add(new HandlingEvent(cargo, toDate("2007-12-04"), new Date(), HandlingEvent.Type.UNLOAD, HONGKONG, crazyVoyage));
195 212
 
196 213
     cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
197 214
     return cargo;
@@ -201,9 +218,9 @@ public class CargoTest extends TestCase {
201 218
     final Cargo cargo = new Cargo(new TrackingId("XYZ"), new RouteSpecification(STOCKHOLM, MELBOURNE, new Date()));
202 219
 
203 220
     List<HandlingEvent> events = new ArrayList<HandlingEvent>();
204
-    events.add(new HandlingEvent(cargo, toDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, voyage));
205
-    events.add(new HandlingEvent(cargo, toDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, voyage));
206
-    events.add(new HandlingEvent(cargo, toDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
221
+    events.add(new HandlingEvent(cargo, toDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, crazyVoyage));
222
+    events.add(new HandlingEvent(cargo, toDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, crazyVoyage));
223
+    events.add(new HandlingEvent(cargo, toDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, crazyVoyage));
207 224
 
208 225
     cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
209 226
     return cargo;
@@ -213,14 +230,14 @@ public class CargoTest extends TestCase {
213 230
     final Cargo cargo = new Cargo(new TrackingId("XYZ"), new RouteSpecification(STOCKHOLM, MELBOURNE, new Date()));
214 231
 
215 232
     List<HandlingEvent> events = new ArrayList<HandlingEvent>();
216
-    events.add(new HandlingEvent(cargo, toDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, voyage));
217
-    events.add(new HandlingEvent(cargo, toDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, voyage));
233
+    events.add(new HandlingEvent(cargo, toDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, crazyVoyage));
234
+    events.add(new HandlingEvent(cargo, toDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, crazyVoyage));
218 235
 
219
-    events.add(new HandlingEvent(cargo, toDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
220
-    events.add(new HandlingEvent(cargo, toDate("2007-12-04"), new Date(), HandlingEvent.Type.UNLOAD, HONGKONG, voyage));
236
+    events.add(new HandlingEvent(cargo, toDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, crazyVoyage));
237
+    events.add(new HandlingEvent(cargo, toDate("2007-12-04"), new Date(), HandlingEvent.Type.UNLOAD, HONGKONG, crazyVoyage));
221 238
 
222
-    events.add(new HandlingEvent(cargo, toDate("2007-12-05"), new Date(), HandlingEvent.Type.LOAD, HONGKONG, voyage));
223
-    events.add(new HandlingEvent(cargo, toDate("2007-12-07"), new Date(), HandlingEvent.Type.UNLOAD, MELBOURNE, voyage));
239
+    events.add(new HandlingEvent(cargo, toDate("2007-12-05"), new Date(), HandlingEvent.Type.LOAD, HONGKONG, crazyVoyage));
240
+    events.add(new HandlingEvent(cargo, toDate("2007-12-07"), new Date(), HandlingEvent.Type.UNLOAD, MELBOURNE, crazyVoyage));
224 241
 
225 242
     cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
226 243
     return cargo;
@@ -230,13 +247,13 @@ public class CargoTest extends TestCase {
230 247
     final Cargo cargo = new Cargo(new TrackingId("XYZ"), new RouteSpecification(STOCKHOLM, MELBOURNE, new Date()));
231 248
 
232 249
     List<HandlingEvent> events = new ArrayList<HandlingEvent>();
233
-    events.add(new HandlingEvent(cargo, toDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, voyage));
234
-    events.add(new HandlingEvent(cargo, toDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, voyage));
250
+    events.add(new HandlingEvent(cargo, toDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, crazyVoyage));
251
+    events.add(new HandlingEvent(cargo, toDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, crazyVoyage));
235 252
 
236
-    events.add(new HandlingEvent(cargo, toDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
237
-    events.add(new HandlingEvent(cargo, toDate("2007-12-04"), new Date(), HandlingEvent.Type.UNLOAD, HONGKONG, voyage));
253
+    events.add(new HandlingEvent(cargo, toDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, crazyVoyage));
254
+    events.add(new HandlingEvent(cargo, toDate("2007-12-04"), new Date(), HandlingEvent.Type.UNLOAD, HONGKONG, crazyVoyage));
238 255
 
239
-    events.add(new HandlingEvent(cargo, toDate("2007-12-05"), new Date(), HandlingEvent.Type.LOAD, HONGKONG, voyage));
256
+    events.add(new HandlingEvent(cargo, toDate("2007-12-05"), new Date(), HandlingEvent.Type.LOAD, HONGKONG, crazyVoyage));
240 257
 
241 258
     cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
242 259
     return cargo;
@@ -255,10 +272,10 @@ public class CargoTest extends TestCase {
255 272
     //Happy path
256 273
     List<HandlingEvent> events = new ArrayList<HandlingEvent>();
257 274
     events.add(new HandlingEvent(cargo, new Date(10), new Date(20), HandlingEvent.Type.RECEIVE, SHANGHAI));
258
-    events.add(new HandlingEvent(cargo, new Date(30), new Date(40), HandlingEvent.Type.LOAD, SHANGHAI, voyage));
259
-    events.add(new HandlingEvent(cargo, new Date(50), new Date(60), HandlingEvent.Type.UNLOAD, ROTTERDAM, voyage));
260
-    events.add(new HandlingEvent(cargo, new Date(70), new Date(80), HandlingEvent.Type.LOAD, ROTTERDAM, voyage));
261
-    events.add(new HandlingEvent(cargo, new Date(90), new Date(100), HandlingEvent.Type.UNLOAD, GOTHENBURG, voyage));
275
+    events.add(new HandlingEvent(cargo, new Date(30), new Date(40), HandlingEvent.Type.LOAD, SHANGHAI, crazyVoyage));
276
+    events.add(new HandlingEvent(cargo, new Date(50), new Date(60), HandlingEvent.Type.UNLOAD, ROTTERDAM, crazyVoyage));
277
+    events.add(new HandlingEvent(cargo, new Date(70), new Date(80), HandlingEvent.Type.LOAD, ROTTERDAM, crazyVoyage));
278
+    events.add(new HandlingEvent(cargo, new Date(90), new Date(100), HandlingEvent.Type.UNLOAD, GOTHENBURG, crazyVoyage));
262 279
     events.add(new HandlingEvent(cargo, new Date(110), new Date(120), HandlingEvent.Type.CLAIM, GOTHENBURG));
263 280
     events.add(new HandlingEvent(cargo, new Date(130), new Date(140), HandlingEvent.Type.CUSTOMS, GOTHENBURG));
264 281
 
@@ -277,9 +294,9 @@ public class CargoTest extends TestCase {
277 294
     cargo = setUpCargoWithItinerary(SHANGHAI, ROTTERDAM, GOTHENBURG);
278 295
 
279 296
     events.add(new HandlingEvent(cargo, new Date(10), new Date(20), HandlingEvent.Type.RECEIVE, SHANGHAI));
280
-    events.add(new HandlingEvent(cargo, new Date(30), new Date(40), HandlingEvent.Type.LOAD, SHANGHAI, voyage));
281
-    events.add(new HandlingEvent(cargo, new Date(50), new Date(60), HandlingEvent.Type.UNLOAD, ROTTERDAM, voyage));
282
-    events.add(new HandlingEvent(cargo, new Date(70), new Date(80), HandlingEvent.Type.LOAD, ROTTERDAM, voyage));
297
+    events.add(new HandlingEvent(cargo, new Date(30), new Date(40), HandlingEvent.Type.LOAD, SHANGHAI, crazyVoyage));
298
+    events.add(new HandlingEvent(cargo, new Date(50), new Date(60), HandlingEvent.Type.UNLOAD, ROTTERDAM, crazyVoyage));
299
+    events.add(new HandlingEvent(cargo, new Date(70), new Date(80), HandlingEvent.Type.LOAD, ROTTERDAM, crazyVoyage));
283 300
 
284 301
     cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
285 302
 
@@ -289,8 +306,8 @@ public class CargoTest extends TestCase {
289 306
     cargo = setUpCargoWithItinerary(SHANGHAI, ROTTERDAM, GOTHENBURG);
290 307
 
291 308
     events.add(new HandlingEvent(cargo, new Date(10), new Date(20), HandlingEvent.Type.RECEIVE, SHANGHAI));
292
-    events.add(new HandlingEvent(cargo, new Date(30), new Date(40), HandlingEvent.Type.LOAD, SHANGHAI, voyage));
293
-    events.add(new HandlingEvent(cargo, new Date(50), new Date(60), HandlingEvent.Type.UNLOAD, ROTTERDAM, voyage));
309
+    events.add(new HandlingEvent(cargo, new Date(30), new Date(40), HandlingEvent.Type.LOAD, SHANGHAI, crazyVoyage));
310
+    events.add(new HandlingEvent(cargo, new Date(50), new Date(60), HandlingEvent.Type.UNLOAD, ROTTERDAM, crazyVoyage));
294 311
     events.add(new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.CLAIM, ROTTERDAM));
295 312
 
296 313
     cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
@@ -298,13 +315,57 @@ public class CargoTest extends TestCase {
298 315
     assertTrue(cargo.delivery().isMisdirected());
299 316
   }
300 317
 
318
+  public void testCustomsClearancePoint() {
319
+    //cargo destination NYC
320
+    final Cargo cargo = new Cargo(new TrackingId("XYZ"),
321
+      new RouteSpecification(SHANGHAI, NEWYORK, new Date()));
322
+
323
+    //SHA-LGB-NYC
324
+    cargo.assignToRoute(new Itinerary(
325
+      Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH),
326
+      Leg.deriveLeg(transcontinental, LONGBEACH, NEWYORK)));
327
+    assertEquals(LONGBEACH, cargo.customsClearancePoint());
328
+
329
+    //SHA-SEA-NYC
330
+    cargo.assignToRoute(new Itinerary(
331
+      Leg.deriveLeg(pacific, SHANGHAI, SEATTLE),
332
+      Leg.deriveLeg(northernRail, SEATTLE, NEWYORK)));
333
+    assertEquals(SEATTLE, cargo.customsClearancePoint());
334
+
335
+    //cargo destination LGB
336
+    //SHA-LGB
337
+    cargo.specifyNewRoute(new RouteSpecification(SHANGHAI, LONGBEACH, new Date()));
338
+    cargo.assignToRoute(new Itinerary(
339
+      Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH)));
340
+    assertEquals(LONGBEACH, cargo.customsClearancePoint());
341
+
342
+    //Cargo destination HAMBURG
343
+    //SHA-LGB-NYC This itinerary does not take
344
+    // the cargo into its CustomsZone, so no clearancePoint.
345
+    cargo.specifyNewRoute(new RouteSpecification(SHANGHAI, HAMBURG, new Date()));
346
+    cargo.assignToRoute(new Itinerary(
347
+      Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH),
348
+      Leg.deriveLeg(transcontinental, LONGBEACH, NEWYORK)));
349
+    assertNull(cargo.customsClearancePoint());
350
+
351
+    //Cargo destination NEWYORK on SHA-LGB-CHI
352
+    //This itinerary does not take the cargo to its destination,
353
+    //but it does enter the CustomsZone, so it has a clearancePoint.
354
+    cargo.specifyNewRoute(new RouteSpecification(SHANGHAI, NEWYORK, new Date()));
355
+    cargo.assignToRoute(new Itinerary(
356
+      Leg.deriveLeg(pacific, SHANGHAI, LONGBEACH),
357
+      Leg.deriveLeg(transcontinental, LONGBEACH, CHICAGO)));
358
+    assertEquals(LONGBEACH, cargo.customsClearancePoint());
359
+
360
+  }
361
+
301 362
   private Cargo setUpCargoWithItinerary(Location origin, Location midpoint, Location destination) {
302 363
     Cargo cargo = new Cargo(new TrackingId("CARGO1"), new RouteSpecification(origin, destination, new Date()));
303 364
 
304 365
     Itinerary itinerary = new Itinerary(
305 366
       Arrays.asList(
306
-        new Leg(voyage, origin, midpoint, new Date(), new Date()),
307
-        new Leg(voyage, midpoint, destination, new Date(), new Date())
367
+        new Leg(crazyVoyage, origin, midpoint, new Date(), new Date()),
368
+        new Leg(crazyVoyage, midpoint, destination, new Date(), new Date())
308 369
       )
309 370
     );
310 371
 

+ 21
- 0
dddsample/src/test/java/se/citerus/dddsample/domain/model/cargo/CustomsZoneTest.java Ver fichero

@@ -0,0 +1,21 @@
1
+package se.citerus.dddsample.domain.model.cargo;
2
+
3
+import junit.framework.TestCase;
4
+import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
5
+
6
+public class CustomsZoneTest extends TestCase {
7
+
8
+  public void testIncludes() {
9
+    assertTrue(US.includes(DALLAS));
10
+    assertFalse(EU.includes(NEWYORK));
11
+  }
12
+
13
+  public void testEntryPoint() {
14
+    assertEquals(LONGBEACH, US.entryPoint(SHANGHAI, LONGBEACH, CHICAGO));
15
+  }
16
+
17
+  public void testClearancePoint() {
18
+    assertEquals(LONGBEACH, US.entryPoint(SHANGHAI, LONGBEACH, CHICAGO));
19
+  }
20
+
21
+}

+ 44
- 32
dddsample/src/test/java/se/citerus/dddsample/domain/model/cargo/ItineraryTest.java Ver fichero

@@ -2,6 +2,7 @@ package se.citerus.dddsample.domain.model.cargo;
2 2
 
3 3
 import junit.framework.TestCase;
4 4
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
5
+import static se.citerus.dddsample.domain.model.handling.HandlingEvent.Type.*;
5 6
 import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
6 7
 import se.citerus.dddsample.domain.model.voyage.Voyage;
7 8
 import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
@@ -13,10 +14,19 @@ import java.util.List;
13 14
 
14 15
 public class ItineraryTest extends TestCase {
15 16
 
16
-
17
-  Voyage voyage, wrongVoyage;
17
+  Voyage voyage, wrongVoyage, pacific, transcontinental;
18 18
 
19 19
   protected void setUp() throws Exception {
20
+
21
+    pacific = new Voyage.Builder(new VoyageNumber("4567"), SHANGHAI).
22
+      addMovement(LONGBEACH, new Date(), new Date()).
23
+      build();
24
+
25
+    transcontinental = new Voyage.Builder(new VoyageNumber("4567"), LONGBEACH).
26
+      addMovement(CHICAGO, new Date(), new Date()).
27
+      addMovement(NEWYORK, new Date(), new Date()).
28
+      build();
29
+
20 30
     voyage = new Voyage.Builder(new VoyageNumber("0123"), SHANGHAI).
21 31
       addMovement(ROTTERDAM, new Date(), new Date()).
22 32
       addMovement(GOTHENBURG, new Date(), new Date()).
@@ -28,7 +38,7 @@ public class ItineraryTest extends TestCase {
28 38
       build();
29 39
   }
30 40
 
31
-  public void testCargoOnTrack() {
41
+  public void testIfCargoIsOnTrack() {
32 42
 
33 43
     TrackingId trackingId = new TrackingId("CARGO1");
34 44
     RouteSpecification routeSpecification = new RouteSpecification(SHANGHAI, GOTHENBURG, new Date());
@@ -41,51 +51,53 @@ public class ItineraryTest extends TestCase {
41 51
       )
42 52
     );
43 53
 
54
+    // HandlingEvent.Load(cargo, RECEIVE, SHANGHAI, toDate("2009-05-03"))
44 55
     //Happy path
45
-    HandlingEvent event = new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.RECEIVE, SHANGHAI);
46
-    assertTrue(itinerary.isExpected(event));
56
+    HandlingEvent receiveShanghai = new HandlingEvent(cargo, new Date(), new Date(), RECEIVE, SHANGHAI);
57
+    assertTrue(itinerary.isExpected(receiveShanghai));
47 58
 
48
-    event = new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.LOAD, SHANGHAI, voyage);
49
-    assertTrue(itinerary.isExpected(event));
59
+    HandlingEvent loadShanghai = new HandlingEvent(cargo, new Date(), new Date(), LOAD, SHANGHAI, voyage);
60
+    assertTrue(itinerary.isExpected(loadShanghai));
50 61
 
51
-    event = new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.UNLOAD, ROTTERDAM, voyage);
52
-    assertTrue(itinerary.isExpected(event));
62
+    HandlingEvent unloadRotterdam = new HandlingEvent(cargo, new Date(), new Date(), UNLOAD, ROTTERDAM, voyage);
63
+    assertTrue(itinerary.isExpected(unloadRotterdam));
53 64
 
54
-    event = new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.LOAD, ROTTERDAM, voyage);
55
-    assertTrue(itinerary.isExpected(event));
65
+    HandlingEvent loadRotterdam = new HandlingEvent(cargo, new Date(), new Date(), LOAD, ROTTERDAM, voyage);
66
+    assertTrue(itinerary.isExpected(loadRotterdam));
56 67
 
57
-    event = new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.UNLOAD, GOTHENBURG, voyage);
58
-    assertTrue(itinerary.isExpected(event));
68
+    HandlingEvent unloadGothenburg = new HandlingEvent(cargo, new Date(), new Date(), UNLOAD, GOTHENBURG, voyage);
69
+    assertTrue(itinerary.isExpected(unloadGothenburg));
59 70
 
60
-    event = new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.CLAIM, GOTHENBURG);
61
-    assertTrue(itinerary.isExpected(event));
71
+    HandlingEvent claimGothenburg = new HandlingEvent(cargo, new Date(), new Date(), CLAIM, GOTHENBURG);
72
+    assertTrue(itinerary.isExpected(claimGothenburg));
62 73
 
63
-    //Customs event changes nothing
64
-    event = new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.CUSTOMS, GOTHENBURG);
65
-    assertTrue(itinerary.isExpected(event));
74
+    //TODO Customs event can only be interpreted properly by knowing the destination of the cargo.
75
+    // This can be inferred from the Itinerary, but it isn't definitive. So, do we answer based on
76
+    // the end of the itinerary (even though this would probably not be used in the app) or do we
77
+    // ignore this at itinerary level somehow and leave it purely as a cargo responsibility.
78
+    // (See customsClearancePoint tests in CargoTest)
79
+//    HandlingEvent customsGothenburg = new HandlingEvent(cargo, new Date(), new Date(), CUSTOMS, GOTHENBURG);
80
+//    assertTrue(itinerary.isExpected(customsGothenburg));
66 81
 
67 82
     //Received at the wrong location
68
-    event = new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.RECEIVE, HANGZOU);
69
-    assertFalse(itinerary.isExpected(event));
83
+    HandlingEvent receiveHangzou = new HandlingEvent(cargo, new Date(), new Date(), RECEIVE, HANGZOU);
84
+    assertFalse(itinerary.isExpected(receiveHangzou));
70 85
 
71 86
     //Loaded to onto the wrong ship, correct location
72
-    event = new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.LOAD, ROTTERDAM, wrongVoyage);
73
-    assertFalse(itinerary.isExpected(event));
87
+    HandlingEvent loadRotterdam666 = new HandlingEvent(cargo, new Date(), new Date(), LOAD, ROTTERDAM, wrongVoyage);
88
+    assertFalse(itinerary.isExpected(loadRotterdam666));
74 89
 
75 90
     //Unloaded from the wrong ship in the wrong location
76
-    event = new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.UNLOAD, HELSINKI, wrongVoyage);
77
-    assertFalse(itinerary.isExpected(event));
91
+    HandlingEvent unloadHelsinki = new HandlingEvent(cargo, new Date(), new Date(), UNLOAD, HELSINKI, wrongVoyage);
92
+    assertFalse(itinerary.isExpected(unloadHelsinki));
78 93
 
79
-    event = new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.CLAIM, ROTTERDAM);
80
-    assertFalse(itinerary.isExpected(event));
94
+    HandlingEvent claimRotterdam = new HandlingEvent(cargo, new Date(), new Date(), CLAIM, ROTTERDAM);
95
+    assertFalse(itinerary.isExpected(claimRotterdam));
81 96
 
82 97
     //Unrouted Cargo shouldn't go anywhere or do anything
83
-    event = new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.RECEIVE, SHANGHAI);
84
-    assertFalse(Itinerary.EMPTY_ITINERARY.isExpected(event));
98
+    assertFalse(Itinerary.EMPTY_ITINERARY.isExpected(receiveShanghai));
99
+    assertFalse(Itinerary.EMPTY_ITINERARY.isExpected(loadShanghai));
85 100
 
86
-    event = new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.LOAD, SHANGHAI, voyage);
87
-    assertFalse(Itinerary.EMPTY_ITINERARY.isExpected(event));
88
-    
89 101
   }
90 102
 
91 103
   public void testCreateItinerary() throws Exception {
@@ -105,4 +117,4 @@ public class ItineraryTest extends TestCase {
105 117
     }
106 118
   }
107 119
 
108
-}
120
+}

+ 6
- 6
dddsample/src/test/java/se/citerus/dddsample/domain/model/cargo/LegTest.java Ver fichero

@@ -13,12 +13,12 @@ public class LegTest {
13 13
 
14 14
   @Test(expected = IllegalArgumentException.class)
15 15
   public void testConstructor() throws Exception {
16
-    new Leg(null, null, null);
16
+    Leg.deriveLeg(null, null, null);
17 17
   }
18 18
 
19 19
   @Test
20 20
   public void legThatFollowsPartOfAVoyage() {
21
-    Leg chicagoToDallas = new Leg(voyage, CHICAGO, DALLAS);
21
+    Leg chicagoToDallas = Leg.deriveLeg(voyage, CHICAGO, DALLAS);
22 22
 
23 23
     assertEquals(chicagoToDallas.loadTime(), toDate("2008-10-24", "21:25"));
24 24
     assertEquals(chicagoToDallas.loadLocation(), CHICAGO);
@@ -28,7 +28,7 @@ public class LegTest {
28 28
 
29 29
   @Test
30 30
   public void legThatFollowsAnEntireVoyage() {
31
-    Leg chicagoToDallas = new Leg(voyage, NEWYORK, DALLAS);
31
+    Leg chicagoToDallas = Leg.deriveLeg(voyage, NEWYORK, DALLAS);
32 32
 
33 33
     assertEquals(chicagoToDallas.loadTime(), toDate("2008-10-24", "07:00"));
34 34
     assertEquals(chicagoToDallas.loadLocation(), NEWYORK);
@@ -38,17 +38,17 @@ public class LegTest {
38 38
 
39 39
   @Test(expected = IllegalArgumentException.class)
40 40
   public void locationsInWrongOrder() {
41
-    new Leg(voyage, DALLAS, CHICAGO);
41
+    Leg.deriveLeg(voyage, DALLAS, CHICAGO);
42 42
   }
43 43
 
44 44
   @Test(expected = IllegalArgumentException.class)
45 45
   public void endLocationNotOnVoyage() {
46
-    new Leg(voyage, CHICAGO, HELSINKI);
46
+    Leg.deriveLeg(voyage, CHICAGO, HELSINKI);
47 47
   }
48 48
 
49 49
   @Test(expected = IllegalArgumentException.class)
50 50
   public void startLocationNotOnVoyage() {
51
-    new Leg(voyage, HONGKONG, DALLAS);
51
+    Leg.deriveLeg(voyage, HONGKONG, DALLAS);
52 52
   }
53 53
 
54 54
 }

+ 12
- 3
dddsample/src/test/java/se/citerus/dddsample/domain/model/handling/HandlingHistoryTest.java Ver fichero

@@ -26,7 +26,7 @@ public class HandlingHistoryTest extends TestCase {
26 26
   protected void setUp() throws Exception {
27 27
     cargo = new Cargo(new TrackingId("ABC"), new RouteSpecification(SHANGHAI, DALLAS, toDate("2009-04-01")));
28 28
     cargo2 = new Cargo(new TrackingId("DEF"), new RouteSpecification(SHANGHAI, NEWYORK, toDate("2009-04-15")));
29
-    
29
+
30 30
     voyage = new Voyage.Builder(new VoyageNumber("X25"), HONGKONG).
31 31
       addMovement(SHANGHAI, new Date(), new Date()).
32 32
       addMovement(DALLAS, new Date(), new Date()).
@@ -39,16 +39,25 @@ public class HandlingHistoryTest extends TestCase {
39 39
 
40 40
   public void testDistinctEventsByCompletionTime() {
41 41
     handlingHistory = HandlingHistory.fromEvents(asList(event2, event1, event1duplicate));
42
-    
42
+
43 43
     assertEquals(asList(event1, event2), handlingHistory.distinctEventsByCompletionTime());
44 44
   }
45 45
 
46 46
   public void testMostRecentlyCompletedEvent() {
47 47
     handlingHistory = HandlingHistory.fromEvents(asList(event2, event1, event1duplicate));
48
-    
48
+
49 49
     assertEquals(event2, handlingHistory.mostRecentlyCompletedEvent());
50 50
   }
51 51
 
52
+  public void testMostRecentLoadOrUnload() {
53
+    // TODO
54
+    HandlingEvent event3Customs = new HandlingEvent(cargo, toDate("2009-03-11"), toDate("2009-03-11"), HandlingEvent.Type.CUSTOMS, DALLAS);
55
+    handlingHistory = HandlingHistory.fromEvents(asList(event2, event1, event1duplicate, event3Customs));
56
+
57
+    assertEquals(event3Customs, handlingHistory.mostRecentlyCompletedEvent());
58
+    assertEquals(event2, handlingHistory.mostRecentPhysicalHandling());
59
+  }
60
+
52 61
   public void testUniqueCargoOfEvents() {
53 62
     try {
54 63
       handlingHistory = HandlingHistory.fromEvents(asList(event1, event2, eventOfCargo2));

+ 13
- 12
dddsample/src/test/java/se/citerus/dddsample/domain/model/location/LocationTest.java Ver fichero

@@ -1,24 +1,24 @@
1 1
 package se.citerus.dddsample.domain.model.location;
2 2
 
3
-import java.util.TimeZone;
4
-
5 3
 import junit.framework.TestCase;
6 4
 
5
+import java.util.TimeZone;
6
+
7 7
 public class LocationTest extends TestCase {
8
-	private static final TimeZone CET = TimeZone.getTimeZone("Europe/Amsterdam"); 
9
-	
8
+  private static final TimeZone CET = TimeZone.getTimeZone("Europe/Amsterdam");
9
+
10 10
   public void testEquals() {
11
-	  
11
+
12 12
     // Same UN locode - equal
13
-    assertTrue(new Location(new UnLocode("ATEST"),"test-name", CET).
14
-        equals(new Location(new UnLocode("ATEST"),"test-name", CET)));
13
+    assertTrue(new Location(new UnLocode("ATEST"), "test-name", CET, null).
14
+      equals(new Location(new UnLocode("ATEST"), "test-name", CET, null)));
15 15
 
16 16
     // Different UN locodes - not equal
17
-    assertFalse(new Location(new UnLocode("ATEST"),"test-name", CET).
18
-         equals(new Location(new UnLocode("TESTB"), "test-name", CET)));
17
+    assertFalse(new Location(new UnLocode("ATEST"), "test-name", CET, null).
18
+      equals(new Location(new UnLocode("TESTB"), "test-name", CET, null)));
19 19
 
20 20
     // Always equal to itself
21
-    Location location = new Location(new UnLocode("ATEST"),"test-name", CET);
21
+    Location location = new Location(new UnLocode("ATEST"), "test-name", CET, null);
22 22
     assertTrue(location.equals(location));
23 23
 
24 24
     // Never equal to null
@@ -28,9 +28,10 @@ public class LocationTest extends TestCase {
28 28
     assertTrue(Location.UNKNOWN.equals(Location.UNKNOWN));
29 29
 
30 30
     try {
31
-      new Location(null, null, null);
31
+      new Location(null, null, null, null);
32 32
       fail("Should not allow any null constructor arguments");
33
-    } catch (IllegalArgumentException expected) {}
33
+    } catch (IllegalArgumentException expected) {
34
+    }
34 35
   }
35 36
 
36 37
 }

+ 10
- 31
dddsample/src/test/java/se/citerus/dddsample/domain/model/voyage/VoyageTest.java Ver fichero

@@ -1,40 +1,19 @@
1 1
 package se.citerus.dddsample.domain.model.voyage;
2 2
 
3 3
 import junit.framework.TestCase;
4
+import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
4 5
 
5
-public class VoyageTest extends TestCase {
6
-
7
-    public void testVoyageNumber() throws Exception {
8
-        //TODO: Test goes here...
9
-    }
10
-
11
-    public void testSchedule() throws Exception {
12
-        //TODO: Test goes here...
13
-    }
14
-
15
-    public void testHashCode() throws Exception {
16
-        //TODO: Test goes here...
17
-    }
6
+import java.util.Date;
18 7
 
19
-    public void testEquals() throws Exception {
20
-        //TODO: Test goes here...
21
-    }
22
-
23
-    public void testSameIdentityAs() throws Exception {
24
-        //TODO: Test goes here...
25
-    }
26
-
27
-    public void testToString() throws Exception {
28
-        //TODO: Test goes here...
29
-    }
30
-
31
-    public void testAddMovement() throws Exception {
32
-        //TODO: Test goes here...
33
-    }
8
+public class VoyageTest extends TestCase {
34 9
 
35
-    public void testBuild() throws Exception {
36
-        //TODO: Test goes here...
37
-    }
38 10
 
11
+  public void testSchedule() {
12
+    Voyage transcontinental = new Voyage.Builder(new VoyageNumber("4567"),
13
+      LONGBEACH).
14
+      addMovement(CHICAGO, new Date(), new Date()).
15
+      addMovement(NEWYORK, new Date(), new Date()).
16
+      build();
17
+  }
39 18
 
40 19
 }

+ 10
- 10
dddsample/src/test/java/se/citerus/dddsample/scenario/CargoLifecycleScenarioTest.java Ver fichero

@@ -45,7 +45,7 @@ public class CargoLifecycleScenarioTest {
45 45
   /**
46 46
    * This is a domain service interface, whose implementation
47 47
    * is part of the infrastructure layer (remote call to external system).
48
-   *
48
+   * <p/>
49 49
    * It is stubbed in this test.
50 50
    */
51 51
   RoutingService routingService = new ScenarioStubRoutingService();
@@ -60,7 +60,7 @@ public class CargoLifecycleScenarioTest {
60 60
     // Route: Hongkong - Long Beach - New York - Stockholm
61 61
     routeCargoFromHongkongToStockholm();
62 62
     checkDeliveryAfterRouting();
63
-                                
63
+
64 64
 
65 65
     receiveInHongkong();
66 66
     checkDeliveryAfterReceiveInHongkong();
@@ -88,28 +88,28 @@ public class CargoLifecycleScenarioTest {
88 88
   }
89 89
 
90 90
   private void unloadInLongBeach() throws CannotCreateHandlingEventException {
91
-    createHandlingEventAndUpdateAggregates(toDate("2009-03-06"), v100, LONG_BEACH, UNLOAD);
91
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-06"), v100, LONGBEACH, UNLOAD);
92 92
   }
93 93
 
94 94
   private void checkDeliveryAfterUnloadInLongBeach() {
95 95
     Cargo cargo = cargoRepository.find(trackingId);
96 96
 
97 97
     assertThat(cargo.delivery().currentVoyage(), is(NONE));
98
-    assertThat(cargo.delivery().lastKnownLocation(), is(LONG_BEACH));
98
+    assertThat(cargo.delivery().lastKnownLocation(), is(LONGBEACH));
99 99
     assertThat(cargo.delivery().transportStatus(), is(IN_PORT));
100
-    assertThat(cargo.delivery().nextExpectedActivity(), is(new HandlingActivity(LOAD, LONG_BEACH, v250)));
100
+    assertThat(cargo.delivery().nextExpectedActivity(), is(new HandlingActivity(LOAD, LONGBEACH, v250)));
101 101
     assertFalse(cargo.delivery().isMisdirected());
102 102
   }
103 103
 
104 104
   private void loadInLongBeach() throws CannotCreateHandlingEventException {
105
-    createHandlingEventAndUpdateAggregates(toDate("2009-03-07"), v250, LONG_BEACH, LOAD);
105
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-07"), v250, LONGBEACH, LOAD);
106 106
   }
107 107
 
108 108
   private void checkDeliveryAfterLoadInLongBeach() {
109 109
     Cargo cargo = cargoRepository.find(trackingId);
110 110
 
111 111
     assertThat(cargo.delivery().currentVoyage(), is(v250));
112
-    assertThat(cargo.delivery().lastKnownLocation(), is(LONG_BEACH));
112
+    assertThat(cargo.delivery().lastKnownLocation(), is(LONGBEACH));
113 113
     assertThat(cargo.delivery().transportStatus(), is(ONBOARD_CARRIER));
114 114
     assertThat(cargo.delivery().nextExpectedActivity(), is(new HandlingActivity(UNLOAD, NEWYORK, v250)));
115 115
     assertFalse(cargo.delivery().isMisdirected());
@@ -244,7 +244,7 @@ public class CargoLifecycleScenarioTest {
244 244
     assertThat(cargo.delivery().currentVoyage(), is(v100));
245 245
     assertThat(cargo.delivery().lastKnownLocation(), is(HONGKONG));
246 246
     assertThat(cargo.delivery().transportStatus(), is(ONBOARD_CARRIER));
247
-    assertThat(cargo.delivery().nextExpectedActivity(), is(new HandlingActivity(UNLOAD, LONG_BEACH, v100)));
247
+    assertThat(cargo.delivery().nextExpectedActivity(), is(new HandlingActivity(UNLOAD, LONGBEACH, v100)));
248 248
     assertFalse(cargo.delivery().isMisdirected());
249 249
   }
250 250
 
@@ -395,8 +395,8 @@ public class CargoLifecycleScenarioTest {
395 395
         // Hongkong - NYC - Chicago - Stockholm, initial routing
396 396
         return Arrays.asList(
397 397
           new Itinerary(Arrays.asList(
398
-            new Leg(v100, HONGKONG, LONG_BEACH, toDate("2009-03-03"), toDate("2009-03-09")),
399
-            new Leg(v250, LONG_BEACH, NEWYORK, toDate("2009-03-10"), toDate("2009-03-14")),
398
+            new Leg(v100, HONGKONG, LONGBEACH, toDate("2009-03-03"), toDate("2009-03-09")),
399
+            new Leg(v250, LONGBEACH, NEWYORK, toDate("2009-03-10"), toDate("2009-03-14")),
400 400
             new Leg(v200, NEWYORK, STOCKHOLM, toDate("2009-03-07"), toDate("2009-03-11"))
401 401
           ))
402 402
         );

+ 5
- 6
dddsample/src/test/java/se/citerus/dddsample/scenario/VoyageRescheduledScenarioTest.java Ver fichero

@@ -15,7 +15,6 @@ import static se.citerus.dddsample.domain.model.voyage.SampleVoyages.*;
15 15
 import se.citerus.dddsample.domain.model.voyage.Voyage;
16 16
 import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
17 17
 
18
-import static java.util.Arrays.asList;
19 18
 import java.util.Date;
20 19
 
21 20
 public class VoyageRescheduledScenarioTest extends TestCase {
@@ -26,11 +25,11 @@ public class VoyageRescheduledScenarioTest extends TestCase {
26 25
     Voyage voyage2 = new Voyage(new VoyageNumber("V2"), NEW_YORK_TO_DALLAS.schedule());
27 26
     Voyage voyage3 = new Voyage(new VoyageNumber("V3"), DALLAS_TO_HELSINKI.schedule());
28 27
 
29
-    Itinerary itinerary = new Itinerary(asList(
30
-      new Leg(voyage1, HANGZOU, NEWYORK),
31
-      new Leg(voyage2, NEWYORK, DALLAS),
32
-      new Leg(voyage3, DALLAS, STOCKHOLM)
33
-    ));
28
+    Itinerary itinerary = new Itinerary(
29
+      Leg.deriveLeg(voyage1, HANGZOU, NEWYORK),
30
+      Leg.deriveLeg(voyage2, NEWYORK, DALLAS),
31
+      Leg.deriveLeg(voyage3, DALLAS, STOCKHOLM)
32
+    );
34 33
 
35 34
     Date oldDepartureTime = toDate("2008-10-24", "07:00");
36 35