Explorar el Código

Added assertions to HandlingHistory that ensures that it refers to a unique cargo, and that a Cargo can't derive its delivery progress from a handling history of another cargo. There are also two descriptive static factory methods for the different possibilities of creating a HandlingHistory.

HandlingEventRepository now looks up handling events from Cargo instead of TrackingId.

Fixed a problem with Hibernate lazy proxies not being initialised when reading a field.
peter_backlund hace 17 años
padre
commit
8929b0e240
Se han modificado 16 ficheros con 187 adiciones y 108 borrados
  1. 1
    1
      dddsample/src/main/java/se/citerus/dddsample/application/impl/CargoInspectionServiceImpl.java
  2. 2
    3
      dddsample/src/main/java/se/citerus/dddsample/application/util/SampleDataGenerator.java
  3. 7
    5
      dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Cargo.java
  4. 3
    3
      dddsample/src/main/java/se/citerus/dddsample/domain/model/handling/HandlingEventRepository.java
  5. 42
    5
      dddsample/src/main/java/se/citerus/dddsample/domain/model/handling/HandlingHistory.java
  6. 14
    7
      dddsample/src/main/java/se/citerus/dddsample/infrastructure/persistence/hibernate/HandlingEventRepositoryHibernate.java
  7. 1
    1
      dddsample/src/main/java/se/citerus/dddsample/interfaces/tracking/CargoTrackingController.java
  8. 56
    50
      dddsample/src/test/java/se/citerus/dddsample/domain/model/cargo/CargoTest.java
  9. 29
    6
      dddsample/src/test/java/se/citerus/dddsample/domain/model/handling/HandlingHistoryTest.java
  10. 7
    4
      dddsample/src/test/java/se/citerus/dddsample/infrastructure/messaging/stub/SynchronousApplicationEventsStub.java
  11. 1
    1
      dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/hibernate/CargoRepositoryTest.java
  12. 2
    2
      dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/hibernate/HandlingEventRepositoryTest.java
  13. 6
    11
      dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/inmemory/CargoRepositoryInMem.java
  14. 13
    6
      dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/inmemory/HandlingEventRepositoryInMem.java
  15. 2
    2
      dddsample/src/test/java/se/citerus/dddsample/infrastructure/routing/ExternalRoutingServiceTest.java
  16. 1
    1
      dddsample/src/test/java/se/citerus/dddsample/interfaces/tracking/CargoTrackingViewAdapterTest.java

+ 1
- 1
dddsample/src/main/java/se/citerus/dddsample/application/impl/CargoInspectionServiceImpl.java Ver fichero

@@ -38,7 +38,7 @@ public class CargoInspectionServiceImpl implements CargoInspectionService {
38 38
       return;
39 39
     }
40 40
 
41
-    final HandlingHistory handlingHistory = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId);
41
+    final HandlingHistory handlingHistory = handlingEventRepository.lookupHandlingHistoryOfCargo(cargo);
42 42
 
43 43
     cargo.deriveDeliveryProgress(handlingHistory);
44 44
 

+ 2
- 3
dddsample/src/main/java/se/citerus/dddsample/application/util/SampleDataGenerator.java Ver fichero

@@ -202,7 +202,6 @@ public class SampleDataGenerator implements ServletContextListener {
202 202
   }
203 203
 
204 204
   public static void loadHibernateData(TransactionTemplate tt, final SessionFactory sf, final HandlingEventFactory handlingEventFactory, final HandlingEventRepository handlingEventRepository) {
205
-    System.out.println("*** Loading Hibernate data ***");
206 205
     tt.execute(new TransactionCallbackWithoutResult() {
207 206
       @Override
208 207
       protected void doInTransactionWithoutResult(TransactionStatus status) {
@@ -250,7 +249,7 @@ public class SampleDataGenerator implements ServletContextListener {
250 249
           throw new RuntimeException(e);
251 250
         }
252 251
 
253
-        HandlingHistory handlingHistory = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId);
252
+        HandlingHistory handlingHistory = handlingEventRepository.lookupHandlingHistoryOfCargo(abc123);
254 253
         abc123.deriveDeliveryProgress(handlingHistory);
255 254
 
256 255
         session.update(abc123);
@@ -295,7 +294,7 @@ public class SampleDataGenerator implements ServletContextListener {
295 294
           throw new RuntimeException(e);
296 295
         }
297 296
 
298
-        HandlingHistory handlingHistory1 = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId1);
297
+        HandlingHistory handlingHistory1 = handlingEventRepository.lookupHandlingHistoryOfCargo(jkl567);
299 298
         jkl567.deriveDeliveryProgress(handlingHistory1);
300 299
 
301 300
         session.update(jkl567);

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

@@ -62,7 +62,7 @@ public class Cargo implements Entity<Cargo> {
62 62
     this.routeSpecification = routeSpecification;
63 63
 
64 64
     this.delivery = Delivery.derivedFrom(
65
-      this.routeSpecification, this.itinerary, HandlingHistory.EMPTY
65
+      this.routeSpecification, this.itinerary, HandlingHistory.emptyForCargo(this)
66 66
     );
67 67
   }
68 68
 
@@ -145,7 +145,9 @@ public class Cargo implements Entity<Cargo> {
145 145
    * @param handlingHistory handling history
146 146
    */
147 147
   public void deriveDeliveryProgress(final HandlingHistory handlingHistory) {
148
-    // TODO filter events on cargo (must be same as this cargo)
148
+    Validate.isTrue(this.sameIdentityAs(handlingHistory.cargo()),
149
+      "Handling history must refer to this cargo, " + this + ". " +
150
+      "Given handlig history refers to cargo " + handlingHistory.cargo());
149 151
 
150 152
     // Delivery is a value object, so we can simply discard the old one
151 153
     // and replace it with a new
@@ -154,7 +156,7 @@ public class Cargo implements Entity<Cargo> {
154 156
 
155 157
   @Override
156 158
   public boolean sameIdentityAs(final Cargo other) {
157
-    return other != null && trackingId.sameValueAs(other.trackingId);
159
+    return other != null && trackingId().sameValueAs(other.trackingId());
158 160
   }
159 161
 
160 162
   /**
@@ -176,12 +178,12 @@ public class Cargo implements Entity<Cargo> {
176 178
    */
177 179
   @Override
178 180
   public int hashCode() {
179
-    return trackingId.hashCode();
181
+    return trackingId().hashCode();
180 182
   }
181 183
 
182 184
   @Override
183 185
   public String toString() {
184
-    return trackingId.toString();
186
+    return trackingId().toString();
185 187
   }
186 188
 
187 189
   Cargo() {

+ 3
- 3
dddsample/src/main/java/se/citerus/dddsample/domain/model/handling/HandlingEventRepository.java Ver fichero

@@ -1,6 +1,6 @@
1 1
 package se.citerus.dddsample.domain.model.handling;
2 2
 
3
-import se.citerus.dddsample.domain.model.cargo.TrackingId;
3
+import se.citerus.dddsample.domain.model.cargo.Cargo;
4 4
 
5 5
 /**
6 6
  * Handling event repository.
@@ -16,9 +16,9 @@ public interface HandlingEventRepository {
16 16
 
17 17
 
18 18
   /**
19
-   * @param trackingId cargo tracking id
19
+   * @param cargo cargo
20 20
    * @return The handling history of this cargo
21 21
    */
22
-  HandlingHistory lookupHandlingHistoryOfCargo(TrackingId trackingId);
22
+  HandlingHistory lookupHandlingHistoryOfCargo(Cargo cargo);
23 23
 
24 24
 }

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

@@ -1,6 +1,7 @@
1 1
 package se.citerus.dddsample.domain.model.handling;
2 2
 
3 3
 import org.apache.commons.lang.Validate;
4
+import se.citerus.dddsample.domain.model.cargo.Cargo;
4 5
 import se.citerus.dddsample.domain.shared.ValueObject;
5 6
 
6 7
 import java.util.*;
@@ -13,15 +14,45 @@ import static java.util.Collections.sort;
13 14
 public class HandlingHistory implements ValueObject<HandlingHistory> {
14 15
 
15 16
   private final List<HandlingEvent> handlingEvents;
17
+  private final Cargo cargo;
16 18
 
17
-  public static final HandlingHistory EMPTY = new HandlingHistory(Collections.<HandlingEvent>emptyList());
19
+  public static HandlingHistory emptyForCargo(final Cargo cargo) {
20
+    return new HandlingHistory(cargo);
21
+  }
22
+
23
+  public static HandlingHistory fromEvents(final Collection<HandlingEvent> handlingEvents) {
24
+    return new HandlingHistory(handlingEvents);
25
+  }
26
+
27
+  private HandlingHistory(final Cargo cargo) {
28
+    Validate.notNull(cargo, "Cargo is required");
29
+    this.cargo = cargo;
30
+    handlingEvents = Collections.emptyList();
31
+  }
18 32
 
19
-  public HandlingHistory(Collection<HandlingEvent> handlingEvents) {
20
-    Validate.notNull(handlingEvents, "Handling events are required");
33
+  private HandlingHistory(final Collection<HandlingEvent> handlingEvents) {
34
+    Validate.notEmpty(handlingEvents, "Handling events are required");
21 35
 
36
+    this.cargo = uniqueCargo(handlingEvents);
22 37
     this.handlingEvents = new ArrayList<HandlingEvent>(handlingEvents);
23 38
   }
24 39
 
40
+  private Cargo uniqueCargo(final Collection<HandlingEvent> handlingEvents) {
41
+    final Iterator<HandlingEvent> it = handlingEvents.iterator();
42
+    final Cargo firstCargo = it.next().cargo();
43
+    Validate.notNull(firstCargo, "Cargo is required");
44
+
45
+    while (it.hasNext()) {
46
+      final Cargo nextCargo = it.next().cargo();
47
+      Validate.isTrue(firstCargo.sameIdentityAs(nextCargo),
48
+        "A handling history can only contain handling events for a unique cargo. " +
49
+        "First event is for cargo " + firstCargo + ", also discovered cargo " + nextCargo
50
+      );
51
+    }
52
+
53
+    return firstCargo;
54
+  }
55
+
25 56
   /**
26 57
    * @return A distinct list (no duplicate registrations) of handling events, ordered by completion time.
27 58
    */
@@ -34,7 +65,7 @@ public class HandlingHistory implements ValueObject<HandlingHistory> {
34 65
   }
35 66
 
36 67
   /**
37
-   * @return Most recently completed event, or null if the delivery history is empty.
68
+   * @return Most recently completed event, or null if the handling history is empty.
38 69
    */
39 70
   public HandlingEvent mostRecentlyCompletedEvent() {
40 71
     final List<HandlingEvent> distinctEvents = distinctEventsByCompletionTime();
@@ -45,6 +76,13 @@ public class HandlingHistory implements ValueObject<HandlingHistory> {
45 76
     }
46 77
   }
47 78
 
79
+  /**
80
+   * @return The cargo to which this handling history refers.
81
+   */
82
+  public Cargo cargo() {
83
+    return cargo;
84
+  }
85
+
48 86
   @Override
49 87
   public boolean sameValueAs(HandlingHistory other) {
50 88
     return other != null && this.handlingEvents.equals(other.handlingEvents);
@@ -70,5 +108,4 @@ public class HandlingHistory implements ValueObject<HandlingHistory> {
70 108
         return he1.completionTime().compareTo(he2.completionTime());
71 109
       }
72 110
     };
73
-
74 111
 }

+ 14
- 7
dddsample/src/main/java/se/citerus/dddsample/infrastructure/persistence/hibernate/HandlingEventRepositoryHibernate.java Ver fichero

@@ -1,11 +1,13 @@
1 1
 package se.citerus.dddsample.infrastructure.persistence.hibernate;
2 2
 
3 3
 import org.springframework.stereotype.Repository;
4
-import se.citerus.dddsample.domain.model.cargo.TrackingId;
4
+import se.citerus.dddsample.domain.model.cargo.Cargo;
5 5
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
6 6
 import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
7 7
 import se.citerus.dddsample.domain.model.handling.HandlingHistory;
8 8
 
9
+import java.util.List;
10
+
9 11
 /**
10 12
  * Hibernate implementation of HandlingEventRepository.
11 13
  *
@@ -19,12 +21,17 @@ public class HandlingEventRepositoryHibernate extends HibernateRepository implem
19 21
   }
20 22
 
21 23
   @Override
22
-  public HandlingHistory lookupHandlingHistoryOfCargo(final TrackingId trackingId) {
23
-    return new HandlingHistory(getSession().createQuery(
24
-            "from HandlingEvent where cargo.trackingId = :tid").
25
-            setParameter("tid", trackingId).
26
-            list()
27
-    );
24
+  public HandlingHistory lookupHandlingHistoryOfCargo(final Cargo cargo) {
25
+    final List handlingEvents = getSession().createQuery(
26
+      "from HandlingEvent where cargo.trackingId = :tid").
27
+      setParameter("tid", cargo.trackingId()).
28
+      list();
29
+
30
+    if (handlingEvents.isEmpty()) {
31
+      return HandlingHistory.emptyForCargo(cargo);
32
+    } else {
33
+      return HandlingHistory.fromEvents(handlingEvents);
34
+    }
28 35
   }
29 36
 
30 37
 }

+ 1
- 1
dddsample/src/main/java/se/citerus/dddsample/interfaces/tracking/CargoTrackingController.java Ver fichero

@@ -56,7 +56,7 @@ public final class CargoTrackingController extends SimpleFormController {
56 56
     if (cargo != null) {
57 57
       final MessageSource messageSource = getApplicationContext();
58 58
       final Locale locale = RequestContextUtils.getLocale(request);
59
-      final List<HandlingEvent> handlingEvents = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId).distinctEventsByCompletionTime();
59
+      final List<HandlingEvent> handlingEvents = handlingEventRepository.lookupHandlingHistoryOfCargo(cargo).distinctEventsByCompletionTime();
60 60
       model.put("cargo", new CargoTrackingViewAdapter(cargo, messageSource, locale, handlingEvents));
61 61
     } else {
62 62
       errors.rejectValue("trackingId", "cargo.unknown_id", new Object[]{trackCommand.getTrackingId()}, "Unknown tracking id");

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

@@ -1,7 +1,7 @@
1 1
 package se.citerus.dddsample.domain.model.cargo;
2 2
 
3 3
 import junit.framework.TestCase;
4
-import se.citerus.dddsample.application.util.DateTestUtil;
4
+import static se.citerus.dddsample.application.util.DateTestUtil.toDate;
5 5
 import static se.citerus.dddsample.domain.model.cargo.RoutingStatus.*;
6 6
 import static se.citerus.dddsample.domain.model.cargo.TransportStatus.NOT_RECEIVED;
7 7
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
@@ -11,9 +11,6 @@ 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.text.DateFormat;
15
-import java.text.ParseException;
16
-import java.text.SimpleDateFormat;
17 14
 import java.util.*;
18 15
 
19 16
 public class CargoTest extends TestCase {
@@ -33,7 +30,7 @@ public class CargoTest extends TestCase {
33 30
 
34 31
   public void testConstruction() throws Exception {
35 32
     final TrackingId trackingId = new TrackingId("XYZ");
36
-    final Date arrivalDeadline = DateTestUtil.toDate("2009-03-13");
33
+    final Date arrivalDeadline = toDate("2009-03-13");
37 34
     final RouteSpecification routeSpecification = new RouteSpecification(
38 35
       STOCKHOLM, MELBOURNE, arrivalDeadline
39 36
     );
@@ -119,7 +116,7 @@ public class CargoTest extends TestCase {
119 116
     // Adding an event unrelated to unloading at final destination
120 117
     events.add(
121 118
       new HandlingEvent(cargo, new Date(10), new Date(), HandlingEvent.Type.RECEIVE, HANGZOU));
122
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
119
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
123 120
     assertFalse(cargo.delivery().isUnloadedAtDestination());
124 121
 
125 122
     Voyage voyage = new Voyage.Builder(new VoyageNumber("0123"), HANGZOU).
@@ -129,29 +126,49 @@ public class CargoTest extends TestCase {
129 126
     // Adding an unload event, but not at the final destination
130 127
     events.add(
131 128
       new HandlingEvent(cargo, new Date(20), new Date(), HandlingEvent.Type.UNLOAD, TOKYO, voyage));
132
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
129
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
133 130
     assertFalse(cargo.delivery().isUnloadedAtDestination());
134 131
 
135 132
     // Adding an event in the final destination, but not unload
136 133
     events.add(
137 134
       new HandlingEvent(cargo, new Date(30), new Date(), HandlingEvent.Type.CUSTOMS, NEWYORK));
138
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
135
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
139 136
     assertFalse(cargo.delivery().isUnloadedAtDestination());
140 137
 
141 138
     // Finally, cargo is unloaded at final destination
142 139
     events.add(
143 140
       new HandlingEvent(cargo, new Date(40), new Date(), HandlingEvent.Type.UNLOAD, NEWYORK, voyage));
144
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
141
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
145 142
     assertTrue(cargo.delivery().isUnloadedAtDestination());
146 143
   }
147 144
 
145
+  public void testDeriveDeliveryFromHandlingHistory() throws Exception {
146
+    RouteSpecification sharedRouteSpec = new RouteSpecification(SHANGHAI, GOTHENBURG, toDate("2009-04-01"));
147
+    Cargo cargo1 = new Cargo(new TrackingId("ABC"), sharedRouteSpec);
148
+    Cargo cargo2 = new Cargo(new TrackingId("DEF"), sharedRouteSpec);
149
+    assertFalse(cargo1.sameIdentityAs(cargo2));
150
+    
151
+    HandlingHistory handlingHistoryOfCargo1 = HandlingHistory.fromEvents(Arrays.asList(
152
+      new HandlingEvent(cargo1, toDate("2009-03-10"), toDate("2009-03-12"), HandlingEvent.Type.RECEIVE, HANGZOU)
153
+    ));
154
+
155
+    // This is ok
156
+    cargo1.deriveDeliveryProgress(handlingHistoryOfCargo1);
157
+
158
+    try {
159
+      cargo2.deriveDeliveryProgress(handlingHistoryOfCargo1);
160
+      fail("A cargo should not be able to derive its delivery progress from a handling history of a different cargo");
161
+    } catch (IllegalArgumentException expected) {
162
+    }
163
+  }
164
+
148 165
   // TODO: Generate test data some better way
149 166
   private Cargo populateCargoReceivedStockholm() throws Exception {
150 167
     final Cargo cargo = new Cargo(new TrackingId("XYZ"), new RouteSpecification(STOCKHOLM, MELBOURNE, new Date()));
151 168
 
152
-    HandlingEvent he = new HandlingEvent(cargo, getDate("2007-12-01"), new Date(), HandlingEvent.Type.RECEIVE, STOCKHOLM);
169
+    HandlingEvent he = new HandlingEvent(cargo, toDate("2007-12-01"), new Date(), HandlingEvent.Type.RECEIVE, STOCKHOLM);
153 170
     events.add(he);
154
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
171
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
155 172
 
156 173
     return cargo;
157 174
   }
@@ -159,8 +176,8 @@ public class CargoTest extends TestCase {
159 176
   private Cargo populateCargoClaimedMelbourne() throws Exception {
160 177
     final Cargo cargo = populateCargoOffMelbourne();
161 178
 
162
-    events.add(new HandlingEvent(cargo, getDate("2007-12-09"), new Date(), HandlingEvent.Type.CLAIM, MELBOURNE));
163
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
179
+    events.add(new HandlingEvent(cargo, toDate("2007-12-09"), new Date(), HandlingEvent.Type.CLAIM, MELBOURNE));
180
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
164 181
 
165 182
     return cargo;
166 183
   }
@@ -169,55 +186,55 @@ public class CargoTest extends TestCase {
169 186
     final Cargo cargo = new Cargo(new TrackingId("XYZ"), new RouteSpecification(STOCKHOLM, MELBOURNE, new Date()));
170 187
 
171 188
 
172
-    events.add(new HandlingEvent(cargo, getDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, voyage));
173
-    events.add(new HandlingEvent(cargo, getDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, voyage));
189
+    events.add(new HandlingEvent(cargo, toDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, voyage));
190
+    events.add(new HandlingEvent(cargo, toDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, voyage));
174 191
 
175
-    events.add(new HandlingEvent(cargo, getDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
176
-    events.add(new HandlingEvent(cargo, getDate("2007-12-04"), new Date(), HandlingEvent.Type.UNLOAD, HONGKONG, voyage));
192
+    events.add(new HandlingEvent(cargo, toDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
193
+    events.add(new HandlingEvent(cargo, toDate("2007-12-04"), new Date(), HandlingEvent.Type.UNLOAD, HONGKONG, voyage));
177 194
 
178
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
195
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
179 196
     return cargo;
180 197
   }
181 198
 
182 199
   private Cargo populateCargoOnHamburg() throws Exception {
183 200
     final Cargo cargo = new Cargo(new TrackingId("XYZ"), new RouteSpecification(STOCKHOLM, MELBOURNE, new Date()));
184 201
 
185
-    events.add(new HandlingEvent(cargo, getDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, voyage));
186
-    events.add(new HandlingEvent(cargo, getDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, voyage));
187
-    events.add(new HandlingEvent(cargo, getDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
202
+    events.add(new HandlingEvent(cargo, toDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, voyage));
203
+    events.add(new HandlingEvent(cargo, toDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, voyage));
204
+    events.add(new HandlingEvent(cargo, toDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
188 205
 
189
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
206
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
190 207
     return cargo;
191 208
   }
192 209
 
193 210
   private Cargo populateCargoOffMelbourne() throws Exception {
194 211
     final Cargo cargo = new Cargo(new TrackingId("XYZ"), new RouteSpecification(STOCKHOLM, MELBOURNE, new Date()));
195 212
 
196
-    events.add(new HandlingEvent(cargo, getDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, voyage));
197
-    events.add(new HandlingEvent(cargo, getDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, voyage));
213
+    events.add(new HandlingEvent(cargo, toDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, voyage));
214
+    events.add(new HandlingEvent(cargo, toDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, voyage));
198 215
 
199
-    events.add(new HandlingEvent(cargo, getDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
200
-    events.add(new HandlingEvent(cargo, getDate("2007-12-04"), new Date(), HandlingEvent.Type.UNLOAD, HONGKONG, voyage));
216
+    events.add(new HandlingEvent(cargo, toDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
217
+    events.add(new HandlingEvent(cargo, toDate("2007-12-04"), new Date(), HandlingEvent.Type.UNLOAD, HONGKONG, voyage));
201 218
 
202
-    events.add(new HandlingEvent(cargo, getDate("2007-12-05"), new Date(), HandlingEvent.Type.LOAD, HONGKONG, voyage));
203
-    events.add(new HandlingEvent(cargo, getDate("2007-12-07"), new Date(), HandlingEvent.Type.UNLOAD, MELBOURNE, voyage));
219
+    events.add(new HandlingEvent(cargo, toDate("2007-12-05"), new Date(), HandlingEvent.Type.LOAD, HONGKONG, voyage));
220
+    events.add(new HandlingEvent(cargo, toDate("2007-12-07"), new Date(), HandlingEvent.Type.UNLOAD, MELBOURNE, voyage));
204 221
 
205
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
222
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
206 223
     return cargo;
207 224
   }
208 225
 
209 226
   private Cargo populateCargoOnHongKong() throws Exception {
210 227
     final Cargo cargo = new Cargo(new TrackingId("XYZ"), new RouteSpecification(STOCKHOLM, MELBOURNE, new Date()));
211 228
 
212
-    events.add(new HandlingEvent(cargo, getDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, voyage));
213
-    events.add(new HandlingEvent(cargo, getDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, voyage));
229
+    events.add(new HandlingEvent(cargo, toDate("2007-12-01"), new Date(), HandlingEvent.Type.LOAD, STOCKHOLM, voyage));
230
+    events.add(new HandlingEvent(cargo, toDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, voyage));
214 231
 
215
-    events.add(new HandlingEvent(cargo, getDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
216
-    events.add(new HandlingEvent(cargo, getDate("2007-12-04"), new Date(), HandlingEvent.Type.UNLOAD, HONGKONG, voyage));
232
+    events.add(new HandlingEvent(cargo, toDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
233
+    events.add(new HandlingEvent(cargo, toDate("2007-12-04"), new Date(), HandlingEvent.Type.UNLOAD, HONGKONG, voyage));
217 234
 
218
-    events.add(new HandlingEvent(cargo, getDate("2007-12-05"), new Date(), HandlingEvent.Type.LOAD, HONGKONG, voyage));
235
+    events.add(new HandlingEvent(cargo, toDate("2007-12-05"), new Date(), HandlingEvent.Type.LOAD, HONGKONG, voyage));
219 236
 
220
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
237
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
221 238
     return cargo;
222 239
   }
223 240
 
@@ -243,7 +260,7 @@ public class CargoTest extends TestCase {
243 260
     handlingEvents.add(new HandlingEvent(cargo, new Date(130), new Date(140), HandlingEvent.Type.CUSTOMS, GOTHENBURG));
244 261
 
245 262
     events.addAll(handlingEvents);
246
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
263
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
247 264
     assertFalse(cargo.delivery().isMisdirected());
248 265
 
249 266
     //Try a couple of failing ones
@@ -253,7 +270,7 @@ public class CargoTest extends TestCase {
253 270
 
254 271
     handlingEvents.add(new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.RECEIVE, HANGZOU));
255 272
     events.addAll(handlingEvents);
256
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
273
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
257 274
 
258 275
     assertTrue(cargo.delivery().isMisdirected());
259 276
 
@@ -267,7 +284,7 @@ public class CargoTest extends TestCase {
267 284
     handlingEvents.add(new HandlingEvent(cargo, new Date(70), new Date(80), HandlingEvent.Type.LOAD, ROTTERDAM, voyage));
268 285
 
269 286
     events.addAll(handlingEvents);
270
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
287
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
271 288
 
272 289
     assertTrue(cargo.delivery().isMisdirected());
273 290
 
@@ -281,7 +298,7 @@ public class CargoTest extends TestCase {
281 298
     handlingEvents.add(new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.CLAIM, ROTTERDAM));
282 299
 
283 300
     events.addAll(handlingEvents);
284
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
301
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
285 302
 
286 303
     assertTrue(cargo.delivery().isMisdirected());
287 304
   }
@@ -300,15 +317,4 @@ public class CargoTest extends TestCase {
300 317
     return cargo;
301 318
   }
302 319
 
303
-  /**
304
-   * Parse an ISO 8601 (YYYY-MM-DD) String to Date
305
-   *
306
-   * @param isoFormat String to parse.
307
-   * @return Created date instance.
308
-   * @throws ParseException Thrown if parsing fails.
309
-   */
310
-  private Date getDate(String isoFormat) throws ParseException {
311
-    final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
312
-    return dateFormat.parse(isoFormat);
313
-  }
314 320
 }

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

@@ -15,31 +15,54 @@ import java.util.Date;
15 15
 
16 16
 public class HandlingHistoryTest extends TestCase {
17 17
   Cargo cargo;
18
+  Cargo cargo2;
18 19
   Voyage voyage;
19 20
   HandlingEvent event1;
20 21
   HandlingEvent event1duplicate;
21 22
   HandlingEvent event2;
23
+  HandlingEvent eventOfCargo2;
22 24
   HandlingHistory handlingHistory;
23 25
 
24 26
   protected void setUp() throws Exception {
25 27
     cargo = new Cargo(new TrackingId("ABC"), new RouteSpecification(SHANGHAI, DALLAS, toDate("2009-04-01")));
28
+    cargo2 = new Cargo(new TrackingId("DEF"), new RouteSpecification(SHANGHAI, NEWYORK, toDate("2009-04-15")));
29
+    
26 30
     voyage = new Voyage.Builder(new VoyageNumber("X25"), HONGKONG).
27 31
       addMovement(SHANGHAI, new Date(), new Date()).
28 32
       addMovement(DALLAS, new Date(), new Date()).
29 33
       build();
30
-    event1 = new HandlingEvent(cargo, toDate("2009-03-05"), new Date(100), HandlingEvent.Type.LOAD, SHANGHAI, voyage);
31
-    event1duplicate = new HandlingEvent(cargo, toDate("2009-03-05"), new Date(200), HandlingEvent.Type.LOAD, SHANGHAI, voyage);
32
-    event2 = new HandlingEvent(cargo, toDate("2009-03-10"), new Date(150), HandlingEvent.Type.UNLOAD, DALLAS, voyage);
33
-
34
-    handlingHistory = new HandlingHistory(asList(event2, event1, event1duplicate));
34
+    event1 = new HandlingEvent(cargo, toDate("2009-03-05"), toDate("2009-03-05"), HandlingEvent.Type.LOAD, SHANGHAI, voyage);
35
+    event1duplicate = new HandlingEvent(cargo, toDate("2009-03-05"), toDate("2009-03-07"), HandlingEvent.Type.LOAD, SHANGHAI, voyage);
36
+    event2 = new HandlingEvent(cargo, toDate("2009-03-10"), toDate("2009-03-06"), HandlingEvent.Type.UNLOAD, DALLAS, voyage);
37
+    eventOfCargo2 = new HandlingEvent(cargo2, toDate("2009-03-11"), toDate("2009-03-08"), HandlingEvent.Type.LOAD, GOTHENBURG, voyage);
35 38
   }
36 39
 
37 40
   public void testDistinctEventsByCompletionTime() {
41
+    handlingHistory = HandlingHistory.fromEvents(asList(event2, event1, event1duplicate));
42
+    
38 43
     assertEquals(asList(event1, event2), handlingHistory.distinctEventsByCompletionTime());
39 44
   }
40 45
 
41 46
   public void testMostRecentlyCompletedEvent() {
47
+    handlingHistory = HandlingHistory.fromEvents(asList(event2, event1, event1duplicate));
48
+    
42 49
     assertEquals(event2, handlingHistory.mostRecentlyCompletedEvent());
43 50
   }
44
-  
51
+
52
+  public void testUniqueCargoOfEvents() {
53
+    try {
54
+      handlingHistory = HandlingHistory.fromEvents(asList(event1, event2, eventOfCargo2));
55
+      fail("A handling history should only accept handling events for a single unique cargo");
56
+    } catch (IllegalArgumentException expected) {
57
+    }
58
+  }
59
+
60
+  public void testCargo() {
61
+    handlingHistory = HandlingHistory.fromEvents(asList(event1, event2));
62
+    assertEquals(cargo, handlingHistory.cargo());
63
+
64
+    handlingHistory = HandlingHistory.fromEvents(asList(eventOfCargo2));
65
+    assertEquals(cargo2, handlingHistory.cargo());
66
+  }
67
+
45 68
 }

+ 7
- 4
dddsample/src/test/java/se/citerus/dddsample/infrastructure/messaging/stub/SynchronousApplicationEventsStub.java Ver fichero

@@ -1,5 +1,7 @@
1 1
 package se.citerus.dddsample.infrastructure.messaging.stub;
2 2
 
3
+import org.apache.commons.logging.Log;
4
+import org.apache.commons.logging.LogFactory;
3 5
 import se.citerus.dddsample.application.ApplicationEvents;
4 6
 import se.citerus.dddsample.application.CargoInspectionService;
5 7
 import se.citerus.dddsample.domain.model.cargo.Cargo;
@@ -9,6 +11,7 @@ import se.citerus.dddsample.interfaces.handling.HandlingEventRegistrationAttempt
9 11
 public class SynchronousApplicationEventsStub implements ApplicationEvents {
10 12
 
11 13
   CargoInspectionService cargoInspectionService;
14
+  private static final Log logger = LogFactory.getLog(SynchronousApplicationEventsStub.class);
12 15
 
13 16
   public void setCargoInspectionService(CargoInspectionService cargoInspectionService) {
14 17
     this.cargoInspectionService = cargoInspectionService;
@@ -16,22 +19,22 @@ public class SynchronousApplicationEventsStub implements ApplicationEvents {
16 19
 
17 20
   @Override
18 21
   public void cargoWasHandled(HandlingEvent event) {
19
-    System.out.println("EVENT: cargo was handled: " + event);
22
+    logger.debug("EVENT: cargo was handled: " + event);
20 23
     cargoInspectionService.inspectCargo(event.cargo().trackingId());
21 24
   }
22 25
 
23 26
   @Override
24 27
   public void cargoWasMisdirected(Cargo cargo) {
25
-    System.out.println("EVENT: cargo was misdirected");
28
+    logger.debug("EVENT: cargo was misdirected");
26 29
   }
27 30
 
28 31
   @Override
29 32
   public void cargoHasArrived(Cargo cargo) {
30
-    System.out.println("EVENT: cargo has arrived: " + cargo.trackingId().idString());
33
+    logger.debug("EVENT: cargo has arrived: " + cargo.trackingId().idString());
31 34
   }
32 35
 
33 36
   @Override
34 37
   public void receivedHandlingEventRegistrationAttempt(HandlingEventRegistrationAttempt attempt) {
35
-    System.out.println("EVENT: received handling event registration attempt");
38
+    logger.debug("EVENT: received handling event registration attempt");
36 39
   }
37 40
 }

+ 1
- 1
dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/hibernate/CargoRepositoryTest.java Ver fichero

@@ -46,7 +46,7 @@ public class CargoRepositoryTest extends AbstractRepositoryTest {
46 46
 
47 47
     assertNotNull(cargo.delivery());
48 48
 
49
-    final List<HandlingEvent> events = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId).distinctEventsByCompletionTime();
49
+    final List<HandlingEvent> events = handlingEventRepository.lookupHandlingHistoryOfCargo(cargo).distinctEventsByCompletionTime();
50 50
     assertEquals(2, events.size());
51 51
 
52 52
     HandlingEvent firstEvent = events.get(0);

+ 2
- 2
dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/hibernate/HandlingEventRepositoryTest.java Ver fichero

@@ -52,8 +52,8 @@ public class HandlingEventRepositoryTest extends AbstractRepositoryTest {
52 52
   }
53 53
 
54 54
   public void testFindEventsForCargo() throws Exception {
55
-    TrackingId trackingId = new TrackingId("XYZ");
56
-    List<HandlingEvent> handlingEvents = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId).distinctEventsByCompletionTime();
55
+    Cargo cargo = cargoRepository.find(new TrackingId("XYZ"));
56
+    List<HandlingEvent> handlingEvents = handlingEventRepository.lookupHandlingHistoryOfCargo(cargo).distinctEventsByCompletionTime();
57 57
     assertEquals(12, handlingEvents.size());
58 58
   }
59 59
 

+ 6
- 11
dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/inmemory/CargoRepositoryInMem.java Ver fichero

@@ -52,23 +52,19 @@ public class CargoRepositoryInMem implements CargoRepository {
52 52
 
53 53
   public void init() throws Exception {
54 54
     final TrackingId xyz = new TrackingId("XYZ");
55
-    final Cargo cargoXYZ = createCargoWithDeliveryHistory(
56
-      xyz, STOCKHOLM, MELBOURNE, handlingEventRepository.lookupHandlingHistoryOfCargo(xyz));
55
+    final Cargo cargoXYZ = createCargoWithDeliveryHistory(xyz, STOCKHOLM, MELBOURNE);
57 56
     cargoDb.put(xyz.idString(), cargoXYZ);
58 57
 
59 58
     final TrackingId zyx = new TrackingId("ZYX");
60
-    final Cargo cargoZYX = createCargoWithDeliveryHistory(
61
-      zyx, MELBOURNE, STOCKHOLM, handlingEventRepository.lookupHandlingHistoryOfCargo(zyx));
59
+    final Cargo cargoZYX = createCargoWithDeliveryHistory(zyx, MELBOURNE, STOCKHOLM);
62 60
     cargoDb.put(zyx.idString(), cargoZYX);
63 61
 
64 62
     final TrackingId abc = new TrackingId("ABC");
65
-    final Cargo cargoABC = createCargoWithDeliveryHistory(
66
-      abc, STOCKHOLM, HELSINKI, handlingEventRepository.lookupHandlingHistoryOfCargo(abc));
63
+    final Cargo cargoABC = createCargoWithDeliveryHistory(abc, STOCKHOLM, HELSINKI);
67 64
     cargoDb.put(abc.idString(), cargoABC);
68 65
 
69 66
     final TrackingId cba = new TrackingId("CBA");
70
-    final Cargo cargoCBA = createCargoWithDeliveryHistory(
71
-      cba, HELSINKI, STOCKHOLM, handlingEventRepository.lookupHandlingHistoryOfCargo(cba));
67
+    final Cargo cargoCBA = createCargoWithDeliveryHistory(cba, HELSINKI, STOCKHOLM);
72 68
     cargoDb.put(cba.idString(), cargoCBA);
73 69
   }
74 70
 
@@ -78,12 +74,11 @@ public class CargoRepositoryInMem implements CargoRepository {
78 74
 
79 75
   public static Cargo createCargoWithDeliveryHistory(TrackingId trackingId,
80 76
                                                      Location origin,
81
-                                                     Location destination,
82
-                                                     HandlingHistory handlingHistory) {
77
+                                                     Location destination) {
83 78
 
84 79
     final RouteSpecification routeSpecification = new RouteSpecification(origin, destination, new Date());
85 80
     final Cargo cargo = new Cargo(trackingId, routeSpecification);
86
-    cargo.deriveDeliveryProgress(handlingHistory);
81
+    cargo.deriveDeliveryProgress(HandlingHistory.emptyForCargo(cargo));
87 82
 
88 83
     return cargo;
89 84
   }

+ 13
- 6
dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/inmemory/HandlingEventRepositoryInMem.java Ver fichero

@@ -1,11 +1,15 @@
1 1
 package se.citerus.dddsample.infrastructure.persistence.inmemory;
2 2
 
3
+import se.citerus.dddsample.domain.model.cargo.Cargo;
3 4
 import se.citerus.dddsample.domain.model.cargo.TrackingId;
4 5
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
5 6
 import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
6 7
 import se.citerus.dddsample.domain.model.handling.HandlingHistory;
7 8
 
8
-import java.util.*;
9
+import java.util.ArrayList;
10
+import java.util.HashMap;
11
+import java.util.List;
12
+import java.util.Map;
9 13
 
10 14
 public class HandlingEventRepositoryInMem implements HandlingEventRepository {
11 15
 
@@ -23,11 +27,14 @@ public class HandlingEventRepositoryInMem implements HandlingEventRepository {
23 27
   }
24 28
 
25 29
   @Override
26
-  public HandlingHistory lookupHandlingHistoryOfCargo(TrackingId trackingId) {
27
-    List<HandlingEvent> events = eventMap.get(trackingId);
28
-    if (events == null) events = Collections.emptyList();
29
-    
30
-    return new HandlingHistory(events);
30
+  public HandlingHistory lookupHandlingHistoryOfCargo(Cargo cargo) {
31
+    List<HandlingEvent> events = eventMap.get(cargo.trackingId());
32
+
33
+    if (events == null) {
34
+      return HandlingHistory.emptyForCargo(cargo);
35
+    } else {
36
+      return HandlingHistory.fromEvents(events);
37
+    }
31 38
   }
32 39
 
33 40
 }

+ 2
- 2
dddsample/src/test/java/se/citerus/dddsample/infrastructure/routing/ExternalRoutingServiceTest.java Ver fichero

@@ -5,6 +5,7 @@ import com.pathfinder.internal.GraphDAO;
5 5
 import com.pathfinder.internal.GraphTraversalServiceImpl;
6 6
 import junit.framework.TestCase;
7 7
 import static org.easymock.EasyMock.*;
8
+import static se.citerus.dddsample.application.util.DateTestUtil.toDate;
8 9
 import se.citerus.dddsample.domain.model.cargo.*;
9 10
 import se.citerus.dddsample.domain.model.location.Location;
10 11
 import se.citerus.dddsample.domain.model.location.LocationRepository;
@@ -15,7 +16,6 @@ import se.citerus.dddsample.domain.model.voyage.VoyageRepository;
15 16
 import se.citerus.dddsample.infrastructure.persistence.inmemory.LocationRepositoryInMem;
16 17
 
17 18
 import java.util.Arrays;
18
-import java.util.Date;
19 19
 import java.util.List;
20 20
 
21 21
 public class ExternalRoutingServiceTest extends TestCase {
@@ -46,7 +46,7 @@ public class ExternalRoutingServiceTest extends TestCase {
46 46
 
47 47
   public void testCalculatePossibleRoutes() {
48 48
     TrackingId trackingId = new TrackingId("ABC");
49
-    RouteSpecification routeSpecification = new RouteSpecification(HONGKONG, HELSINKI, new Date());
49
+    RouteSpecification routeSpecification = new RouteSpecification(HONGKONG, HELSINKI, toDate("2009-04-01"));
50 50
     Cargo cargo = new Cargo(trackingId, routeSpecification);
51 51
 
52 52
     expect(voyageRepository.find(isA(VoyageNumber.class))).andStubReturn(SampleVoyages.CM002);

+ 1
- 1
dddsample/src/test/java/se/citerus/dddsample/interfaces/tracking/CargoTrackingViewAdapterTest.java Ver fichero

@@ -24,7 +24,7 @@ public class CargoTrackingViewAdapterTest extends TestCase {
24 24
     events.add(new HandlingEvent(cargo, new Date(3), new Date(4), HandlingEvent.Type.LOAD, HANGZOU, CM001));
25 25
     events.add(new HandlingEvent(cargo, new Date(5), new Date(6), HandlingEvent.Type.UNLOAD, HELSINKI, CM001));
26 26
 
27
-    cargo.deriveDeliveryProgress(new HandlingHistory(events));
27
+    cargo.deriveDeliveryProgress(HandlingHistory.fromEvents(events));
28 28
 
29 29
     StaticApplicationContext applicationContext = new StaticApplicationContext();
30 30
     applicationContext.addMessage("cargo.status.IN_PORT", Locale.GERMAN, "In port {0}");