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

Renamed DeliveryHistory to HandlingHistory.

Made next expected activity persistent and derived on handling.
peter_backlund 17 лет назад
Родитель
Сommit
7f48617bf4
22 измененных файлов: 272 добавлений и 176 удалений
  1. 5
    1
      dddsample/src/main/java/se/citerus/dddsample/application/HandlingEventService.java
  2. 3
    5
      dddsample/src/main/java/se/citerus/dddsample/application/impl/CargoInspectionServiceImpl.java
  3. 5
    9
      dddsample/src/main/java/se/citerus/dddsample/application/util/SampleDataGenerator.java
  4. 53
    47
      dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Cargo.java
  5. 13
    27
      dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Delivery.java
  6. 4
    0
      dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/HandlingActivity.java
  7. 0
    11
      dddsample/src/main/java/se/citerus/dddsample/domain/model/handling/HandlingEvent.java
  8. 2
    4
      dddsample/src/main/java/se/citerus/dddsample/domain/model/handling/HandlingEventRepository.java
  9. 74
    0
      dddsample/src/main/java/se/citerus/dddsample/domain/model/handling/HandlingHistory.java
  10. 2
    0
      dddsample/src/main/java/se/citerus/dddsample/infrastructure/persistence/hibernate/CargoRepositoryHibernate.java
  11. 7
    7
      dddsample/src/main/java/se/citerus/dddsample/infrastructure/persistence/hibernate/HandlingEventRepositoryHibernate.java
  12. 1
    1
      dddsample/src/main/java/se/citerus/dddsample/interfaces/tracking/CargoTrackingController.java
  13. 11
    0
      dddsample/src/main/resources/se/citerus/dddsample/infrastructure/persistence/hibernate/Cargo.hbm.xml
  14. 1
    29
      dddsample/src/main/webapp/WEB-INF/jsp/pub/track.jsp
  15. 15
    14
      dddsample/src/test/java/se/citerus/dddsample/domain/model/cargo/CargoTest.java
  16. 45
    0
      dddsample/src/test/java/se/citerus/dddsample/domain/model/handling/HandlingHistoryTest.java
  17. 1
    1
      dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/hibernate/CargoRepositoryTest.java
  18. 2
    1
      dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/hibernate/HandlingEventRepositoryTest.java
  19. 7
    7
      dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/inmemory/CargoRepositoryInMem.java
  20. 4
    4
      dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/inmemory/HandlingEventRepositoryInMem.java
  21. 2
    1
      dddsample/src/test/java/se/citerus/dddsample/interfaces/tracking/CargoTrackingViewAdapterTest.java
  22. 15
    7
      dddsample/src/test/java/se/citerus/dddsample/scenario/CargoLifecycleScenarioTest.java

+ 5
- 1
dddsample/src/main/java/se/citerus/dddsample/application/HandlingEventService.java Просмотреть файл

22
    * @param unLocode UN locode for the location where the event occurred
22
    * @param unLocode UN locode for the location where the event occurred
23
    * @param type type of event
23
    * @param type type of event
24
    */
24
    */
25
-  void registerHandlingEvent(Date completionTime, TrackingId trackingId, VoyageNumber voyageNumber, UnLocode unLocode, HandlingEvent.Type type);
25
+  void registerHandlingEvent(Date completionTime,
26
+                             TrackingId trackingId,
27
+                             VoyageNumber voyageNumber,
28
+                             UnLocode unLocode,
29
+                             HandlingEvent.Type type);
26
 
30
 
27
 }
31
 }

+ 3
- 5
dddsample/src/main/java/se/citerus/dddsample/application/impl/CargoInspectionServiceImpl.java Просмотреть файл

9
 import se.citerus.dddsample.domain.model.cargo.Cargo;
9
 import se.citerus.dddsample.domain.model.cargo.Cargo;
10
 import se.citerus.dddsample.domain.model.cargo.CargoRepository;
10
 import se.citerus.dddsample.domain.model.cargo.CargoRepository;
11
 import se.citerus.dddsample.domain.model.cargo.TrackingId;
11
 import se.citerus.dddsample.domain.model.cargo.TrackingId;
12
-import se.citerus.dddsample.domain.model.handling.HandlingEvent;
13
 import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
12
 import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
14
-
15
-import java.util.List;
13
+import se.citerus.dddsample.domain.model.handling.HandlingHistory;
16
 
14
 
17
 public class CargoInspectionServiceImpl implements CargoInspectionService {
15
 public class CargoInspectionServiceImpl implements CargoInspectionService {
18
 
16
 
40
       return;
38
       return;
41
     }
39
     }
42
 
40
 
43
-    final List<HandlingEvent> handlingEvents = handlingEventRepository.findEventsForCargo(trackingId);
41
+    final HandlingHistory handlingHistory = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId);
44
 
42
 
45
-    cargo.deriveDeliveryProgress(handlingEvents);
43
+    cargo.deriveDeliveryProgress(handlingHistory);
46
 
44
 
47
     if (cargo.isMisdirected()) {
45
     if (cargo.isMisdirected()) {
48
       applicationEvents.cargoWasMisdirected(cargo);
46
       applicationEvents.cargoWasMisdirected(cargo);

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

12
 import org.springframework.web.context.support.WebApplicationContextUtils;
12
 import org.springframework.web.context.support.WebApplicationContextUtils;
13
 import static se.citerus.dddsample.application.util.DateTestUtil.toDate;
13
 import static se.citerus.dddsample.application.util.DateTestUtil.toDate;
14
 import se.citerus.dddsample.domain.model.cargo.*;
14
 import se.citerus.dddsample.domain.model.cargo.*;
15
-import se.citerus.dddsample.domain.model.handling.CannotCreateHandlingEventException;
16
-import se.citerus.dddsample.domain.model.handling.HandlingEvent;
17
-import se.citerus.dddsample.domain.model.handling.HandlingEventFactory;
18
-import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
15
+import se.citerus.dddsample.domain.model.handling.*;
19
 import se.citerus.dddsample.domain.model.location.Location;
16
 import se.citerus.dddsample.domain.model.location.Location;
20
 import se.citerus.dddsample.domain.model.location.LocationRepository;
17
 import se.citerus.dddsample.domain.model.location.LocationRepository;
21
 import se.citerus.dddsample.domain.model.location.SampleLocations;
18
 import se.citerus.dddsample.domain.model.location.SampleLocations;
31
 import java.text.SimpleDateFormat;
28
 import java.text.SimpleDateFormat;
32
 import static java.util.Arrays.asList;
29
 import static java.util.Arrays.asList;
33
 import java.util.Date;
30
 import java.util.Date;
34
-import java.util.List;
35
 
31
 
36
 /**
32
 /**
37
  * Provides sample data.
33
  * Provides sample data.
254
           throw new RuntimeException(e);
250
           throw new RuntimeException(e);
255
         }
251
         }
256
 
252
 
257
-        List<HandlingEvent> handlingEvents = handlingEventRepository.findEventsForCargo(trackingId);
258
-        abc123.deriveDeliveryProgress(handlingEvents);
253
+        HandlingHistory handlingHistory = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId);
254
+        abc123.deriveDeliveryProgress(handlingHistory);
259
 
255
 
260
         session.update(abc123);
256
         session.update(abc123);
261
 
257
 
299
           throw new RuntimeException(e);
295
           throw new RuntimeException(e);
300
         }
296
         }
301
 
297
 
302
-        List<HandlingEvent> handlingEvents1 = handlingEventRepository.findEventsForCargo(trackingId1);
303
-        jkl567.deriveDeliveryProgress(handlingEvents1);
298
+        HandlingHistory handlingHistory1 = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId);
299
+        jkl567.deriveDeliveryProgress(handlingHistory1);
304
 
300
 
305
         session.update(jkl567);
301
         session.update(jkl567);
306
       }
302
       }

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

5
 import static se.citerus.dddsample.domain.model.cargo.RoutingStatus.*;
5
 import static se.citerus.dddsample.domain.model.cargo.RoutingStatus.*;
6
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
6
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
7
 import static se.citerus.dddsample.domain.model.handling.HandlingEvent.Type.*;
7
 import static se.citerus.dddsample.domain.model.handling.HandlingEvent.Type.*;
8
+import se.citerus.dddsample.domain.model.handling.HandlingHistory;
8
 import se.citerus.dddsample.domain.model.location.Location;
9
 import se.citerus.dddsample.domain.model.location.Location;
9
 import se.citerus.dddsample.domain.shared.DomainObjectUtils;
10
 import se.citerus.dddsample.domain.shared.DomainObjectUtils;
10
 
11
 
11
-import java.util.Collections;
12
 import java.util.Date;
12
 import java.util.Date;
13
 import java.util.Iterator;
13
 import java.util.Iterator;
14
-import java.util.List;
15
 
14
 
16
 /**
15
 /**
17
  * A Cargo. This is the central class in the domain model,
16
  * A Cargo. This is the central class in the domain model,
49
   private Delivery delivery;
48
   private Delivery delivery;
50
   private RouteSpecification routeSpecification;
49
   private RouteSpecification routeSpecification;
51
   private RoutingStatus routingStatus;
50
   private RoutingStatus routingStatus;
51
+  private HandlingActivity nextExpectedActivity;
52
   private boolean misdirected;
52
   private boolean misdirected;
53
   private Date eta;
53
   private Date eta;
54
   
54
   
65
     this.origin = routeSpecification.origin();
65
     this.origin = routeSpecification.origin();
66
     this.routeSpecification = routeSpecification;
66
     this.routeSpecification = routeSpecification;
67
 
67
 
68
-    deriveDeliveryProgress(Collections.<HandlingEvent>emptyList());
68
+    deriveDeliveryProgress(HandlingHistory.EMPTY);
69
   }
69
   }
70
 
70
 
71
   /**
71
   /**
130
     // Handling consistency within the Cargo aggregate synchronously
130
     // Handling consistency within the Cargo aggregate synchronously
131
     this.routingStatus = deriveRoutingStatus();
131
     this.routingStatus = deriveRoutingStatus();
132
     this.misdirected = deriveMisdirectionStatus();
132
     this.misdirected = deriveMisdirectionStatus();
133
+    this.nextExpectedActivity = deriveNextExpectedActivity();
133
     this.eta = deriveEta();
134
     this.eta = deriveEta();
134
   }
135
   }
135
 
136
 
180
    * @return the next expected activity
181
    * @return the next expected activity
181
    */
182
    */
182
   public HandlingActivity nextExpectedActivity() {
183
   public HandlingActivity nextExpectedActivity() {
183
-    if (!onTrack()) return NO_ACTIVITY;
184
-
185
-    final HandlingEvent lastEvent = delivery().lastEvent();
186
-
187
-    if (lastEvent == null) return new HandlingActivity(RECEIVE, origin());
188
-
189
-    switch (lastEvent.type()) {
190
-
191
-      case LOAD:
192
-        for (Leg leg : itinerary().legs()) {
193
-          if (leg.loadLocation().sameIdentityAs(lastEvent.location())) {
194
-            return new HandlingActivity(UNLOAD, leg.unloadLocation(), leg.voyage());
195
-          }
196
-        }
197
-
198
-        return NO_ACTIVITY;
199
-
200
-      case UNLOAD:
201
-        for (Iterator<Leg> it = itinerary().legs().iterator(); it.hasNext();) {
202
-          final Leg leg = it.next();
203
-          if (leg.unloadLocation().sameIdentityAs(lastEvent.location())) {
204
-            if (it.hasNext()) {
205
-              final Leg nextLeg = it.next();
206
-              return new HandlingActivity(LOAD, nextLeg.loadLocation(), nextLeg.voyage());
207
-            } else {
208
-              return new HandlingActivity(CLAIM, leg.unloadLocation());
209
-            }
210
-          }
211
-        }
212
-
213
-        return NO_ACTIVITY;
214
-
215
-      case RECEIVE:
216
-        final Leg firstLeg = itinerary().legs().iterator().next();
217
-        return new HandlingActivity(LOAD, firstLeg.loadLocation(), firstLeg.voyage());
218
-
219
-      case CLAIM:
220
-      default:
221
-        return NO_ACTIVITY;
222
-    }
184
+    return nextExpectedActivity;
223
   }
185
   }
224
 
186
 
225
   /**
187
   /**
235
    * but changes to the delivery history (when a cargo is handled) cause the status update
197
    * but changes to the delivery history (when a cargo is handled) cause the status update
236
    * to happen <b>asynchronously</b> since {@link HandlingEvent} is in a different aggregate.
198
    * to happen <b>asynchronously</b> since {@link HandlingEvent} is in a different aggregate.
237
    *
199
    *
238
-   * @param handlingEvents all handling events for this cargo
200
+   * @param handlingHistory delivery history
239
    */
201
    */
240
-  public void deriveDeliveryProgress(final List<HandlingEvent> handlingEvents) {
202
+  public void deriveDeliveryProgress(final HandlingHistory handlingHistory) {
241
     // Delivery is a value object, so we can simply discard the old one
203
     // Delivery is a value object, so we can simply discard the old one
242
     // and replace it with a new
204
     // and replace it with a new
243
-    this.delivery = Delivery.derivedFrom(handlingEvents);
205
+    this.delivery = Delivery.derivedFrom(handlingHistory);
244
     this.routingStatus = deriveRoutingStatus();
206
     this.routingStatus = deriveRoutingStatus();
245
     this.misdirected = deriveMisdirectionStatus();
207
     this.misdirected = deriveMisdirectionStatus();
246
     this.eta = deriveEta();
208
     this.eta = deriveEta();
209
+    this.nextExpectedActivity = deriveNextExpectedActivity();
247
   }
210
   }
248
 
211
 
249
   /**
212
   /**
285
     }
248
     }
286
   }
249
   }
287
 
250
 
251
+  private HandlingActivity deriveNextExpectedActivity() {
252
+    if (!onTrack()) return NO_ACTIVITY;
253
+
254
+    final HandlingEvent lastEvent = delivery().lastEvent();
255
+
256
+    if (lastEvent == null) return new HandlingActivity(RECEIVE, origin());
257
+
258
+    switch (lastEvent.type()) {
259
+
260
+      case LOAD:
261
+        for (Leg leg : itinerary().legs()) {
262
+          if (leg.loadLocation().sameIdentityAs(lastEvent.location())) {
263
+            return new HandlingActivity(UNLOAD, leg.unloadLocation(), leg.voyage());
264
+          }
265
+        }
266
+
267
+        return NO_ACTIVITY;
268
+
269
+      case UNLOAD:
270
+        for (Iterator<Leg> it = itinerary().legs().iterator(); it.hasNext();) {
271
+          final Leg leg = it.next();
272
+          if (leg.unloadLocation().sameIdentityAs(lastEvent.location())) {
273
+            if (it.hasNext()) {
274
+              final Leg nextLeg = it.next();
275
+              return new HandlingActivity(LOAD, nextLeg.loadLocation(), nextLeg.voyage());
276
+            } else {
277
+              return new HandlingActivity(CLAIM, leg.unloadLocation());
278
+            }
279
+          }
280
+        }
281
+
282
+        return NO_ACTIVITY;
283
+
284
+      case RECEIVE:
285
+        final Leg firstLeg = itinerary().legs().iterator().next();
286
+        return new HandlingActivity(LOAD, firstLeg.loadLocation(), firstLeg.voyage());
287
+
288
+      case CLAIM:
289
+      default:
290
+        return NO_ACTIVITY;
291
+    }
292
+  }
293
+
288
   /**
294
   /**
289
    * @return true if cargo is on track, i.e. routed and not misdirected
295
    * @return true if cargo is on track, i.e. routed and not misdirected
290
    */
296
    */
291
   private boolean onTrack() {
297
   private boolean onTrack() {
292
-    return routingStatus().equals(ROUTED) && !misdirected;
298
+    return routingStatus.equals(ROUTED) && !misdirected;
293
   }
299
   }
294
   
300
   
295
   @Override
301
   @Override

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

6
 import se.citerus.dddsample.domain.model.ValueObject;
6
 import se.citerus.dddsample.domain.model.ValueObject;
7
 import static se.citerus.dddsample.domain.model.cargo.TransportStatus.*;
7
 import static se.citerus.dddsample.domain.model.cargo.TransportStatus.*;
8
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
8
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
9
+import se.citerus.dddsample.domain.model.handling.HandlingHistory;
9
 import se.citerus.dddsample.domain.model.location.Location;
10
 import se.citerus.dddsample.domain.model.location.Location;
10
 import se.citerus.dddsample.domain.model.voyage.Voyage;
11
 import se.citerus.dddsample.domain.model.voyage.Voyage;
11
 import se.citerus.dddsample.domain.shared.DomainObjectUtils;
12
 import se.citerus.dddsample.domain.shared.DomainObjectUtils;
12
 
13
 
13
-import java.util.ArrayList;
14
-import java.util.Collection;
15
-import static java.util.Collections.EMPTY_SET;
16
-import static java.util.Collections.sort;
17
-import java.util.HashSet;
18
-import java.util.List;
19
-
20
 /**
14
 /**
21
- * The actual result of the cargo transportation, as opposed to
15
+ * The actual transportation of the cargo, as opposed to
22
  * the customer requirement (RouteSpecification) and the plan (Itinerary). 
16
  * the customer requirement (RouteSpecification) and the plan (Itinerary). 
23
  *
17
  *
24
  */
18
  */
25
 public class Delivery implements ValueObject<Delivery> {
19
 public class Delivery implements ValueObject<Delivery> {
26
 
20
 
27
-  public static final Delivery EMPTY_DELIVERY = Delivery.derivedFrom(EMPTY_SET);
21
+  public static final Delivery EMPTY_DELIVERY = Delivery.derivedFrom(HandlingHistory.EMPTY);
28
 
22
 
29
   private TransportStatus transportStatus;
23
   private TransportStatus transportStatus;
30
   private Location lastKnownLocation;
24
   private Location lastKnownLocation;
31
   private Voyage currentVoyage;
25
   private Voyage currentVoyage;
32
   private HandlingEvent lastEvent;
26
   private HandlingEvent lastEvent;
33
 
27
 
28
+  private Delivery(HandlingEvent lastEvent) {
29
+    this.lastEvent = lastEvent;
30
+  }
31
+
34
   /**
32
   /**
35
    * @return Transport status
33
    * @return Transport status
36
    */
34
    */
53
   }
51
   }
54
 
52
 
55
   /**
53
   /**
56
-   * @param handlingEvents handling events
54
+   * @param handlingHistory delivery history
57
    * @return An up to date Delivery derived from this collection of handling events.
55
    * @return An up to date Delivery derived from this collection of handling events.
58
    */
56
    */
59
-  static Delivery derivedFrom(final Collection<HandlingEvent> handlingEvents) {
60
-    Validate.notNull(handlingEvents, "Handling events are required");
57
+  static Delivery derivedFrom(HandlingHistory handlingHistory) {
58
+    Validate.notNull(handlingHistory, "Delivery history is required");
61
     
59
     
62
-    final List<HandlingEvent> eventsByCompletionTime =
63
-      new ArrayList<HandlingEvent>(
64
-        new HashSet<HandlingEvent>(handlingEvents));
65
-    sort(eventsByCompletionTime, HandlingEvent.BY_COMPLETION_TIME_COMPARATOR);
66
-
67
-    final Delivery delivery = new Delivery();
68
-    delivery.calculateLastEvent(eventsByCompletionTime);
60
+    final Delivery delivery = new Delivery(
61
+      handlingHistory.mostRecentlyCompletedEvent()
62
+    );
69
     delivery.calculateTransportStatus();
63
     delivery.calculateTransportStatus();
70
     delivery.calculateLastKnownLocation();
64
     delivery.calculateLastKnownLocation();
71
     delivery.calculateCurrentVoyage();
65
     delivery.calculateCurrentVoyage();
79
     return lastEvent;
73
     return lastEvent;
80
   }
74
   }
81
 
75
 
82
-  private void calculateLastEvent(final List<HandlingEvent> handlingEvents) {
83
-    if (handlingEvents.isEmpty()) {
84
-      lastEvent =  null;
85
-    } else {
86
-      lastEvent = handlingEvents.get(handlingEvents.size() - 1);
87
-    }
88
-  }
89
-
90
   private void calculateTransportStatus() {
76
   private void calculateTransportStatus() {
91
     if (lastEvent == null) {
77
     if (lastEvent == null) {
92
       transportStatus = NOT_RECEIVED;
78
       transportStatus = NOT_RECEIVED;

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

81
     return sameValueAs(other);
81
     return sameValueAs(other);
82
   }
82
   }
83
 
83
 
84
+  HandlingActivity() {
85
+    // Needed by Hibernate
86
+  }
87
+  
84
 }
88
 }

+ 0
- 11
dddsample/src/main/java/se/citerus/dddsample/domain/model/handling/HandlingEvent.java Просмотреть файл

12
 import se.citerus.dddsample.domain.model.voyage.Voyage;
12
 import se.citerus.dddsample.domain.model.voyage.Voyage;
13
 import se.citerus.dddsample.domain.shared.DomainObjectUtils;
13
 import se.citerus.dddsample.domain.shared.DomainObjectUtils;
14
 
14
 
15
-import java.util.Comparator;
16
 import java.util.Date;
15
 import java.util.Date;
17
 
16
 
18
 /**
17
 /**
33
  */
32
  */
34
 public final class HandlingEvent implements DomainEvent<HandlingEvent> {
33
 public final class HandlingEvent implements DomainEvent<HandlingEvent> {
35
 
34
 
36
-  /**
37
-   * Comparator used to be able to sort HandlingEvents according to their completion time
38
-   */
39
-  public static final Comparator<HandlingEvent> BY_COMPLETION_TIME_COMPARATOR =
40
-    new Comparator<HandlingEvent>() {
41
-      public int compare(final HandlingEvent he1, final HandlingEvent he2) {
42
-        return he1.completionTime().compareTo(he2.completionTime());
43
-      }
44
-    };
45
-
46
   private Type type;
35
   private Type type;
47
   private Voyage voyage;
36
   private Voyage voyage;
48
   private Location location;
37
   private Location location;

+ 2
- 4
dddsample/src/main/java/se/citerus/dddsample/domain/model/handling/HandlingEventRepository.java Просмотреть файл

2
 
2
 
3
 import se.citerus.dddsample.domain.model.cargo.TrackingId;
3
 import se.citerus.dddsample.domain.model.cargo.TrackingId;
4
 
4
 
5
-import java.util.List;
6
-
7
 /**
5
 /**
8
  * Handling event repository.
6
  * Handling event repository.
9
  */
7
  */
19
 
17
 
20
   /**
18
   /**
21
    * @param trackingId cargo tracking id
19
    * @param trackingId cargo tracking id
22
-   * @return All handling events for this cargo
20
+   * @return The handling history of this cargo
23
    */
21
    */
24
-  List<HandlingEvent> findEventsForCargo(TrackingId trackingId);
22
+  HandlingHistory lookupHandlingHistoryOfCargo(TrackingId trackingId);
25
 
23
 
26
 }
24
 }

+ 74
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/model/handling/HandlingHistory.java Просмотреть файл

1
+package se.citerus.dddsample.domain.model.handling;
2
+
3
+import org.apache.commons.lang.Validate;
4
+import se.citerus.dddsample.domain.model.ValueObject;
5
+
6
+import java.util.*;
7
+import static java.util.Collections.sort;
8
+
9
+/**
10
+ * The handling history of a cargo.
11
+ *
12
+ */
13
+public class HandlingHistory implements ValueObject<HandlingHistory> {
14
+
15
+  private final List<HandlingEvent> handlingEvents;
16
+
17
+  public static final HandlingHistory EMPTY = new HandlingHistory(Collections.<HandlingEvent>emptyList());
18
+
19
+  public HandlingHistory(Collection<HandlingEvent> handlingEvents) {
20
+    Validate.notNull(handlingEvents, "Handling events are required");
21
+
22
+    this.handlingEvents = new ArrayList<HandlingEvent>(handlingEvents);
23
+  }
24
+
25
+  /**
26
+   * @return A distinct list (no duplicate registrations) of handling events, ordered by completion time.
27
+   */
28
+  public List<HandlingEvent> distinctEventsByCompletionTime() {
29
+    final List<HandlingEvent> ordered = new ArrayList<HandlingEvent>(
30
+      new HashSet<HandlingEvent>(handlingEvents)
31
+    );
32
+    sort(ordered, BY_COMPLETION_TIME_COMPARATOR);
33
+    return Collections.unmodifiableList(ordered);
34
+  }
35
+
36
+  /**
37
+   * @return Most recently completed event, or null if the delivery history is empty.
38
+   */
39
+  public HandlingEvent mostRecentlyCompletedEvent() {
40
+    final List<HandlingEvent> distinctEvents = distinctEventsByCompletionTime();
41
+    if (distinctEvents.isEmpty()) {
42
+      return null;
43
+    } else {
44
+      return distinctEvents.get(distinctEvents.size() - 1);
45
+    }
46
+  }
47
+
48
+  @Override
49
+  public boolean sameValueAs(HandlingHistory other) {
50
+    return other != null && this.handlingEvents.equals(other.handlingEvents);
51
+  }
52
+
53
+  @Override
54
+  public boolean equals(Object o) {
55
+    if (this == o) return true;
56
+    if (o == null || getClass() != o.getClass()) return false;
57
+
58
+    final HandlingHistory other = (HandlingHistory) o;
59
+    return sameValueAs(other);
60
+  }
61
+
62
+  @Override
63
+  public int hashCode() {
64
+    return handlingEvents.hashCode();
65
+  }
66
+
67
+  private static final Comparator<HandlingEvent> BY_COMPLETION_TIME_COMPARATOR =
68
+    new Comparator<HandlingEvent>() {
69
+      public int compare(final HandlingEvent he1, final HandlingEvent he2) {
70
+        return he1.completionTime().compareTo(he2.completionTime());
71
+      }
72
+    };
73
+
74
+}

+ 2
- 0
dddsample/src/main/java/se/citerus/dddsample/infrastructure/persistence/hibernate/CargoRepositoryHibernate.java Просмотреть файл

23
 
23
 
24
   public void store(Cargo cargo) {
24
   public void store(Cargo cargo) {
25
     getSession().saveOrUpdate(cargo);
25
     getSession().saveOrUpdate(cargo);
26
+    // Delete-orphan does not seem to work correctly when the parent is a component
26
     getSession().createSQLQuery("delete from Leg where cargo_id = null").executeUpdate();
27
     getSession().createSQLQuery("delete from Leg where cargo_id = null").executeUpdate();
27
   }
28
   }
28
 
29
 
29
   public TrackingId nextTrackingId() {
30
   public TrackingId nextTrackingId() {
31
+    // TODO use an actual DB sequence here, UUID is for in-mem
30
     final String random = UUID.randomUUID().toString().toUpperCase();
32
     final String random = UUID.randomUUID().toString().toUpperCase();
31
     return new TrackingId(
33
     return new TrackingId(
32
       random.substring(0, random.indexOf("-"))
34
       random.substring(0, random.indexOf("-"))

+ 7
- 7
dddsample/src/main/java/se/citerus/dddsample/infrastructure/persistence/hibernate/HandlingEventRepositoryHibernate.java Просмотреть файл

4
 import se.citerus.dddsample.domain.model.cargo.TrackingId;
4
 import se.citerus.dddsample.domain.model.cargo.TrackingId;
5
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
5
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
6
 import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
6
 import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
7
-
8
-import java.util.List;
7
+import se.citerus.dddsample.domain.model.handling.HandlingHistory;
9
 
8
 
10
 /**
9
 /**
11
  * Hibernate implementation of HandlingEventRepository.
10
  * Hibernate implementation of HandlingEventRepository.
15
 public class HandlingEventRepositoryHibernate extends HibernateRepository implements HandlingEventRepository {
14
 public class HandlingEventRepositoryHibernate extends HibernateRepository implements HandlingEventRepository {
16
 
15
 
17
   @Override
16
   @Override
18
-  public void store(HandlingEvent event) {
17
+  public void store(final HandlingEvent event) {
19
     getSession().save(event);
18
     getSession().save(event);
20
   }
19
   }
21
 
20
 
22
   @Override
21
   @Override
23
-  public List<HandlingEvent> findEventsForCargo(TrackingId tid) {
24
-    return getSession().createQuery(
22
+  public HandlingHistory lookupHandlingHistoryOfCargo(final TrackingId trackingId) {
23
+    return new HandlingHistory(getSession().createQuery(
25
             "from HandlingEvent where cargo.trackingId = :tid").
24
             "from HandlingEvent where cargo.trackingId = :tid").
26
-            setParameter("tid", tid).
27
-            list();
25
+            setParameter("tid", trackingId).
26
+            list()
27
+    );
28
   }
28
   }
29
 
29
 
30
 }
30
 }

+ 1
- 1
dddsample/src/main/java/se/citerus/dddsample/interfaces/tracking/CargoTrackingController.java Просмотреть файл

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

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

23
       </type>
23
       </type>
24
     </property>
24
     </property>
25
 
25
 
26
+    <component name="nextExpectedActivity" update="true">
27
+      <many-to-one name="location" column="next_expected_location_id" foreign-key="next_expected_location_fk"/>
28
+      <property name="type" column="next_expected_handling_event_type">
29
+        <type name="org.hibernate.type.EnumType">
30
+          <param name="enumClass">se.citerus.dddsample.domain.model.handling.HandlingEvent$Type</param>
31
+          <param name="type">12</param><!-- 12 is java.sql.Types.VARCHAR -->
32
+        </type>
33
+      </property>
34
+      <many-to-one name="voyage" column="next_expected_voyage_id" foreign-key="next_expected_voyage_fk"/>
35
+    </component>
36
+
26
     <component name="trackingId" unique="true" update="false">
37
     <component name="trackingId" unique="true" update="false">
27
       <property name="id" column="tracking_id"/>
38
       <property name="id" column="tracking_id"/>
28
     </component>
39
     </component>

+ 1
- 29
dddsample/src/main/webapp/WEB-INF/jsp/pub/track.jsp Просмотреть файл

41
       <p class="notify"><img src="${rc.contextPath}/images/error.png" alt="" />Cargo is misdirected</p>
41
       <p class="notify"><img src="${rc.contextPath}/images/error.png" alt="" />Cargo is misdirected</p>
42
     </c:if>
42
     </c:if>
43
     <c:if test="${not empty cargo.events}">
43
     <c:if test="${not empty cargo.events}">
44
-      <h3>Delivery History</h3>
45
-      <%--
46
-      <table cellspacing="4">
47
-        <thead>
48
-          <tr>
49
-            <td>Event</td>
50
-            <td>Location</td>
51
-            <td>Time</td>
52
-            <td>Voyage number</td>
53
-            <td></td>
54
-          </tr>
55
-        </thead>
56
-        <tbody>
57
-          <c:forEach items="${cargo.events}" var="event">
58
-            <tr class="event-type-${event.type}">
59
-              <td>${event.type}</td>
60
-              <td>${event.location}</td>
61
-              <td>${event.time}</td>
62
-              <td>${event.voyageNumber}</td>
63
-              <td>
64
-                <img src="${rc.contextPath}/images/${event.expected ? "tick" : "cross"}.png" alt=""/>
65
-              </td>
66
-            </tr>
67
-          </c:forEach>
68
-        </tbody>
69
-      </table>
70
-      --%>
44
+      <h3>Handling History</h3>
71
         <ul style="list-style-type: none;">
45
         <ul style="list-style-type: none;">
72
             <c:forEach items="${cargo.events}" var="leg">
46
             <c:forEach items="${cargo.events}" var="leg">
73
             <li>
47
             <li>
76
             </li>
50
             </li>
77
             </c:forEach>
51
             </c:forEach>
78
         </ul>
52
         </ul>
79
-
80
-
81
     </c:if>
53
     </c:if>
82
   </div>
54
   </div>
83
   </c:if>
55
   </c:if>

+ 15
- 14
dddsample/src/test/java/se/citerus/dddsample/domain/model/cargo/CargoTest.java Просмотреть файл

5
 import static se.citerus.dddsample.domain.model.cargo.RoutingStatus.*;
5
 import static se.citerus.dddsample.domain.model.cargo.RoutingStatus.*;
6
 import static se.citerus.dddsample.domain.model.cargo.TransportStatus.NOT_RECEIVED;
6
 import static se.citerus.dddsample.domain.model.cargo.TransportStatus.NOT_RECEIVED;
7
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
7
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
8
+import se.citerus.dddsample.domain.model.handling.HandlingHistory;
8
 import se.citerus.dddsample.domain.model.location.Location;
9
 import se.citerus.dddsample.domain.model.location.Location;
9
 import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
10
 import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
10
 import se.citerus.dddsample.domain.model.voyage.Voyage;
11
 import se.citerus.dddsample.domain.model.voyage.Voyage;
118
     // Adding an event unrelated to unloading at final destination
119
     // Adding an event unrelated to unloading at final destination
119
     events.add(
120
     events.add(
120
       new HandlingEvent(cargo, new Date(10), new Date(), HandlingEvent.Type.RECEIVE, HANGZOU));
121
       new HandlingEvent(cargo, new Date(10), new Date(), HandlingEvent.Type.RECEIVE, HANGZOU));
121
-    cargo.deriveDeliveryProgress(events);
122
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
122
     assertFalse(cargo.isUnloadedAtDestination());
123
     assertFalse(cargo.isUnloadedAtDestination());
123
 
124
 
124
     Voyage voyage = new Voyage.Builder(new VoyageNumber("0123"), HANGZOU).
125
     Voyage voyage = new Voyage.Builder(new VoyageNumber("0123"), HANGZOU).
128
     // Adding an unload event, but not at the final destination
129
     // Adding an unload event, but not at the final destination
129
     events.add(
130
     events.add(
130
       new HandlingEvent(cargo, new Date(20), new Date(), HandlingEvent.Type.UNLOAD, TOKYO, voyage));
131
       new HandlingEvent(cargo, new Date(20), new Date(), HandlingEvent.Type.UNLOAD, TOKYO, voyage));
131
-    cargo.deriveDeliveryProgress(events);
132
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
132
     assertFalse(cargo.isUnloadedAtDestination());
133
     assertFalse(cargo.isUnloadedAtDestination());
133
 
134
 
134
     // Adding an event in the final destination, but not unload
135
     // Adding an event in the final destination, but not unload
135
     events.add(
136
     events.add(
136
       new HandlingEvent(cargo, new Date(30), new Date(), HandlingEvent.Type.CUSTOMS, NEWYORK));
137
       new HandlingEvent(cargo, new Date(30), new Date(), HandlingEvent.Type.CUSTOMS, NEWYORK));
137
-    cargo.deriveDeliveryProgress(events);
138
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
138
     assertFalse(cargo.isUnloadedAtDestination());
139
     assertFalse(cargo.isUnloadedAtDestination());
139
 
140
 
140
     // Finally, cargo is unloaded at final destination
141
     // Finally, cargo is unloaded at final destination
141
     events.add(
142
     events.add(
142
       new HandlingEvent(cargo, new Date(40), new Date(), HandlingEvent.Type.UNLOAD, NEWYORK, voyage));
143
       new HandlingEvent(cargo, new Date(40), new Date(), HandlingEvent.Type.UNLOAD, NEWYORK, voyage));
143
-    cargo.deriveDeliveryProgress(events);
144
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
144
     assertTrue(cargo.isUnloadedAtDestination());
145
     assertTrue(cargo.isUnloadedAtDestination());
145
   }
146
   }
146
 
147
 
150
 
151
 
151
     HandlingEvent he = new HandlingEvent(cargo, getDate("2007-12-01"), new Date(), HandlingEvent.Type.RECEIVE, STOCKHOLM);
152
     HandlingEvent he = new HandlingEvent(cargo, getDate("2007-12-01"), new Date(), HandlingEvent.Type.RECEIVE, STOCKHOLM);
152
     events.add(he);
153
     events.add(he);
153
-    cargo.deriveDeliveryProgress(events);
154
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
154
 
155
 
155
     return cargo;
156
     return cargo;
156
   }
157
   }
159
     final Cargo cargo = populateCargoOffMelbourne();
160
     final Cargo cargo = populateCargoOffMelbourne();
160
 
161
 
161
     events.add(new HandlingEvent(cargo, getDate("2007-12-09"), new Date(), HandlingEvent.Type.CLAIM, MELBOURNE));
162
     events.add(new HandlingEvent(cargo, getDate("2007-12-09"), new Date(), HandlingEvent.Type.CLAIM, MELBOURNE));
162
-    cargo.deriveDeliveryProgress(events);
163
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
163
 
164
 
164
     return cargo;
165
     return cargo;
165
   }
166
   }
174
     events.add(new HandlingEvent(cargo, getDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
175
     events.add(new HandlingEvent(cargo, getDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
175
     events.add(new HandlingEvent(cargo, getDate("2007-12-04"), new Date(), HandlingEvent.Type.UNLOAD, HONGKONG, voyage));
176
     events.add(new HandlingEvent(cargo, getDate("2007-12-04"), new Date(), HandlingEvent.Type.UNLOAD, HONGKONG, voyage));
176
 
177
 
177
-    cargo.deriveDeliveryProgress(events);
178
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
178
     return cargo;
179
     return cargo;
179
   }
180
   }
180
 
181
 
185
     events.add(new HandlingEvent(cargo, getDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, voyage));
186
     events.add(new HandlingEvent(cargo, getDate("2007-12-02"), new Date(), HandlingEvent.Type.UNLOAD, HAMBURG, voyage));
186
     events.add(new HandlingEvent(cargo, getDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
187
     events.add(new HandlingEvent(cargo, getDate("2007-12-03"), new Date(), HandlingEvent.Type.LOAD, HAMBURG, voyage));
187
 
188
 
188
-    cargo.deriveDeliveryProgress(events);
189
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
189
     return cargo;
190
     return cargo;
190
   }
191
   }
191
 
192
 
201
     events.add(new HandlingEvent(cargo, getDate("2007-12-05"), new Date(), HandlingEvent.Type.LOAD, HONGKONG, voyage));
202
     events.add(new HandlingEvent(cargo, getDate("2007-12-05"), new Date(), HandlingEvent.Type.LOAD, HONGKONG, voyage));
202
     events.add(new HandlingEvent(cargo, getDate("2007-12-07"), new Date(), HandlingEvent.Type.UNLOAD, MELBOURNE, voyage));
203
     events.add(new HandlingEvent(cargo, getDate("2007-12-07"), new Date(), HandlingEvent.Type.UNLOAD, MELBOURNE, voyage));
203
 
204
 
204
-    cargo.deriveDeliveryProgress(events);
205
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
205
     return cargo;
206
     return cargo;
206
   }
207
   }
207
 
208
 
216
 
217
 
217
     events.add(new HandlingEvent(cargo, getDate("2007-12-05"), new Date(), HandlingEvent.Type.LOAD, HONGKONG, voyage));
218
     events.add(new HandlingEvent(cargo, getDate("2007-12-05"), new Date(), HandlingEvent.Type.LOAD, HONGKONG, voyage));
218
 
219
 
219
-    cargo.deriveDeliveryProgress(events);
220
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
220
     return cargo;
221
     return cargo;
221
   }
222
   }
222
 
223
 
242
     handlingEvents.add(new HandlingEvent(cargo, new Date(130), new Date(140), HandlingEvent.Type.CUSTOMS, GOTHENBURG));
243
     handlingEvents.add(new HandlingEvent(cargo, new Date(130), new Date(140), HandlingEvent.Type.CUSTOMS, GOTHENBURG));
243
 
244
 
244
     events.addAll(handlingEvents);
245
     events.addAll(handlingEvents);
245
-    cargo.deriveDeliveryProgress(events);
246
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
246
     assertFalse(cargo.isMisdirected());
247
     assertFalse(cargo.isMisdirected());
247
 
248
 
248
     //Try a couple of failing ones
249
     //Try a couple of failing ones
252
 
253
 
253
     handlingEvents.add(new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.RECEIVE, HANGZOU));
254
     handlingEvents.add(new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.RECEIVE, HANGZOU));
254
     events.addAll(handlingEvents);
255
     events.addAll(handlingEvents);
255
-    cargo.deriveDeliveryProgress(events);
256
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
256
 
257
 
257
     assertTrue(cargo.isMisdirected());
258
     assertTrue(cargo.isMisdirected());
258
 
259
 
266
     handlingEvents.add(new HandlingEvent(cargo, new Date(70), new Date(80), HandlingEvent.Type.LOAD, ROTTERDAM, voyage));
267
     handlingEvents.add(new HandlingEvent(cargo, new Date(70), new Date(80), HandlingEvent.Type.LOAD, ROTTERDAM, voyage));
267
 
268
 
268
     events.addAll(handlingEvents);
269
     events.addAll(handlingEvents);
269
-    cargo.deriveDeliveryProgress(events);
270
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
270
 
271
 
271
     assertTrue(cargo.isMisdirected());
272
     assertTrue(cargo.isMisdirected());
272
 
273
 
280
     handlingEvents.add(new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.CLAIM, ROTTERDAM));
281
     handlingEvents.add(new HandlingEvent(cargo, new Date(), new Date(), HandlingEvent.Type.CLAIM, ROTTERDAM));
281
 
282
 
282
     events.addAll(handlingEvents);
283
     events.addAll(handlingEvents);
283
-    cargo.deriveDeliveryProgress(events);
284
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
284
 
285
 
285
     assertTrue(cargo.isMisdirected());
286
     assertTrue(cargo.isMisdirected());
286
   }
287
   }

+ 45
- 0
dddsample/src/test/java/se/citerus/dddsample/domain/model/handling/HandlingHistoryTest.java Просмотреть файл

1
+package se.citerus.dddsample.domain.model.handling;
2
+
3
+import junit.framework.TestCase;
4
+import static se.citerus.dddsample.application.util.DateTestUtil.toDate;
5
+import se.citerus.dddsample.domain.model.cargo.Cargo;
6
+import se.citerus.dddsample.domain.model.cargo.RouteSpecification;
7
+import se.citerus.dddsample.domain.model.cargo.TrackingId;
8
+import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
9
+import se.citerus.dddsample.domain.model.voyage.Voyage;
10
+import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
11
+
12
+import static java.util.Arrays.asList;
13
+import java.util.Date;
14
+
15
+
16
+public class HandlingHistoryTest extends TestCase {
17
+  Cargo cargo;
18
+  Voyage voyage;
19
+  HandlingEvent event1;
20
+  HandlingEvent event1duplicate;
21
+  HandlingEvent event2;
22
+  HandlingHistory handlingHistory;
23
+
24
+  protected void setUp() throws Exception {
25
+    cargo = new Cargo(new TrackingId("ABC"), new RouteSpecification(SHANGHAI, DALLAS, toDate("2009-04-01")));
26
+    voyage = new Voyage.Builder(new VoyageNumber("X25"), HONGKONG).
27
+      addMovement(SHANGHAI, new Date(), new Date()).
28
+      addMovement(DALLAS, new Date(), new Date()).
29
+      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));
35
+  }
36
+
37
+  public void testDistinctEventsByCompletionTime() {
38
+    assertEquals(asList(event1, event2), handlingHistory.distinctEventsByCompletionTime());
39
+  }
40
+
41
+  public void testMostRecentlyCompletedEvent() {
42
+    assertEquals(event2, handlingHistory.mostRecentlyCompletedEvent());
43
+  }
44
+  
45
+}

+ 1
- 1
dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/hibernate/CargoRepositoryTest.java Просмотреть файл

46
 
46
 
47
     assertNotNull(cargo.delivery());
47
     assertNotNull(cargo.delivery());
48
 
48
 
49
-    final List<HandlingEvent> events = handlingEventRepository.findEventsForCargo(trackingId);
49
+    final List<HandlingEvent> events = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId).distinctEventsByCompletionTime();
50
     assertEquals(2, events.size());
50
     assertEquals(2, events.size());
51
 
51
 
52
     HandlingEvent firstEvent = events.get(0);
52
     HandlingEvent firstEvent = events.get(0);

+ 2
- 1
dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/hibernate/HandlingEventRepositoryTest.java Просмотреть файл

52
   }
52
   }
53
 
53
 
54
   public void testFindEventsForCargo() throws Exception {
54
   public void testFindEventsForCargo() throws Exception {
55
-    List<HandlingEvent> handlingEvents = handlingEventRepository.findEventsForCargo(new TrackingId("XYZ"));
55
+    TrackingId trackingId = new TrackingId("XYZ");
56
+    List<HandlingEvent> handlingEvents = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId).distinctEventsByCompletionTime();
56
     assertEquals(12, handlingEvents.size());
57
     assertEquals(12, handlingEvents.size());
57
   }
58
   }
58
 
59
 

+ 7
- 7
dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/inmemory/CargoRepositoryInMem.java Просмотреть файл

5
 import se.citerus.dddsample.domain.model.cargo.CargoRepository;
5
 import se.citerus.dddsample.domain.model.cargo.CargoRepository;
6
 import se.citerus.dddsample.domain.model.cargo.RouteSpecification;
6
 import se.citerus.dddsample.domain.model.cargo.RouteSpecification;
7
 import se.citerus.dddsample.domain.model.cargo.TrackingId;
7
 import se.citerus.dddsample.domain.model.cargo.TrackingId;
8
-import se.citerus.dddsample.domain.model.handling.HandlingEvent;
9
 import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
8
 import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
9
+import se.citerus.dddsample.domain.model.handling.HandlingHistory;
10
 import se.citerus.dddsample.domain.model.location.Location;
10
 import se.citerus.dddsample.domain.model.location.Location;
11
 import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
11
 import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
12
 
12
 
59
   public void init() throws Exception {
59
   public void init() throws Exception {
60
     final TrackingId xyz = new TrackingId("XYZ");
60
     final TrackingId xyz = new TrackingId("XYZ");
61
     final Cargo cargoXYZ = createCargoWithDeliveryHistory(
61
     final Cargo cargoXYZ = createCargoWithDeliveryHistory(
62
-      xyz, STOCKHOLM, MELBOURNE, handlingEventRepository.findEventsForCargo(xyz));
62
+      xyz, STOCKHOLM, MELBOURNE, handlingEventRepository.lookupHandlingHistoryOfCargo(xyz));
63
     cargoDb.put(xyz.idString(), cargoXYZ);
63
     cargoDb.put(xyz.idString(), cargoXYZ);
64
 
64
 
65
     final TrackingId zyx = new TrackingId("ZYX");
65
     final TrackingId zyx = new TrackingId("ZYX");
66
     final Cargo cargoZYX = createCargoWithDeliveryHistory(
66
     final Cargo cargoZYX = createCargoWithDeliveryHistory(
67
-      zyx, MELBOURNE, STOCKHOLM, handlingEventRepository.findEventsForCargo(zyx));
67
+      zyx, MELBOURNE, STOCKHOLM, handlingEventRepository.lookupHandlingHistoryOfCargo(zyx));
68
     cargoDb.put(zyx.idString(), cargoZYX);
68
     cargoDb.put(zyx.idString(), cargoZYX);
69
 
69
 
70
     final TrackingId abc = new TrackingId("ABC");
70
     final TrackingId abc = new TrackingId("ABC");
71
     final Cargo cargoABC = createCargoWithDeliveryHistory(
71
     final Cargo cargoABC = createCargoWithDeliveryHistory(
72
-      abc, STOCKHOLM, HELSINKI, handlingEventRepository.findEventsForCargo(abc));
72
+      abc, STOCKHOLM, HELSINKI, handlingEventRepository.lookupHandlingHistoryOfCargo(abc));
73
     cargoDb.put(abc.idString(), cargoABC);
73
     cargoDb.put(abc.idString(), cargoABC);
74
 
74
 
75
     final TrackingId cba = new TrackingId("CBA");
75
     final TrackingId cba = new TrackingId("CBA");
76
     final Cargo cargoCBA = createCargoWithDeliveryHistory(
76
     final Cargo cargoCBA = createCargoWithDeliveryHistory(
77
-      cba, HELSINKI, STOCKHOLM, handlingEventRepository.findEventsForCargo(cba));
77
+      cba, HELSINKI, STOCKHOLM, handlingEventRepository.lookupHandlingHistoryOfCargo(cba));
78
     cargoDb.put(cba.idString(), cargoCBA);
78
     cargoDb.put(cba.idString(), cargoCBA);
79
   }
79
   }
80
 
80
 
85
   public static Cargo createCargoWithDeliveryHistory(TrackingId trackingId,
85
   public static Cargo createCargoWithDeliveryHistory(TrackingId trackingId,
86
                                                      Location origin,
86
                                                      Location origin,
87
                                                      Location destination,
87
                                                      Location destination,
88
-                                                     Collection<HandlingEvent> events) {
88
+                                                     HandlingHistory handlingHistory) {
89
 
89
 
90
     final RouteSpecification routeSpecification = new RouteSpecification(origin, destination, new Date());
90
     final RouteSpecification routeSpecification = new RouteSpecification(origin, destination, new Date());
91
     final Cargo cargo = new Cargo(trackingId, routeSpecification);
91
     final Cargo cargo = new Cargo(trackingId, routeSpecification);
92
-    cargo.deriveDeliveryProgress(new ArrayList<HandlingEvent>(events));
92
+    cargo.deriveDeliveryProgress(handlingHistory);
93
 
93
 
94
     return cargo;
94
     return cargo;
95
   }
95
   }

+ 4
- 4
dddsample/src/test/java/se/citerus/dddsample/infrastructure/persistence/inmemory/HandlingEventRepositoryInMem.java Просмотреть файл

6
 import se.citerus.dddsample.domain.model.cargo.TrackingId;
6
 import se.citerus.dddsample.domain.model.cargo.TrackingId;
7
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
7
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
8
 import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
8
 import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
9
+import se.citerus.dddsample.domain.model.handling.HandlingHistory;
9
 import se.citerus.dddsample.domain.model.voyage.CarrierMovement;
10
 import se.citerus.dddsample.domain.model.voyage.CarrierMovement;
10
 import se.citerus.dddsample.domain.model.voyage.VoyageRepository;
11
 import se.citerus.dddsample.domain.model.voyage.VoyageRepository;
11
 
12
 
12
 import java.text.DateFormat;
13
 import java.text.DateFormat;
13
 import java.text.ParseException;
14
 import java.text.ParseException;
14
 import java.text.SimpleDateFormat;
15
 import java.text.SimpleDateFormat;
15
-import java.util.ArrayList;
16
 import java.util.Date;
16
 import java.util.Date;
17
 import java.util.HashMap;
17
 import java.util.HashMap;
18
-import java.util.List;
19
 
18
 
20
 public class HandlingEventRepositoryInMem implements HandlingEventRepository {
19
 public class HandlingEventRepositoryInMem implements HandlingEventRepository {
21
   private final Log logger = LogFactory.getLog(getClass());
20
   private final Log logger = LogFactory.getLog(getClass());
86
     eventDB.put(event.cargo().trackingId().idString(), event);
85
     eventDB.put(event.cargo().trackingId().idString(), event);
87
   }
86
   }
88
 
87
 
89
-  public List<HandlingEvent> findEventsForCargo(TrackingId trackingId) {
90
-    return new ArrayList();
88
+  @Override
89
+  public HandlingHistory lookupHandlingHistoryOfCargo(TrackingId trackingId) {
90
+    return HandlingHistory.EMPTY;
91
   }
91
   }
92
 
92
 
93
   /**
93
   /**

+ 2
- 1
dddsample/src/test/java/se/citerus/dddsample/interfaces/tracking/CargoTrackingViewAdapterTest.java Просмотреть файл

6
 import se.citerus.dddsample.domain.model.cargo.RouteSpecification;
6
 import se.citerus.dddsample.domain.model.cargo.RouteSpecification;
7
 import se.citerus.dddsample.domain.model.cargo.TrackingId;
7
 import se.citerus.dddsample.domain.model.cargo.TrackingId;
8
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
8
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
9
+import se.citerus.dddsample.domain.model.handling.HandlingHistory;
9
 import static se.citerus.dddsample.domain.model.location.SampleLocations.HANGZOU;
10
 import static se.citerus.dddsample.domain.model.location.SampleLocations.HANGZOU;
10
 import static se.citerus.dddsample.domain.model.location.SampleLocations.HELSINKI;
11
 import static se.citerus.dddsample.domain.model.location.SampleLocations.HELSINKI;
11
 import static se.citerus.dddsample.domain.model.voyage.SampleVoyages.CM001;
12
 import static se.citerus.dddsample.domain.model.voyage.SampleVoyages.CM001;
23
     events.add(new HandlingEvent(cargo, new Date(3), new Date(4), HandlingEvent.Type.LOAD, HANGZOU, CM001));
24
     events.add(new HandlingEvent(cargo, new Date(3), new Date(4), HandlingEvent.Type.LOAD, HANGZOU, CM001));
24
     events.add(new HandlingEvent(cargo, new Date(5), new Date(6), HandlingEvent.Type.UNLOAD, HELSINKI, CM001));
25
     events.add(new HandlingEvent(cargo, new Date(5), new Date(6), HandlingEvent.Type.UNLOAD, HELSINKI, CM001));
25
 
26
 
26
-    cargo.deriveDeliveryProgress(events);
27
+    cargo.deriveDeliveryProgress(new HandlingHistory(events));
27
 
28
 
28
     StaticApplicationContext applicationContext = new StaticApplicationContext();
29
     StaticApplicationContext applicationContext = new StaticApplicationContext();
29
     applicationContext.addMessage("cargo.status.IN_PORT", Locale.GERMAN, "In port {0}");
30
     applicationContext.addMessage("cargo.status.IN_PORT", Locale.GERMAN, "In port {0}");

+ 15
- 7
dddsample/src/test/java/se/citerus/dddsample/scenario/CargoLifecycleScenarioTest.java Просмотреть файл

9
 import se.citerus.dddsample.application.impl.CargoInspectionServiceImpl;
9
 import se.citerus.dddsample.application.impl.CargoInspectionServiceImpl;
10
 import se.citerus.dddsample.application.impl.HandlingEventServiceImpl;
10
 import se.citerus.dddsample.application.impl.HandlingEventServiceImpl;
11
 import se.citerus.dddsample.domain.model.cargo.*;
11
 import se.citerus.dddsample.domain.model.cargo.*;
12
+import static se.citerus.dddsample.domain.model.cargo.RoutingStatus.*;
12
 import static se.citerus.dddsample.domain.model.cargo.TransportStatus.*;
13
 import static se.citerus.dddsample.domain.model.cargo.TransportStatus.*;
13
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
14
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
14
 import static se.citerus.dddsample.domain.model.handling.HandlingEvent.Type.*;
15
 import static se.citerus.dddsample.domain.model.handling.HandlingEvent.Type.*;
15
 import se.citerus.dddsample.domain.model.handling.HandlingEventFactory;
16
 import se.citerus.dddsample.domain.model.handling.HandlingEventFactory;
16
 import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
17
 import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
18
+import se.citerus.dddsample.domain.model.handling.HandlingHistory;
17
 import se.citerus.dddsample.domain.model.location.Location;
19
 import se.citerus.dddsample.domain.model.location.Location;
18
 import se.citerus.dddsample.domain.model.location.LocationRepository;
20
 import se.citerus.dddsample.domain.model.location.LocationRepository;
19
 import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
21
 import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
103
     Cargo cargo = cargoRepository.find(trackingId);
105
     Cargo cargo = cargoRepository.find(trackingId);
104
     assertNotNull(cargo);
106
     assertNotNull(cargo);
105
     assertEquals(NOT_RECEIVED, cargo.delivery().transportStatus());
107
     assertEquals(NOT_RECEIVED, cargo.delivery().transportStatus());
106
-    assertEquals(RoutingStatus.NOT_ROUTED, cargo.routingStatus());
108
+    assertEquals(NOT_ROUTED, cargo.routingStatus());
107
     assertFalse(cargo.isMisdirected());
109
     assertFalse(cargo.isMisdirected());
108
     assertNull(cargo.estimatedTimeOfArrival());
110
     assertNull(cargo.estimatedTimeOfArrival());
109
     assertNull(cargo.nextExpectedActivity());
111
     assertNull(cargo.nextExpectedActivity());
120
     Itinerary itinerary = selectPreferedItinerary(itineraries);
122
     Itinerary itinerary = selectPreferedItinerary(itineraries);
121
     cargo.assignToRoute(itinerary);
123
     cargo.assignToRoute(itinerary);
122
 
124
 
123
-    assertEquals(RoutingStatus.ROUTED, cargo.routingStatus());
125
+    assertEquals(NOT_RECEIVED, cargo.delivery().transportStatus());
126
+    assertEquals(ROUTED, cargo.routingStatus());
124
     assertNotNull(cargo.estimatedTimeOfArrival());
127
     assertNotNull(cargo.estimatedTimeOfArrival());
125
     assertEquals(new HandlingActivity(RECEIVE, HONGKONG), cargo.nextExpectedActivity());
128
     assertEquals(new HandlingActivity(RECEIVE, HONGKONG), cargo.nextExpectedActivity());
126
 
129
 
142
       new Date(100), trackingId, null, HONGKONG.unLocode(), RECEIVE
145
       new Date(100), trackingId, null, HONGKONG.unLocode(), RECEIVE
143
     );
146
     );
144
 
147
 
148
+    assertEquals(IN_PORT, cargo.delivery().transportStatus());
149
+    assertEquals(HONGKONG, cargo.delivery().lastKnownLocation());
150
+    
145
     // Next event: Load onto voyage CM003 in Hongkong
151
     // Next event: Load onto voyage CM003 in Hongkong
146
     handlingEventService.registerHandlingEvent(
152
     handlingEventService.registerHandlingEvent(
147
       new Date(200), trackingId, CM003.voyageNumber(), HONGKONG.unLocode(), LOAD
153
       new Date(200), trackingId, CM003.voyageNumber(), HONGKONG.unLocode(), LOAD
160
       because there is no voyage with the specified voyage number,
166
       because there is no voyage with the specified voyage number,
161
       and there's no location with the specified UN Locode either.
167
       and there's no location with the specified UN Locode either.
162
 
168
 
163
-      This attempt will be rejected and will not affet the cargo delivery in any way.
169
+      This attempt will be rejected and will not affect the cargo delivery in any way.
164
      */
170
      */
165
     final VoyageNumber noSuchVoyageNumber = new VoyageNumber("XX000");
171
     final VoyageNumber noSuchVoyageNumber = new VoyageNumber("XX000");
166
     final UnLocode noSuchUnLocode = new UnLocode("ZZZZZ");
172
     final UnLocode noSuchUnLocode = new UnLocode("ZZZZZ");
190
     cargo.specifyNewRoute(fromTokyo);
196
     cargo.specifyNewRoute(fromTokyo);
191
 
197
 
192
     // The old itinerary does not satisfy the new specification
198
     // The old itinerary does not satisfy the new specification
193
-    assertEquals(RoutingStatus.MISROUTED, cargo.routingStatus());
199
+    assertEquals(MISROUTED, cargo.routingStatus());
194
     assertNull(cargo.nextExpectedActivity());
200
     assertNull(cargo.nextExpectedActivity());
195
 
201
 
196
     // Repeat procedure of selecting one out of a number of possible routes satisfying the route spec
202
     // Repeat procedure of selecting one out of a number of possible routes satisfying the route spec
199
     cargo.assignToRoute(newItinerary);
205
     cargo.assignToRoute(newItinerary);
200
 
206
 
201
     // New itinerary should satisfy new route
207
     // New itinerary should satisfy new route
202
-    assertEquals(RoutingStatus.ROUTED, cargo.routingStatus());
208
+    assertEquals(ROUTED, cargo.routingStatus());
203
 
209
 
204
     // TODO we can't handle the face that after a reroute, the cargo isn't misdirected anymore
210
     // TODO we can't handle the face that after a reroute, the cargo isn't misdirected anymore
205
     //assertFalse(cargo.isMisdirected());
211
     //assertFalse(cargo.isMisdirected());
323
     handlingEventRepository = new HandlingEventRepository() {
329
     handlingEventRepository = new HandlingEventRepository() {
324
       Map<TrackingId, List<HandlingEvent>> eventMap = new HashMap<TrackingId, List<HandlingEvent>>();
330
       Map<TrackingId, List<HandlingEvent>> eventMap = new HashMap<TrackingId, List<HandlingEvent>>();
325
 
331
 
332
+      @Override
326
       public void store(HandlingEvent event) {
333
       public void store(HandlingEvent event) {
327
         final TrackingId trackingId = event.cargo().trackingId();
334
         final TrackingId trackingId = event.cargo().trackingId();
328
         List<HandlingEvent> list = eventMap.get(trackingId);
335
         List<HandlingEvent> list = eventMap.get(trackingId);
333
         list.add(event);
340
         list.add(event);
334
       }
341
       }
335
 
342
 
336
-      public List<HandlingEvent> findEventsForCargo(TrackingId trackingId) {
337
-        return eventMap.get(trackingId);
343
+      @Override
344
+      public HandlingHistory lookupHandlingHistoryOfCargo(TrackingId trackingId) {
345
+        return new HandlingHistory(eventMap.get(trackingId));
338
       }
346
       }
339
     };
347
     };
340
 
348