1 package se.citerus.dddsample.domain.model.cargo;
2
3 import org.apache.commons.lang.Validate;
4 import org.apache.commons.lang.builder.EqualsBuilder;
5 import org.apache.commons.lang.builder.HashCodeBuilder;
6 import static se.citerus.dddsample.domain.model.cargo.RoutingStatus.*;
7 import static se.citerus.dddsample.domain.model.cargo.TransportStatus.*;
8 import se.citerus.dddsample.domain.model.handling.HandlingEvent;
9 import se.citerus.dddsample.domain.model.handling.HandlingHistory;
10 import se.citerus.dddsample.domain.model.location.Location;
11 import se.citerus.dddsample.domain.model.voyage.Voyage;
12 import se.citerus.dddsample.domain.shared.DomainObjectUtils;
13 import se.citerus.dddsample.domain.shared.ValueObject;
14
15 import java.util.Date;
16 import java.util.Iterator;
17
18
19
20
21
22
23 public class Delivery implements ValueObject<Delivery> {
24
25 private TransportStatus transportStatus;
26 private Location lastKnownLocation;
27 private Voyage currentVoyage;
28 private boolean misdirected;
29 private Date eta;
30 private HandlingActivity nextExpectedActivity;
31 private boolean isUnloadedAtDestination;
32 private RoutingStatus routingStatus;
33 private Date calculatedAt;
34 private HandlingEvent lastEvent;
35
36 private static final Date ETA_UNKOWN = null;
37 private static final HandlingActivity NO_ACTIVITY = null;
38
39
40
41
42
43
44
45
46
47
48 Delivery updateOnRouting(RouteSpecification routeSpecification, Itinerary itinerary) {
49 Validate.notNull(routeSpecification, "Route specification is required");
50
51 return new Delivery(this.lastEvent, itinerary, routeSpecification);
52 }
53
54
55
56
57
58
59
60
61
62
63 static Delivery derivedFrom(RouteSpecification routeSpecification, Itinerary itinerary, HandlingHistory handlingHistory) {
64 Validate.notNull(routeSpecification, "Route specification is required");
65 Validate.notNull(handlingHistory, "Delivery history is required");
66
67 final HandlingEvent lastEvent = handlingHistory.mostRecentlyCompletedEvent();
68
69 return new Delivery(lastEvent, itinerary, routeSpecification);
70 }
71
72
73
74
75
76
77
78
79 private Delivery(HandlingEvent lastEvent, Itinerary itinerary, RouteSpecification routeSpecification) {
80 this.calculatedAt = new Date();
81 this.lastEvent = lastEvent;
82
83 this.misdirected = calculateMisdirectionStatus(itinerary);
84 this.routingStatus = calculateRoutingStatus(itinerary, routeSpecification);
85 this.transportStatus = calculateTransportStatus();
86 this.lastKnownLocation = calculateLastKnownLocation();
87 this.currentVoyage = calculateCurrentVoyage();
88 this.eta = calculateEta(itinerary);
89 this.nextExpectedActivity = calculateNextExpectedActivity(routeSpecification, itinerary);
90 this.isUnloadedAtDestination = calculateUnloadedAtDestination(routeSpecification);
91 }
92
93
94
95
96 public TransportStatus transportStatus() {
97 return transportStatus;
98 }
99
100
101
102
103 public Location lastKnownLocation() {
104 return DomainObjectUtils.nullSafe(lastKnownLocation, Location.UNKNOWN);
105 }
106
107
108
109
110 public Voyage currentVoyage() {
111 return DomainObjectUtils.nullSafe(currentVoyage, Voyage.NONE);
112 }
113
114
115
116
117
118
119
120
121
122
123
124
125 public boolean isMisdirected() {
126 return misdirected;
127 }
128
129
130
131
132 public Date estimatedTimeOfArrival() {
133 if (eta != ETA_UNKOWN) {
134 return new Date(eta.getTime());
135 } else {
136 return ETA_UNKOWN;
137 }
138 }
139
140
141
142
143 public HandlingActivity nextExpectedActivity() {
144 return nextExpectedActivity;
145 }
146
147
148
149
150 public boolean isUnloadedAtDestination() {
151 return isUnloadedAtDestination;
152 }
153
154
155
156
157 public RoutingStatus routingStatus() {
158 return routingStatus;
159 }
160
161
162
163
164 public Date calculatedAt() {
165 return new Date(calculatedAt.getTime());
166 }
167
168
169
170
171
172
173
174 private TransportStatus calculateTransportStatus() {
175 if (lastEvent == null) {
176 return NOT_RECEIVED;
177 }
178
179 switch (lastEvent.type()) {
180 case LOAD:
181 return ONBOARD_CARRIER;
182 case UNLOAD:
183 case RECEIVE:
184 case CUSTOMS:
185 return IN_PORT;
186 case CLAIM:
187 return CLAIMED;
188 default:
189 return UNKNOWN;
190 }
191 }
192
193 private Location calculateLastKnownLocation() {
194 if (lastEvent != null) {
195 return lastEvent.location();
196 } else {
197 return null;
198 }
199 }
200
201 private Voyage calculateCurrentVoyage() {
202 if (transportStatus().equals(ONBOARD_CARRIER) && lastEvent != null) {
203 return lastEvent.voyage();
204 } else {
205 return null;
206 }
207 }
208
209 private boolean calculateMisdirectionStatus(Itinerary itinerary) {
210 if (lastEvent == null) {
211 return false;
212 } else {
213 return !itinerary.isExpected(lastEvent);
214 }
215 }
216
217 private Date calculateEta(Itinerary itinerary) {
218 if (onTrack()) {
219 return itinerary.finalArrivalDate();
220 } else {
221 return ETA_UNKOWN;
222 }
223 }
224
225 private HandlingActivity calculateNextExpectedActivity(RouteSpecification routeSpecification, Itinerary itinerary) {
226 if (!onTrack()) return NO_ACTIVITY;
227
228 if (lastEvent == null) return new HandlingActivity(HandlingEvent.Type.RECEIVE, routeSpecification.origin());
229
230 switch (lastEvent.type()) {
231
232 case LOAD:
233 for (Leg leg : itinerary.legs()) {
234 if (leg.loadLocation().sameIdentityAs(lastEvent.location())) {
235 return new HandlingActivity(HandlingEvent.Type.UNLOAD, leg.unloadLocation(), leg.voyage());
236 }
237 }
238
239 return NO_ACTIVITY;
240
241 case UNLOAD:
242 for (Iterator<Leg> it = itinerary.legs().iterator(); it.hasNext();) {
243 final Leg leg = it.next();
244 if (leg.unloadLocation().sameIdentityAs(lastEvent.location())) {
245 if (it.hasNext()) {
246 final Leg nextLeg = it.next();
247 return new HandlingActivity(HandlingEvent.Type.LOAD, nextLeg.loadLocation(), nextLeg.voyage());
248 } else {
249 return new HandlingActivity(HandlingEvent.Type.CLAIM, leg.unloadLocation());
250 }
251 }
252 }
253
254 return NO_ACTIVITY;
255
256 case RECEIVE:
257 final Leg firstLeg = itinerary.legs().iterator().next();
258 return new HandlingActivity(HandlingEvent.Type.LOAD, firstLeg.loadLocation(), firstLeg.voyage());
259
260 case CLAIM:
261 default:
262 return NO_ACTIVITY;
263 }
264 }
265
266 private RoutingStatus calculateRoutingStatus(Itinerary itinerary, RouteSpecification routeSpecification) {
267 if (itinerary == null) {
268 return NOT_ROUTED;
269 } else {
270 if (routeSpecification.isSatisfiedBy(itinerary)) {
271 return ROUTED;
272 } else {
273 return MISROUTED;
274 }
275 }
276 }
277
278 private boolean calculateUnloadedAtDestination(RouteSpecification routeSpecification) {
279 return lastEvent != null &&
280 HandlingEvent.Type.UNLOAD.sameValueAs(lastEvent.type()) &&
281 routeSpecification.destination().sameIdentityAs(lastEvent.location());
282 }
283
284 private boolean onTrack() {
285 return routingStatus.equals(ROUTED) && !misdirected;
286 }
287
288 @Override
289 public boolean sameValueAs(final Delivery other) {
290 return other != null && new EqualsBuilder().
291 append(this.transportStatus, other.transportStatus).
292 append(this.lastKnownLocation, other.lastKnownLocation).
293 append(this.currentVoyage, other.currentVoyage).
294 append(this.misdirected, other.misdirected).
295 append(this.eta, other.eta).
296 append(this.nextExpectedActivity, other.nextExpectedActivity).
297 append(this.isUnloadedAtDestination, other.isUnloadedAtDestination).
298 append(this.routingStatus, other.routingStatus).
299 append(this.calculatedAt, other.calculatedAt).
300 append(this.lastEvent, other.lastEvent).
301 isEquals();
302 }
303
304 @Override
305 public boolean equals(final Object o) {
306 if (this == o) return true;
307 if (o == null || getClass() != o.getClass()) return false;
308
309 final Delivery other = (Delivery) o;
310
311 return sameValueAs(other);
312 }
313
314 @Override
315 public int hashCode() {
316 return new HashCodeBuilder().
317 append(transportStatus).
318 append(lastKnownLocation).
319 append(currentVoyage).
320 append(misdirected).
321 append(eta).
322 append(nextExpectedActivity).
323 append(isUnloadedAtDestination).
324 append(routingStatus).
325 append(calculatedAt).
326 append(lastEvent).
327 toHashCode();
328 }
329
330 Delivery() {
331
332 }
333 }