allNodes) {
62 | Collections.shuffle(allNodes);
63 | final int total = allNodes.size();
64 | final int chunk = total > 4 ? 1 + random.nextInt(5) : total;
65 | return allNodes.subList(0, chunk);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/com/pathfinder/internal/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Internal parts of the pathfinder application.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/com/pathfinder/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | This is the pathfinder application context, which is separate from "our" application and context.
5 | Our domain model with cargo, itinerary, handling event etc does not exist here.
6 | The routing domain service implementation works against the API exposed by
7 | this context.
8 |
9 |
10 | It is not related to the core application at all, and is only part of this source tree for
11 | developer convenience.
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/Application.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample;
2 |
3 | import com.pathfinder.config.PathfinderApplicationContext;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 | import org.springframework.context.annotation.Import;
7 | import se.citerus.dddsample.config.DDDSampleApplicationContext;
8 |
9 | @Import({DDDSampleApplicationContext.class,
10 | PathfinderApplicationContext.class})
11 | @SpringBootApplication
12 | public class Application {
13 |
14 | public static void main(String[] args) throws Exception {
15 | SpringApplication.run(Application.class, args);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/application/ApplicationEvents.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.application;
2 |
3 | import se.citerus.dddsample.domain.model.cargo.Cargo;
4 | import se.citerus.dddsample.domain.model.handling.HandlingEvent;
5 | import se.citerus.dddsample.interfaces.handling.HandlingEventRegistrationAttempt;
6 |
7 | /**
8 | * This interface provides a way to let other parts
9 | * of the system know about events that have occurred.
10 | *
11 | * It may be implemented synchronously or asynchronously, using
12 | * for example JMS.
13 | */
14 | public interface ApplicationEvents {
15 |
16 | /**
17 | * A cargo has been handled.
18 | *
19 | * @param event handling event
20 | */
21 | void cargoWasHandled(HandlingEvent event);
22 |
23 | /**
24 | * A cargo has been misdirected.
25 | *
26 | * @param cargo cargo
27 | */
28 | void cargoWasMisdirected(Cargo cargo);
29 |
30 | /**
31 | * A cargo has arrived at its final destination.
32 | *
33 | * @param cargo cargo
34 | */
35 | void cargoHasArrived(Cargo cargo);
36 |
37 | /**
38 | * A handling event regitration attempt is received.
39 | *
40 | * @param attempt handling event registration attempt
41 | */
42 | void receivedHandlingEventRegistrationAttempt(HandlingEventRegistrationAttempt attempt);
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/application/BookingService.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.application;
2 |
3 | import se.citerus.dddsample.domain.model.cargo.Itinerary;
4 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
5 | import se.citerus.dddsample.domain.model.location.UnLocode;
6 |
7 | import java.time.Instant;
8 | import java.util.List;
9 |
10 | /**
11 | * Cargo booking service.
12 | */
13 | public interface BookingService {
14 |
15 | /**
16 | * Registers a new cargo in the tracking system, not yet routed.
17 | *
18 | * @param origin cargo origin
19 | * @param destination cargo destination
20 | * @param arrivalDeadline arrival deadline
21 | * @return Cargo tracking id
22 | */
23 | TrackingId bookNewCargo(UnLocode origin, UnLocode destination, Instant arrivalDeadline);
24 |
25 | /**
26 | * Requests a list of itineraries describing possible routes for this cargo.
27 | *
28 | * @param trackingId cargo tracking id
29 | * @return A list of possible itineraries for this cargo
30 | */
31 | List requestPossibleRoutesForCargo(TrackingId trackingId);
32 |
33 | /**
34 | * @param itinerary itinerary describing the selected route
35 | * @param trackingId cargo tracking id
36 | */
37 | void assignCargoToRoute(Itinerary itinerary, TrackingId trackingId);
38 |
39 | /**
40 | * Changes the destination of a cargo.
41 | *
42 | * @param trackingId cargo tracking id
43 | * @param unLocode UN locode of new destination
44 | */
45 | void changeDestination(TrackingId trackingId, UnLocode unLocode);
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/application/CargoInspectionService.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.application;
2 |
3 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
4 |
5 | /**
6 | * Cargo inspection service.
7 | */
8 | public interface CargoInspectionService {
9 |
10 | /**
11 | * Inspect cargo and send relevant notifications to interested parties,
12 | * for example if a cargo has been misdirected, or unloaded
13 | * at the final destination.
14 | *
15 | * @param trackingId cargo tracking id
16 | */
17 | void inspectCargo(TrackingId trackingId);
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/application/HandlingEventService.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.application;
2 |
3 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
4 | import se.citerus.dddsample.domain.model.handling.CannotCreateHandlingEventException;
5 | import se.citerus.dddsample.domain.model.handling.HandlingEvent;
6 | import se.citerus.dddsample.domain.model.location.UnLocode;
7 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
8 |
9 | import java.time.Instant;
10 |
11 |
12 | /**
13 | * Handling event service.
14 | */
15 | public interface HandlingEventService {
16 |
17 | /**
18 | * Registers a handling event in the system, and notifies interested
19 | * parties that a cargo has been handled.
20 | *
21 | * @param completionTime when the event was completed
22 | * @param trackingId cargo tracking id
23 | * @param voyageNumber voyage number
24 | * @param unLocode UN locode for the location where the event occurred
25 | * @param type type of event
26 | * @throws se.citerus.dddsample.domain.model.handling.CannotCreateHandlingEventException
27 | * if a handling event that represents an actual event that's relevant to a cargo we're tracking
28 | * can't be created from the parameters
29 | */
30 | void registerHandlingEvent(Instant completionTime,
31 | TrackingId trackingId,
32 | VoyageNumber voyageNumber,
33 | UnLocode unLocode,
34 | HandlingEvent.Type type) throws CannotCreateHandlingEventException;
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/application/impl/CargoInspectionServiceImpl.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.application.impl;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.transaction.annotation.Transactional;
6 | import se.citerus.dddsample.application.ApplicationEvents;
7 | import se.citerus.dddsample.application.CargoInspectionService;
8 | import se.citerus.dddsample.domain.model.cargo.Cargo;
9 | import se.citerus.dddsample.domain.model.cargo.CargoRepository;
10 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
11 | import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
12 | import se.citerus.dddsample.domain.model.handling.HandlingHistory;
13 |
14 | import java.lang.invoke.MethodHandles;
15 | import java.util.Objects;
16 |
17 | public class CargoInspectionServiceImpl implements CargoInspectionService {
18 |
19 | private final ApplicationEvents applicationEvents;
20 | private final CargoRepository cargoRepository;
21 | private final HandlingEventRepository handlingEventRepository;
22 | private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
23 |
24 | public CargoInspectionServiceImpl(final ApplicationEvents applicationEvents,
25 | final CargoRepository cargoRepository,
26 | final HandlingEventRepository handlingEventRepository) {
27 | this.applicationEvents = applicationEvents;
28 | this.cargoRepository = cargoRepository;
29 | this.handlingEventRepository = handlingEventRepository;
30 | }
31 |
32 | @Override
33 | @Transactional
34 | public void inspectCargo(final TrackingId trackingId) {
35 | Objects.requireNonNull(trackingId, "Tracking ID is required");
36 |
37 | final Cargo cargo = cargoRepository.find(trackingId);
38 | if (cargo == null) {
39 | logger.warn("Can't inspect non-existing cargo {}", trackingId);
40 | return;
41 | }
42 |
43 | final HandlingHistory handlingHistory = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId);
44 |
45 | cargo.deriveDeliveryProgress(handlingHistory);
46 |
47 | if (cargo.delivery().isMisdirected()) {
48 | applicationEvents.cargoWasMisdirected(cargo);
49 | }
50 |
51 | if (cargo.delivery().isUnloadedAtDestination()) {
52 | applicationEvents.cargoHasArrived(cargo);
53 | }
54 |
55 | cargoRepository.store(cargo);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/application/impl/HandlingEventServiceImpl.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.application.impl;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.transaction.annotation.Transactional;
6 | import se.citerus.dddsample.application.ApplicationEvents;
7 | import se.citerus.dddsample.application.HandlingEventService;
8 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
9 | import se.citerus.dddsample.domain.model.handling.CannotCreateHandlingEventException;
10 | import se.citerus.dddsample.domain.model.handling.HandlingEvent;
11 | import se.citerus.dddsample.domain.model.handling.HandlingEventFactory;
12 | import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
13 | import se.citerus.dddsample.domain.model.location.UnLocode;
14 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
15 |
16 | import java.lang.invoke.MethodHandles;
17 | import java.time.Instant;
18 |
19 | public class HandlingEventServiceImpl implements HandlingEventService {
20 |
21 | private final ApplicationEvents applicationEvents;
22 | private final HandlingEventRepository handlingEventRepository;
23 | private final HandlingEventFactory handlingEventFactory;
24 | private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
25 |
26 | public HandlingEventServiceImpl(final HandlingEventRepository handlingEventRepository,
27 | final ApplicationEvents applicationEvents,
28 | final HandlingEventFactory handlingEventFactory) {
29 | this.handlingEventRepository = handlingEventRepository;
30 | this.applicationEvents = applicationEvents;
31 | this.handlingEventFactory = handlingEventFactory;
32 | }
33 |
34 | @Override
35 | @Transactional(rollbackFor = CannotCreateHandlingEventException.class)
36 | public void registerHandlingEvent(final Instant completionTime,
37 | final TrackingId trackingId,
38 | final VoyageNumber voyageNumber,
39 | final UnLocode unLocode,
40 | final HandlingEvent.Type type) throws CannotCreateHandlingEventException {
41 | final Instant registrationTime = Instant.now();
42 | /* Using a factory to create a HandlingEvent (aggregate). This is where
43 | it is determined whether the incoming data, the attempt, actually is capable
44 | of representing a real handling event. */
45 | final HandlingEvent event = handlingEventFactory.createHandlingEvent(
46 | registrationTime, completionTime, trackingId, voyageNumber, unLocode, type
47 | );
48 |
49 | /* Store the new handling event, which updates the persistent
50 | state of the handling event aggregate (but not the cargo aggregate -
51 | that happens asynchronously!)
52 | */
53 | handlingEventRepository.store(event);
54 |
55 | /* Publish an event stating that a cargo has been handled. */
56 | applicationEvents.cargoWasHandled(event);
57 |
58 | logger.info("Registered handling event: {}", event);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/application/impl/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Implementation of the application layer.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/application/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | This is the application layer: code that's needed for the application to
5 | performs its tasks. It defines, or is defined by, use cases.
6 |
7 |
8 | It is thin in terms of knowledge of domain business logic,
9 | although it may be large in terms of lines of code.
10 | It coordinates the domain layer objects to perform the actual tasks.
11 |
12 |
13 | This layer is suitable for spanning transactions, security checks and high-level logging.
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/application/util/DateUtils.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.application.util;
2 |
3 | import java.time.Instant;
4 | import java.time.format.DateTimeParseException;
5 |
6 | /**
7 | * A few utils for working with Date in tests.
8 | *
9 | */
10 | public final class DateUtils {
11 |
12 | /**
13 | * @param date date string as yyyy-MM-dd
14 | * @return Date representation
15 | */
16 | public static Instant toDate(final String date) {
17 | return toDate(date, "00:00");
18 | }
19 |
20 | /**
21 | * @param date date string as yyyy-MM-dd
22 | * @param time time string as HH:mm
23 | * @return Date representation
24 | */
25 | public static Instant toDate(final String date, final String time) {
26 | try {
27 | return Instant.parse(date + "T" + time + ":00Z");
28 | } catch (DateTimeParseException e) {
29 | throw new RuntimeException(e);
30 | }
31 | }
32 |
33 | /**
34 | * Prevent instantiation.
35 | */
36 | private DateUtils() {
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/application/util/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Various utilities.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/cargo/CargoFactory.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.cargo;
2 |
3 | import se.citerus.dddsample.domain.model.location.Location;
4 | import se.citerus.dddsample.domain.model.location.LocationRepository;
5 | import se.citerus.dddsample.domain.model.location.UnLocode;
6 |
7 | import java.time.Instant;
8 |
9 |
10 | public class CargoFactory {
11 | private final LocationRepository locationRepository;
12 | private final CargoRepository cargoRepository;
13 |
14 | public CargoFactory(LocationRepository locationRepository, CargoRepository cargoRepository) {
15 | this.locationRepository = locationRepository;
16 | this.cargoRepository = cargoRepository;
17 | }
18 |
19 | public Cargo createCargo(UnLocode originUnLoCode, UnLocode destinationUnLoCode, Instant arrivalDeadline) {
20 | final TrackingId trackingId = cargoRepository.nextTrackingId();
21 | final Location origin = locationRepository.find(originUnLoCode);
22 | final Location destination = locationRepository.find(destinationUnLoCode);
23 | final RouteSpecification routeSpecification = new RouteSpecification(origin, destination, arrivalDeadline);
24 |
25 | return new Cargo(trackingId, routeSpecification);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/cargo/CargoRepository.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.cargo;
2 |
3 | import java.util.List;
4 |
5 | public interface CargoRepository {
6 |
7 | /**
8 | * Finds a cargo using given id.
9 | *
10 | * @param trackingId Id
11 | * @return Cargo if found, else {@code null}
12 | */
13 | Cargo find(TrackingId trackingId);
14 |
15 | /**
16 | * Finds all cargo.
17 | *
18 | * @return All cargo.
19 | */
20 | List getAll();
21 |
22 | /**
23 | * Saves given cargo.
24 | *
25 | * @param cargo cargo to save
26 | */
27 | void store(Cargo cargo);
28 |
29 | /**
30 | * @return A unique, generated tracking Id.
31 | */
32 | TrackingId nextTrackingId();
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/cargo/HandlingActivity.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.cargo;
2 |
3 | import jakarta.persistence.*;
4 | import org.apache.commons.lang3.builder.EqualsBuilder;
5 | import org.apache.commons.lang3.builder.HashCodeBuilder;
6 | import se.citerus.dddsample.domain.model.handling.HandlingEvent;
7 | import se.citerus.dddsample.domain.model.location.Location;
8 | import se.citerus.dddsample.domain.model.voyage.Voyage;
9 | import se.citerus.dddsample.domain.shared.ValueObject;
10 |
11 | import java.util.Objects;
12 |
13 | /**
14 | * A handling activity represents how and where a cargo can be handled,
15 | * and can be used to express predictions about what is expected to
16 | * happen to a cargo in the future.
17 | *
18 | */
19 | @Embeddable
20 | public class HandlingActivity implements ValueObject {
21 |
22 | // TODO make HandlingActivity a part of HandlingEvent too? There is some overlap.
23 |
24 | @Enumerated(value = EnumType.STRING)
25 | @Column(name = "next_expected_handling_event_type")
26 | public HandlingEvent.Type type;
27 |
28 | @ManyToOne()
29 | @JoinColumn(name = "next_expected_location_id")
30 | public Location location;
31 |
32 | @ManyToOne
33 | @JoinColumn(name = "next_expected_voyage_id")
34 | public Voyage voyage;
35 |
36 | public HandlingActivity(final HandlingEvent.Type type, final Location location) {
37 | Objects.requireNonNull(type, "Handling event type is required");
38 | Objects.requireNonNull(location, "Location is required");
39 |
40 | this.type = type;
41 | this.location = location;
42 | }
43 |
44 | public HandlingActivity(final HandlingEvent.Type type, final Location location, final Voyage voyage) {
45 | Objects.requireNonNull(type, "Handling event type is required");
46 | Objects.requireNonNull(location, "Location is required");
47 | Objects.requireNonNull(location, "Voyage is required");
48 |
49 | this.type = type;
50 | this.location = location;
51 | this.voyage = voyage;
52 | }
53 |
54 | public HandlingEvent.Type type() {
55 | return type;
56 | }
57 |
58 | public Location location() {
59 | return location;
60 | }
61 |
62 | public Voyage voyage() {
63 | return voyage;
64 | }
65 |
66 | @Override
67 | public boolean sameValueAs(final HandlingActivity other) {
68 | return other != null && new EqualsBuilder().
69 | append(this.type, other.type).
70 | append(this.location, other.location).
71 | append(this.voyage, other.voyage).
72 | isEquals();
73 | }
74 |
75 | @Override
76 | public int hashCode() {
77 | return new HashCodeBuilder().
78 | append(this.type).
79 | append(this.location).
80 | append(this.voyage).
81 | toHashCode();
82 | }
83 |
84 | @Override
85 | public boolean equals(final Object obj) {
86 | if (obj == this) return true;
87 | if (obj == null) return false;
88 | if (obj.getClass() != this.getClass()) return false;
89 |
90 | HandlingActivity other = (HandlingActivity) obj;
91 |
92 | return sameValueAs(other);
93 | }
94 |
95 | protected HandlingActivity() {
96 | // Needed by Hibernate
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/cargo/Leg.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.cargo;
2 |
3 | import jakarta.persistence.*;
4 | import org.apache.commons.lang3.Validate;
5 | import org.apache.commons.lang3.builder.EqualsBuilder;
6 | import org.apache.commons.lang3.builder.HashCodeBuilder;
7 | import se.citerus.dddsample.domain.model.location.Location;
8 | import se.citerus.dddsample.domain.model.voyage.Voyage;
9 | import se.citerus.dddsample.domain.shared.ValueObject;
10 |
11 | import java.time.Instant;
12 |
13 | /**
14 | * An itinerary consists of one or more legs.
15 | */
16 | @Entity(name = "Leg")
17 | @Table(name = "Leg")
18 | public class Leg implements ValueObject {
19 |
20 | @Id
21 | @GeneratedValue(strategy = GenerationType.AUTO)
22 | private long id;
23 |
24 | @ManyToOne
25 | @JoinColumn(name="voyage_id")
26 | private Voyage voyage;
27 |
28 | @ManyToOne
29 | @JoinColumn(name = "load_location_id")
30 | private Location loadLocation;
31 |
32 | @Column(name = "load_time")
33 | private Instant loadTime;
34 |
35 | @ManyToOne
36 | @JoinColumn(name = "unload_location_id")
37 | private Location unloadLocation;
38 |
39 | @Column(name = "unload_time")
40 | private Instant unloadTime;
41 |
42 | public Leg(Voyage voyage, Location loadLocation, Location unloadLocation, Instant loadTime, Instant unloadTime) {
43 | Validate.noNullElements(new Object[] {voyage, loadLocation, unloadLocation, loadTime, unloadTime});
44 |
45 | this.voyage = voyage;
46 | this.loadLocation = loadLocation;
47 | this.unloadLocation = unloadLocation;
48 | this.loadTime = loadTime;
49 | this.unloadTime = unloadTime;
50 | }
51 |
52 | public Voyage voyage() {
53 | return voyage;
54 | }
55 |
56 | public Location loadLocation() {
57 | return loadLocation;
58 | }
59 |
60 | public Location unloadLocation() {
61 | return unloadLocation;
62 | }
63 |
64 | public Instant loadTime() {
65 | return loadTime;
66 | }
67 |
68 | public Instant unloadTime() {
69 | return unloadTime;
70 | }
71 |
72 | @Override
73 | public boolean sameValueAs(final Leg other) {
74 | return other != null && new EqualsBuilder().
75 | append(this.voyage, other.voyage).
76 | append(this.loadLocation, other.loadLocation).
77 | append(this.unloadLocation, other.unloadLocation).
78 | append(this.loadTime, other.loadTime).
79 | append(this.unloadTime, other.unloadTime).
80 | isEquals();
81 | }
82 |
83 | @Override
84 | public boolean equals(final Object o) {
85 | if (this == o) return true;
86 | if (o == null || getClass() != o.getClass()) return false;
87 |
88 | Leg leg = (Leg) o;
89 |
90 | return sameValueAs(leg);
91 | }
92 |
93 | @Override
94 | public int hashCode() {
95 | return new HashCodeBuilder().
96 | append(voyage).
97 | append(loadLocation).
98 | append(unloadLocation).
99 | append(loadTime).
100 | append(unloadTime).
101 | toHashCode();
102 | }
103 |
104 | protected Leg() {
105 | // Needed by Hibernate
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/cargo/RoutingStatus.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.cargo;
2 |
3 | import se.citerus.dddsample.domain.shared.ValueObject;
4 |
5 | /**
6 | * Routing status.
7 | */
8 | public enum RoutingStatus implements ValueObject {
9 | NOT_ROUTED, ROUTED, MISROUTED;
10 |
11 | @Override
12 | public boolean sameValueAs(final RoutingStatus other) {
13 | return this.equals(other);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/cargo/TrackingId.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.cargo;
2 |
3 | import org.apache.commons.lang3.Validate;
4 | import se.citerus.dddsample.domain.shared.ValueObject;
5 |
6 | import java.util.Objects;
7 |
8 | /**
9 | * Uniquely identifies a particular cargo. Automatically generated by the application.
10 | *
11 | */
12 | public final class TrackingId implements ValueObject {
13 |
14 | private String id;
15 |
16 | /**
17 | * Constructor.
18 | *
19 | * @param id Id string.
20 | */
21 | public TrackingId(final String id) {
22 | Objects.requireNonNull(id);
23 | Validate.notEmpty(id);
24 | this.id = id;
25 | }
26 |
27 | /**
28 | * @return String representation of this tracking id.
29 | */
30 | public String idString() {
31 | return id;
32 | }
33 |
34 | @Override
35 | public boolean equals(Object o) {
36 | if (this == o) return true;
37 | if (o == null || getClass() != o.getClass()) return false;
38 |
39 | TrackingId other = (TrackingId) o;
40 |
41 | return sameValueAs(other);
42 | }
43 |
44 | @Override
45 | public int hashCode() {
46 | return id.hashCode();
47 | }
48 |
49 | @Override
50 | public boolean sameValueAs(TrackingId other) {
51 | return other != null && this.id.equals(other.id);
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | return id;
57 | }
58 |
59 | TrackingId() {
60 | // Needed by Hibernate
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/cargo/TransportStatus.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.cargo;
2 |
3 | import se.citerus.dddsample.domain.shared.ValueObject;
4 |
5 | /**
6 | * Represents the different transport statuses for a cargo.
7 | */
8 | public enum TransportStatus implements ValueObject {
9 | NOT_RECEIVED, IN_PORT, ONBOARD_CARRIER, CLAIMED, UNKNOWN;
10 |
11 | @Override
12 | public boolean sameValueAs(final TransportStatus other) {
13 | return this.equals(other);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/cargo/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The cargo aggregate. Cargo is the aggregate root.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/handling/CannotCreateHandlingEventException.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.handling;
2 |
3 | /**
4 | * If a {@link se.citerus.dddsample.domain.model.handling.HandlingEvent} can't be
5 | * created from a given set of parameters.
6 | *
7 | * It is a checked exception because it's not a programming error, but rather a
8 | * special case that the application is built to handle. It can occur during normal
9 | * program execution.
10 | */
11 | public class CannotCreateHandlingEventException extends Exception {
12 | public CannotCreateHandlingEventException(Exception e) {
13 | super(e);
14 | }
15 |
16 | public CannotCreateHandlingEventException() {
17 | super();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/handling/HandlingEventRepository.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.handling;
2 |
3 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
4 |
5 | /**
6 | * Handling event repository.
7 | */
8 | public interface HandlingEventRepository {
9 |
10 | /**
11 | * Stores a (new) handling event.
12 | *
13 | * @param event handling event to save
14 | */
15 | void store(HandlingEvent event);
16 |
17 |
18 | /**
19 | * @param trackingId cargo tracking id
20 | * @return The handling history of this cargo
21 | */
22 | HandlingHistory lookupHandlingHistoryOfCargo(TrackingId trackingId);
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/handling/HandlingHistory.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.handling;
2 |
3 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
4 | import se.citerus.dddsample.domain.shared.ValueObject;
5 |
6 | import java.util.*;
7 | import java.util.stream.Collectors;
8 |
9 | /**
10 | * The handling history of a cargo.
11 | */
12 | public class HandlingHistory implements ValueObject {
13 |
14 | private final List handlingEvents;
15 |
16 | public static final HandlingHistory EMPTY = new HandlingHistory(Collections.emptyList());
17 |
18 | public HandlingHistory(Collection handlingEvents) {
19 | Objects.requireNonNull(handlingEvents, "Handling events are required");
20 |
21 | this.handlingEvents = new ArrayList<>(handlingEvents);
22 | }
23 |
24 | /**
25 | * @return A distinct list (no duplicate registrations) of handling events, ordered by completion time.
26 | */
27 | public List distinctEventsByCompletionTime() {
28 | final List ordered = new ArrayList<>(
29 | new HashSet<>(handlingEvents)
30 | );
31 | ordered.sort(BY_COMPLETION_TIME_COMPARATOR);
32 | return Collections.unmodifiableList(ordered);
33 | }
34 |
35 | /**
36 | * @return Most recently completed event, or null if the delivery history is empty.
37 | */
38 | public HandlingEvent mostRecentlyCompletedEvent() {
39 | final List distinctEvents = distinctEventsByCompletionTime();
40 | if (distinctEvents.isEmpty()) {
41 | return null;
42 | } else {
43 | return distinctEvents.get(distinctEvents.size() - 1);
44 | }
45 | }
46 |
47 | /**
48 | * Filters handling history events to remove events for unrelated cargo.
49 | * @param trackingId the trackingId of the cargo to filter events for.
50 | * @return A new handling history with events matching the supplied tracking id.
51 | */
52 | public HandlingHistory filterOnCargo(TrackingId trackingId) {
53 | List events = handlingEvents.stream()
54 | .filter(he -> he.cargo().trackingId().sameValueAs(trackingId))
55 | .collect(Collectors.toList());
56 | return new HandlingHistory(events);
57 | }
58 |
59 | @Override
60 | public boolean sameValueAs(HandlingHistory other) {
61 | return other != null && this.handlingEvents.equals(other.handlingEvents);
62 | }
63 |
64 | @Override
65 | public boolean equals(Object o) {
66 | if (this == o) return true;
67 | if (o == null || getClass() != o.getClass()) return false;
68 |
69 | final HandlingHistory other = (HandlingHistory) o;
70 | return sameValueAs(other);
71 | }
72 |
73 | @Override
74 | public int hashCode() {
75 | return handlingEvents.hashCode();
76 | }
77 |
78 | private static final Comparator BY_COMPLETION_TIME_COMPARATOR =
79 | Comparator.comparing(HandlingEvent::completionTime);
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/handling/UnknownCargoException.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.handling;
2 |
3 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
4 |
5 | /**
6 | * Thrown when trying to register an event with an unknown tracking id.
7 | */
8 | public final class UnknownCargoException extends CannotCreateHandlingEventException {
9 |
10 | private final TrackingId trackingId;
11 |
12 | /**
13 | * @param trackingId cargo tracking id
14 | */
15 | public UnknownCargoException(final TrackingId trackingId) {
16 | this.trackingId = trackingId;
17 | }
18 |
19 | /**
20 | * {@inheritDoc}
21 | */
22 | @Override
23 | public String getMessage() {
24 | return "No cargo with tracking id " + trackingId.idString() + " exists in the system";
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/handling/UnknownLocationException.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.handling;
2 |
3 | import se.citerus.dddsample.domain.model.location.UnLocode;
4 |
5 | public class UnknownLocationException extends CannotCreateHandlingEventException {
6 |
7 | private final UnLocode unlocode;
8 |
9 | public UnknownLocationException(final UnLocode unlocode) {
10 | this.unlocode = unlocode;
11 | }
12 |
13 | @Override
14 | public String getMessage() {
15 | return "No location with UN locode " + unlocode.idString() + " exists in the system";
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/handling/UnknownVoyageException.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.handling;
2 |
3 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
4 |
5 | /**
6 | * Thrown when trying to register an event with an unknown carrier movement id.
7 | */
8 | public class UnknownVoyageException extends CannotCreateHandlingEventException {
9 |
10 | private final VoyageNumber voyageNumber;
11 |
12 | public UnknownVoyageException(VoyageNumber voyageNumber) {
13 | this.voyageNumber = voyageNumber;
14 | }
15 |
16 | @Override
17 | public String getMessage() {
18 | return "No voyage with number " + voyageNumber.idString() + " exists in the system";
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/handling/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The handling aggregate. HandlingEvent is the aggregate root.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/location/Location.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.location;
2 |
3 | import jakarta.persistence.*;
4 | import se.citerus.dddsample.domain.shared.DomainEntity;
5 |
6 | import java.util.Objects;
7 |
8 | /**
9 | * A location is our model is stops on a journey, such as cargo
10 | * origin or destination, or carrier movement endpoints.
11 | * It is uniquely identified by a UN Locode.
12 | *
13 | */
14 | @Entity(name = "Location")
15 | @Table(name = "Location")
16 | public final class Location implements DomainEntity {
17 |
18 | @Id
19 | @GeneratedValue(strategy = GenerationType.AUTO)
20 | private long id;
21 |
22 | @Column(nullable = false, unique = true, updatable = false)
23 | private String unlocode;
24 |
25 | @Column(nullable = false)
26 | private String name;
27 |
28 | /**
29 | * Special Location object that marks an unknown location.
30 | */
31 | public static final Location UNKNOWN = new Location(
32 | new UnLocode("XXXXX"), "Unknown location"
33 | );
34 |
35 | /**
36 | * Package-level constructor, visible for test and sample data purposes.
37 | *
38 | * @param unLocode UN Locode
39 | * @param name location name
40 | * @throws IllegalArgumentException if the UN Locode or name is null
41 | */
42 | public Location(final UnLocode unLocode, final String name) {
43 | Objects.requireNonNull(unLocode);
44 | Objects.requireNonNull(name);
45 |
46 | this.unlocode = unLocode.idString();
47 | this.name = name;
48 | }
49 |
50 | // Used by JPA
51 | public Location(String unloCode, String name) {
52 | this.unlocode = unloCode;
53 | this.name = name;
54 | }
55 |
56 | /**
57 | * @return UN Locode for this location.
58 | */
59 | public UnLocode unLocode() {
60 | return new UnLocode(unlocode);
61 | }
62 |
63 | /**
64 | * @return Actual name of this location, e.g. "Stockholm".
65 | */
66 | public String name() {
67 | return name;
68 | }
69 |
70 | public String code() {
71 | return unlocode;
72 | }
73 |
74 | public long id() {
75 | return id;
76 | }
77 |
78 | /**
79 | * @param object to compare
80 | * @return Since this is an entiy this will be true iff UN locodes are equal.
81 | */
82 | @Override
83 | public boolean equals(final Object object) {
84 | if (object == null) {
85 | return false;
86 | }
87 | if (this == object) {
88 | return true;
89 | }
90 | if (!(object instanceof Location other)) {
91 | return false;
92 | }
93 | return sameIdentityAs(other);
94 | }
95 |
96 | @Override
97 | public boolean sameIdentityAs(final Location other) {
98 | return this.unlocode.equals(other.unlocode);
99 | }
100 |
101 | /**
102 | * @return Hash code of UN locode.
103 | */
104 | @Override
105 | public int hashCode() {
106 | return unlocode.hashCode();
107 | }
108 |
109 | @Override
110 | public String toString() {
111 | return name + " [" + unlocode + "]";
112 | }
113 |
114 | Location() {
115 | // Needed by Hibernate
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/location/LocationRepository.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.location;
2 |
3 | import java.util.List;
4 |
5 | public interface LocationRepository {
6 |
7 | /**
8 | * Finds a location using given unlocode.
9 | *
10 | * @param unLocode UNLocode.
11 | * @return Location.
12 | */
13 | Location find(UnLocode unLocode);
14 |
15 | /**
16 | * Finds all locations.
17 | *
18 | * @return All locations.
19 | */
20 | List getAll();
21 |
22 | Location store(Location location);
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/location/UnLocode.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.location;
2 |
3 | import org.apache.commons.lang3.Validate;
4 | import se.citerus.dddsample.domain.shared.ValueObject;
5 |
6 | import java.util.Objects;
7 | import java.util.regex.Pattern;
8 |
9 | /**
10 | * United nations location code.
11 | *
12 | * http://www.unece.org/cefact/locode/
13 | * http://www.unece.org/cefact/locode/DocColumnDescription.htm#LOCODE
14 | */
15 | public final class UnLocode implements ValueObject {
16 |
17 | private String unlocode;
18 |
19 | // Country code is exactly two letters.
20 | // Location code is usually three letters, but may contain the numbers 2-9 as well
21 | private static final Pattern VALID_PATTERN = Pattern.compile("[a-zA-Z]{2}[a-zA-Z2-9]{3}");
22 |
23 | /**
24 | * Constructor.
25 | *
26 | * @param countryAndLocation Location string.
27 | */
28 | public UnLocode(final String countryAndLocation) {
29 | Objects.requireNonNull(countryAndLocation, "Country and location may not be null");
30 | Validate.isTrue(VALID_PATTERN.matcher(countryAndLocation).matches(),
31 | countryAndLocation + " is not a valid UN/LOCODE (does not match pattern)");
32 |
33 | this.unlocode = countryAndLocation.toUpperCase();
34 | }
35 |
36 | /**
37 | * @return country code and location code concatenated, always upper case.
38 | */
39 | public String idString() {
40 | return unlocode;
41 | }
42 |
43 | @Override
44 | public boolean equals(final Object o) {
45 | if (this == o) return true;
46 | if (o == null || getClass() != o.getClass()) return false;
47 |
48 | UnLocode other = (UnLocode) o;
49 |
50 | return sameValueAs(other);
51 | }
52 |
53 | @Override
54 | public int hashCode() {
55 | return unlocode.hashCode();
56 | }
57 |
58 | @Override
59 | public boolean sameValueAs(UnLocode other) {
60 | return other != null && this.unlocode.equals(other.unlocode);
61 | }
62 |
63 | @Override
64 | public String toString() {
65 | return idString();
66 | }
67 |
68 | UnLocode() {
69 | // Needed by Hibernate
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/location/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The location aggregate. Location is the aggregate root.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The domain model. This is the heart of the application. Each aggregate is contained in
5 | its own subpackage, along with the repository interface, factories and exceptions where
6 | applicable.
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/voyage/Schedule.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.voyage;
2 |
3 | import org.apache.commons.lang3.Validate;
4 | import org.apache.commons.lang3.builder.HashCodeBuilder;
5 | import se.citerus.dddsample.domain.shared.ValueObject;
6 |
7 | import java.util.Collections;
8 | import java.util.List;
9 | import java.util.Objects;
10 |
11 | /**
12 | * A voyage schedule.
13 | *
14 | */
15 | public class Schedule implements ValueObject {
16 |
17 | private List carrierMovements = Collections.emptyList();
18 |
19 | public static final Schedule EMPTY = new Schedule();
20 |
21 | public Schedule(final List carrierMovements) {
22 | Objects.requireNonNull(carrierMovements);
23 | Validate.noNullElements(carrierMovements);
24 | Validate.notEmpty(carrierMovements);
25 |
26 | this.carrierMovements = carrierMovements;
27 | }
28 |
29 | /**
30 | * @return Carrier movements.
31 | */
32 | public List carrierMovements() {
33 | return Collections.unmodifiableList(carrierMovements);
34 | }
35 |
36 | @Override
37 | public boolean sameValueAs(final Schedule other) {
38 | return other != null && this.carrierMovements.equals(other.carrierMovements);
39 | }
40 |
41 | @Override
42 | public boolean equals(final Object o) {
43 | if (this == o) return true;
44 | if (o == null || getClass() != o.getClass()) return false;
45 |
46 | final Schedule that = (Schedule) o;
47 |
48 | return sameValueAs(that);
49 | }
50 |
51 | @Override
52 | public int hashCode() {
53 | return new HashCodeBuilder().append(this.carrierMovements).toHashCode();
54 | }
55 |
56 | Schedule() {
57 | // Needed by Hibernate
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/voyage/VoyageNumber.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.voyage;
2 |
3 | import se.citerus.dddsample.domain.shared.ValueObject;
4 |
5 | import java.util.Objects;
6 |
7 | /**
8 | * Identifies a voyage.
9 | *
10 | */
11 | public class VoyageNumber implements ValueObject {
12 |
13 | private String number;
14 |
15 | public VoyageNumber(String number) {
16 | Objects.requireNonNull(number);
17 |
18 | this.number = number;
19 | }
20 |
21 | @Override
22 | public boolean equals(Object o) {
23 | if (this == o) return true;
24 | if (o == null) return false;
25 | if (!(o instanceof VoyageNumber)) return false;
26 |
27 | final VoyageNumber other = (VoyageNumber) o;
28 |
29 | return sameValueAs(other);
30 | }
31 |
32 | @Override
33 | public int hashCode() {
34 | return number.hashCode();
35 | }
36 |
37 | @Override
38 | public boolean sameValueAs(VoyageNumber other) {
39 | return other != null && this.number.equals(other.number);
40 | }
41 |
42 | @Override
43 | public String toString() {
44 | return number;
45 | }
46 |
47 | public String idString() {
48 | return number;
49 | }
50 |
51 | VoyageNumber() {
52 | // Needed by Hibernate
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/voyage/VoyageRepository.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.voyage;
2 |
3 | public interface VoyageRepository {
4 |
5 | /**
6 | * Finds a voyage using voyage number.
7 | *
8 | * @param voyageNumber voyage number
9 | * @return The voyage, or null if not found.
10 | */
11 | Voyage find(VoyageNumber voyageNumber);
12 |
13 | void store(Voyage voyage);
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/model/voyage/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The voyage aggregate. Voyage is the aggregate root.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The domain model, services and repository interfaces. This is the central part of the
5 | application. The ubiquitous language is used in classes, interfaces and method signatures,
6 | and every concept in here is familiar to a expert in the cargo shiping domain.
7 |
8 |
9 | There is no infrastructure or user interface related code here, except for things like
10 | transactional and security metadata which is likely to be relevant to a domain expert
11 | ("Either all of foo succeeds or none of it does", "In order to do bar you need to be a Supervisor", and so on).
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/service/RoutingService.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.service;
2 |
3 | import se.citerus.dddsample.domain.model.cargo.Itinerary;
4 | import se.citerus.dddsample.domain.model.cargo.RouteSpecification;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * Routing service.
10 | *
11 | */
12 | public interface RoutingService {
13 |
14 | /**
15 | * @param routeSpecification route specification
16 | * @return A list of itineraries that satisfy the specification. May be an empty list if no route is found.
17 | */
18 | List fetchRoutesForSpecification(RouteSpecification routeSpecification);
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/service/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Domain services. Those services that may be implemented purely using the domain layer
5 | have their implementations here, other implementations may be part of the aplication
6 | or infrastructure layers.
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/shared/AbstractSpecification.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.shared;
2 |
3 |
4 | /**
5 | * Abstract base implementation of composite {@link Specification} with default
6 | * implementations for {@code and}, {@code or} and {@code not}.
7 | */
8 | public abstract class AbstractSpecification implements Specification {
9 |
10 | /**
11 | * {@inheritDoc}
12 | */
13 | public abstract boolean isSatisfiedBy(T t);
14 |
15 | /**
16 | * {@inheritDoc}
17 | */
18 | public Specification and(final Specification specification) {
19 | return new AndSpecification(this, specification);
20 | }
21 |
22 | /**
23 | * {@inheritDoc}
24 | */
25 | public Specification or(final Specification specification) {
26 | return new OrSpecification(this, specification);
27 | }
28 |
29 | /**
30 | * {@inheritDoc}
31 | */
32 | public Specification not(final Specification specification) {
33 | return new NotSpecification(specification);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/shared/AndSpecification.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.shared;
2 |
3 | /**
4 | * AND specification, used to create a new specifcation that is the AND of two other specifications.
5 | */
6 | public class AndSpecification extends AbstractSpecification {
7 |
8 | private Specification spec1;
9 | private Specification spec2;
10 |
11 | /**
12 | * Create a new AND specification based on two other spec.
13 | *
14 | * @param spec1 Specification one.
15 | * @param spec2 Specification two.
16 | */
17 | public AndSpecification(final Specification spec1, final Specification spec2) {
18 | this.spec1 = spec1;
19 | this.spec2 = spec2;
20 | }
21 |
22 | /**
23 | * {@inheritDoc}
24 | */
25 | public boolean isSatisfiedBy(final T t) {
26 | return spec1.isSatisfiedBy(t) && spec2.isSatisfiedBy(t);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/shared/DomainEntity.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.shared;
2 |
3 | /**
4 | * An entity, as explained in the DDD book.
5 | *
6 | */
7 | public interface DomainEntity {
8 |
9 | /**
10 | * Entities compare by identity, not by attributes.
11 | *
12 | * @param other The other entity.
13 | * @return true if the identities are the same, regardless of other attributes.
14 | */
15 | boolean sameIdentityAs(T other);
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/shared/DomainEvent.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.shared;
2 |
3 | /**
4 | * A domain event is something that is unique, but does not have a lifecycle.
5 | * The identity may be explicit, for example the sequence number of a payment,
6 | * or it could be derived from various aspects of the event such as where, when and what
7 | * has happened.
8 | */
9 | public interface DomainEvent {
10 |
11 | /**
12 | * @param other The other domain event.
13 | * @return true
if the given domain event and this event are regarded as being the same event.
14 | */
15 | boolean sameEventAs(T other);
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/shared/NotSpecification.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.shared;
2 |
3 | /**
4 | * NOT decorator, used to create a new specifcation that is the inverse (NOT) of the given spec.
5 | */
6 | public class NotSpecification extends AbstractSpecification {
7 |
8 | private Specification spec1;
9 |
10 | /**
11 | * Create a new NOT specification based on another spec.
12 | *
13 | * @param spec1 Specification instance to not.
14 | */
15 | public NotSpecification(final Specification spec1) {
16 | this.spec1 = spec1;
17 | }
18 |
19 | /**
20 | * {@inheritDoc}
21 | */
22 | public boolean isSatisfiedBy(final T t) {
23 | return !spec1.isSatisfiedBy(t);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/shared/OrSpecification.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.shared;
2 |
3 | /**
4 | * OR specification, used to create a new specifcation that is the OR of two other specifications.
5 | */
6 | public class OrSpecification extends AbstractSpecification {
7 |
8 | private Specification spec1;
9 | private Specification spec2;
10 |
11 | /**
12 | * Create a new OR specification based on two other spec.
13 | *
14 | * @param spec1 Specification one.
15 | * @param spec2 Specification two.
16 | */
17 | public OrSpecification(final Specification spec1, final Specification spec2) {
18 | this.spec1 = spec1;
19 | this.spec2 = spec2;
20 | }
21 |
22 | /**
23 | * {@inheritDoc}
24 | */
25 | public boolean isSatisfiedBy(final T t) {
26 | return spec1.isSatisfiedBy(t) || spec2.isSatisfiedBy(t);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/shared/Specification.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.shared;
2 |
3 | /**
4 | * Specificaiton interface.
5 | *
6 | * Use {@link se.citerus.dddsample.domain.shared.AbstractSpecification} as base for creating specifications, and
7 | * only the method {@link #isSatisfiedBy(Object)} must be implemented.
8 | */
9 | public interface Specification {
10 |
11 | /**
12 | * Check if {@code t} is satisfied by the specification.
13 | *
14 | * @param t Object to test.
15 | * @return {@code true} if {@code t} satisfies the specification.
16 | */
17 | boolean isSatisfiedBy(T t);
18 |
19 | /**
20 | * Create a new specification that is the AND operation of {@code this} specification and another specification.
21 | * @param specification Specification to AND.
22 | * @return A new specification.
23 | */
24 | Specification and(Specification specification);
25 |
26 | /**
27 | * Create a new specification that is the OR operation of {@code this} specification and another specification.
28 | * @param specification Specification to OR.
29 | * @return A new specification.
30 | */
31 | Specification or(Specification specification);
32 |
33 | /**
34 | * Create a new specification that is the NOT operation of {@code this} specification.
35 | * @param specification Specification to NOT.
36 | * @return A new specification.
37 | */
38 | Specification not(Specification specification);
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/shared/ValueObject.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.shared;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * A value object, as described in the DDD book.
7 | *
8 | */
9 | public interface ValueObject extends Serializable {
10 |
11 | /**
12 | * Value objects compare by the values of their attributes, they don't have an identity.
13 | *
14 | * @param other The other value object.
15 | * @return true
if the given value object's and this value object's attributes are the same.
16 | */
17 | boolean sameValueAs(T other);
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/domain/shared/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Pattern interfaces and support code for the domain layer.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/infrastructure/messaging/jms/CargoHandledConsumer.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.messaging.jms;
2 |
3 | import jakarta.jms.Message;
4 | import jakarta.jms.MessageListener;
5 | import jakarta.jms.TextMessage;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import se.citerus.dddsample.application.CargoInspectionService;
9 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
10 |
11 | import java.lang.invoke.MethodHandles;
12 |
13 | /**
14 | * Consumes JMS messages and delegates notification of misdirected
15 | * cargo to the tracking service.
16 | *
17 | * This is a programmatic hook into the JMS infrastructure to
18 | * make cargo inspection message-driven.
19 | */
20 | public class CargoHandledConsumer implements MessageListener {
21 |
22 | private final CargoInspectionService cargoInspectionService;
23 | private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
24 |
25 | public CargoHandledConsumer(CargoInspectionService cargoInspectionService) {
26 | this.cargoInspectionService = cargoInspectionService;
27 | }
28 |
29 | @Override
30 | public void onMessage(final Message message) {
31 | try {
32 | final TextMessage textMessage = (TextMessage) message;
33 | final String trackingidString = textMessage.getText();
34 |
35 | cargoInspectionService.inspectCargo(new TrackingId(trackingidString));
36 | } catch (Exception e) {
37 | logger.error("Error consuming CargoHandled message", e);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/infrastructure/messaging/jms/HandlingEventRegistrationAttemptConsumer.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.messaging.jms;
2 |
3 | import jakarta.jms.Message;
4 | import jakarta.jms.MessageListener;
5 | import jakarta.jms.ObjectMessage;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import se.citerus.dddsample.application.HandlingEventService;
9 | import se.citerus.dddsample.interfaces.handling.HandlingEventRegistrationAttempt;
10 |
11 | import java.lang.invoke.MethodHandles;
12 |
13 | /**
14 | * Consumes handling event registration attempt messages and delegates to
15 | * proper registration.
16 | *
17 | */
18 | public class HandlingEventRegistrationAttemptConsumer implements MessageListener {
19 |
20 | private final HandlingEventService handlingEventService;
21 | private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
22 |
23 | public HandlingEventRegistrationAttemptConsumer(HandlingEventService handlingEventService) {
24 | this.handlingEventService = handlingEventService;
25 | }
26 |
27 | @Override
28 | public void onMessage(final Message message) {
29 | try {
30 | final ObjectMessage om = (ObjectMessage) message;
31 | HandlingEventRegistrationAttempt attempt = (HandlingEventRegistrationAttempt) om.getObject();
32 | handlingEventService.registerHandlingEvent(
33 | attempt.getCompletionTime(),
34 | attempt.getTrackingId(),
35 | attempt.getVoyageNumber(),
36 | attempt.getUnLocode(),
37 | attempt.getType()
38 | );
39 | } catch (Exception e) {
40 | logger.error("Error consuming HandlingEventRegistrationAttempt message", e);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/infrastructure/messaging/jms/JmsApplicationEventsImpl.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.messaging.jms;
2 |
3 | import jakarta.jms.Destination;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.jms.core.JmsOperations;
7 | import se.citerus.dddsample.application.ApplicationEvents;
8 | import se.citerus.dddsample.domain.model.cargo.Cargo;
9 | import se.citerus.dddsample.domain.model.handling.HandlingEvent;
10 | import se.citerus.dddsample.interfaces.handling.HandlingEventRegistrationAttempt;
11 |
12 | import java.lang.invoke.MethodHandles;
13 |
14 | /**
15 | * JMS based implementation.
16 | */
17 | public final class JmsApplicationEventsImpl implements ApplicationEvents {
18 | private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
19 |
20 | private final JmsOperations jmsOperations;
21 | private final Destination cargoHandledQueue;
22 | private final Destination misdirectedCargoQueue;
23 | private final Destination deliveredCargoQueue;
24 | private final Destination rejectedRegistrationAttemptsQueue; // TODO why is this unused?
25 | private final Destination handlingEventQueue;
26 |
27 | public JmsApplicationEventsImpl(JmsOperations jmsOperations, Destination cargoHandledQueue, Destination misdirectedCargoQueue, Destination deliveredCargoQueue, Destination rejectedRegistrationAttemptsQueue, Destination handlingEventQueue) {
28 | this.jmsOperations = jmsOperations;
29 | this.cargoHandledQueue = cargoHandledQueue;
30 | this.misdirectedCargoQueue = misdirectedCargoQueue;
31 | this.deliveredCargoQueue = deliveredCargoQueue;
32 | this.rejectedRegistrationAttemptsQueue = rejectedRegistrationAttemptsQueue;
33 | this.handlingEventQueue = handlingEventQueue;
34 | }
35 |
36 | @Override
37 | public void cargoWasHandled(final HandlingEvent event) {
38 | final Cargo cargo = event.cargo();
39 | logger.info("Cargo was handled {}", cargo);
40 | jmsOperations.send(cargoHandledQueue, session -> session.createTextMessage(cargo.trackingId().idString()));
41 | }
42 |
43 | @Override
44 | public void cargoWasMisdirected(final Cargo cargo) {
45 | logger.info("Cargo was misdirected {}", cargo);
46 | jmsOperations.send(misdirectedCargoQueue, session -> session.createTextMessage(cargo.trackingId().idString()));
47 | }
48 |
49 | @Override
50 | public void cargoHasArrived(final Cargo cargo) {
51 | logger.info("Cargo has arrived {}", cargo);
52 | jmsOperations.send(deliveredCargoQueue, session -> session.createTextMessage(cargo.trackingId().idString()));
53 | }
54 |
55 | @Override
56 | public void receivedHandlingEventRegistrationAttempt(final HandlingEventRegistrationAttempt attempt) {
57 | logger.info("Received handling event registration attempt {}", attempt);
58 | jmsOperations.send(handlingEventQueue, session -> session.createObjectMessage(attempt));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/infrastructure/messaging/jms/SimpleLoggingConsumer.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.messaging.jms;
2 |
3 | import jakarta.jms.Message;
4 | import jakarta.jms.MessageListener;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import java.lang.invoke.MethodHandles;
9 |
10 | public class SimpleLoggingConsumer implements MessageListener {
11 |
12 | private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
13 |
14 | @Override
15 | public void onMessage(Message message) {
16 | logger.debug("Received JMS message: {}", message);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/infrastructure/messaging/jms/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Asynchronous messaging implemented using JMS. This is part of the infrastructure.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/infrastructure/persistence/hibernate/CargoRepositoryHibernate.java:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/main/java/se/citerus/dddsample/infrastructure/persistence/hibernate/CargoRepositoryHibernate.java
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/infrastructure/persistence/jpa/CargoRepositoryJPA.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.persistence.jpa;
2 |
3 | import org.springframework.data.jpa.repository.Query;
4 | import org.springframework.data.repository.CrudRepository;
5 | import se.citerus.dddsample.domain.model.cargo.Cargo;
6 | import se.citerus.dddsample.domain.model.cargo.CargoRepository;
7 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
8 |
9 | import java.util.List;
10 | import java.util.stream.Collectors;
11 | import java.util.stream.StreamSupport;
12 |
13 | /**
14 | * Hibernate implementation of CargoRepository.
15 | */
16 | public interface CargoRepositoryJPA extends CrudRepository, CargoRepository {
17 |
18 | default Cargo find(TrackingId trackingId) {
19 | return findByTrackingId(trackingId.idString());
20 | }
21 |
22 | @Query("select c from Cargo c where c.trackingId = :trackingId")
23 | Cargo findByTrackingId(String trackingId);
24 |
25 | default void store(final Cargo cargo) {
26 | save(cargo);
27 | }
28 |
29 | default List getAll() {
30 | return StreamSupport.stream(findAll().spliterator(), false)
31 | .collect(Collectors.toList());
32 | }
33 |
34 | @Query(value = "SELECT UPPER(SUBSTR(CAST(UUID() AS VARCHAR(38)), 0, 9)) AS id FROM (VALUES(0))", nativeQuery = true)
35 | String nextTrackingIdString();
36 |
37 | default TrackingId nextTrackingId() {
38 | return new TrackingId(nextTrackingIdString());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/infrastructure/persistence/jpa/HandlingEventRepositoryJPA.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.persistence.jpa;
2 |
3 | import org.springframework.data.jpa.repository.Query;
4 | import org.springframework.data.repository.CrudRepository;
5 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
6 | import se.citerus.dddsample.domain.model.handling.HandlingEvent;
7 | import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
8 | import se.citerus.dddsample.domain.model.handling.HandlingHistory;
9 |
10 | import java.util.List;
11 |
12 | /**
13 | * Hibernate implementation of HandlingEventRepository.
14 | *
15 | */
16 | public interface HandlingEventRepositoryJPA extends CrudRepository, HandlingEventRepository {
17 |
18 | default void store(final HandlingEvent event) {
19 | save(event);
20 | }
21 |
22 | default HandlingHistory lookupHandlingHistoryOfCargo(final TrackingId trackingId) {
23 | return new HandlingHistory(getHandlingHistoryOfCargo(trackingId.idString()));
24 | }
25 |
26 | @Query("select he from HandlingEvent he where he.cargo.trackingId = :trackingId and he.location is not NULL")
27 | List getHandlingHistoryOfCargo(String trackingId);
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/infrastructure/persistence/jpa/LocationRepositoryJPA.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.persistence.jpa;
2 |
3 | import org.springframework.data.jpa.repository.Query;
4 | import org.springframework.data.repository.CrudRepository;
5 | import se.citerus.dddsample.domain.model.location.Location;
6 | import se.citerus.dddsample.domain.model.location.LocationRepository;
7 | import se.citerus.dddsample.domain.model.location.UnLocode;
8 |
9 | import java.util.List;
10 | import java.util.stream.Collectors;
11 | import java.util.stream.StreamSupport;
12 |
13 | public interface LocationRepositoryJPA extends CrudRepository, LocationRepository {
14 |
15 | default Location find(final UnLocode unLocode) {
16 | return findByUnLoCode(unLocode.idString());
17 | }
18 |
19 | @Query("select loc from Location loc where loc.unlocode = :unlocode")
20 | Location findByUnLoCode(String unlocode);
21 |
22 | @Override
23 | default List getAll() {
24 | return StreamSupport.stream(findAll().spliterator(), false)
25 | .collect(Collectors.toList());
26 | }
27 |
28 | default Location store(Location location) {
29 | return save(location);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/infrastructure/persistence/jpa/VoyageRepositoryJPA.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.persistence.jpa;
2 |
3 | import org.springframework.data.jpa.repository.Query;
4 | import org.springframework.data.repository.CrudRepository;
5 | import se.citerus.dddsample.domain.model.voyage.Voyage;
6 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
7 | import se.citerus.dddsample.domain.model.voyage.VoyageRepository;
8 |
9 | /**
10 | * Hibernate implementation of CarrierMovementRepository.
11 | */
12 | public interface VoyageRepositoryJPA extends CrudRepository, VoyageRepository {
13 |
14 | default Voyage find(final VoyageNumber voyageNumber) {
15 | return findByVoyageNumber(voyageNumber.idString());
16 | }
17 |
18 | @Query("select v from Voyage v where v.voyageNumber = :voyageNumber")
19 | Voyage findByVoyageNumber(String voyageNumber);
20 |
21 | @Override
22 | default void store(Voyage voyage) {
23 | save(voyage);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/infrastructure/persistence/jpa/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Hibernate implementations of the repository interfaces. This is part of the infrastructure.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/infrastructure/routing/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Communicates with the Pathfinder external routing service.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/infrastructure/sampledata/SampleLocations.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.sampledata;
2 |
3 | import se.citerus.dddsample.domain.model.location.Location;
4 | import se.citerus.dddsample.domain.model.location.UnLocode;
5 |
6 | import java.lang.reflect.Field;
7 | import java.util.ArrayList;
8 | import java.util.HashMap;
9 | import java.util.List;
10 | import java.util.Map;
11 |
12 | /**
13 | * Sample locations, for test purposes.
14 | *
15 | */
16 | public class SampleLocations {
17 |
18 | public static final Location HONGKONG = new Location(new UnLocode("CNHKG"), "Hongkong");
19 | public static final Location MELBOURNE = new Location(new UnLocode("AUMEL"), "Melbourne");
20 | public static final Location STOCKHOLM = new Location(new UnLocode("SESTO"), "Stockholm");
21 | public static final Location HELSINKI = new Location(new UnLocode("FIHEL"), "Helsinki");
22 | public static final Location CHICAGO = new Location(new UnLocode("USCHI"), "Chicago");
23 | public static final Location TOKYO = new Location(new UnLocode("JNTKO"), "Tokyo");
24 | public static final Location HAMBURG = new Location(new UnLocode("DEHAM"), "Hamburg");
25 | public static final Location SHANGHAI = new Location(new UnLocode("CNSHA"), "Shanghai");
26 | public static final Location ROTTERDAM = new Location(new UnLocode("NLRTM"), "Rotterdam");
27 | public static final Location GOTHENBURG = new Location(new UnLocode("SEGOT"), "Göteborg");
28 | public static final Location HANGZHOU = new Location(new UnLocode("CNHGH"), "Hangzhou");
29 | public static final Location NEWYORK = new Location(new UnLocode("USNYC"), "New York");
30 | public static final Location DALLAS = new Location(new UnLocode("USDAL"), "Dallas");
31 |
32 | public static final Map ALL = new HashMap<>();
33 |
34 | static {
35 | for (Field field : SampleLocations.class.getDeclaredFields()) {
36 | if (field.getType().equals(Location.class)) {
37 | try {
38 | Location location = (Location) field.get(null);
39 | ALL.put(location.unLocode(), location);
40 | } catch (IllegalAccessException e) {
41 | throw new RuntimeException(e);
42 | }
43 | }
44 | }
45 | }
46 |
47 | public static List getAll() {
48 | return new ArrayList<>(ALL.values());
49 | }
50 |
51 | public static Location lookup(UnLocode unLocode) {
52 | return ALL.get(unLocode);
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/facade/BookingServiceFacade.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.booking.facade;
2 |
3 | import se.citerus.dddsample.interfaces.booking.facade.dto.CargoRoutingDTO;
4 | import se.citerus.dddsample.interfaces.booking.facade.dto.LocationDTO;
5 | import se.citerus.dddsample.interfaces.booking.facade.dto.RouteCandidateDTO;
6 |
7 | import java.rmi.RemoteException;
8 | import java.time.Instant;
9 | import java.util.List;
10 |
11 | /**
12 | * This facade shields the domain layer - model, services, repositories -
13 | * from concerns about such things as the user interface.
14 | */
15 | public interface BookingServiceFacade {
16 |
17 | String bookNewCargo(String origin, String destination, Instant arrivalDeadline) throws RemoteException;
18 |
19 | CargoRoutingDTO loadCargoForRouting(String trackingId) throws RemoteException;
20 |
21 | void assignCargoToRoute(String trackingId, RouteCandidateDTO route) throws RemoteException;
22 |
23 | void changeDestination(String trackingId, String destinationUnLocode) throws RemoteException;
24 |
25 | List requestPossibleRoutesForCargo(String trackingId) throws RemoteException;
26 |
27 | List listShippingLocations() throws RemoteException;
28 |
29 | List listAllCargos() throws RemoteException;
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/facade/dto/CargoRoutingDTO.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.booking.facade.dto;
2 |
3 | import java.io.Serializable;
4 | import java.time.Instant;
5 | import java.time.ZoneOffset;
6 | import java.time.ZonedDateTime;
7 | import java.util.ArrayList;
8 | import java.util.Collections;
9 | import java.util.List;
10 |
11 | /**
12 | * DTO for registering and routing a cargo.
13 | */
14 | public final class CargoRoutingDTO implements Serializable {
15 |
16 | private final String trackingId;
17 | private final String origin;
18 | private final String finalDestination;
19 | private final Instant arrivalDeadline;
20 | private final boolean misrouted;
21 | private final List legs;
22 |
23 | /**
24 | * Constructor.
25 | *
26 | * @param trackingId
27 | * @param origin
28 | * @param finalDestination
29 | * @param arrivalDeadline
30 | * @param misrouted
31 | */
32 | public CargoRoutingDTO(String trackingId, String origin, String finalDestination, Instant arrivalDeadline, boolean misrouted) {
33 | this.trackingId = trackingId;
34 | this.origin = origin;
35 | this.finalDestination = finalDestination;
36 | this.arrivalDeadline = arrivalDeadline;
37 | this.misrouted = misrouted;
38 | this.legs = new ArrayList();
39 | }
40 |
41 | public String getTrackingId() {
42 | return trackingId;
43 | }
44 |
45 | public String getOrigin() {
46 | return origin;
47 | }
48 |
49 | public String getFinalDestination() {
50 | return finalDestination;
51 | }
52 |
53 | public void addLeg(String voyageNumber, String from, String to, Instant loadTime, Instant unloadTime) {
54 | legs.add(new LegDTO(voyageNumber, from, to, loadTime, unloadTime));
55 | }
56 |
57 | /**
58 | * @return An unmodifiable list DTOs.
59 | */
60 | public List getLegs() {
61 | return Collections.unmodifiableList(legs);
62 | }
63 |
64 | public boolean isMisrouted() {
65 | return misrouted;
66 | }
67 |
68 | public boolean isRouted() {
69 | return !legs.isEmpty();
70 | }
71 |
72 | public ZonedDateTime getArrivalDeadline() {
73 | return arrivalDeadline.atZone(ZoneOffset.UTC);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/facade/dto/LegDTO.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.booking.facade.dto;
2 |
3 | import java.io.Serializable;
4 | import java.time.Instant;
5 |
6 | /**
7 | * DTO for a leg in an itinerary.
8 | */
9 | public final class LegDTO implements Serializable {
10 |
11 | private final String voyageNumber;
12 | private final String from;
13 | private final String to;
14 | private final Instant loadTime;
15 | private final Instant unloadTime;
16 |
17 | /**
18 | * Constructor.
19 | *
20 | * @param voyageNumber
21 | * @param from
22 | * @param to
23 | * @param loadTime
24 | * @param unloadTime
25 | */
26 | public LegDTO(final String voyageNumber, final String from, final String to, Instant loadTime, Instant unloadTime) {
27 | this.voyageNumber = voyageNumber;
28 | this.from = from;
29 | this.to = to;
30 | this.loadTime = loadTime;
31 | this.unloadTime = unloadTime;
32 | }
33 |
34 | public String getVoyageNumber() {
35 | return voyageNumber;
36 | }
37 |
38 | public String getFrom() {
39 | return from;
40 | }
41 |
42 | public String getTo() {
43 | return to;
44 | }
45 |
46 | public Instant getLoadTime() {
47 | return loadTime;
48 | }
49 |
50 | public Instant getUnloadTime() {
51 | return unloadTime;
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/facade/dto/LocationDTO.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.booking.facade.dto;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * Location DTO.
7 | */
8 | public class LocationDTO implements Serializable {
9 |
10 | private final String unLocode;
11 | private final String name;
12 |
13 | public LocationDTO(String unLocode, String name) {
14 | this.unLocode = unLocode;
15 | this.name = name;
16 | }
17 |
18 | public String getUnLocode() {
19 | return unLocode;
20 | }
21 |
22 | public String getName() {
23 | return name;
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/facade/dto/RouteCandidateDTO.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.booking.facade.dto;
2 |
3 | import java.io.Serializable;
4 | import java.util.Collections;
5 | import java.util.List;
6 |
7 | /**
8 | * DTO for presenting and selecting an itinerary from a collection of candidates.
9 | */
10 | public final class RouteCandidateDTO implements Serializable {
11 |
12 | private final List legs;
13 |
14 | /**
15 | * Constructor.
16 | *
17 | * @param legs The legs for this itinerary.
18 | */
19 | public RouteCandidateDTO(final List legs) {
20 | this.legs = legs;
21 | }
22 |
23 | /**
24 | * @return An unmodifiable list DTOs.
25 | */
26 | public List getLegs() {
27 | return Collections.unmodifiableList(legs);
28 | }
29 | }
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/facade/dto/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | DTOs for the remote booking client API.
4 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/facade/internal/assembler/CargoRoutingDTOAssembler.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.booking.facade.internal.assembler;
2 |
3 | import se.citerus.dddsample.domain.model.cargo.Cargo;
4 | import se.citerus.dddsample.domain.model.cargo.Leg;
5 | import se.citerus.dddsample.domain.model.cargo.RoutingStatus;
6 | import se.citerus.dddsample.interfaces.booking.facade.dto.CargoRoutingDTO;
7 |
8 | /**
9 | * Assembler class for the CargoRoutingDTO.
10 | */
11 | public class CargoRoutingDTOAssembler {
12 |
13 | /**
14 | *
15 | * @param cargo cargo
16 | * @return A cargo routing DTO
17 | */
18 | public CargoRoutingDTO toDTO(final Cargo cargo) {
19 | final CargoRoutingDTO dto = new CargoRoutingDTO(
20 | cargo.trackingId().idString(),
21 | cargo.origin().unLocode().idString(),
22 | cargo.routeSpecification().destination().unLocode().idString(),
23 | cargo.routeSpecification().arrivalDeadline(),
24 | cargo.delivery().routingStatus().sameValueAs(RoutingStatus.MISROUTED));
25 | for (Leg leg : cargo.itinerary().legs()) {
26 | dto.addLeg(
27 | leg.voyage().voyageNumber().idString(),
28 | leg.loadLocation().unLocode().idString(),
29 | leg.unloadLocation().unLocode().idString(),
30 | leg.loadTime(),
31 | leg.unloadTime());
32 | }
33 | return dto;
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/facade/internal/assembler/ItineraryCandidateDTOAssembler.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.booking.facade.internal.assembler;
2 |
3 | import se.citerus.dddsample.domain.model.cargo.Itinerary;
4 | import se.citerus.dddsample.domain.model.cargo.Leg;
5 | import se.citerus.dddsample.domain.model.location.Location;
6 | import se.citerus.dddsample.domain.model.location.LocationRepository;
7 | import se.citerus.dddsample.domain.model.location.UnLocode;
8 | import se.citerus.dddsample.domain.model.voyage.Voyage;
9 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
10 | import se.citerus.dddsample.domain.model.voyage.VoyageRepository;
11 | import se.citerus.dddsample.interfaces.booking.facade.dto.LegDTO;
12 | import se.citerus.dddsample.interfaces.booking.facade.dto.RouteCandidateDTO;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | /**
18 | * Assembler class for the ItineraryCandidateDTO.
19 | */
20 | public class ItineraryCandidateDTOAssembler {
21 |
22 | /**
23 | * @param itinerary itinerary
24 | * @return A route candidate DTO
25 | */
26 | public RouteCandidateDTO toDTO(final Itinerary itinerary) {
27 | final List legDTOs = new ArrayList(itinerary.legs().size());
28 | for (Leg leg : itinerary.legs()) {
29 | legDTOs.add(toLegDTO(leg));
30 | }
31 | return new RouteCandidateDTO(legDTOs);
32 | }
33 |
34 | /**
35 | * @param leg leg
36 | * @return A leg DTO
37 | */
38 | protected LegDTO toLegDTO(final Leg leg) {
39 | final VoyageNumber voyageNumber = leg.voyage().voyageNumber();
40 | final UnLocode from = leg.loadLocation().unLocode();
41 | final UnLocode to = leg.unloadLocation().unLocode();
42 | return new LegDTO(voyageNumber.idString(), from.idString(), to.idString(), leg.loadTime(), leg.unloadTime());
43 | }
44 |
45 | /**
46 | * @param routeCandidateDTO route candidate DTO
47 | * @param voyageRepository voyage repository
48 | * @param locationRepository location repository
49 | * @return An itinerary
50 | */
51 | public Itinerary fromDTO(final RouteCandidateDTO routeCandidateDTO,
52 | final VoyageRepository voyageRepository,
53 | final LocationRepository locationRepository) {
54 | final List legs = new ArrayList(routeCandidateDTO.getLegs().size());
55 | for (LegDTO legDTO : routeCandidateDTO.getLegs()) {
56 | final VoyageNumber voyageNumber = new VoyageNumber(legDTO.getVoyageNumber());
57 | final Voyage voyage = voyageRepository.find(voyageNumber);
58 | final Location from = locationRepository.find(new UnLocode(legDTO.getFrom()));
59 | final Location to = locationRepository.find(new UnLocode(legDTO.getTo()));
60 | legs.add(new Leg(voyage, from, to, legDTO.getLoadTime(), legDTO.getUnloadTime()));
61 | }
62 | return new Itinerary(legs);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/facade/internal/assembler/LocationDTOAssembler.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.booking.facade.internal.assembler;
2 |
3 | import se.citerus.dddsample.domain.model.location.Location;
4 | import se.citerus.dddsample.interfaces.booking.facade.dto.LocationDTO;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | public class LocationDTOAssembler {
10 |
11 | public LocationDTO toDTO(Location location) {
12 | return new LocationDTO(location.unLocode().idString(), location.name());
13 | }
14 |
15 | public List toDTOList(List allLocations) {
16 | final List dtoList = new ArrayList(allLocations.size());
17 | for (Location location : allLocations) {
18 | dtoList.add(toDTO(location));
19 | }
20 | return dtoList;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/facade/internal/assembler/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | DTO assemblers.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/facade/internal/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Internal parts of the remote facade API.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/facade/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Remote service facades with supporting DTO classes and assemblers. Sits on top of the domain service
5 | layer, and forms the boundary of the O/R-mapper unit-of-work scope when sending data to the user interface.
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/web/RegistrationCommand.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.booking.web;
2 |
3 | /**
4 | *
5 | */
6 | public final class RegistrationCommand {
7 |
8 | private String originUnlocode;
9 | private String destinationUnlocode;
10 | private String arrivalDeadline;
11 |
12 | public String getOriginUnlocode() {
13 | return originUnlocode;
14 | }
15 |
16 | public void setOriginUnlocode(final String originUnlocode) {
17 | this.originUnlocode = originUnlocode;
18 | }
19 |
20 | public String getDestinationUnlocode() {
21 | return destinationUnlocode;
22 | }
23 |
24 | public void setDestinationUnlocode(final String destinationUnlocode) {
25 | this.destinationUnlocode = destinationUnlocode;
26 | }
27 |
28 | public String getArrivalDeadline() {
29 | return arrivalDeadline;
30 | }
31 |
32 | public void setArrivalDeadline(String arrivalDeadline) {
33 | this.arrivalDeadline = arrivalDeadline;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/web/RouteAssignmentCommand.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.booking.web;
2 |
3 | import java.time.Instant;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | public class RouteAssignmentCommand {
8 |
9 | private String trackingId;
10 | private List legs = new ArrayList<>();
11 |
12 | public String getTrackingId() {
13 | return trackingId;
14 | }
15 |
16 | public void setTrackingId(String trackingId) {
17 | this.trackingId = trackingId;
18 | }
19 |
20 | public List getLegs() {
21 | return legs;
22 | }
23 |
24 | public void setLegs(List legs) {
25 | this.legs = legs;
26 | }
27 |
28 | public static final class LegCommand {
29 | private String voyageNumber;
30 | private String fromUnLocode;
31 | private String toUnLocode;
32 | private Instant fromDate;
33 | private Instant toDate;
34 |
35 | public String getVoyageNumber() {
36 | return voyageNumber;
37 | }
38 |
39 | public void setVoyageNumber(final String voyageNumber) {
40 | this.voyageNumber = voyageNumber;
41 | }
42 |
43 | public String getFromUnLocode() {
44 | return fromUnLocode;
45 | }
46 |
47 | public void setFromUnLocode(final String fromUnLocode) {
48 | this.fromUnLocode = fromUnLocode;
49 | }
50 |
51 | public String getToUnLocode() {
52 | return toUnLocode;
53 | }
54 |
55 | public void setToUnLocode(final String toUnLocode) {
56 | this.toUnLocode = toUnLocode;
57 | }
58 |
59 | public Instant getFromDate() {
60 | return fromDate;
61 | }
62 |
63 | public void setFromDate(String fromDate) {
64 | this.fromDate = Instant.parse(fromDate);
65 | }
66 |
67 | public Instant getToDate() {
68 | return toDate;
69 | }
70 |
71 | public void setToDate(String toDate) {
72 | this.toDate = Instant.parse(toDate);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/booking/web/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Web user interfaces for booking, routing and re-routing cargo.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/handling/HandlingEventRegistrationAttempt.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.handling;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.apache.commons.lang3.builder.ToStringStyle;
5 | import org.springframework.lang.NonNull;
6 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
7 | import se.citerus.dddsample.domain.model.handling.HandlingEvent;
8 | import se.citerus.dddsample.domain.model.location.UnLocode;
9 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
10 |
11 | import java.io.Serializable;
12 | import java.time.Instant;
13 |
14 | /**
15 | * This is a simple transfer object for passing incoming handling event
16 | * registration attempts to proper the registration procedure.
17 | *
18 | * It is used as a message queue element.
19 | */
20 | public final class HandlingEventRegistrationAttempt implements Serializable {
21 |
22 | private final Instant registrationTime;
23 | private final Instant completionTime;
24 | private final TrackingId trackingId;
25 | private final VoyageNumber voyageNumber;
26 | private final HandlingEvent.Type type;
27 | private final UnLocode unLocode;
28 |
29 | public HandlingEventRegistrationAttempt(@NonNull Instant registrationTime,
30 | @NonNull Instant completionTime,
31 | @NonNull TrackingId trackingId,
32 | @NonNull VoyageNumber voyageNumber,
33 | @NonNull HandlingEvent.Type type,
34 | @NonNull UnLocode unLocode) {
35 | this.registrationTime = registrationTime;
36 | this.completionTime = completionTime;
37 | this.trackingId = trackingId;
38 | this.voyageNumber = voyageNumber;
39 | this.type = type;
40 | this.unLocode = unLocode;
41 | }
42 |
43 | public Instant getCompletionTime() {
44 | return completionTime;
45 | }
46 |
47 | public TrackingId getTrackingId() {
48 | return trackingId;
49 | }
50 |
51 | public VoyageNumber getVoyageNumber() {
52 | return voyageNumber;
53 | }
54 |
55 | public HandlingEvent.Type getType() {
56 | return type;
57 | }
58 |
59 | public UnLocode getUnLocode() {
60 | return unLocode;
61 | }
62 |
63 | public Instant getRegistrationTime() {
64 | return registrationTime;
65 | }
66 |
67 | @Override
68 | public String toString() {
69 | return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/handling/file/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Handles event registration by file upload.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/handling/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Interfaces for receiving handling events into the system.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/handling/ws/HandlingReport.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.handling.ws;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | import java.time.LocalDateTime;
6 | import java.util.List;
7 |
8 | public class HandlingReport {
9 | @JsonProperty(required = true)
10 | public LocalDateTime completionTime;
11 |
12 | @JsonProperty(required = true)
13 | public List trackingIds;
14 |
15 | @JsonProperty(required = true)
16 | public String type;
17 |
18 | @JsonProperty(required = true)
19 | public String unLocode;
20 |
21 | public String voyageNumber;
22 |
23 | public HandlingReport(LocalDateTime completionTime, List trackingIds, String type, String unLocode, String voyageNumber) {
24 | this.completionTime = completionTime;
25 | this.trackingIds = trackingIds;
26 | this.type = type;
27 | this.unLocode = unLocode;
28 | this.voyageNumber = voyageNumber;
29 | }
30 |
31 | public LocalDateTime getCompletionTime() {
32 | return completionTime;
33 | }
34 |
35 | public void setCompletionTime(LocalDateTime completionTime) {
36 | this.completionTime = completionTime;
37 | }
38 |
39 | public List getTrackingIds() {
40 | return trackingIds;
41 | }
42 |
43 | public void setTrackingIds(List trackingIds) {
44 | this.trackingIds = trackingIds;
45 | }
46 |
47 | public String getType() {
48 | return type;
49 | }
50 |
51 | public void setType(String type) {
52 | this.type = type;
53 | }
54 |
55 | public String getUnLocode() {
56 | return unLocode;
57 | }
58 |
59 | public void setUnLocode(String unLocode) {
60 | this.unLocode = unLocode;
61 | }
62 |
63 | public String getVoyageNumber() {
64 | return voyageNumber;
65 | }
66 |
67 | public void setVoyageNumber(String voyageNumber) {
68 | this.voyageNumber = voyageNumber;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/handling/ws/HandlingReportService.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.handling.ws;
2 |
3 | import org.springframework.http.ResponseEntity;
4 |
5 | public interface HandlingReportService {
6 |
7 | ResponseEntity> submitReport(HandlingReport handlingReport);
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/handling/ws/HandlingReportServiceImpl.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.handling.ws;
2 |
3 | import jakarta.validation.Valid;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.http.ResponseEntity;
7 | import org.springframework.web.bind.annotation.PostMapping;
8 | import org.springframework.web.bind.annotation.RequestBody;
9 | import org.springframework.web.bind.annotation.RestController;
10 | import se.citerus.dddsample.application.ApplicationEvents;
11 | import se.citerus.dddsample.interfaces.handling.HandlingEventRegistrationAttempt;
12 |
13 | import java.lang.invoke.MethodHandles;
14 | import java.util.List;
15 |
16 | import static org.springframework.http.HttpStatus.CREATED;
17 | import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
18 | import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
19 | import static se.citerus.dddsample.interfaces.handling.HandlingReportParser.parse;
20 |
21 | /**
22 | * This web service endpoint implementation performs basic validation and parsing
23 | * of incoming data, and in case of a valid registration attempt, sends an asynchronous message
24 | * with the information to the handling event registration system for proper registration.
25 | */
26 | @RestController
27 | public class HandlingReportServiceImpl implements HandlingReportService {
28 | private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
29 |
30 | private final ApplicationEvents applicationEvents;
31 |
32 | public HandlingReportServiceImpl(ApplicationEvents applicationEvents) {
33 | this.applicationEvents = applicationEvents;
34 | }
35 |
36 | @PostMapping(value = "/handlingReport", produces = APPLICATION_JSON_VALUE, consumes = APPLICATION_JSON_VALUE)
37 | @Override
38 | public ResponseEntity> submitReport(@Valid @RequestBody HandlingReport handlingReport) {
39 | try {
40 | List attempts = parse(handlingReport);
41 | attempts.forEach(applicationEvents::receivedHandlingEventRegistrationAttempt);
42 | } catch (Exception e) {
43 | logger.error("Unexpected error in submitReport", e);
44 | return ResponseEntity.status(INTERNAL_SERVER_ERROR).body("Internal server error: " + e.getMessage());
45 | }
46 | return ResponseEntity.status(CREATED).build();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/handling/ws/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Web service interface for registering handling events.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/tracking/TrackCommand.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.tracking;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 |
5 | import static org.apache.commons.lang3.builder.ToStringStyle.MULTI_LINE_STYLE;
6 |
7 | public final class TrackCommand {
8 |
9 | /**
10 | * The tracking id.
11 | */
12 | private String trackingId;
13 |
14 | public String getTrackingId() {
15 | return trackingId;
16 | }
17 |
18 | public void setTrackingId(final String trackingId) {
19 | this.trackingId = trackingId;
20 | }
21 |
22 | @Override
23 | public String toString() {
24 | return ToStringBuilder.reflectionToString(this, MULTI_LINE_STYLE);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/tracking/TrackCommandValidator.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.tracking;
2 |
3 | import org.springframework.lang.NonNull;
4 | import org.springframework.validation.Errors;
5 | import org.springframework.validation.ValidationUtils;
6 | import org.springframework.validation.Validator;
7 |
8 | /**
9 | * Validator for {@link TrackCommand}s.
10 | */
11 | public final class TrackCommandValidator implements Validator {
12 |
13 | public boolean supports(@NonNull final Class> clazz) {
14 | return TrackCommand.class.isAssignableFrom(clazz);
15 | }
16 |
17 | public void validate(@NonNull final Object object,@NonNull final Errors errors) {
18 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "trackingId", "error.required", "Required");
19 | }
20 |
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/tracking/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Public tracking web interface.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/tracking/ws/CargoTrackingDTO.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.tracking.ws;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * A data-transport object class representing a view of a Cargo entity.
7 | * Used by the REST API for public cargo tracking.
8 | */
9 | public class CargoTrackingDTO {
10 |
11 | public final String trackingId;
12 | public final String statusText;
13 | public final String destination;
14 | public final String eta;
15 | public final String nextExpectedActivity;
16 | public final boolean isMisdirected;
17 | public final List handlingEvents;
18 |
19 | public CargoTrackingDTO(String trackingId, String statusText, String destination, String eta, String nextExpectedActivity, boolean isMisdirected, List handlingEvents) {
20 | this.trackingId = trackingId;
21 | this.statusText = statusText;
22 | this.destination = destination;
23 | this.eta = eta;
24 | this.nextExpectedActivity = nextExpectedActivity;
25 | this.isMisdirected = isMisdirected;
26 | this.handlingEvents = handlingEvents;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/tracking/ws/CargoTrackingRestService.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.tracking.ws;
2 |
3 | import jakarta.servlet.http.HttpServletRequest;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.context.MessageSource;
7 | import org.springframework.http.ResponseEntity;
8 | import org.springframework.web.bind.annotation.GetMapping;
9 | import org.springframework.web.bind.annotation.PathVariable;
10 | import org.springframework.web.bind.annotation.RestController;
11 | import org.springframework.web.servlet.support.RequestContextUtils;
12 | import org.springframework.web.util.UriTemplate;
13 | import se.citerus.dddsample.domain.model.cargo.Cargo;
14 | import se.citerus.dddsample.domain.model.cargo.CargoRepository;
15 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
16 | import se.citerus.dddsample.domain.model.handling.HandlingEvent;
17 | import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
18 |
19 | import java.lang.invoke.MethodHandles;
20 | import java.net.URI;
21 | import java.util.List;
22 | import java.util.Locale;
23 |
24 | import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
25 |
26 | @RestController
27 | public class CargoTrackingRestService {
28 | private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
29 |
30 | private final CargoRepository cargoRepository;
31 | private final HandlingEventRepository handlingEventRepository;
32 | private final MessageSource messageSource;
33 |
34 | public CargoTrackingRestService(CargoRepository cargoRepository, HandlingEventRepository handlingEventRepository, MessageSource messageSource) {
35 | this.cargoRepository = cargoRepository;
36 | this.handlingEventRepository = handlingEventRepository;
37 | this.messageSource = messageSource;
38 | }
39 |
40 | @GetMapping(value = "/api/track/{trackingId}", produces = APPLICATION_JSON_VALUE)
41 | public ResponseEntity trackCargo(final HttpServletRequest request,
42 | @PathVariable("trackingId") String trackingId) {
43 | try {
44 | Locale locale = RequestContextUtils.getLocale(request);
45 | TrackingId trkId = new TrackingId(trackingId);
46 | Cargo cargo = cargoRepository.find(trkId);
47 | if (cargo == null) {
48 | URI uri = new UriTemplate(request.getContextPath() + "/api/track/{trackingId}").expand(trackingId);
49 | return ResponseEntity.notFound().location(uri).build();
50 | }
51 | final List handlingEvents = handlingEventRepository.lookupHandlingHistoryOfCargo(trkId)
52 | .distinctEventsByCompletionTime();
53 | return ResponseEntity.ok(CargoTrackingDTOConverter.convert(cargo, handlingEvents, messageSource, locale));
54 | } catch (Exception e) {
55 | log.error("Unexpected error in trackCargo endpoint", e);
56 | return ResponseEntity.status(500).build();
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/se/citerus/dddsample/interfaces/tracking/ws/HandlingEventDTO.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.tracking.ws;
2 |
3 | /**
4 | * A data-transport object class represnting a view of a HandlingEvent owned by a Cargo.
5 | * Used by the REST API for public cargo tracking.
6 | */
7 | public class HandlingEventDTO {
8 |
9 | public final String location;
10 | public final String time;
11 | public final String type;
12 | public final String voyageNumber;
13 | public final boolean isExpected;
14 | public final String description;
15 |
16 | public HandlingEventDTO(String location, String time, String type, String voyageNumber, boolean isExpected, String description) {
17 | this.location = location;
18 | this.time = time;
19 | this.type = type;
20 | this.voyageNumber = voyageNumber;
21 | this.isExpected = isExpected;
22 | this.description = description;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | servlet:
3 | context-path: /dddsample
4 |
5 | logging:
6 | level:
7 | jdbc: debug
8 | 'se.citerus.dddsample': debug
9 |
10 | uploadDirectory: /tmp/upload
11 | parseFailureDirectory: /tmp/failed
12 |
13 | brokerUrl: "vm://localhost?broker.persistent=false&broker.useJmx=false"
14 |
15 | spring:
16 | dataSource:
17 | url: jdbc:hsqldb:mem:dddsample
18 | username: sa
19 | password: ""
20 |
21 | jdbc:
22 | datasource-proxy:
23 | enabled: true
24 | query:
25 | logger-name: jdbc
26 | enable-logging: true
27 | multiline: false
28 | include-parameter-values: true
29 |
--------------------------------------------------------------------------------
/src/main/resources/messages_en.properties:
--------------------------------------------------------------------------------
1 | cargo.status.NOT_RECEIVED=Not received
2 | cargo.status.IN_PORT=In port {0}
3 | cargo.status.ONBOARD_CARRIER=Onboard voyage {0}
4 | cargo.status.CLAIMED=Claimed
5 | cargo.status.UNKNOWN=Unknown
6 |
7 | deliveryHistory.eventDescription.NOT_RECEIVED=Cargo has not yet been received.
8 |
9 | deliveryHistory.eventDescription.LOAD=Loaded onto voyage {0} in {1}, at {2}.
10 | deliveryHistory.eventDescription.UNLOAD=Unloaded off voyage {0} in {1}, at {2}.
11 | deliveryHistory.eventDescription.RECEIVE=Received in {0}, at {1}.
12 | deliveryHistory.eventDescription.CLAIM=Claimed in {0}, at {1}.
13 | deliveryHistory.eventDescription.CUSTOMS=Cleared customs in {0}, at {1}.
--------------------------------------------------------------------------------
/src/main/resources/static/admin.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | font: Verdana, Arial, sans-serif;
7 | color: black;
8 | padding: 10px;
9 | }
10 |
11 | h1 {
12 | font-weight: lighter;
13 | font-variant: small-caps;
14 | }
15 |
16 | ul#menu {
17 | border: 1px solid #ccc;
18 | margin: 40px 0 20px 0;
19 | padding: 10px 0;
20 | background: #eee;
21 | }
22 |
23 | ul#menu li {
24 | margin: 0 0 0 10px;
25 | display: inline;
26 | }
27 |
28 | ul#menu a {
29 | text-decoration: none;
30 | }
31 |
32 | ul#menu a:hover {
33 | text-decoration: underline;
34 | }
35 |
36 | table {
37 | }
38 |
39 | table td {
40 | padding: 4px 10px;
41 | }
42 |
43 | table thead {
44 | font-weight: bold;
45 | }
46 |
47 | table caption {
48 | text-align: left;
49 | font-weight: bold;
50 | }
51 |
52 | img#logotype {
53 | float: right;
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/resources/static/calendar.css:
--------------------------------------------------------------------------------
1 | .calendarTrigger {
2 | width: 16px;
3 | height: 16px;
4 | cursor: pointer;
5 | vertical-align: top;
6 | margin-top: 1px;
7 | }
8 |
9 | .calendar {
10 | position: absolute;
11 | display: inherit;
12 |
13 | font-family: tahoma, verdana, arial, helvetica, sans-serif;
14 | border: 1px solid blue;
15 | background-color: white;
16 | }
17 |
18 |
19 | .calendar table {
20 | border: 4px solid lightblue;
21 | margin: 0px;
22 | padding: 0px;
23 | border-spacing: 2px;
24 | border-collapse: separate;
25 | }
26 |
27 | .calendar table .goPrevious,
28 | .calendar table .goNext {
29 | width: 12px;
30 | background-repeat: no-repeat;
31 | cursor: pointer;
32 | }
33 |
34 | .calendar table .goPrevious {
35 | background-image: url( 'navigate_left.gif' );
36 | background-position: left center;
37 | }
38 |
39 | .calendar table .goNext {
40 | background-image: url( 'navigate_right.gif' );
41 | background-position: right center;
42 | }
43 |
44 | .calendar table thead tr:first-child td {
45 | font-weight: bold;
46 | }
47 |
48 | .calendar table td {
49 | width: 15px;
50 | height: 15px;
51 | font-family: tahoma, verdana, arial, helvetica, sans-serif;
52 | font-size: 8pt;
53 | text-align: center;
54 | padding: 0px 0px 0px 1px;
55 | border: 0px;
56 | vertical-align: middle;
57 | }
58 | .calendar table tbody td {
59 | border: 1px solid gray;
60 | cursor: pointer;
61 | }
62 | .calendar .disabled {
63 | background-color: #BBBBBB;
64 | color: #555555;
65 | }
66 | .calendar .past {
67 | color: #AAAAAA;
68 | }
69 | .calendar .future {
70 | }
71 | .calendar .today {
72 | background-color: pink;
73 | }
74 | .calendar .active {
75 | background-color: #FFAA00;
76 | border: 1px solid black;
77 | }
78 |
79 | .calendar table tbody td:hover {
80 | border: 1px solid blue;
81 | background-color: lightblue;
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/resources/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/main/resources/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/src/main/resources/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/main/resources/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/src/main/resources/static/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/main/resources/static/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/src/main/resources/static/images/calendarTrigger.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/main/resources/static/images/calendarTrigger.gif
--------------------------------------------------------------------------------
/src/main/resources/static/images/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/main/resources/static/images/cross.png
--------------------------------------------------------------------------------
/src/main/resources/static/images/dddsample_logotype.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/main/resources/static/images/dddsample_logotype.png
--------------------------------------------------------------------------------
/src/main/resources/static/images/dddsample_logotype_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/main/resources/static/images/dddsample_logotype_small.png
--------------------------------------------------------------------------------
/src/main/resources/static/images/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/main/resources/static/images/error.png
--------------------------------------------------------------------------------
/src/main/resources/static/images/shade.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/main/resources/static/images/shade.png
--------------------------------------------------------------------------------
/src/main/resources/static/images/tick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/main/resources/static/images/tick.png
--------------------------------------------------------------------------------
/src/main/resources/static/images/web_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/main/resources/static/images/web_logo.png
--------------------------------------------------------------------------------
/src/main/resources/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | DDDSample
7 |
8 |
9 |
10 | Welcome to the DDDSample application.
11 | There are two web interfaces available:
12 |
16 | The Incident Logging application, that is used to register handling events, is a stand-alone application and a separate download.
17 | Please visit the project website for more information and a screencast demonstration of how the application works.
18 | This project is a joint effort by Eric Evans' company Domain Language
19 | and the Swedish software consulting company Citerus .
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/resources/templates/admin/list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 | Cargo Administration
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | All cargos
17 |
18 |
19 | Tracking ID
20 | Origin
21 | Destination
22 | Routed
23 |
24 |
25 |
26 |
27 |
28 | Tracking id
30 |
31 | Origin
32 | Final destination
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/main/resources/templates/admin/pickNewDestination.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 | Cargo Administration
8 |
9 |
10 |
15 |
16 |
17 |
18 |
19 |
49 |
50 |
--------------------------------------------------------------------------------
/src/main/resources/templates/admin/registrationForm.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 | Cargo Administration
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
59 |
60 |
61 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/main/resources/templates/admin/selectItinerary.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 | Cargo Administration
8 |
9 |
10 |
11 |
12 |
13 |
14 | Select route
15 |
16 |
17 | Cargo is going from to
19 |
20 |
21 |
22 |
23 |
No routes found that satisfy the route specification.
24 | Try setting an arrival deadline futher into the future (a few weeks at least).
25 |
26 |
27 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/src/main/resources/templates/admin/show.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 | Cargo Administration
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Origin
19 |
20 |
21 |
22 | Destination
23 |
24 |
25 |
26 |
27 |
28 | Change destination
29 |
30 |
31 |
32 | Arrival deadline
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Cargo is misrouted - reroute
41 | this cargo
42 |
43 |
44 | Itinerary
45 |
46 |
47 | Voyage number
48 | Load
49 | Unload
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | Not routed - Route this cargo
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/main/resources/templates/adminDecorator.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 | Cargo Booking and Routing
10 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/site/apt/changelog.apt:
--------------------------------------------------------------------------------
1 | ---------
2 | Changelog
3 | ---------
4 |
5 | Changelog
6 |
7 | * 1.1 (March 2009)
8 |
9 | * Remodeled Cargo and HandlingEvent aggregates to make boundaries
10 | and asynchronous updates more explicit. Cargo delivery progress is now
11 | inspected and stored on handling, asynchronously, as an example of
12 | how aggregates may be inconsistent for a (short) period of time.
13 |
14 | * Restructured packages to have a one-to-one mapping between layers and packages.
15 |
16 | * Gathered all aspects of the cargo delivery in the Delivery value object
17 | in the Cargo aggregate.
18 |
19 | * RouteSpecification is now a persistent part of the Cargo aggregate.
20 |
21 | * Made handling event registration completely asynchronous.
22 |
23 | * Implemented "next expected event" for tracking.
24 |
25 | * The old CarrierMovement aggregate is remodeled, and the root entity
26 | is now Voyage. A voyage has a schedule containing a list of carrier
27 | movements.
28 |
29 | * Legs and carrier movements have departure/arrival and load/unload times, respectively.
30 |
31 | * ETA for itineraries
32 |
33 | * Web service for registering handling events is now more clearly
34 | downstream from handling report aggregation service context,
35 | with Java code generated from a given WSDL.
36 |
37 | * There is now a directory scanner routine that picks up and parses CSV
38 | files containing handling event information, as an example of an alternative
39 | interface.
40 |
41 | * Updated framework dependencies.
42 |
43 | * Screencast showing what the application can do.
44 |
45 | * 1.0 (September 2008)
46 |
47 | * Initial public release
48 |
49 |
--------------------------------------------------------------------------------
/src/site/apt/handlingEventRegistration.apt:
--------------------------------------------------------------------------------
1 | --------------------------------
2 | Registration of a handling event
3 | --------------------------------
4 |
5 | TODO
6 |
--------------------------------------------------------------------------------
/src/site/apt/index.apt:
--------------------------------------------------------------------------------
1 | ------------
2 | Introduction
3 | ------------
4 |
5 |
6 |
7 | [images/frontpage.jpeg]
8 |
9 | One of the most requested aids to coming up to speed on DDD has been a
10 | running example application. Starting from a simple set of functions
11 | and a model based on the cargo example used in {{{http://www.domaindrivendesign.org/books/index.html#DDD}Eric Evans' book}},
12 | we have built a running application with which to demonstrate a practical
13 | implementation of the building block patterns as well as illustrate the
14 | impact of aggregates and bounded contexts.
15 |
16 | News
17 |
18 | * 2009-03-25: New public release: <<1.1.0>>. See {{{changelog.html}changelog}} for details.
19 |
20 | * 2009-03-09: Sample application tutorial at the {{{http://qconlondon.com/london-2009/}QCon conference}} in London.
21 |
22 | * 2009-01-27: Sample application tutorial at the {{{http://www.jfokus.se/jfokus/}JFokus conference}} in Stockholm.
23 |
24 | * 2008-09-15: First public release: <<1.0>>.
25 |
26 | Purpose
27 |
28 | * A how-to example for implementing a typical DDD application
29 |
30 | Our sample does not show *the* way to do it, but a decent way.
31 | Eventually, the same design could be reimplemented on various popular platforms,
32 | to give the same assistance to people working on those platforms,
33 | and also help those who must transition between the platforms.
34 |
35 | * Support discussion of implementation practices
36 |
37 | Variations could show trade-offs of alternative approaches,
38 | helping the community to clarify and refine best practices for building DDD applications.
39 |
40 | * Lab mouse for controlled experiments
41 |
42 | There's a lot of interest in new tools or frameworks for DDD.
43 |
44 | Reimplementing the sample app using a new platform will give a side-by-side comparison
45 | with the "conventional" implementation, which can demonstrate the value of the new platform
46 | and provide validation or other feedback to the developers of the platform.
47 |
48 | Caveats
49 |
50 | Domain-driven design is a very broad topic, and contains lots of things that are difficult or impossible
51 | to incorporate into the code base of a sample application. Perhaps most important is communication with the domain
52 | expert, iterative modelling and the discovery of a ubiquitous language. This application is a snapshot in time,
53 | the result of a development effort that you need to imagine has been utilizing domain-driven design,
54 | to show how one can structure an application around an isolated, rich domain model in a realistic environment.
55 |
56 | []
57 |
58 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/src/site/apt/patterns-reference.apt:
--------------------------------------------------------------------------------
1 | ------------------------------------
Patterns reference
------------------------------------
Patrik Fredriksson
------------------------------------
March 11, 2009
Patterns reference
In {{{http://www.domaindrivendesign.org/books/index.html#DDD}Eric Evans' book}} a number of patterns on Domain-Driven Design are presented. Many of these patterns are implemented in the sample application. Use this patterns reference to find out which patterns are implemented where!
A patterns summary can be downloaded at {{{http://www.domaindrivendesign.org/discussion/index.html}domaindrivendesign.org}}.
Tactical Design Patterns
*Building Blocks of a Model-Driven Design
Aggregate [{{{characterization.html#Aggregates}discussion}}] [{{{xref/se/citerus/dddsample/domain/model/cargo/package-summary.html}code example}}]
Domain Event [{{{characterization.html#Domain_Event}discussion}}] [{{{xref/se/citerus/dddsample/domain/model/handling/HandlingEvent.html}code example}}]
Entity [{{{characterization.html#Entities}discussion}}] [{{{xref/se/citerus/dddsample/domain/model/cargo/Cargo.html}code example}}]
Value Object [{{{characterization.html#Value_Objects}discussion}}] [{{{xref/se/citerus/dddsample/domain/model/cargo/Leg.html}code example}}]
Repository [{{{characterization.html#Repositories}discussion}}] [{{{xref/se/citerus/dddsample/domain/model/cargo/CargoRepository.html}code example}}]
Service [{{{characterization.html#Services}discussion}}] [{{{xref/se/citerus/dddsample/domain/service/RoutingService.html}code example}}]
Specification [{{{xref/se/citerus/dddsample/domain/model/cargo/RouteSpecification.html}code example}}]
Layered Architecture [discussion]
Service Layer [{{{characterization.html#Services}discussion}}]
*Supple Design
Intention-Revealing Interfaces [discussion] [{{{xref/se/citerus/dddsample/domain/model/cargo/Cargo.html}code example}}]
Side-Effect-Free Functions [discussion] [{{{xref/se/citerus/dddsample/domain/model/cargo/Cargo.html}code example}}]
Strategic Design Patterns
*Maintaining Model Integrity
Anti-corruption Layer [discussion] [{{{xref/se/citerus/dddsample/interfaces/handling/ws/HandlingReportServiceImpl.html}code example}}]
--------------------------------------------------------------------------------
/src/site/apt/roadmap.apt:
--------------------------------------------------------------------------------
1 | -------
2 | Roadmap
3 | -------
4 |
5 | Roadmap
6 |
7 | This application is a work of progress, and these are some ideas for continued development:
8 |
9 | * Ports to C#/.NET and other frameworks
10 |
11 | * Handling voyage delays and intentional rescheduling, how that affects itineraries
12 |
13 | * More one the time aspect: timezones for locations and handling events,
14 | notify on delayed delivery, port to {{{http://timeandmoney.sourceforge.net/}TimeAndMoney}} or similar date/time framework
--------------------------------------------------------------------------------
/src/site/resources/css/site.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/site/resources/css/site.css
--------------------------------------------------------------------------------
/src/site/resources/images/banner-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/site/resources/images/banner-left.png
--------------------------------------------------------------------------------
/src/site/resources/images/eclipse1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/site/resources/images/eclipse1.png
--------------------------------------------------------------------------------
/src/site/resources/images/eclipse2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/site/resources/images/eclipse2.png
--------------------------------------------------------------------------------
/src/site/resources/images/eclipse3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/site/resources/images/eclipse3.png
--------------------------------------------------------------------------------
/src/site/resources/images/eclipse4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/site/resources/images/eclipse4.png
--------------------------------------------------------------------------------
/src/site/resources/images/frontpage.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/site/resources/images/frontpage.jpeg
--------------------------------------------------------------------------------
/src/site/resources/images/layers.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citerus/dddsample-core/d8b896254ed4e18ba8117dcd5bf9a54708bdeaaf/src/site/resources/images/layers.jpg
--------------------------------------------------------------------------------
/src/site/site.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | org.apache.maven.skins
4 | maven-fluido-skin
5 | 2.1.0
6 |
7 |
8 | DDDSample
9 | images/banner-left.png
10 | http://dddsample.sf.net
11 |
12 |
13 |
14 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/site/xdoc/screencast.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | Screencast
8 |
9 |
10 |
11 |
12 |
13 | Here is a 10 minute screencast that shows the different interfaces in action,
14 | and the scope of the application.
15 |
16 | It shows:
17 |
18 | The welcome page
19 | Tracking the preloaded cargos
20 | Viewing the preloaded cargos in the admin application
21 | Booking of a new cargo
22 | Routing of the new cargo
23 | Registering of a few expected events for the new cargo,
24 | then tracking to confirm it's on track
25 | Registering of an unexpected event for the new cargo,
26 | then tracking to see that it becomes misdirected
27 | Registering event using the scheduled file import interface
28 |
29 |
30 | It's a little difficult to read the text, but you can use it as a guide for
31 | playing around on your own with the actual application.
32 |
33 |
34 |
35 |
36 |
38 |
39 |
40 | The recording was made using IShowU HD .
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/acceptance/AbstractAcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.acceptance;
2 |
3 | import org.junit.jupiter.api.AfterEach;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.extension.ExtendWith;
6 | import org.openqa.selenium.WebDriver;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.boot.test.context.SpringBootTest;
9 | import org.springframework.boot.test.web.server.LocalServerPort;
10 | import org.springframework.test.context.junit.jupiter.SpringExtension;
11 | import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDriverBuilder;
12 | import org.springframework.web.context.WebApplicationContext;
13 | import se.citerus.dddsample.Application;
14 |
15 | @ExtendWith(SpringExtension.class)
16 | @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
17 | properties = {"spring.datasource.url=jdbc:hsqldb:mem:dddsample_acceptance_test"})
18 | public abstract class AbstractAcceptanceTest {
19 |
20 | @Autowired
21 | private WebApplicationContext context;
22 |
23 | protected WebDriver driver;
24 |
25 | @LocalServerPort
26 | public int port;
27 |
28 | @BeforeEach
29 | public void setup() {
30 | driver = MockMvcHtmlUnitDriverBuilder.webAppContextSetup(context).contextPath("/dddsample").build();
31 | }
32 |
33 | @AfterEach
34 | public void tearDown() {
35 | driver.quit();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/acceptance/AdminAcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.acceptance;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.test.annotation.DirtiesContext;
5 | import se.citerus.dddsample.acceptance.pages.*;
6 |
7 | import java.time.LocalDate;
8 | import java.time.temporal.ChronoUnit;
9 |
10 | import static org.assertj.core.api.Java6Assertions.assertThat;
11 |
12 | public class AdminAcceptanceTest extends AbstractAcceptanceTest {
13 |
14 | @DirtiesContext
15 | @Test
16 | public void adminSiteCargoListContainsCannedCargo() {
17 | AdminPage page = new AdminPage(driver, port);
18 | page.listAllCargo();
19 |
20 | assertThat(page.listedCargoContains("ABC123")).isTrue()
21 | .withFailMessage("Cargo list doesn't contain ABC123");
22 | assertThat(page.listedCargoContains("JKL567")).isTrue()
23 | .withFailMessage("Cargo list doesn't contain JKL567");
24 | }
25 |
26 | @DirtiesContext
27 | @Test
28 | public void adminSiteCanBookNewCargo() {
29 | AdminPage adminPage = new AdminPage(driver, port);
30 |
31 | CargoBookingPage cargoBookingPage = adminPage.bookNewCargo();
32 | cargoBookingPage.selectOrigin("NLRTM");
33 | cargoBookingPage.selectDestination("USDAL");
34 | LocalDate arrivalDeadline = LocalDate.now().plus(3, ChronoUnit.WEEKS);
35 | cargoBookingPage.selectArrivalDeadline(arrivalDeadline);
36 | CargoDetailsPage cargoDetailsPage = cargoBookingPage.book();
37 |
38 | String newCargoTrackingId = cargoDetailsPage.getTrackingId();
39 | adminPage = cargoDetailsPage.listAllCargo();
40 | assertThat(adminPage.listedCargoContains(newCargoTrackingId)).isTrue()
41 | .withFailMessage("Cargo list doesn't contain %s", newCargoTrackingId);
42 |
43 | cargoDetailsPage = adminPage.showDetailsFor(newCargoTrackingId);
44 | cargoDetailsPage.expectOriginOf("NLRTM");
45 | cargoDetailsPage.expectDestinationOf("USDAL");
46 |
47 | CargoDestinationPage cargoDestinationPage = cargoDetailsPage.changeDestination();
48 | cargoDetailsPage = cargoDestinationPage.selectDestinationTo("AUMEL");
49 | cargoDetailsPage.expectDestinationOf("AUMEL");
50 | cargoDetailsPage.expectArrivalDeadlineOf(arrivalDeadline);
51 |
52 | // Route cargo
53 | cargoDetailsPage.expectRoutedOf("Not routed");
54 | CargoRoutingPage cargoRoutingPage = cargoDetailsPage.routeCargo();
55 | cargoRoutingPage.expectAtLeastOneRoute();
56 | CargoDetailsPage routedCargoDetailsPage = cargoRoutingPage.assignCargoToFirstRoute();
57 | routedCargoDetailsPage.expectItinerary();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/acceptance/CustomerAcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.acceptance;
2 |
3 | import org.junit.jupiter.api.BeforeEach;
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.test.annotation.DirtiesContext;
6 | import se.citerus.dddsample.acceptance.pages.CustomerPage;
7 |
8 | public class CustomerAcceptanceTest extends AbstractAcceptanceTest {
9 | private CustomerPage customerPage;
10 |
11 | @BeforeEach
12 | public void goToCustomerPage() {
13 | customerPage = new CustomerPage(driver, port);
14 | }
15 |
16 | @DirtiesContext
17 | @Test
18 | public void customerSiteCanTrackValidCargo() {
19 | customerPage.trackCargoWithIdOf("ABC123");
20 | customerPage.expectCargoLocation("New York");
21 | }
22 |
23 | @DirtiesContext
24 | @Test
25 | public void customerSiteErrorsOnInvalidCargo() {
26 | customerPage.trackCargoWithIdOf("XXX999");
27 | customerPage.expectErrorFor("Unknown tracking id");
28 | }
29 |
30 | @DirtiesContext
31 | @Test
32 | public void customerSiteNotifiesOnMisdirectedCargo() {
33 | customerPage.trackCargoWithIdOf("JKL567");
34 | customerPage.expectNotificationOf("Cargo is misdirected");
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/acceptance/pages/AdminPage.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.acceptance.pages;
2 |
3 | import org.openqa.selenium.By;
4 | import org.openqa.selenium.WebDriver;
5 | import org.openqa.selenium.WebElement;
6 |
7 | import java.util.List;
8 | import java.util.Optional;
9 |
10 | import static org.assertj.core.api.Assertions.assertThat;
11 |
12 | public class AdminPage {
13 | private final WebDriver driver;
14 | private final int port;
15 |
16 | public AdminPage(WebDriver driver, int port) {
17 | this.driver = driver;
18 | this.port = port;
19 | driver.get(String.format("http://localhost:%d/dddsample/admin/list", port));
20 | assertThat("Cargo Administration").isEqualTo(driver.getTitle());
21 | }
22 |
23 | public void listAllCargo() {
24 | driver.findElement(By.linkText("List all cargos")).click();
25 | assertThat("Cargo Administration").isEqualTo(driver.getTitle());
26 | }
27 |
28 | public CargoBookingPage bookNewCargo() {
29 | driver.findElement(By.linkText("Book new cargo")).click();
30 |
31 | return new CargoBookingPage(driver, port);
32 | }
33 |
34 | public boolean listedCargoContains(String expectedTrackingId) {
35 | List cargoList = driver.findElements(By.cssSelector("#body table tbody tr td a"));
36 | Optional matchingCargo = cargoList.stream().filter(cargo -> cargo.getText().equals(expectedTrackingId)).findFirst();
37 | return matchingCargo.isPresent();
38 | }
39 |
40 | public CargoDetailsPage showDetailsFor(String cargoTrackingId) {
41 | driver.findElement(By.linkText(cargoTrackingId)).click();
42 |
43 | return new CargoDetailsPage(driver, port);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/acceptance/pages/CargoBookingPage.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.acceptance.pages;
2 |
3 | import org.openqa.selenium.By;
4 | import org.openqa.selenium.WebDriver;
5 | import org.openqa.selenium.WebElement;
6 | import org.openqa.selenium.support.ui.Select;
7 |
8 | import java.time.LocalDate;
9 | import java.time.format.DateTimeFormatter;
10 |
11 | import static org.assertj.core.api.Assertions.assertThat;
12 |
13 | public class CargoBookingPage {
14 |
15 | private final WebDriver driver;
16 | private final int port;
17 |
18 | public CargoBookingPage(WebDriver driver, int port) {
19 | this.driver = driver;
20 | this.port = port;
21 |
22 | WebElement newCargoTableCaption = driver.findElement(By.cssSelector("table caption"));
23 |
24 | assertThat("Book new cargo").isEqualTo(newCargoTableCaption.getText());
25 | }
26 |
27 | public void selectOrigin(String origin) {
28 | Select select = new Select(driver.findElement(By.name("originUnlocode")));
29 | select.selectByVisibleText(origin);
30 | }
31 |
32 | public void selectDestination(String destination) {
33 | Select select = new Select(driver.findElement(By.name("destinationUnlocode")));
34 | select.selectByVisibleText(destination);
35 | }
36 |
37 | public CargoDetailsPage book() {
38 | driver.findElement(By.name("originUnlocode")).submit();
39 |
40 | return new CargoDetailsPage(driver, port);
41 | }
42 |
43 | public void selectArrivalDeadline(LocalDate arrivalDeadline) {
44 | WebElement datePicker = driver.findElement(By.id("arrivalDeadline"));
45 | datePicker.clear();
46 | datePicker.sendKeys(arrivalDeadline.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/acceptance/pages/CargoDestinationPage.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.acceptance.pages;
2 |
3 | import org.openqa.selenium.By;
4 | import org.openqa.selenium.WebDriver;
5 | import org.openqa.selenium.WebElement;
6 | import org.openqa.selenium.support.ui.Select;
7 |
8 | import static org.assertj.core.api.Assertions.assertThat;
9 |
10 | public class CargoDestinationPage {
11 | private final WebDriver driver;
12 | private final int port;
13 |
14 | public CargoDestinationPage(WebDriver driver, int port) {
15 | this.driver = driver;
16 | this.port = port;
17 | WebElement cargoDestinationHeader = driver.findElement(By.cssSelector("table caption"));
18 |
19 | assertThat(cargoDestinationHeader.getText()).startsWith("Change destination for cargo ");
20 | }
21 |
22 | public CargoDetailsPage selectDestinationTo(String destination) {
23 | WebElement destinationPicker = driver.findElement(By.name("unlocode"));
24 | Select select = new Select(destinationPicker);
25 | select.selectByVisibleText(destination);
26 |
27 | destinationPicker.submit();
28 |
29 | CargoDetailsPage cargoDetailsPage = new CargoDetailsPage(driver, port);
30 | return cargoDetailsPage;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/acceptance/pages/CargoDetailsPage.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.acceptance.pages;
2 |
3 | import org.openqa.selenium.By;
4 | import org.openqa.selenium.WebDriver;
5 | import org.openqa.selenium.WebElement;
6 |
7 | import java.time.LocalDate;
8 | import java.time.format.DateTimeFormatter;
9 |
10 | import static org.assertj.core.api.Assertions.assertThat;
11 | import static org.junit.jupiter.api.Assertions.assertEquals;
12 |
13 | public class CargoDetailsPage {
14 | public static final String TRACKING_ID_HEADER = "Details for cargo ";
15 | public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy");
16 | private final WebDriver driver;
17 | private final int port;
18 | private String trackingId;
19 |
20 | public CargoDetailsPage(WebDriver driver, int port) {
21 | this.driver = driver;
22 | this.port = port;
23 |
24 | WebElement newCargoTableCaption = driver.findElement(By.cssSelector("table caption"));
25 |
26 | assertThat(newCargoTableCaption.getText()).startsWith(TRACKING_ID_HEADER);
27 | trackingId = newCargoTableCaption.getText().replaceFirst(TRACKING_ID_HEADER, "");
28 | }
29 |
30 | public String getTrackingId() {
31 | return trackingId;
32 | }
33 |
34 | public AdminPage listAllCargo() {
35 | driver.findElement(By.linkText("List all cargos")).click();
36 |
37 | return new AdminPage(driver, port);
38 | }
39 |
40 | public void expectOriginOf(String expectedOrigin) {
41 | String actualOrigin = driver.findElement(By.xpath("//div[@id='container']/table/tbody/tr[1]/td[2]")).getText();
42 |
43 | assertThat(expectedOrigin).isEqualTo(actualOrigin);
44 | }
45 |
46 | public void expectDestinationOf(String expectedDestination) {
47 | String actualDestination = driver.findElement(By.xpath("//div[@id='container']/table/tbody/tr[2]/td[2]")).getText();
48 |
49 | assertThat(expectedDestination).isEqualTo(actualDestination);
50 | }
51 |
52 | public CargoDestinationPage changeDestination() {
53 | driver.findElement(By.linkText("Change destination")).click();
54 |
55 | return new CargoDestinationPage(driver, port);
56 | }
57 |
58 | public void expectArrivalDeadlineOf(LocalDate expectedArrivalDeadline) {
59 | String actualArrivalDeadline = driver.findElement(By.xpath("//div[@id='container']/table/tbody/tr[4]/td[2]")).getText();
60 |
61 | assertThat(actualArrivalDeadline).isEqualTo(expectedArrivalDeadline.format(FORMATTER));
62 | }
63 |
64 | public void expectRoutedOf(String routingStatus) {
65 | String actualRoutingStatus = driver.findElement(By.xpath("//div[@id='container']/p[2]/strong")).getText();
66 |
67 | assertEquals(routingStatus, actualRoutingStatus);
68 | }
69 |
70 | public CargoRoutingPage routeCargo() {
71 | driver.findElement(By.linkText("Route this cargo")).click();
72 |
73 | return new CargoRoutingPage(driver, port);
74 | }
75 |
76 | public void expectItinerary() {
77 | String itineraryText = driver.findElement(By.xpath("//div[@id='container']/table[2]/caption")).getText();
78 | assertEquals("Itinerary", itineraryText);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/acceptance/pages/CargoRoutingPage.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.acceptance.pages;
2 |
3 | import org.openqa.selenium.By;
4 | import org.openqa.selenium.WebDriver;
5 | import org.openqa.selenium.WebElement;
6 |
7 | import static org.assertj.core.api.Assertions.assertThat;
8 | import static org.junit.jupiter.api.Assertions.assertEquals;
9 |
10 | public class CargoRoutingPage {
11 | private WebDriver driver;
12 | private int port;
13 |
14 | public CargoRoutingPage (WebDriver driver, int port) {
15 | this.driver = driver;
16 | this.port = port;
17 |
18 | WebElement routingHeader = driver.findElement(By.xpath("//h1"));
19 | assertThat(routingHeader.getText().equals("Cargo Booking and Routing"));
20 | }
21 |
22 | public void expectAtLeastOneRoute() {
23 | WebElement assignRouteCaption = driver.findElement(By.cssSelector("form table caption"));
24 | assertEquals("Route candidate 1", assignRouteCaption.getText());
25 | }
26 |
27 | public CargoDetailsPage assignCargoToFirstRoute() {
28 | WebElement assignCargoForm = driver.findElement(By.xpath("//div[@id='container']/form[1]"));
29 | assignCargoForm.submit();
30 |
31 | return new CargoDetailsPage(driver, port);
32 | }
33 |
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/acceptance/pages/CustomerPage.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.acceptance.pages;
2 |
3 | import org.openqa.selenium.By;
4 | import org.openqa.selenium.WebDriver;
5 | import org.openqa.selenium.WebElement;
6 |
7 | import static org.assertj.core.api.Assertions.assertThat;
8 |
9 | public class CustomerPage {
10 | private final WebDriver driver;
11 |
12 | public CustomerPage(WebDriver driver, int port) {
13 | this.driver = driver;
14 | driver.get(String.format("http://localhost:%d/dddsample/track", port));
15 | assertThat("Tracking cargo").isEqualTo(driver.getTitle());
16 | }
17 |
18 | public void trackCargoWithIdOf(String trackingId) {
19 | WebElement element = driver.findElement(By.id("idInput"));
20 | element.sendKeys(trackingId);
21 | element.submit();
22 |
23 | }
24 |
25 | public void expectCargoLocation(String expectedLocation) {
26 | WebElement cargoSummary = driver.findElement(By.cssSelector("#result h2"));
27 | assertThat(cargoSummary.getText()).endsWith(expectedLocation);
28 | }
29 |
30 | public void expectErrorFor(String expectedErrorMessage) {
31 | WebElement error = driver.findElement(By.cssSelector(".error"));
32 | assertThat(error.getText()).endsWith(expectedErrorMessage);
33 | }
34 |
35 | public void expectNotificationOf(String expectedNotificationMessage) {
36 | WebElement error = driver.findElement(By.cssSelector(".notify"));
37 | assertThat(error.getText()).endsWith(expectedNotificationMessage);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/application/BookingServiceTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.application;
2 |
3 | import org.junit.jupiter.api.BeforeEach;
4 | import org.junit.jupiter.api.Test;
5 | import se.citerus.dddsample.application.impl.BookingServiceImpl;
6 | import se.citerus.dddsample.domain.model.cargo.Cargo;
7 | import se.citerus.dddsample.domain.model.cargo.CargoFactory;
8 | import se.citerus.dddsample.domain.model.cargo.CargoRepository;
9 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
10 | import se.citerus.dddsample.domain.model.location.LocationRepository;
11 | import se.citerus.dddsample.domain.model.location.UnLocode;
12 | import se.citerus.dddsample.domain.service.RoutingService;
13 |
14 | import java.time.Instant;
15 |
16 | import static org.assertj.core.api.Assertions.assertThat;
17 | import static org.mockito.ArgumentMatchers.any;
18 | import static org.mockito.ArgumentMatchers.isA;
19 | import static org.mockito.Mockito.*;
20 | import static se.citerus.dddsample.infrastructure.sampledata.SampleLocations.CHICAGO;
21 | import static se.citerus.dddsample.infrastructure.sampledata.SampleLocations.STOCKHOLM;
22 |
23 | public class BookingServiceTest {
24 |
25 | BookingServiceImpl bookingService;
26 | CargoRepository cargoRepository;
27 | LocationRepository locationRepository;
28 | RoutingService routingService;
29 | CargoFactory cargoFactory;
30 |
31 | @BeforeEach
32 | public void setUp() {
33 | cargoRepository = mock(CargoRepository.class);
34 | locationRepository = mock(LocationRepository.class);
35 | routingService = mock(RoutingService.class);
36 | cargoFactory = new CargoFactory(locationRepository, cargoRepository);
37 | bookingService = new BookingServiceImpl(cargoRepository, locationRepository, routingService, cargoFactory);
38 | }
39 |
40 | @Test
41 | public void testRegisterNew() {
42 | TrackingId expectedTrackingId = new TrackingId("TRK1");
43 | UnLocode fromUnlocode = new UnLocode("USCHI");
44 | UnLocode toUnlocode = new UnLocode("SESTO");
45 |
46 | when(cargoRepository.nextTrackingId()).thenReturn(expectedTrackingId);
47 | when(locationRepository.find(fromUnlocode)).thenReturn(CHICAGO);
48 | when(locationRepository.find(toUnlocode)).thenReturn(STOCKHOLM);
49 |
50 | TrackingId trackingId = bookingService.bookNewCargo(fromUnlocode, toUnlocode, Instant.now());
51 | assertThat(trackingId).isEqualTo(expectedTrackingId);
52 | verify(cargoRepository, times(1)).store(isA(Cargo.class));
53 | verify(locationRepository, times(2)).find(any(UnLocode.class));
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/application/HandlingEventServiceTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.application;
2 |
3 | import org.junit.jupiter.api.BeforeEach;
4 | import org.junit.jupiter.api.Test;
5 | import se.citerus.dddsample.application.impl.HandlingEventServiceImpl;
6 | import se.citerus.dddsample.domain.model.cargo.Cargo;
7 | import se.citerus.dddsample.domain.model.cargo.CargoRepository;
8 | import se.citerus.dddsample.domain.model.cargo.RouteSpecification;
9 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
10 | import se.citerus.dddsample.domain.model.handling.HandlingEvent;
11 | import se.citerus.dddsample.domain.model.handling.HandlingEventFactory;
12 | import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
13 | import se.citerus.dddsample.domain.model.location.LocationRepository;
14 | import se.citerus.dddsample.domain.model.voyage.VoyageRepository;
15 |
16 | import java.time.Instant;
17 |
18 | import static org.mockito.ArgumentMatchers.isA;
19 | import static org.mockito.Mockito.*;
20 | import static se.citerus.dddsample.infrastructure.sampledata.SampleLocations.*;
21 | import static se.citerus.dddsample.infrastructure.sampledata.SampleVoyages.CM001;
22 |
23 | public class HandlingEventServiceTest {
24 | private HandlingEventServiceImpl service;
25 | private ApplicationEvents applicationEvents;
26 | private CargoRepository cargoRepository;
27 | private VoyageRepository voyageRepository;
28 | private HandlingEventRepository handlingEventRepository;
29 | private LocationRepository locationRepository;
30 |
31 | private final Cargo cargo = new Cargo(new TrackingId("ABC"), new RouteSpecification(HAMBURG, TOKYO, Instant.now()));
32 |
33 | @BeforeEach
34 | public void setUp() {
35 | cargoRepository = mock(CargoRepository.class);
36 | voyageRepository = mock(VoyageRepository.class);
37 | handlingEventRepository = mock(HandlingEventRepository.class);
38 | locationRepository = mock(LocationRepository.class);
39 | applicationEvents = mock(ApplicationEvents.class);
40 |
41 | HandlingEventFactory handlingEventFactory = new HandlingEventFactory(cargoRepository, voyageRepository, locationRepository);
42 | service = new HandlingEventServiceImpl(handlingEventRepository, applicationEvents, handlingEventFactory);
43 | }
44 |
45 | @Test
46 | public void testRegisterEvent() throws Exception {
47 | when(cargoRepository.find(cargo.trackingId())).thenReturn(cargo);
48 | when(voyageRepository.find(CM001.voyageNumber())).thenReturn(CM001);
49 | when(locationRepository.find(STOCKHOLM.unLocode())).thenReturn(STOCKHOLM);
50 |
51 | service.registerHandlingEvent(Instant.now(), cargo.trackingId(), CM001.voyageNumber(), STOCKHOLM.unLocode(), HandlingEvent.Type.LOAD);
52 | verify(handlingEventRepository, times(1)).store(isA(HandlingEvent.class));
53 | verify(applicationEvents, times(1)).cargoWasHandled(isA(HandlingEvent.class));
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/domain/model/cargo/LegTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.cargo;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static org.assertj.core.api.Assertions.fail;
6 |
7 | public class LegTest {
8 |
9 | @Test
10 | public void testConstructor() {
11 | try {
12 | new Leg(null,null,null,null,null);
13 | fail("Should not accept null constructor arguments");
14 | } catch (IllegalArgumentException expected) {}
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/domain/model/cargo/RouteSpecificationTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.cargo;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import se.citerus.dddsample.domain.model.voyage.Voyage;
5 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
6 |
7 | import java.util.List;
8 |
9 | import static org.assertj.core.api.Assertions.assertThat;
10 | import static se.citerus.dddsample.application.util.DateUtils.toDate;
11 | import static se.citerus.dddsample.infrastructure.sampledata.SampleLocations.*;
12 |
13 | public class RouteSpecificationTest {
14 |
15 | final Voyage hongKongTokyoNewYork = new Voyage.Builder(
16 | new VoyageNumber("V001"), HONGKONG).
17 | addMovement(TOKYO, toDate("2009-02-01"), toDate("2009-02-05")).
18 | addMovement(NEWYORK, toDate("2009-02-06"), toDate("2009-02-10")).
19 | addMovement(HONGKONG, toDate("2009-02-11"), toDate("2009-02-14")).
20 | build();
21 |
22 | final Voyage dallasNewYorkChicago = new Voyage.Builder(
23 | new VoyageNumber("V002"), DALLAS).
24 | addMovement(NEWYORK, toDate("2009-02-06"), toDate("2009-02-07")).
25 | addMovement(CHICAGO, toDate("2009-02-12"), toDate("2009-02-20")).
26 | build();
27 |
28 | // TODO:
29 | // it shouldn't be possible to create Legs that have load/unload locations
30 | // and/or dates that don't match the voyage's carrier movements.
31 | final Itinerary itinerary = new Itinerary(List.of(
32 | new Leg(hongKongTokyoNewYork, HONGKONG, NEWYORK,
33 | toDate("2009-02-01"), toDate("2009-02-10")),
34 | new Leg(dallasNewYorkChicago, NEWYORK, CHICAGO,
35 | toDate("2009-02-12"), toDate("2009-02-20")))
36 | );
37 | @Test
38 | public void testIsSatisfiedBy_Success() {
39 | RouteSpecification routeSpecification = new RouteSpecification(
40 | HONGKONG, CHICAGO, toDate("2009-03-01")
41 | );
42 |
43 | assertThat(routeSpecification.isSatisfiedBy(itinerary)).isTrue();
44 | }
45 |
46 | @Test
47 | public void testIsSatisfiedBy_WrongOrigin() {
48 | RouteSpecification routeSpecification = new RouteSpecification(
49 | HANGZHOU, CHICAGO, toDate("2009-03-01")
50 | );
51 |
52 | assertThat(routeSpecification.isSatisfiedBy(itinerary)).isFalse();
53 | }
54 | @Test
55 | public void testIsSatisfiedBy_WrongDestination() {
56 | RouteSpecification routeSpecification = new RouteSpecification(
57 | HONGKONG, DALLAS, toDate("2009-03-01")
58 | );
59 |
60 | assertThat(routeSpecification.isSatisfiedBy(itinerary)).isFalse();
61 | }
62 | @Test
63 | public void testIsSatisfiedBy_MissedDeadline() {
64 | RouteSpecification routeSpecification = new RouteSpecification(
65 | HONGKONG, CHICAGO, toDate("2009-02-15")
66 | );
67 |
68 | assertThat(routeSpecification.isSatisfiedBy(itinerary)).isFalse();
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/domain/model/cargo/TrackingIdTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.cargo;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
6 |
7 | public class TrackingIdTest {
8 |
9 | @Test
10 | public void testConstructor() {
11 | assertThatThrownBy(() -> new TrackingId(null)).isInstanceOf(NullPointerException.class);
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/domain/model/handling/HandlingHistoryTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.handling;
2 |
3 | import org.junit.jupiter.api.BeforeEach;
4 | import org.junit.jupiter.api.Test;
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 se.citerus.dddsample.domain.model.voyage.Voyage;
9 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
10 |
11 | import java.time.Instant;
12 | import java.util.List;
13 |
14 | import static org.assertj.core.api.Assertions.assertThat;
15 | import static se.citerus.dddsample.application.util.DateUtils.toDate;
16 | import static se.citerus.dddsample.infrastructure.sampledata.SampleLocations.*;
17 |
18 | public class HandlingHistoryTest {
19 | Cargo cargo;
20 | Voyage voyage;
21 | HandlingEvent event1;
22 | HandlingEvent event1duplicate;
23 | HandlingEvent event2;
24 | HandlingHistory handlingHistory;
25 |
26 | @BeforeEach
27 | public void setUp() {
28 | cargo = new Cargo(new TrackingId("ABC"), new RouteSpecification(SHANGHAI, DALLAS, toDate("2009-04-01")));
29 | voyage = new Voyage.Builder(new VoyageNumber("X25"), HONGKONG).
30 | addMovement(SHANGHAI, Instant.now(), Instant.now()).
31 | addMovement(DALLAS, Instant.now(), Instant.now()).
32 | build();
33 | event1 = new HandlingEvent(cargo, toDate("2009-03-05"), Instant.ofEpochMilli(100), HandlingEvent.Type.LOAD, SHANGHAI, voyage);
34 | event1duplicate = new HandlingEvent(cargo, toDate("2009-03-05"), Instant.ofEpochMilli(200), HandlingEvent.Type.LOAD, SHANGHAI, voyage);
35 | event2 = new HandlingEvent(cargo, toDate("2009-03-10"), Instant.ofEpochMilli(150), HandlingEvent.Type.UNLOAD, DALLAS, voyage);
36 |
37 | handlingHistory = new HandlingHistory(List.of(event2, event1, event1duplicate));
38 | }
39 |
40 | @Test
41 | public void testDistinctEventsByCompletionTime() {
42 | assertThat(handlingHistory.distinctEventsByCompletionTime()).isEqualTo(List.of(event1, event2));
43 | }
44 |
45 | @Test
46 | public void testMostRecentlyCompletedEvent() {
47 | assertThat(handlingHistory.mostRecentlyCompletedEvent()).isEqualTo(event2);
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/domain/model/location/LocationTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.location;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThat;
6 | import static org.assertj.core.api.Assertions.fail;
7 |
8 | public class LocationTest {
9 |
10 | @Test
11 | public void testEquals() {
12 | // Same UN locode - equal
13 | assertThat(new Location(new UnLocode("ATEST"),"test-name").
14 | equals(new Location(new UnLocode("ATEST"),"test-name"))).isTrue();
15 |
16 | // Different UN locodes - not equal
17 | assertThat(new Location(new UnLocode("ATEST"),"test-name").
18 | equals(new Location(new UnLocode("TESTB"), "test-name"))).isFalse();
19 |
20 | // Always equal to itself
21 | Location location = new Location(new UnLocode("ATEST"),"test-name");
22 | assertThat(location.equals(location)).isTrue();
23 |
24 | // Never equal to null
25 | assertThat(location.equals(null)).isFalse();
26 |
27 | // Special UNKNOWN location is equal to itself
28 | assertThat(Location.UNKNOWN.equals(Location.UNKNOWN)).isTrue();
29 |
30 | try {
31 | new Location((UnLocode) null, null);
32 | fail("Should not allow any null constructor arguments");
33 | } catch (NullPointerException expected) {}
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/domain/model/location/UnLocodeTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.location;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.junit.jupiter.params.ParameterizedTest;
5 | import org.junit.jupiter.params.provider.EmptySource;
6 | import org.junit.jupiter.params.provider.NullSource;
7 | import org.junit.jupiter.params.provider.ValueSource;
8 |
9 | import static org.assertj.core.api.Assertions.assertThat;
10 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
11 |
12 | public class UnLocodeTest {
13 |
14 | @ValueSource(strings = {"AA234","AAA9B","AAAAA"})
15 | @ParameterizedTest
16 | public void shouldAllowCreationOfValidUnLoCodes(String input) {
17 | assertThat(new UnLocode(input)).isNotNull();
18 | }
19 |
20 | @ValueSource(strings = {"AAAA", "AAAAAA", "AAAA", "AAAAAA", "22AAA", "AA111"})
21 | @NullSource
22 | @EmptySource
23 | @ParameterizedTest
24 | public void shouldPreventCreationOfInvalidUnLoCodes(String input) {
25 | assertThatThrownBy(() -> new UnLocode(input)).isInstanceOfAny(NullPointerException.class, IllegalArgumentException.class);
26 | }
27 |
28 | @Test
29 | public void testIdString() {
30 | assertThat(new UnLocode("AbcDe").idString()).isEqualTo("ABCDE");
31 | }
32 |
33 | @Test
34 | public void testEquals() {
35 | UnLocode allCaps = new UnLocode("ABCDE");
36 | UnLocode mixedCase = new UnLocode("aBcDe");
37 |
38 | assertThat(allCaps.equals(mixedCase)).isTrue();
39 | assertThat(mixedCase.equals(allCaps)).isTrue();
40 | assertThat(allCaps.equals(allCaps)).isTrue();
41 |
42 | assertThat(allCaps.equals(null)).isFalse();
43 | assertThat(allCaps.equals(new UnLocode("FGHIJ"))).isFalse();
44 | }
45 |
46 | @Test
47 | public void testHashCode() {
48 | UnLocode allCaps = new UnLocode("ABCDE");
49 | UnLocode mixedCase = new UnLocode("aBcDe");
50 |
51 | assertThat(mixedCase.hashCode()).isEqualTo(allCaps.hashCode());
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/domain/model/voyage/CarrierMovementTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.model.voyage;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.time.Instant;
6 |
7 | import static org.assertj.core.api.Assertions.assertThat;
8 | import static org.assertj.core.api.Assertions.fail;
9 | import static se.citerus.dddsample.infrastructure.sampledata.SampleLocations.HAMBURG;
10 | import static se.citerus.dddsample.infrastructure.sampledata.SampleLocations.STOCKHOLM;
11 |
12 | public class CarrierMovementTest {
13 |
14 | @Test
15 | public void testConstructor() {
16 | try {
17 | new CarrierMovement(null, null, Instant.now(), Instant.now());
18 | fail("Should not accept null constructor arguments");
19 | } catch (IllegalArgumentException expected) {}
20 |
21 | try {
22 | new CarrierMovement(STOCKHOLM, null, Instant.now(), Instant.now());
23 | fail("Should not accept null constructor arguments");
24 | } catch (IllegalArgumentException expected) {}
25 |
26 | // Legal
27 | new CarrierMovement(STOCKHOLM, HAMBURG, Instant.now(), Instant.now());
28 | }
29 |
30 | @Test
31 | public void testSameValueAsEqualsHashCode() {
32 | long referenceTime = System.currentTimeMillis();
33 |
34 | // One could, in theory, use the same Date(referenceTime) for all of these movements
35 | // However, in practice, carrier movements will be initialized by different processes
36 | // so we might have different Date that reference the same time, and we want to be
37 | // certain that sameValueAs does the right thing in that case.
38 | CarrierMovement cm1 = new CarrierMovement(STOCKHOLM, HAMBURG, Instant.ofEpochMilli(referenceTime), Instant.ofEpochMilli(referenceTime));
39 | CarrierMovement cm2 = new CarrierMovement(STOCKHOLM, HAMBURG, Instant.ofEpochMilli(referenceTime), Instant.ofEpochMilli(referenceTime));
40 | CarrierMovement cm3 = new CarrierMovement(HAMBURG, STOCKHOLM, Instant.ofEpochMilli(referenceTime), Instant.ofEpochMilli(referenceTime));
41 | CarrierMovement cm4 = new CarrierMovement(HAMBURG, STOCKHOLM, Instant.ofEpochMilli(referenceTime), Instant.ofEpochMilli(referenceTime));
42 |
43 | assertThat(cm1.sameValueAs(cm2)).isTrue();
44 | assertThat(cm2.sameValueAs(cm3)).isFalse();
45 | assertThat(cm3.sameValueAs(cm4)).isTrue();
46 |
47 | assertThat(cm1.equals(cm2)).isTrue();
48 | assertThat(cm2.equals(cm3)).isFalse();
49 | assertThat(cm3.equals(cm4)).isTrue();
50 |
51 | assertThat(cm1.hashCode() == cm2.hashCode()).isTrue();
52 | assertThat(cm2.hashCode() == cm3.hashCode()).isFalse();
53 | assertThat(cm3.hashCode() == cm4.hashCode()).isTrue();
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/domain/shared/AlwaysFalseSpec.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.shared;
2 |
3 | public class AlwaysFalseSpec extends AbstractSpecification {
4 | public boolean isSatisfiedBy(Object o) {
5 | return false;
6 | }
7 | }
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/domain/shared/AlwaysTrueSpec.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.shared;
2 |
3 | public class AlwaysTrueSpec extends AbstractSpecification {
4 | public boolean isSatisfiedBy(Object o) {
5 | return true;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/domain/shared/AndSpecificationTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.shared;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | public class AndSpecificationTest {
8 |
9 | @Test
10 | public void testAndIsSatisifedBy() {
11 | AlwaysTrueSpec trueSpec = new AlwaysTrueSpec();
12 | AlwaysFalseSpec falseSpec = new AlwaysFalseSpec();
13 |
14 | AndSpecification andSpecification = new AndSpecification(trueSpec, trueSpec);
15 | assertThat(andSpecification.isSatisfiedBy(new Object())).isTrue();
16 |
17 | andSpecification = new AndSpecification(falseSpec, trueSpec);
18 | assertThat(andSpecification.isSatisfiedBy(new Object())).isFalse();
19 |
20 | andSpecification = new AndSpecification(trueSpec, falseSpec);
21 | assertThat(andSpecification.isSatisfiedBy(new Object())).isFalse();
22 |
23 | andSpecification = new AndSpecification(falseSpec, falseSpec);
24 | assertThat(andSpecification.isSatisfiedBy(new Object())).isFalse();
25 |
26 | }
27 | }
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/domain/shared/NotSpecificationTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.shared;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | public class NotSpecificationTest {
8 |
9 | @Test
10 | public void testAndIsSatisifedBy() {
11 | AlwaysTrueSpec trueSpec = new AlwaysTrueSpec();
12 | AlwaysFalseSpec falseSpec = new AlwaysFalseSpec();
13 |
14 | NotSpecification notSpecification = new NotSpecification(trueSpec);
15 | assertThat(notSpecification.isSatisfiedBy(new Object())).isFalse();
16 |
17 | notSpecification = new NotSpecification(falseSpec);
18 | assertThat(notSpecification.isSatisfiedBy(new Object())).isTrue();
19 |
20 | }
21 | }
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/domain/shared/OrSpecificationTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.domain.shared;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | public class OrSpecificationTest {
8 |
9 | @Test
10 | public void testAndIsSatisifedBy() {
11 | AlwaysTrueSpec trueSpec = new AlwaysTrueSpec();
12 | AlwaysFalseSpec falseSpec = new AlwaysFalseSpec();
13 |
14 | OrSpecification orSpecification = new OrSpecification(trueSpec, trueSpec);
15 | assertThat(orSpecification.isSatisfiedBy(new Object())).isTrue();
16 |
17 | orSpecification = new OrSpecification(falseSpec, trueSpec);
18 | assertThat(orSpecification.isSatisfiedBy(new Object())).isTrue();
19 |
20 | orSpecification = new OrSpecification(trueSpec, falseSpec);
21 | assertThat(orSpecification.isSatisfiedBy(new Object())).isTrue();
22 |
23 | orSpecification = new OrSpecification(falseSpec, falseSpec);
24 | assertThat(orSpecification.isSatisfiedBy(new Object())).isFalse();
25 |
26 | }
27 | }
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/infrastructure/messaging/stub/SynchronousApplicationEventsStub.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.messaging.stub;
2 |
3 | import se.citerus.dddsample.application.ApplicationEvents;
4 | import se.citerus.dddsample.application.CargoInspectionService;
5 | import se.citerus.dddsample.domain.model.cargo.Cargo;
6 | import se.citerus.dddsample.domain.model.handling.HandlingEvent;
7 | import se.citerus.dddsample.interfaces.handling.HandlingEventRegistrationAttempt;
8 |
9 | public class SynchronousApplicationEventsStub implements ApplicationEvents {
10 |
11 | CargoInspectionService cargoInspectionService;
12 |
13 | public void setCargoInspectionService(CargoInspectionService cargoInspectionService) {
14 | this.cargoInspectionService = cargoInspectionService;
15 | }
16 |
17 | @Override
18 | public void cargoWasHandled(HandlingEvent event) {
19 | System.out.println("EVENT: cargo was handled: " + event);
20 | cargoInspectionService.inspectCargo(event.cargo().trackingId());
21 | }
22 |
23 | @Override
24 | public void cargoWasMisdirected(Cargo cargo) {
25 | System.out.println("EVENT: cargo was misdirected");
26 | }
27 |
28 | @Override
29 | public void cargoHasArrived(Cargo cargo) {
30 | System.out.println("EVENT: cargo has arrived: " + cargo.trackingId().idString());
31 | }
32 |
33 | @Override
34 | public void receivedHandlingEventRegistrationAttempt(HandlingEventRegistrationAttempt attempt) {
35 | System.out.println("EVENT: received handling event registration attempt");
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/infrastructure/persistence/inmemory/HandlingEventRepositoryInMem.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.persistence.inmemory;
2 |
3 | import se.citerus.dddsample.domain.model.cargo.TrackingId;
4 | import se.citerus.dddsample.domain.model.handling.HandlingEvent;
5 | import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
6 | import se.citerus.dddsample.domain.model.handling.HandlingHistory;
7 |
8 | import java.util.*;
9 |
10 | public class HandlingEventRepositoryInMem implements HandlingEventRepository {
11 |
12 | private final Map> eventMap = new HashMap<>();
13 |
14 | @Override
15 | public void store(HandlingEvent event) {
16 | final TrackingId trackingId = event.cargo().trackingId();
17 | List list = eventMap.computeIfAbsent(trackingId, k -> new ArrayList<>());
18 | list.add(event);
19 | }
20 |
21 | @Override
22 | public HandlingHistory lookupHandlingHistoryOfCargo(TrackingId trackingId) {
23 | List events = eventMap.get(trackingId);
24 | if (events == null) events = Collections.emptyList();
25 |
26 | return new HandlingHistory(events);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/infrastructure/persistence/inmemory/LocationRepositoryInMem.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.persistence.inmemory;
2 |
3 | import se.citerus.dddsample.domain.model.location.Location;
4 | import se.citerus.dddsample.domain.model.location.LocationRepository;
5 | import se.citerus.dddsample.domain.model.location.UnLocode;
6 | import se.citerus.dddsample.infrastructure.sampledata.SampleLocations;
7 |
8 | import java.util.List;
9 |
10 | public class LocationRepositoryInMem implements LocationRepository {
11 |
12 | public Location find(UnLocode unLocode) {
13 | for (Location location : SampleLocations.getAll()) {
14 | if (location.unLocode().equals(unLocode)) {
15 | return location;
16 | }
17 | }
18 | return null;
19 | }
20 |
21 | public List getAll() {
22 | return SampleLocations.getAll();
23 | }
24 |
25 | @Override
26 | public Location store(Location location) {
27 | return location;
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/infrastructure/persistence/inmemory/VoyageRepositoryInMem.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.persistence.inmemory;
2 |
3 | import se.citerus.dddsample.domain.model.voyage.Voyage;
4 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
5 | import se.citerus.dddsample.domain.model.voyage.VoyageRepository;
6 | import se.citerus.dddsample.infrastructure.sampledata.SampleVoyages;
7 |
8 | public final class VoyageRepositoryInMem implements VoyageRepository {
9 |
10 | public Voyage find(VoyageNumber voyageNumber) {
11 | return SampleVoyages.lookup(voyageNumber);
12 | }
13 |
14 | @Override
15 | public void store(Voyage voyage) {
16 | // noop
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/infrastructure/persistence/jpa/CarrierMovementRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.persistence.jpa;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.junit.jupiter.api.extension.ExtendWith;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
7 | import org.springframework.test.annotation.DirtiesContext;
8 | import org.springframework.test.context.ContextConfiguration;
9 | import org.springframework.test.context.junit.jupiter.SpringExtension;
10 | import org.springframework.transaction.annotation.Transactional;
11 | import se.citerus.dddsample.domain.model.voyage.Voyage;
12 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
13 | import se.citerus.dddsample.domain.model.voyage.VoyageRepository;
14 |
15 | import static org.assertj.core.api.Assertions.assertThat;
16 |
17 | @ExtendWith(SpringExtension.class)
18 | @DataJpaTest
19 | @ContextConfiguration(classes = TestRepositoryConfig.class)
20 | @Transactional
21 | @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
22 | public class CarrierMovementRepositoryTest {
23 |
24 | @Autowired
25 | VoyageRepository voyageRepository;
26 |
27 | @Test
28 | public void testFind() {
29 | Voyage voyage = voyageRepository.find(new VoyageNumber("0100S"));
30 | assertThat(voyage).isNotNull();
31 | assertThat(voyage.voyageNumber().idString()).isEqualTo("0100S");
32 | /* TODO adapt
33 | assertThat(carrierMovement.departureLocation()).isEqualTo(STOCKHOLM);
34 | assertThat(carrierMovement.arrivalLocation()).isEqualTo(HELSINKI);
35 | assertThat(carrierMovement.departureTime()).isEqualTo(DateTestUtil.toDate("2007-09-23", "02:00"));
36 | assertThat(carrierMovement.arrivalTime()).isEqualTo(DateTestUtil.toDate("2007-09-23", "03:00"));
37 | */
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/infrastructure/persistence/jpa/LocationRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.persistence.jpa;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.junit.jupiter.api.extension.ExtendWith;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
7 | import org.springframework.test.annotation.DirtiesContext;
8 | import org.springframework.test.context.ContextConfiguration;
9 | import org.springframework.test.context.junit.jupiter.SpringExtension;
10 | import org.springframework.transaction.annotation.Transactional;
11 | import se.citerus.dddsample.domain.model.location.Location;
12 | import se.citerus.dddsample.domain.model.location.LocationRepository;
13 | import se.citerus.dddsample.domain.model.location.UnLocode;
14 |
15 | import java.util.List;
16 |
17 | import static org.assertj.core.api.Assertions.assertThat;
18 |
19 | @ExtendWith(SpringExtension.class)
20 | @DataJpaTest
21 | @ContextConfiguration(classes = TestRepositoryConfig.class)
22 | @Transactional
23 | @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
24 | public class LocationRepositoryTest {
25 | @Autowired
26 | private LocationRepository locationRepository;
27 |
28 | @Test
29 | public void testFind() {
30 | final UnLocode melbourne = new UnLocode("AUMEL");
31 | Location location = locationRepository.find(melbourne);
32 | assertThat(location).isNotNull();
33 | assertThat(location.unLocode()).isEqualTo(melbourne);
34 |
35 | assertThat(locationRepository.find(new UnLocode("NOLOC"))).isNull();
36 | }
37 |
38 | @Test
39 | public void testFindAll() {
40 | List allLocations = locationRepository.getAll();
41 |
42 | assertThat(allLocations).isNotNull().hasSize(13);
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/infrastructure/persistence/jpa/TestRepositoryConfig.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.infrastructure.persistence.jpa;
2 |
3 | import org.springframework.boot.test.context.TestConfiguration;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Primary;
6 | import se.citerus.dddsample.interfaces.handling.file.UploadDirectoryScanner;
7 |
8 | /**
9 | * This config is required by the repository tests to avoid a strange behavior where the UploadDirectoryScanner
10 | * creates directories despite the file paths not having been initialized properly.
11 | */
12 | @TestConfiguration
13 | public class TestRepositoryConfig {
14 | @Primary
15 | @Bean
16 | public UploadDirectoryScanner uploadDirectoryScanner() {
17 | return new UploadDirectoryScanner(null, null, null) {
18 | @Override
19 | public void afterPropertiesSet() {
20 | // noop
21 | }
22 | };
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/interfaces/booking/facade/internal/assembler/CargoRoutingDTOAssemblerTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.booking.facade.internal.assembler;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import se.citerus.dddsample.domain.model.cargo.*;
5 | import se.citerus.dddsample.domain.model.location.Location;
6 | import se.citerus.dddsample.interfaces.booking.facade.dto.CargoRoutingDTO;
7 | import se.citerus.dddsample.interfaces.booking.facade.dto.LegDTO;
8 |
9 | import java.time.Instant;
10 | import java.util.List;
11 |
12 | import static org.assertj.core.api.Assertions.assertThat;
13 | import static se.citerus.dddsample.infrastructure.sampledata.SampleLocations.*;
14 | import static se.citerus.dddsample.infrastructure.sampledata.SampleVoyages.CM001;
15 |
16 | public class CargoRoutingDTOAssemblerTest {
17 |
18 | @Test
19 | public void testToDTO() {
20 | final CargoRoutingDTOAssembler assembler = new CargoRoutingDTOAssembler();
21 |
22 | final Location origin = STOCKHOLM;
23 | final Location destination = MELBOURNE;
24 | final Cargo cargo = new Cargo(new TrackingId("XYZ"), new RouteSpecification(origin, destination, Instant.now()));
25 |
26 | final Itinerary itinerary = new Itinerary(
27 | List.of(
28 | new Leg(CM001, origin, SHANGHAI, Instant.now(), Instant.now()),
29 | new Leg(CM001, ROTTERDAM, destination, Instant.now(), Instant.now())
30 | )
31 | );
32 |
33 | cargo.assignToRoute(itinerary);
34 |
35 | final CargoRoutingDTO dto = assembler.toDTO(cargo);
36 |
37 | assertThat(dto.getLegs()).hasSize(2);
38 |
39 | LegDTO legDTO = dto.getLegs().get(0);
40 | assertThat(legDTO.getVoyageNumber()).isEqualTo("CM001");
41 | assertThat(legDTO.getFrom()).isEqualTo("SESTO");
42 | assertThat(legDTO.getTo()).isEqualTo("CNSHA");
43 |
44 | legDTO = dto.getLegs().get(1);
45 | assertThat(legDTO.getVoyageNumber()).isEqualTo("CM001");
46 | assertThat(legDTO.getFrom()).isEqualTo("NLRTM");
47 | assertThat(legDTO.getTo()).isEqualTo("AUMEL");
48 | }
49 |
50 | @Test
51 | public void testToDTO_NoItinerary() {
52 | final CargoRoutingDTOAssembler assembler = new CargoRoutingDTOAssembler();
53 |
54 | final Cargo cargo = new Cargo(new TrackingId("XYZ"), new RouteSpecification(STOCKHOLM, MELBOURNE, Instant.now()));
55 | final CargoRoutingDTO dto = assembler.toDTO(cargo);
56 |
57 | assertThat(dto.getTrackingId()).isEqualTo("XYZ");
58 | assertThat(dto.getOrigin()).isEqualTo("SESTO");
59 | assertThat(dto.getFinalDestination()).isEqualTo("AUMEL");
60 | assertThat(dto.getLegs().isEmpty()).isTrue();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/interfaces/booking/facade/internal/assembler/LocationDTOAssemblerTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.booking.facade.internal.assembler;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import se.citerus.dddsample.domain.model.location.Location;
5 | import se.citerus.dddsample.interfaces.booking.facade.dto.LocationDTO;
6 |
7 | import java.util.List;
8 |
9 | import static org.assertj.core.api.Assertions.assertThat;
10 | import static se.citerus.dddsample.infrastructure.sampledata.SampleLocations.HAMBURG;
11 | import static se.citerus.dddsample.infrastructure.sampledata.SampleLocations.STOCKHOLM;
12 |
13 | public class LocationDTOAssemblerTest {
14 |
15 | @Test
16 | public void testToDTOList() {
17 | final LocationDTOAssembler assembler = new LocationDTOAssembler();
18 | final List locationList = List.of(STOCKHOLM, HAMBURG);
19 |
20 | final List dtos = assembler.toDTOList(locationList);
21 |
22 | assertThat(dtos).hasSize(2);
23 |
24 | LocationDTO dto = dtos.get(0);
25 | assertThat(dto.getUnLocode()).isEqualTo("SESTO");
26 | assertThat(dto.getName()).isEqualTo("Stockholm");
27 |
28 | dto = dtos.get(1);
29 | assertThat(dto.getUnLocode()).isEqualTo("DEHAM");
30 | assertThat(dto.getName()).isEqualTo("Hamburg");
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/interfaces/booking/web/ItinerarySelectionCommandTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.booking.web;
2 |
3 | import org.assertj.core.groups.Tuple;
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.mock.web.MockHttpServletRequest;
6 | import org.springframework.web.bind.ServletRequestDataBinder;
7 |
8 | import java.util.List;
9 |
10 | import static org.assertj.core.api.Assertions.assertThat;
11 |
12 | public class ItinerarySelectionCommandTest {
13 |
14 | RouteAssignmentCommand command;
15 | MockHttpServletRequest request;
16 |
17 | @Test
18 | public void testBind() {
19 | command = new RouteAssignmentCommand();
20 | request = new MockHttpServletRequest();
21 |
22 | request.addParameter("legs[0].voyageNumber", "CM01");
23 | request.addParameter("legs[0].fromUnLocode", "AAAAA");
24 | request.addParameter("legs[0].toUnLocode", "BBBBB");
25 |
26 | request.addParameter("legs[1].voyageNumber", "CM02");
27 | request.addParameter("legs[1].fromUnLocode", "CCCCC");
28 | request.addParameter("legs[1].toUnLocode", "DDDDD");
29 |
30 | request.addParameter("trackingId", "XYZ");
31 |
32 | ServletRequestDataBinder binder = new ServletRequestDataBinder(command);
33 | binder.bind(request);
34 |
35 | assertThat(command.getLegs()).hasSize(2).extracting("voyageNumber", "fromUnLocode", "toUnLocode")
36 | .containsAll(List.of(Tuple.tuple("CM01", "AAAAA", "BBBBB"), Tuple.tuple("CM02", "CCCCC", "DDDDD")));
37 |
38 | assertThat(command.getTrackingId()).isEqualTo("XYZ");
39 | }
40 | }
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/interfaces/tracking/TrackCommandValidatorTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.tracking;
2 |
3 | import org.junit.jupiter.api.BeforeEach;
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.validation.BeanPropertyBindingResult;
6 | import org.springframework.validation.BindingResult;
7 | import org.springframework.validation.FieldError;
8 |
9 | import static org.assertj.core.api.Assertions.assertThat;
10 |
11 | public class TrackCommandValidatorTest {
12 |
13 | TrackCommandValidator validator;
14 |
15 | @BeforeEach
16 | public void setUp() {
17 | validator = new TrackCommandValidator();
18 | }
19 |
20 | @Test
21 | public void testValidateIllegalId() {
22 | TrackCommand command = new TrackCommand();
23 | BindingResult errors = new BeanPropertyBindingResult(command, "command");
24 | validator.validate(command, errors);
25 |
26 | assertThat(errors.getErrorCount()).isEqualTo(1);
27 | FieldError error = errors.getFieldError("trackingId");
28 | assertThat(error).isNotNull();
29 | assertThat(error.getRejectedValue()).isNull();
30 | assertThat(error.getCode()).isEqualTo("error.required");
31 | }
32 |
33 | @Test
34 | public void testValidateSuccess() {
35 | TrackCommand command = new TrackCommand();
36 | command.setTrackingId("non-empty");
37 | BindingResult errors = new BeanPropertyBindingResult(command, "command");
38 | validator.validate(command, errors);
39 |
40 | assertThat(errors.hasErrors()).isFalse();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/java/se/citerus/dddsample/interfaces/tracking/ws/CargoTrackingRestServiceIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package se.citerus.dddsample.interfaces.tracking.ws;
2 |
3 | import org.junit.jupiter.api.BeforeAll;
4 | import org.junit.jupiter.api.Test;
5 | import org.junit.jupiter.api.extension.ExtendWith;
6 | import org.springframework.boot.test.context.SpringBootTest;
7 | import org.springframework.boot.test.web.server.LocalServerPort;
8 | import org.springframework.http.RequestEntity;
9 | import org.springframework.http.ResponseEntity;
10 | import org.springframework.test.context.junit.jupiter.SpringExtension;
11 | import org.springframework.transaction.annotation.Transactional;
12 | import org.springframework.util.StreamUtils;
13 | import org.springframework.web.client.HttpClientErrorException;
14 | import org.springframework.web.client.RestTemplate;
15 | import org.springframework.web.util.UriTemplate;
16 | import se.citerus.dddsample.Application;
17 |
18 | import java.net.URI;
19 | import java.nio.charset.StandardCharsets;
20 | import java.util.Locale;
21 |
22 | import static org.assertj.core.api.Assertions.assertThat;
23 | import static org.assertj.core.api.Assertions.fail;
24 |
25 | @ExtendWith(SpringExtension.class)
26 | @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
27 | public class CargoTrackingRestServiceIntegrationTest {
28 |
29 | @LocalServerPort
30 | private int port;
31 |
32 | private final RestTemplate restTemplate = new RestTemplate();
33 |
34 | @BeforeAll
35 | static void beforeClass() {
36 | // The expected date time values in the resonse are formatted in US locale.
37 | // Set the locale in the tests so that they won't fail in non-US locales such as Europe.
38 | Locale.setDefault(Locale.US);
39 | }
40 |
41 | @Transactional
42 | @Test
43 | void shouldReturn200ResponseAndJsonWhenRequestingCargoWithIdABC123() throws Exception {
44 | URI uri = new UriTemplate("http://localhost:{port}/dddsample/api/track/ABC123").expand(port);
45 | RequestEntity request = RequestEntity.get(uri).build();
46 |
47 | ResponseEntity response = restTemplate.exchange(request, String.class);
48 |
49 | assertThat(response.getStatusCode().value()).isEqualTo(200);
50 | String expected = StreamUtils.copyToString(getClass().getResourceAsStream("/sampleCargoTrackingResponse.json"), StandardCharsets.UTF_8);
51 | assertThat(response.getHeaders().get("Content-Type")).containsExactly("application/json");
52 | assertThat(response.getBody()).isEqualTo(expected);
53 | }
54 |
55 | @Test
56 | void shouldReturnValidationErrorResponseWhenInvalidHandlingReportIsSubmitted() throws Exception {
57 | URI uri = new UriTemplate("http://localhost:{port}/dddsample/api/track/MISSING").expand(port);
58 | RequestEntity request = RequestEntity.get(uri).build();
59 |
60 | try {
61 | restTemplate.exchange(request, String.class);
62 | fail("Did not throw HttpClientErrorException");
63 | } catch (HttpClientErrorException e) {
64 | assertThat(e.getResponseHeaders().getLocation()).isEqualTo(new URI("/dddsample/api/track/MISSING"));
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/test/resources/config/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | dataSource:
3 | url: jdbc:hsqldb:mem:dddsample_test
4 | main:
5 | allow-bean-definition-overriding: true
6 | server:
7 | error:
8 | include-message: always
--------------------------------------------------------------------------------
/src/test/resources/handling_events.csv:
--------------------------------------------------------------------------------
1 | 2009-03-06 12:30 ABC123 0200T USNYC LOAD
2 | 2009-03-08 04:00 ABC123 0200T USDAL UNLOAD
3 | 2009-03-09 08:12 ABC123 0300A USDAL LOAD
4 | 2009-03-12 19:25 ABC123 0300A FIHEL UNLOAD
5 |
--------------------------------------------------------------------------------
/src/test/resources/sampleCargoTrackingResponse.json:
--------------------------------------------------------------------------------
1 | {"trackingId":"ABC123","statusText":"In port New York","destination":"Helsinki","eta":"2009-03-12T00:00:00Z","nextExpectedActivity":"Next expected activity is to load cargo onto voyage 0200T in New York","isMisdirected":false,"handlingEvents":[{"location":"Hongkong","time":"2009-03-01T00:00:00Z","type":"RECEIVE","voyageNumber":"","isExpected":true,"description":"Received in Hongkong, at Mar 1, 2009, 12:00:00 AM."},{"location":"Hongkong","time":"2009-03-02T00:00:00Z","type":"LOAD","voyageNumber":"0100S","isExpected":true,"description":"Loaded onto voyage 0100S in Hongkong, at Mar 2, 2009, 12:00:00 AM."},{"location":"New York","time":"2009-03-05T00:00:00Z","type":"UNLOAD","voyageNumber":"0100S","isExpected":true,"description":"Unloaded off voyage 0100S in New York, at Mar 5, 2009, 12:00:00 AM."}]}
--------------------------------------------------------------------------------
/src/test/resources/sampleHandlingReport.json:
--------------------------------------------------------------------------------
1 | {
2 | "completionTime": "2022-01-01T13:37:00",
3 | "trackingIds": ["ABC123"],
4 | "type": "LOAD",
5 | "unLocode": "SESTO",
6 | "voyageNumber": "0100S"
7 | }
--------------------------------------------------------------------------------
/src/test/resources/sampleHandlingReportFile.csv:
--------------------------------------------------------------------------------
1 | 2022-10-29 13:37 ABC123 0101 SESTO CUSTOMS
--------------------------------------------------------------------------------
/src/test/resources/sampleInvalidHandlingReportFile.csv:
--------------------------------------------------------------------------------
1 | 2022-10-29 13:37 ABC123 0101 XXX CUSTOMS
--------------------------------------------------------------------------------