ソースを参照

Initial support for rescheduling a voyage and updating an itinerary to reflect schedule changes.

peter_backlund 17 年 前
コミット
25ea78762c

+ 24
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Itinerary.java ファイルの表示

@@ -3,8 +3,10 @@ package se.citerus.dddsample.domain.model.cargo;
3 3
 import org.apache.commons.lang.Validate;
4 4
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
5 5
 import se.citerus.dddsample.domain.model.location.Location;
6
+import se.citerus.dddsample.domain.model.voyage.Voyage;
6 7
 import se.citerus.dddsample.domain.shared.ValueObject;
7 8
 
9
+import java.util.ArrayList;
8 10
 import java.util.Collections;
9 11
 import java.util.Date;
10 12
 import java.util.List;
@@ -29,6 +31,10 @@ public class Itinerary implements ValueObject<Itinerary> {
29 31
     Validate.notEmpty(legs);
30 32
     Validate.noNullElements(legs);
31 33
 
34
+    // TODO
35
+    // Validate that legs are in proper order, connected
36
+    // and that load/unload times are doable?
37
+
32 38
     this.legs = legs;
33 39
   }
34 40
 
@@ -144,6 +150,24 @@ public class Itinerary implements ValueObject<Itinerary> {
144 150
   }
145 151
 
146 152
   /**
153
+   * @param rescheduledVoyage the voyage that has been rescheduled
154
+   * @return A new itinerary which is a copy of the old one, adjusted for the delay of the given voyage.
155
+   */
156
+  public Itinerary withRescheduledVoyage(final Voyage rescheduledVoyage) {
157
+    final List<Leg> newLegsList = new ArrayList<Leg>(this.legs.size());
158
+
159
+    for (Leg leg : this.legs) {
160
+      if (leg.voyage().sameIdentityAs(rescheduledVoyage)) {
161
+        newLegsList.add(leg.withRescheduledVoyage(rescheduledVoyage));
162
+      } else {
163
+        newLegsList.add(leg);
164
+      }
165
+    }
166
+
167
+    return new Itinerary(newLegsList);
168
+  }
169
+
170
+  /**
147 171
    * @param other itinerary to compare
148 172
    * @return <code>true</code> if the legs in this and the other itinerary are all equal.
149 173
    */

+ 41
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Leg.java ファイルの表示

@@ -4,6 +4,7 @@ import org.apache.commons.lang.Validate;
4 4
 import org.apache.commons.lang.builder.EqualsBuilder;
5 5
 import org.apache.commons.lang.builder.HashCodeBuilder;
6 6
 import se.citerus.dddsample.domain.model.location.Location;
7
+import se.citerus.dddsample.domain.model.voyage.CarrierMovement;
7 8
 import se.citerus.dddsample.domain.model.voyage.Voyage;
8 9
 import se.citerus.dddsample.domain.shared.ValueObject;
9 10
 
@@ -30,6 +31,32 @@ public class Leg implements ValueObject<Leg> {
30 31
     this.unloadTime = unloadTime;
31 32
   }
32 33
 
34
+  public Leg(final Voyage voyage, final Location loadLocation, final Location unloadLocation) {
35
+    Validate.notNull(voyage, "Voyage is required");
36
+    Validate.notNull(loadLocation, "Load location is required");
37
+    Validate.notNull(unloadLocation, "Unload location is required");
38
+    Validate.isTrue(!loadLocation.sameIdentityAs(unloadLocation));
39
+
40
+    CarrierMovement loadCm = null, unloadCm = null;
41
+    for (CarrierMovement carrierMovement : voyage.schedule().carrierMovements()) {
42
+      if (unloadCm == null && carrierMovement.departureLocation().sameIdentityAs(loadLocation)) {
43
+        loadCm = carrierMovement;
44
+      }
45
+      if (loadCm != null && carrierMovement.arrivalLocation().sameIdentityAs(unloadLocation)) {
46
+        unloadCm = carrierMovement;
47
+      }
48
+    }
49
+
50
+    Validate.notNull(loadCm, "Load location is not valid for this voyage");
51
+    Validate.notNull(unloadCm, "Unload location is not valid for this voyage");
52
+
53
+    this.voyage = voyage;
54
+    this.loadLocation = loadCm.departureLocation();
55
+    this.loadTime = loadCm.departureTime();
56
+    this.unloadLocation = unloadCm.arrivalLocation();
57
+    this.unloadTime = unloadCm.arrivalTime();
58
+  }
59
+
33 60
   public Voyage voyage() {
34 61
     return voyage;
35 62
   }
@@ -50,6 +77,14 @@ public class Leg implements ValueObject<Leg> {
50 77
     return new Date(unloadTime.getTime());
51 78
   }
52 79
 
80
+  /**
81
+   * @param voyage voyage
82
+   * @return A new leg with the same load and unload locations, but with updated load/unload times.
83
+   */
84
+  Leg withRescheduledVoyage(final Voyage voyage) {
85
+    return new Leg(voyage, loadLocation, unloadLocation);
86
+  }
87
+
53 88
   @Override
54 89
   public boolean sameValueAs(final Leg other) {
55 90
     return other != null && new EqualsBuilder().
@@ -82,6 +117,12 @@ public class Leg implements ValueObject<Leg> {
82 117
       toHashCode();
83 118
   }
84 119
 
120
+  @Override
121
+  public String toString() {
122
+    return "Load in " + loadLocation + " at " + loadTime +
123
+           ", unload in " + unloadLocation + " at " + unloadTime;
124
+  }
125
+
85 126
   Leg() {
86 127
     // Needed by Hibernate
87 128
   }

+ 13
- 1
dddsample/src/main/java/se/citerus/dddsample/domain/model/voyage/CarrierMovement.java ファイルの表示

@@ -73,6 +73,19 @@ public final class CarrierMovement implements ValueObject<CarrierMovement> {
73 73
     return new Date(arrivalTime.getTime());
74 74
   }
75 75
 
76
+  /**
77
+   * @param newDepartureTime new departure time
78
+   * @return A new CarrierMovement which is a copy of the old one but with a new departure time
79
+   */
80
+  CarrierMovement withDepartureTime(final Date newDepartureTime) {
81
+    return new CarrierMovement(
82
+      departureLocation,
83
+      arrivalLocation,
84
+      newDepartureTime,
85
+      arrivalTime
86
+    );
87
+  }
88
+
76 89
   @Override
77 90
   public boolean equals(final Object o) {
78 91
     if (this == o) return true;
@@ -109,5 +122,4 @@ public final class CarrierMovement implements ValueObject<CarrierMovement> {
109 122
 
110 123
   // Auto-generated surrogate key
111 124
   private Long id;
112
-
113 125
 }

+ 20
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/model/voyage/Voyage.java ファイルの表示

@@ -43,6 +43,25 @@ public class Voyage implements Entity<Voyage> {
43 43
     return schedule;
44 44
   }
45 45
 
46
+  /**
47
+   * @param location location from where the rescheduled departure happens.
48
+   * @param newDepartureTime new departure time
49
+   */
50
+  public void departureRescheduled(final Location location, final Date newDepartureTime) {
51
+    final int size = schedule.carrierMovements().size();
52
+    final List<CarrierMovement> carrierMovements = new ArrayList<CarrierMovement>(size);
53
+
54
+    for (CarrierMovement carrierMovement : schedule.carrierMovements()) {
55
+      if (carrierMovement.departureLocation().sameIdentityAs(location)) {
56
+        carrierMovements.add(carrierMovement.withDepartureTime(newDepartureTime));
57
+      } else {
58
+        carrierMovements.add(carrierMovement);
59
+      }
60
+    }
61
+
62
+    this.schedule = new Schedule(carrierMovements);
63
+  }
64
+
46 65
   @Override
47 66
   public int hashCode() {
48 67
     return voyageNumber.hashCode();
@@ -76,6 +95,7 @@ public class Voyage implements Entity<Voyage> {
76 95
   // Needed by Hibernate
77 96
   private Long id;
78 97
 
98
+
79 99
   /**
80 100
    * Builder pattern is used for incremental construction
81 101
    * of a Voyage aggregate. This serves as an aggregate factory. 

+ 1
- 9
dddsample/src/test/java/se/citerus/dddsample/domain/model/cargo/ItineraryTest.java ファイルの表示

@@ -3,7 +3,6 @@ package se.citerus.dddsample.domain.model.cargo;
3 3
 import junit.framework.TestCase;
4 4
 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
5 5
 import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
6
-import se.citerus.dddsample.domain.model.voyage.CarrierMovement;
7 6
 import se.citerus.dddsample.domain.model.voyage.Voyage;
8 7
 import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
9 8
 
@@ -13,10 +12,7 @@ import java.util.Date;
13 12
 import java.util.List;
14 13
 
15 14
 public class ItineraryTest extends TestCase {
16
-  private final CarrierMovement abc = new CarrierMovement(SHANGHAI, ROTTERDAM, new Date(), new Date());
17
-  private final CarrierMovement def = new CarrierMovement(ROTTERDAM, GOTHENBURG, new Date(), new Date());
18
-  private final CarrierMovement ghi = new CarrierMovement(ROTTERDAM, NEWYORK, new Date(), new Date());
19
-  private final CarrierMovement jkl = new CarrierMovement(SHANGHAI, HELSINKI, new Date(), new Date());
15
+
20 16
 
21 17
   Voyage voyage, wrongVoyage;
22 18
 
@@ -92,10 +88,6 @@ public class ItineraryTest extends TestCase {
92 88
     
93 89
   }
94 90
 
95
-  public void testNextExpectedEvent() throws Exception {
96
-
97
-  }
98
-
99 91
   public void testCreateItinerary() throws Exception {
100 92
     try {
101 93
       new Itinerary(new ArrayList<Leg>());

+ 47
- 6
dddsample/src/test/java/se/citerus/dddsample/domain/model/cargo/LegTest.java ファイルの表示

@@ -1,13 +1,54 @@
1 1
 package se.citerus.dddsample.domain.model.cargo;
2 2
 
3
-import junit.framework.TestCase;
3
+import static org.junit.Assert.assertEquals;
4
+import org.junit.Test;
5
+import static se.citerus.dddsample.application.util.DateTestUtil.toDate;
6
+import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
7
+import static se.citerus.dddsample.domain.model.voyage.SampleVoyages.NEW_YORK_TO_DALLAS;
8
+import se.citerus.dddsample.domain.model.voyage.Voyage;
4 9
 
5
-public class LegTest extends TestCase {
10
+public class LegTest {
6 11
 
12
+  final Voyage voyage = NEW_YORK_TO_DALLAS;
13
+
14
+  @Test(expected = IllegalArgumentException.class)
7 15
   public void testConstructor() throws Exception {
8
-    try {
9
-      new Leg(null,null,null,null,null);
10
-      fail("Should not accept null constructor arguments");
11
-    } catch (IllegalArgumentException expected) {}
16
+    new Leg(null, null, null);
17
+  }
18
+
19
+  @Test
20
+  public void legThatFollowsPartOfAVoyage() {
21
+    Leg chicagoToDallas = new Leg(voyage, CHICAGO, DALLAS);
22
+
23
+    assertEquals(chicagoToDallas.loadTime(), toDate("2008-10-24", "21:25"));
24
+    assertEquals(chicagoToDallas.loadLocation(), CHICAGO);
25
+    assertEquals(chicagoToDallas.unloadTime(), toDate("2008-10-25", "19:30"));
26
+    assertEquals(chicagoToDallas.unloadLocation(), DALLAS);
27
+  }
28
+
29
+  @Test
30
+  public void legThatFollowsAnEntireVoyage() {
31
+    Leg chicagoToDallas = new Leg(voyage, NEWYORK, DALLAS);
32
+
33
+    assertEquals(chicagoToDallas.loadTime(), toDate("2008-10-24", "07:00"));
34
+    assertEquals(chicagoToDallas.loadLocation(), NEWYORK);
35
+    assertEquals(chicagoToDallas.unloadTime(), toDate("2008-10-25", "19:30"));
36
+    assertEquals(chicagoToDallas.unloadLocation(), DALLAS);
12 37
   }
38
+
39
+  @Test(expected = IllegalArgumentException.class)
40
+  public void locationsInWrongOrder() {
41
+    new Leg(voyage, DALLAS, CHICAGO);
42
+  }
43
+
44
+  @Test(expected = IllegalArgumentException.class)
45
+  public void endLocationNotOnVoyage() {
46
+    new Leg(voyage, CHICAGO, HELSINKI);
47
+  }
48
+
49
+  @Test(expected = IllegalArgumentException.class)
50
+  public void startLocationNotOnVoyage() {
51
+    new Leg(voyage, HONGKONG, DALLAS);
52
+  }
53
+
13 54
 }

+ 49
- 0
dddsample/src/test/java/se/citerus/dddsample/scenario/VoyageRescheduledScenarioTest.java ファイルの表示

@@ -0,0 +1,49 @@
1
+/**
2
+ * Purpose
3
+ * @author peter
4
+ * @created 2009-aug-05
5
+ * $Id$
6
+ */
7
+package se.citerus.dddsample.scenario;
8
+
9
+import junit.framework.TestCase;
10
+import static se.citerus.dddsample.application.util.DateTestUtil.toDate;
11
+import se.citerus.dddsample.domain.model.cargo.Itinerary;
12
+import se.citerus.dddsample.domain.model.cargo.Leg;
13
+import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
14
+import static se.citerus.dddsample.domain.model.voyage.SampleVoyages.*;
15
+import se.citerus.dddsample.domain.model.voyage.Voyage;
16
+import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
17
+
18
+import static java.util.Arrays.asList;
19
+import java.util.Date;
20
+
21
+public class VoyageRescheduledScenarioTest extends TestCase {
22
+
23
+  public void testVoyageRescheduled() throws Exception {
24
+    // Creating new voyages to avoid rescheduling shared ones, breaking other tests
25
+    Voyage voyage1 = new Voyage(new VoyageNumber("V1"), HONGKONG_TO_NEW_YORK.schedule());
26
+    Voyage voyage2 = new Voyage(new VoyageNumber("V2"), NEW_YORK_TO_DALLAS.schedule());
27
+    Voyage voyage3 = new Voyage(new VoyageNumber("V3"), DALLAS_TO_HELSINKI.schedule());
28
+
29
+    Itinerary itinerary = new Itinerary(asList(
30
+      new Leg(voyage1, HANGZOU, NEWYORK),
31
+      new Leg(voyage2, NEWYORK, DALLAS),
32
+      new Leg(voyage3, DALLAS, STOCKHOLM)
33
+    ));
34
+
35
+    Date oldDepartureTime = toDate("2008-10-24", "07:00");
36
+
37
+    assertEquals(itinerary.legs().get(1).loadTime(), oldDepartureTime);
38
+    assertEquals(voyage2.schedule().carrierMovements().get(0).departureTime(), oldDepartureTime);
39
+
40
+    Date newDepartureTime = toDate("2008-10-24", "18:00");
41
+
42
+    voyage2.departureRescheduled(NEWYORK, newDepartureTime);
43
+    Itinerary newItinerary = itinerary.withRescheduledVoyage(voyage2);
44
+
45
+    assertEquals(voyage2.schedule().carrierMovements().get(0).departureTime(), newDepartureTime);
46
+    assertEquals(newItinerary.legs().get(1).loadTime(), newDepartureTime);
47
+  }
48
+
49
+}