├── src ├── site │ ├── resources │ │ ├── css │ │ │ └── site.css │ │ └── images │ │ │ ├── eclipse1.png │ │ │ ├── eclipse2.png │ │ │ ├── eclipse3.png │ │ │ ├── eclipse4.png │ │ │ ├── layers.jpg │ │ │ ├── frontpage.jpeg │ │ │ └── banner-left.png │ ├── apt │ │ ├── handlingEventRegistration.apt │ │ ├── roadmap.apt │ │ ├── changelog.apt │ │ ├── patterns-reference.apt │ │ └── index.apt │ ├── site.xml │ └── xdoc │ │ └── screencast.xml ├── main │ ├── resources │ │ ├── hibernate.properties │ │ ├── jdbc.properties │ │ ├── log4j.properties │ │ ├── context-domain.xml │ │ ├── messages_en.properties │ │ ├── se │ │ │ └── citerus │ │ │ │ └── dddsample │ │ │ │ └── infrastructure │ │ │ │ └── persistence │ │ │ │ └── hibernate │ │ │ │ ├── Location.hbm.xml │ │ │ │ ├── CarrierMovement.hbm.xml │ │ │ │ ├── Leg.hbm.xml │ │ │ │ ├── Voyage.hbm.xml │ │ │ │ └── HandlingEvent.hbm.xml │ │ ├── com │ │ │ └── pathfinder │ │ │ │ └── internal │ │ │ │ └── applicationContext.xml │ │ ├── context-infrastructure.xml │ │ ├── context-application.xml │ │ ├── hibernate.cfg.xml │ │ └── context-infrastructure-persistence.xml │ ├── webapp │ │ ├── images │ │ │ ├── cross.png │ │ │ ├── error.png │ │ │ ├── shade.png │ │ │ ├── tick.png │ │ │ ├── web_logo.png │ │ │ ├── calendarTrigger.gif │ │ │ ├── dddsample_logotype.png │ │ │ └── dddsample_logotype_small.png │ │ ├── WEB-INF │ │ │ ├── decorators.xml │ │ │ ├── jspf │ │ │ │ └── include.jspf │ │ │ ├── jsp │ │ │ │ ├── admin │ │ │ │ │ ├── list.jsp │ │ │ │ │ ├── pickNewDestination.jsp │ │ │ │ │ ├── show.jsp │ │ │ │ │ ├── selectItinerary.jsp │ │ │ │ │ └── registrationForm.jsp │ │ │ │ ├── publicDecorator.jsp │ │ │ │ ├── adminDecorator.jsp │ │ │ │ └── pub │ │ │ │ │ └── track.jsp │ │ │ ├── tracking-servlet.xml │ │ │ ├── booking-servlet.xml │ │ │ └── web.xml │ │ ├── admin.css │ │ ├── index.jsp │ │ └── calendar.css │ └── java │ │ ├── se │ │ └── citerus │ │ │ └── dddsample │ │ │ ├── application │ │ │ ├── util │ │ │ │ ├── package.html │ │ │ │ └── DateTestUtil.java │ │ │ ├── impl │ │ │ │ ├── package.html │ │ │ │ ├── CargoInspectionServiceImpl.java │ │ │ │ └── HandlingEventServiceImpl.java │ │ │ ├── package.html │ │ │ ├── CargoInspectionService.java │ │ │ ├── ApplicationEvents.java │ │ │ ├── HandlingEventService.java │ │ │ └── BookingService.java │ │ │ ├── interfaces │ │ │ ├── tracking │ │ │ │ ├── package.html │ │ │ │ ├── TrackCommand.java │ │ │ │ └── TrackCommandValidator.java │ │ │ ├── booking │ │ │ │ ├── facade │ │ │ │ │ ├── internal │ │ │ │ │ │ ├── assembler │ │ │ │ │ │ │ ├── package.html │ │ │ │ │ │ │ ├── LocationDTOAssembler.java │ │ │ │ │ │ │ ├── CargoRoutingDTOAssembler.java │ │ │ │ │ │ │ └── ItineraryCandidateDTOAssembler.java │ │ │ │ │ │ └── package.html │ │ │ │ │ ├── package.html │ │ │ │ │ ├── dto │ │ │ │ │ │ ├── LocationDTO.java │ │ │ │ │ │ ├── RouteCandidateDTO.java │ │ │ │ │ │ ├── LegDTO.java │ │ │ │ │ │ ├── CargoRoutingDTO.java │ │ │ │ │ │ └── package.html │ │ │ │ │ └── BookingServiceFacade.java │ │ │ │ └── web │ │ │ │ │ ├── package.html │ │ │ │ │ ├── BookingDispatcherServlet.java │ │ │ │ │ ├── RegistrationCommand.java │ │ │ │ │ └── RouteAssignmentCommand.java │ │ │ └── handling │ │ │ │ ├── file │ │ │ │ └── package.html │ │ │ │ ├── package.html │ │ │ │ ├── ws │ │ │ │ └── package.html │ │ │ │ └── HandlingEventRegistrationAttempt.java │ │ │ ├── domain │ │ │ ├── model │ │ │ │ ├── cargo │ │ │ │ │ ├── package.html │ │ │ │ │ ├── RoutingStatus.java │ │ │ │ │ ├── TransportStatus.java │ │ │ │ │ ├── CargoRepository.java │ │ │ │ │ ├── TrackingId.java │ │ │ │ │ ├── Leg.java │ │ │ │ │ └── HandlingActivity.java │ │ │ │ ├── voyage │ │ │ │ │ ├── package.html │ │ │ │ │ ├── VoyageRepository.java │ │ │ │ │ ├── VoyageNumber.java │ │ │ │ │ └── Schedule.java │ │ │ │ ├── location │ │ │ │ │ ├── package.html │ │ │ │ │ ├── LocationRepository.java │ │ │ │ │ ├── UnLocode.java │ │ │ │ │ ├── SampleLocations.java │ │ │ │ │ └── Location.java │ │ │ │ ├── handling │ │ │ │ │ ├── package.html │ │ │ │ │ ├── UnknownLocationException.java │ │ │ │ │ ├── HandlingEventRepository.java │ │ │ │ │ ├── UnknownVoyageException.java │ │ │ │ │ ├── CannotCreateHandlingEventException.java │ │ │ │ │ ├── UnknownCargoException.java │ │ │ │ │ └── HandlingHistory.java │ │ │ │ └── package.html │ │ │ ├── shared │ │ │ │ ├── package.html │ │ │ │ ├── experimental │ │ │ │ │ ├── package.html │ │ │ │ │ ├── Identity.java │ │ │ │ │ ├── Entity.java │ │ │ │ │ ├── DomainEvent.java │ │ │ │ │ ├── ValueObject.java │ │ │ │ │ ├── ValueObjectSupport.java │ │ │ │ │ └── EntitySupport.java │ │ │ │ ├── Entity.java │ │ │ │ ├── ValueObject.java │ │ │ │ ├── DomainEvent.java │ │ │ │ ├── NotSpecification.java │ │ │ │ ├── DomainObjectUtils.java │ │ │ │ ├── OrSpecification.java │ │ │ │ ├── AndSpecification.java │ │ │ │ ├── AbstractSpecification.java │ │ │ │ └── Specification.java │ │ │ ├── service │ │ │ │ ├── package.html │ │ │ │ └── RoutingService.java │ │ │ └── package.html │ │ │ └── infrastructure │ │ │ ├── routing │ │ │ └── package.html │ │ │ ├── messaging │ │ │ └── jms │ │ │ │ ├── package.html │ │ │ │ ├── SimpleLoggingConsumer.java │ │ │ │ ├── CargoHandledConsumer.java │ │ │ │ └── HandlingEventRegistrationAttemptConsumer.java │ │ │ └── persistence │ │ │ └── hibernate │ │ │ ├── package.html │ │ │ ├── HibernateRepository.java │ │ │ ├── VoyageRepositoryHibernate.java │ │ │ ├── LocationRepositoryHibernate.java │ │ │ ├── HandlingEventRepositoryHibernate.java │ │ │ └── CargoRepositoryHibernate.java │ │ └── com │ │ ├── pathfinder │ │ ├── api │ │ │ ├── package.html │ │ │ ├── TransitPath.java │ │ │ ├── GraphTraversalService.java │ │ │ └── TransitEdge.java │ │ ├── internal │ │ │ ├── package.html │ │ │ └── GraphDAO.java │ │ └── package.html │ │ └── aggregator │ │ ├── package.html │ │ ├── package-info.java │ │ ├── HandlingReportErrors.java │ │ ├── SubmitReportResponse.java │ │ ├── HandlingReportService.java │ │ ├── HandlingReportErrors_Exception.java │ │ ├── SubmitReport.java │ │ └── HandlingReportServiceService.java └── test │ ├── resources │ ├── handling_events.csv │ ├── jdbc.properties │ └── log4j.properties │ └── java │ └── se │ └── citerus │ └── dddsample │ ├── domain │ ├── shared │ │ ├── AlwaysFalseSpec.java │ │ ├── AlwaysTrueSpec.java │ │ ├── NotSpecificationTest.java │ │ ├── OrSpecificationTest.java │ │ ├── AndSpecificationTest.java │ │ └── experimental │ │ │ ├── ValueObjectSupportTest.java │ │ │ └── EntitySupportTest.java │ └── model │ │ ├── cargo │ │ ├── LegTest.java │ │ ├── TrackingIdTest.java │ │ └── RouteSpecificationTest.java │ │ ├── voyage │ │ ├── ScheduleTest.java │ │ ├── VoyageNumberTest.java │ │ ├── VoyageTest.java │ │ └── CarrierMovementTest.java │ │ ├── location │ │ ├── LocationTest.java │ │ └── UnLocodeTest.java │ │ └── handling │ │ └── HandlingHistoryTest.java │ ├── infrastructure │ ├── persistence │ │ ├── inmemory │ │ │ ├── VoyageRepositoryInMem.java │ │ │ ├── LocationRepositoryInMem.java │ │ │ └── HandlingEventRepositoryInMem.java │ │ └── hibernate │ │ │ ├── LocationRepositoryTest.java │ │ │ ├── CarrierMovementRepositoryTest.java │ │ │ ├── HandlingEventRepositoryTest.java │ │ │ └── AbstractRepositoryTest.java │ └── messaging │ │ └── stub │ │ └── SynchronousApplicationEventsStub.java │ ├── interfaces │ ├── booking │ │ ├── web │ │ │ ├── CargoAdminControllerTest.java │ │ │ └── ItinerarySelectionCommandTest.java │ │ └── facade │ │ │ └── internal │ │ │ └── assembler │ │ │ ├── LocationDTOAssemblerTest.java │ │ │ └── CargoRoutingDTOAssemblerTest.java │ └── tracking │ │ └── TrackCommandValidatorTest.java │ └── application │ ├── BookingServiceTest.java │ └── HandlingEventServiceTest.java └── license.txt /src/site/resources/css/site.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/hibernate.properties: -------------------------------------------------------------------------------- 1 | hibernate.hbm2ddl.auto=create-drop 2 | hibernate.format_sql=true -------------------------------------------------------------------------------- /src/main/webapp/images/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/main/webapp/images/cross.png -------------------------------------------------------------------------------- /src/main/webapp/images/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/main/webapp/images/error.png -------------------------------------------------------------------------------- /src/main/webapp/images/shade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/main/webapp/images/shade.png -------------------------------------------------------------------------------- /src/main/webapp/images/tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/main/webapp/images/tick.png -------------------------------------------------------------------------------- /src/main/webapp/images/web_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/main/webapp/images/web_logo.png -------------------------------------------------------------------------------- /src/site/resources/images/eclipse1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/site/resources/images/eclipse1.png -------------------------------------------------------------------------------- /src/site/resources/images/eclipse2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/site/resources/images/eclipse2.png -------------------------------------------------------------------------------- /src/site/resources/images/eclipse3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/site/resources/images/eclipse3.png -------------------------------------------------------------------------------- /src/site/resources/images/eclipse4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/site/resources/images/eclipse4.png -------------------------------------------------------------------------------- /src/site/resources/images/layers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/site/resources/images/layers.jpg -------------------------------------------------------------------------------- /src/site/resources/images/frontpage.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/site/resources/images/frontpage.jpeg -------------------------------------------------------------------------------- /src/main/webapp/images/calendarTrigger.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/main/webapp/images/calendarTrigger.gif -------------------------------------------------------------------------------- /src/site/resources/images/banner-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/site/resources/images/banner-left.png -------------------------------------------------------------------------------- /src/main/webapp/images/dddsample_logotype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/main/webapp/images/dddsample_logotype.png -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/application/util/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Various utilities. 5 |

6 | 7 | -------------------------------------------------------------------------------- /src/main/java/com/pathfinder/api/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Public API for the pathfinder application. 5 |

6 | 7 | -------------------------------------------------------------------------------- /src/main/java/com/aggregator/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Generated from the upstream Aggregation Service WSDL 5 |

6 | 7 | -------------------------------------------------------------------------------- /src/main/webapp/images/dddsample_logotype_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joolu/ddd-sample/HEAD/src/main/webapp/images/dddsample_logotype_small.png -------------------------------------------------------------------------------- /src/main/java/com/pathfinder/internal/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Internal parts of the pathfinder application. 5 |

6 | 7 | -------------------------------------------------------------------------------- /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/application/impl/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Implementation of the application layer. 5 |

6 | 7 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/interfaces/booking/facade/internal/assembler/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | DTO assemblers. 5 |

6 | 7 | -------------------------------------------------------------------------------- /src/site/apt/handlingEventRegistration.apt: -------------------------------------------------------------------------------- 1 | -------------------------------- 2 | Registration of a handling event 3 | -------------------------------- 4 | 5 | TODO 6 | -------------------------------------------------------------------------------- /src/main/java/com/aggregator/package-info.java: -------------------------------------------------------------------------------- 1 | @javax.xml.bind.annotation.XmlSchema(namespace = "http://ws.handling.interfaces.dddsample.citerus.se/") 2 | package com.aggregator; 3 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/handling/ws/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Web service interface for registering handling events. 5 |

6 | 7 | -------------------------------------------------------------------------------- /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/infrastructure/routing/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Communicates with the Pathfinder external routing service. 5 |

6 | 7 | -------------------------------------------------------------------------------- /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/resources/jdbc.properties: -------------------------------------------------------------------------------- 1 | hibernate.connection.driver_class=org.hsqldb.jdbcDriver 2 | hibernate.connection.url=jdbc:hsqldb:mem:dddsample 3 | hibernate.connection.username=sa 4 | hibernate.connection.password= -------------------------------------------------------------------------------- /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/jdbc.properties: -------------------------------------------------------------------------------- 1 | hibernate.connection.driver_class=org.hsqldb.jdbcDriver 2 | hibernate.connection.url=jdbc:hsqldb:mem:dddsample_test 3 | hibernate.connection.username=sa 4 | hibernate.connection.password= -------------------------------------------------------------------------------- /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/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/domain/shared/experimental/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Experimental versions of pattern interfaces and support code for the domain layer. 5 | Not used in the application. 6 |

7 | 8 | -------------------------------------------------------------------------------- /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/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/webapp/WEB-INF/decorators.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | /admin 4 | 5 | 6 | /public 7 | 8 | -------------------------------------------------------------------------------- /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/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/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 | } 14 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=info, stdout 2 | 3 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 4 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n 6 | 7 | log4j.logger.net.sf.ehcache.config.ConfigurationFactory=error 8 | 9 | log4j.logger.org.hibernate.SQL=debug 10 | log4j.logger.org.hibernate.hbm2ddl=debug 11 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/domain/model/cargo/LegTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.cargo; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class LegTest extends TestCase { 6 | 7 | public void testConstructor() throws Exception { 8 | try { 9 | new Leg(null,null,null,null,null); 10 | fail("Should not accept null constructor arguments"); 11 | } catch (IllegalArgumentException expected) {} 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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/test/java/se/citerus/dddsample/domain/model/cargo/TrackingIdTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.cargo; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class TrackingIdTest extends TestCase { 6 | 7 | public void testConstructor() throws Exception { 8 | try { 9 | new TrackingId(null); 10 | fail("Should not accept null constructor arguments"); 11 | } catch (IllegalArgumentException expected) {} 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/domain/shared/Entity.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.shared; 2 | 3 | /** 4 | * An entity, as explained in the DDD book. 5 | * 6 | */ 7 | public interface Entity { 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, regardles of other attributes. 14 | */ 15 | boolean sameIdentityAs(T other); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jspf/include.jspf: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> 2 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 3 | <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> 4 | <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> 5 | <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> 6 | 7 | <%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator" %> -------------------------------------------------------------------------------- /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/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/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 findAll(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /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/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/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=info, stdout 2 | 3 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 4 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n 6 | 7 | log4j.logger.net.sf.ehcache.config.ConfigurationFactory=error 8 | 9 | log4j.logger.se.citerus.dddsample=debug 10 | log4j.logger.org.hibernate.SQL=debug 11 | #log4j.logger.org.hibernate.tool.hbm2ddl.SchemaExport=debug 12 | 13 | #log4j.logger.org.springframework.orm=debug 14 | #log4j.logger.org.springframework.transaction=debug 15 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/interfaces/booking/web/BookingDispatcherServlet.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.interfaces.booking.web; 2 | 3 | import org.springframework.web.context.WebApplicationContext; 4 | import org.springframework.web.servlet.DispatcherServlet; 5 | 6 | public class BookingDispatcherServlet extends DispatcherServlet { 7 | 8 | @Override 9 | protected WebApplicationContext findWebApplicationContext() { 10 | // The booking web application should be standalone, 11 | // and not use the main application context as parent. 12 | return null; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /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/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/shared/experimental/Identity.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.shared.experimental; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Every class that inherits from {@link se.citerus.dddsample.domain.shared.experimental.EntitySupport} 10 | * must have exactly one field annotated with this annotation. 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.FIELD) 14 | public @interface Identity { 15 | } 16 | -------------------------------------------------------------------------------- /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/infrastructure/messaging/jms/SimpleLoggingConsumer.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.infrastructure.messaging.jms; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | 6 | import javax.jms.Message; 7 | import javax.jms.MessageListener; 8 | 9 | public class SimpleLoggingConsumer implements MessageListener { 10 | 11 | private final Log logger = LogFactory.getLog(SimpleLoggingConsumer.class); 12 | 13 | @Override 14 | public void onMessage(Message message) { 15 | logger.debug("Received JMS message: " + message); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/context-domain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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.SampleVoyages; 4 | import se.citerus.dddsample.domain.model.voyage.Voyage; 5 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber; 6 | import se.citerus.dddsample.domain.model.voyage.VoyageRepository; 7 | 8 | public final class VoyageRepositoryInMem implements VoyageRepository { 9 | 10 | public Voyage find(VoyageNumber voyageNumber) { 11 | return SampleVoyages.lookup(voyageNumber); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /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/domain/shared/experimental/Entity.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.shared.experimental; 2 | 3 | /** 4 | * An entity, as explained in the DDD book. 5 | * 6 | */ 7 | public interface Entity { 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, regardles of other attributes. 14 | */ 15 | boolean sameIdentityAs(T other); 16 | 17 | /** 18 | * Entities have an identity. 19 | * 20 | * @return The identity of this entity. 21 | */ 22 | ID identity(); 23 | 24 | } -------------------------------------------------------------------------------- /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/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/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/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/shared/experimental/DomainEvent.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.shared.experimental; 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 | } -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/domain/shared/experimental/ValueObject.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.shared.experimental; 2 | 3 | /** 4 | * A value object. 5 | * 6 | */ 7 | public interface ValueObject { 8 | 9 | /** 10 | * Value objects compare by the values of their attributes, they don't have an identity. 11 | * 12 | * @param other The other value object. 13 | * @return true if the given value object's and this value object's attributes are the same. 14 | */ 15 | boolean sameValueAs(T other); 16 | 17 | /** 18 | * Value objects can be freely copied. 19 | * 20 | * @return A safe, deep copy of this value object. 21 | */ 22 | T copy(); 23 | 24 | } -------------------------------------------------------------------------------- /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/test/java/se/citerus/dddsample/domain/shared/NotSpecificationTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.shared; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class NotSpecificationTest extends TestCase { 6 | 7 | public void testAndIsSatisifedBy() throws Exception { 8 | AlwaysTrueSpec trueSpec = new AlwaysTrueSpec(); 9 | AlwaysFalseSpec falseSpec = new AlwaysFalseSpec(); 10 | 11 | NotSpecification notSpecification = new NotSpecification(trueSpec); 12 | assertFalse(notSpecification.isSatisfiedBy(new Object())); 13 | 14 | notSpecification = new NotSpecification(falseSpec); 15 | assertTrue(notSpecification.isSatisfiedBy(new Object())); 16 | 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/java/com/pathfinder/api/TransitPath.java: -------------------------------------------------------------------------------- 1 | package com.pathfinder.api; 2 | 3 | import java.io.Serializable; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | /** 8 | * 9 | */ 10 | public final class TransitPath implements Serializable { 11 | 12 | private final List transitEdges; 13 | 14 | /** 15 | * Constructor. 16 | * 17 | * @param transitEdges The legs for this itinerary. 18 | */ 19 | public TransitPath(final List transitEdges) { 20 | this.transitEdges = transitEdges; 21 | } 22 | 23 | /** 24 | * @return An unmodifiable list DTOs. 25 | */ 26 | public List getTransitEdges() { 27 | return Collections.unmodifiableList(transitEdges); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /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/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/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/interfaces/tracking/TrackCommand.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.interfaces.tracking; 2 | 3 | import org.apache.commons.lang.builder.ToStringBuilder; 4 | import static org.apache.commons.lang.builder.ToStringStyle.MULTI_LINE_STYLE; 5 | 6 | public final class TrackCommand { 7 | 8 | /** 9 | * The tracking id. 10 | */ 11 | private String trackingId; 12 | 13 | public String getTrackingId() { 14 | return trackingId; 15 | } 16 | 17 | public void setTrackingId(final String trackingId) { 18 | this.trackingId = trackingId; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return ToStringBuilder.reflectionToString(this, MULTI_LINE_STYLE); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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 findAll(); 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/infrastructure/persistence/hibernate/HibernateRepository.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.infrastructure.persistence.hibernate; 2 | 3 | import org.hibernate.Session; 4 | import org.hibernate.SessionFactory; 5 | import org.springframework.beans.factory.annotation.Required; 6 | 7 | /** 8 | * Functionality common to all Hibernate repositories. 9 | */ 10 | public abstract class HibernateRepository { 11 | 12 | private SessionFactory sessionFactory; 13 | 14 | @Required 15 | public void setSessionFactory(final SessionFactory sessionFactory) { 16 | this.sessionFactory = sessionFactory; 17 | } 18 | 19 | protected Session getSession() { 20 | return sessionFactory.getCurrentSession(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/interfaces/tracking/TrackCommandValidator.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.interfaces.tracking; 2 | 3 | import org.springframework.validation.Errors; 4 | import org.springframework.validation.ValidationUtils; 5 | import org.springframework.validation.Validator; 6 | 7 | /** 8 | * Validator for {@link TrackCommand}s. 9 | */ 10 | public final class TrackCommandValidator implements Validator { 11 | 12 | public boolean supports(final Class clazz) { 13 | return TrackCommand.class.isAssignableFrom(clazz); 14 | } 15 | 16 | public void validate(final Object object, final Errors errors) { 17 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "trackingId", "error.required", "Required"); 18 | } 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/domain/model/voyage/ScheduleTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.voyage; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class ScheduleTest extends TestCase { 6 | 7 | public void testCarrierMovements() throws Exception { 8 | //TODO: Test goes here... 9 | } 10 | 11 | public void testSameValueAs() throws Exception { 12 | //TODO: Test goes here... 13 | } 14 | 15 | public void testCopy() throws Exception { 16 | //TODO: Test goes here... 17 | } 18 | 19 | public void testEquals() throws Exception { 20 | //TODO: Test goes here... 21 | } 22 | 23 | public void testHashCode() throws Exception { 24 | //TODO: Test goes here... 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/se/citerus/dddsample/infrastructure/persistence/hibernate/Location.hbm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /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/domain/shared/DomainObjectUtils.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.shared; 2 | 3 | /** 4 | * Utility code for domain classes. 5 | * 6 | */ 7 | public class DomainObjectUtils { 8 | 9 | /** 10 | * @param actual actual value 11 | * @param safe a null-safe value 12 | * @param type 13 | * @return actual value, if it's not null, or safe value if the actual value is null. 14 | */ 15 | public static T nullSafe(T actual, T safe) { 16 | return actual == null ? safe : actual; 17 | } 18 | 19 | // TODO wrappers for some of the commons-lang code: 20 | // 21 | // EqualsBuilder that uses sameIdentity/sameValue, 22 | // better validation (varargs etc) 23 | 24 | /** 25 | * Prevent instantiation. 26 | */ 27 | private DomainObjectUtils() { 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/pathfinder/internal/GraphDAO.java: -------------------------------------------------------------------------------- 1 | package com.pathfinder.internal; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Random; 7 | 8 | public class GraphDAO { 9 | 10 | private static final Random random = new Random(); 11 | 12 | public List listLocations() { 13 | return new ArrayList(Arrays.asList( 14 | "CNHKG", "AUMEL", "SESTO", "FIHEL", "USCHI", "JNTKO", "DEHAM", "CNSHA", "NLRTM", "SEGOT", "CNHGH", "USNYC", "USDAL" 15 | )); 16 | } 17 | 18 | public String getVoyageNumber(String from, String to) { 19 | final int i = random.nextInt(5); 20 | if (i == 0) return "0100S"; 21 | if (i == 1) return "0200T"; 22 | if (i == 2) return "0300A"; 23 | if (i == 3) return "0301S"; 24 | return "0400S"; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /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/webapp/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/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/test/java/se/citerus/dddsample/domain/model/voyage/VoyageNumberTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.voyage; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class VoyageNumberTest extends TestCase { 6 | 7 | public void testEquals() throws Exception { 8 | //TODO: Test goes here... 9 | } 10 | 11 | public void testHashCode() throws Exception { 12 | //TODO: Test goes here... 13 | } 14 | 15 | public void testSameValueAs() throws Exception { 16 | //TODO: Test goes here... 17 | } 18 | 19 | public void testCopy() throws Exception { 20 | //TODO: Test goes here... 21 | } 22 | 23 | public void testToString() throws Exception { 24 | //TODO: Test goes here... 25 | } 26 | 27 | public void testIdString() throws Exception { 28 | //TODO: Test goes here... 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /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/infrastructure/persistence/hibernate/VoyageRepositoryHibernate.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.infrastructure.persistence.hibernate; 2 | 3 | import org.springframework.stereotype.Repository; 4 | import se.citerus.dddsample.domain.model.voyage.Voyage; 5 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber; 6 | import se.citerus.dddsample.domain.model.voyage.VoyageRepository; 7 | 8 | /** 9 | * Hibernate implementation of CarrierMovementRepository. 10 | */ 11 | @Repository 12 | public final class VoyageRepositoryHibernate extends HibernateRepository implements VoyageRepository { 13 | 14 | public Voyage find(final VoyageNumber voyageNumber) { 15 | return (Voyage) getSession(). 16 | createQuery("from Voyage where voyageNumber = :vn"). 17 | setParameter("vn", voyageNumber). 18 | uniqueResult(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /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/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.SampleLocations; 6 | import se.citerus.dddsample.domain.model.location.UnLocode; 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 findAll() { 22 | return SampleLocations.getAll(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/aggregator/HandlingReportErrors.java: -------------------------------------------------------------------------------- 1 | 2 | package com.aggregator; 3 | 4 | import javax.xml.bind.annotation.XmlAccessType; 5 | import javax.xml.bind.annotation.XmlAccessorType; 6 | import javax.xml.bind.annotation.XmlType; 7 | 8 | 9 | /** 10 | *

Java class for HandlingReportErrors complex type. 11 | * 12 | *

The following schema fragment specifies the expected content contained within this class. 13 | * 14 | *

15 |  * <complexType name="HandlingReportErrors">
16 |  *   <complexContent>
17 |  *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
18 |  *       <sequence>
19 |  *       </sequence>
20 |  *     </restriction>
21 |  *   </complexContent>
22 |  * </complexType>
23 |  * 
24 | * 25 | * 26 | */ 27 | @XmlAccessorType(XmlAccessType.FIELD) 28 | @XmlType(name = "HandlingReportErrors") 29 | public class HandlingReportErrors { 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/aggregator/SubmitReportResponse.java: -------------------------------------------------------------------------------- 1 | 2 | package com.aggregator; 3 | 4 | import javax.xml.bind.annotation.XmlAccessType; 5 | import javax.xml.bind.annotation.XmlAccessorType; 6 | import javax.xml.bind.annotation.XmlType; 7 | 8 | 9 | /** 10 | *

Java class for submitReportResponse complex type. 11 | * 12 | *

The following schema fragment specifies the expected content contained within this class. 13 | * 14 | *

15 |  * <complexType name="submitReportResponse">
16 |  *   <complexContent>
17 |  *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
18 |  *       <sequence>
19 |  *       </sequence>
20 |  *     </restriction>
21 |  *   </complexContent>
22 |  * </complexType>
23 |  * 
24 | * 25 | * 26 | */ 27 | @XmlAccessorType(XmlAccessType.FIELD) 28 | @XmlType(name = "submitReportResponse") 29 | public class SubmitReportResponse { 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/infrastructure/persistence/hibernate/LocationRepositoryHibernate.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.infrastructure.persistence.hibernate; 2 | 3 | import org.springframework.stereotype.Repository; 4 | import se.citerus.dddsample.domain.model.location.Location; 5 | import se.citerus.dddsample.domain.model.location.LocationRepository; 6 | import se.citerus.dddsample.domain.model.location.UnLocode; 7 | 8 | import java.util.List; 9 | 10 | @Repository 11 | public final class LocationRepositoryHibernate extends HibernateRepository implements LocationRepository { 12 | 13 | public Location find(final UnLocode unLocode) { 14 | return (Location) getSession(). 15 | createQuery("from Location where unLocode = ?"). 16 | setParameter(0, unLocode). 17 | uniqueResult(); 18 | } 19 | 20 | public List findAll() { 21 | return getSession().createQuery("from Location").list(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/resources/com/pathfinder/internal/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/admin/list.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cargo Administration 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
All cargos
Tracking IDOriginDestinationRouted
20 | 21 | 22 | 23 | ${cargo.trackingId} 24 | ${cargo.origin}${cargo.finalDestination}${cargo.misrouted ? "Misrouted" : (cargo.routed ? "Yes" : "No")}
32 | 33 | -------------------------------------------------------------------------------- /src/main/java/com/pathfinder/api/GraphTraversalService.java: -------------------------------------------------------------------------------- 1 | package com.pathfinder.api; 2 | 3 | import java.rmi.Remote; 4 | import java.rmi.RemoteException; 5 | import java.util.List; 6 | import java.util.Properties; 7 | 8 | /** 9 | * Part of the external graph traversal API exposed by the routing team 10 | * and used by us (booking and tracking team). 11 | * 12 | */ 13 | public interface GraphTraversalService extends Remote { 14 | 15 | /** 16 | * @param originUnLocode origin UN Locode 17 | * @param destinationUnLocode destination UN Locode 18 | * @param limitations restrictions on the path selection, as key-value according to some API specification 19 | * @return A list of transit paths 20 | * @throws RemoteException RMI problem 21 | */ 22 | List findShortestPath(String originUnLocode, 23 | String destinationUnLocode, 24 | Properties limitations) throws RemoteException; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /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/test/java/se/citerus/dddsample/domain/shared/OrSpecificationTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.shared; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class OrSpecificationTest extends TestCase { 6 | 7 | public void testAndIsSatisifedBy() throws Exception { 8 | AlwaysTrueSpec trueSpec = new AlwaysTrueSpec(); 9 | AlwaysFalseSpec falseSpec = new AlwaysFalseSpec(); 10 | 11 | OrSpecification orSpecification = new OrSpecification(trueSpec, trueSpec); 12 | assertTrue(orSpecification.isSatisfiedBy(new Object())); 13 | 14 | orSpecification = new OrSpecification(falseSpec, trueSpec); 15 | assertTrue(orSpecification.isSatisfiedBy(new Object())); 16 | 17 | orSpecification = new OrSpecification(trueSpec, falseSpec); 18 | assertTrue(orSpecification.isSatisfiedBy(new Object())); 19 | 20 | orSpecification = new OrSpecification(falseSpec, falseSpec); 21 | assertFalse(orSpecification.isSatisfiedBy(new Object())); 22 | 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/resources/se/citerus/dddsample/infrastructure/persistence/hibernate/CarrierMovement.hbm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/domain/shared/AndSpecificationTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.shared; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class AndSpecificationTest extends TestCase { 6 | 7 | public void testAndIsSatisifedBy() throws Exception { 8 | AlwaysTrueSpec trueSpec = new AlwaysTrueSpec(); 9 | AlwaysFalseSpec falseSpec = new AlwaysFalseSpec(); 10 | 11 | AndSpecification andSpecification = new AndSpecification(trueSpec, trueSpec); 12 | assertTrue(andSpecification.isSatisfiedBy(new Object())); 13 | 14 | andSpecification = new AndSpecification(falseSpec, trueSpec); 15 | assertFalse(andSpecification.isSatisfiedBy(new Object())); 16 | 17 | andSpecification = new AndSpecification(trueSpec, falseSpec); 18 | assertFalse(andSpecification.isSatisfiedBy(new Object())); 19 | 20 | andSpecification = new AndSpecification(falseSpec, falseSpec); 21 | assertFalse(andSpecification.isSatisfiedBy(new Object())); 22 | 23 | } 24 | } -------------------------------------------------------------------------------- /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/resources/se/citerus/dddsample/infrastructure/persistence/hibernate/Leg.hbm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/se/citerus/dddsample/infrastructure/persistence/hibernate/Voyage.hbm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/application/util/DateTestUtil.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.application.util; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | 7 | /** 8 | * A few utils for working with Date in tests. 9 | * 10 | */ 11 | public final class DateTestUtil { 12 | 13 | /** 14 | * @param date date string as yyyy-MM-dd 15 | * @return Date representation 16 | */ 17 | public static Date toDate(final String date) { 18 | return toDate(date, "00:00.00.000"); 19 | } 20 | 21 | /** 22 | * @param date date string as yyyy-MM-dd 23 | * @param time time string as HH:mm 24 | * @return Date representation 25 | */ 26 | public static Date toDate(final String date, final String time) { 27 | try { 28 | return new SimpleDateFormat("yyyy-MM-dd HH:mm").parse(date + " " + time); 29 | } catch (ParseException e) { 30 | throw new RuntimeException(e); 31 | } 32 | } 33 | 34 | /** 35 | * Prevent instantiation. 36 | */ 37 | private DateTestUtil() { 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/publicDecorator.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <decorator:title/> 7 | 8 | 11 | 12 | 13 | 14 |
15 |
16 | Domain Driven Delivery 17 |
18 |
19 | 20 |
21 | 24 |
25 | 26 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/domain/model/voyage/VoyageTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.voyage; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class VoyageTest extends TestCase { 6 | 7 | public void testVoyageNumber() throws Exception { 8 | //TODO: Test goes here... 9 | } 10 | 11 | public void testSchedule() throws Exception { 12 | //TODO: Test goes here... 13 | } 14 | 15 | public void testHashCode() throws Exception { 16 | //TODO: Test goes here... 17 | } 18 | 19 | public void testEquals() throws Exception { 20 | //TODO: Test goes here... 21 | } 22 | 23 | public void testSameIdentityAs() throws Exception { 24 | //TODO: Test goes here... 25 | } 26 | 27 | public void testToString() throws Exception { 28 | //TODO: Test goes here... 29 | } 30 | 31 | public void testAddMovement() throws Exception { 32 | //TODO: Test goes here... 33 | } 34 | 35 | public void testBuild() throws Exception { 36 | //TODO: Test goes here... 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2009 Citerus AB and Domain Language, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/interfaces/booking/web/CargoAdminControllerTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.interfaces.booking.web; 2 | 3 | import junit.framework.TestCase; 4 | import org.easymock.EasyMock; 5 | import org.springframework.mock.web.MockHttpServletRequest; 6 | import org.springframework.mock.web.MockHttpServletResponse; 7 | import se.citerus.dddsample.interfaces.booking.facade.BookingServiceFacade; 8 | 9 | public class CargoAdminControllerTest extends TestCase { 10 | 11 | CargoAdminController controller; 12 | BookingServiceFacade bookingServiceFacade; 13 | MockHttpServletRequest request; 14 | MockHttpServletResponse response; 15 | 16 | public CargoAdminControllerTest() { 17 | controller = new CargoAdminController(); 18 | bookingServiceFacade = EasyMock.createMock(BookingServiceFacade.class); 19 | controller.setBookingServiceFacade(bookingServiceFacade); 20 | } 21 | 22 | public void testAssignItinerary() throws Exception { 23 | request = new MockHttpServletRequest("GET","assignItinerary.html"); 24 | response = new MockHttpServletResponse(); 25 | 26 | controller.handleRequest(request, response); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/context-infrastructure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/adminDecorator.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <decorator:title/> 7 | 8 | 11 | 12 | 13 | 14 |
15 | " alt=""/> 16 |

Cargo Booking and Routing

17 | 29 |
30 | 31 |
32 |
33 | 34 | -------------------------------------------------------------------------------- /src/main/resources/context-application.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/domain/model/location/LocationTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.location; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class LocationTest extends TestCase { 6 | 7 | public void testEquals() { 8 | // Same UN locode - equal 9 | assertTrue(new Location(new UnLocode("ATEST"),"test-name"). 10 | equals(new Location(new UnLocode("ATEST"),"test-name"))); 11 | 12 | // Different UN locodes - not equal 13 | assertFalse(new Location(new UnLocode("ATEST"),"test-name"). 14 | equals(new Location(new UnLocode("TESTB"), "test-name"))); 15 | 16 | // Always equal to itself 17 | Location location = new Location(new UnLocode("ATEST"),"test-name"); 18 | assertTrue(location.equals(location)); 19 | 20 | // Never equal to null 21 | assertFalse(location.equals(null)); 22 | 23 | // Special UNKNOWN location is equal to itself 24 | assertTrue(Location.UNKNOWN.equals(Location.UNKNOWN)); 25 | 26 | try { 27 | new Location(null, null); 28 | fail("Should not allow any null constructor arguments"); 29 | } catch (IllegalArgumentException expected) {} 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/domain/shared/experimental/ValueObjectSupportTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.shared.experimental; 2 | 3 | import junit.framework.TestCase; 4 | 5 | 6 | public class ValueObjectSupportTest extends TestCase { 7 | 8 | public void testEquals() { 9 | final AValueObject vo1 = new AValueObject("A"); 10 | final AValueObject vo2 = new AValueObject("A"); 11 | final BValueObject vo3 = new BValueObject("A", 1); 12 | 13 | assertEquals(vo1, vo2); 14 | assertEquals(vo2, vo1); 15 | assertFalse(vo2.equals(vo3)); 16 | assertFalse(vo3.equals(vo2)); 17 | 18 | assertTrue(vo1.sameValueAs(vo2)); 19 | assertFalse(vo2.sameValueAs(vo3)); 20 | } 21 | 22 | class AValueObject extends ValueObjectSupport { 23 | String s; 24 | 25 | AValueObject(String s) { 26 | this.s = s; 27 | } 28 | 29 | AValueObject() { 30 | } 31 | } 32 | 33 | class BValueObject extends AValueObject { 34 | int x; 35 | 36 | BValueObject(String s, int x) { 37 | super(s); 38 | this.x = x; 39 | } 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/infrastructure/persistence/hibernate/HandlingEventRepositoryHibernate.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.infrastructure.persistence.hibernate; 2 | 3 | import org.springframework.stereotype.Repository; 4 | import se.citerus.dddsample.domain.model.cargo.TrackingId; 5 | import se.citerus.dddsample.domain.model.handling.HandlingEvent; 6 | import se.citerus.dddsample.domain.model.handling.HandlingEventRepository; 7 | import se.citerus.dddsample.domain.model.handling.HandlingHistory; 8 | 9 | /** 10 | * Hibernate implementation of HandlingEventRepository. 11 | * 12 | */ 13 | @Repository 14 | public class HandlingEventRepositoryHibernate extends HibernateRepository implements HandlingEventRepository { 15 | 16 | @Override 17 | public void store(final HandlingEvent event) { 18 | getSession().save(event); 19 | } 20 | 21 | @Override 22 | public HandlingHistory lookupHandlingHistoryOfCargo(final TrackingId trackingId) { 23 | return new HandlingHistory(getSession().createQuery( 24 | "from HandlingEvent where cargo.trackingId = :tid"). 25 | setParameter("tid", trackingId). 26 | list() 27 | ); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/hibernate.cfg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | org.hibernate.cache.NoCacheProvider 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/infrastructure/persistence/hibernate/LocationRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.infrastructure.persistence.hibernate; 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.util.List; 8 | 9 | public class LocationRepositoryTest extends AbstractRepositoryTest { 10 | private LocationRepository locationRepository; 11 | 12 | public void testFind() throws Exception { 13 | final UnLocode melbourne = new UnLocode("AUMEL"); 14 | Location location = locationRepository.find(melbourne); 15 | assertNotNull(location); 16 | assertEquals(melbourne, location.unLocode()); 17 | 18 | assertNull(locationRepository.find(new UnLocode("NOLOC"))); 19 | } 20 | 21 | public void testFindAll() throws Exception { 22 | List allLocations = locationRepository.findAll(); 23 | 24 | assertNotNull(allLocations); 25 | assertEquals(7, allLocations.size()); 26 | } 27 | 28 | public void setLocationRepository(LocationRepository locationRepository) { 29 | this.locationRepository = locationRepository; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/infrastructure/persistence/hibernate/CarrierMovementRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.infrastructure.persistence.hibernate; 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 | 7 | public class CarrierMovementRepositoryTest extends AbstractRepositoryTest { 8 | 9 | VoyageRepository voyageRepository; 10 | 11 | public void setVoyageRepository(VoyageRepository voyageRepository) { 12 | this.voyageRepository = voyageRepository; 13 | } 14 | 15 | public void testFind() throws Exception { 16 | Voyage voyage = voyageRepository.find(new VoyageNumber("0101")); 17 | assertNotNull(voyage); 18 | assertEquals("0101", voyage.voyageNumber().idString()); 19 | /* TODO adapt 20 | assertEquals(STOCKHOLM, carrierMovement.departureLocation()); 21 | assertEquals(HELSINKI, carrierMovement.arrivalLocation()); 22 | assertEquals(DateTestUtil.toDate("2007-09-23", "02:00"), carrierMovement.departureTime()); 23 | assertEquals(DateTestUtil.toDate("2007-09-23", "03:00"), carrierMovement.arrivalTime()); 24 | */ 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/tracking-servlet.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /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 junit.framework.TestCase; 4 | import se.citerus.dddsample.domain.model.location.Location; 5 | import static se.citerus.dddsample.domain.model.location.SampleLocations.HAMBURG; 6 | import static se.citerus.dddsample.domain.model.location.SampleLocations.STOCKHOLM; 7 | import se.citerus.dddsample.interfaces.booking.facade.dto.LocationDTO; 8 | 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | public class LocationDTOAssemblerTest extends TestCase { 13 | 14 | public void testToDTOList() { 15 | final LocationDTOAssembler assembler = new LocationDTOAssembler(); 16 | final List locationList = Arrays.asList(STOCKHOLM, HAMBURG); 17 | 18 | final List dtos = assembler.toDTOList(locationList); 19 | 20 | assertEquals(2, dtos.size()); 21 | 22 | LocationDTO dto = dtos.get(0); 23 | assertEquals("SESTO", dto.getUnLocode()); 24 | assertEquals("Stockholm", dto.getName()); 25 | 26 | dto = dtos.get(1); 27 | assertEquals("DEHAM", dto.getUnLocode()); 28 | assertEquals("Hamburg", dto.getName()); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/admin/pickNewDestination.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cargo Administration 4 | 9 | 10 | 11 |
12 |
" method="post"> 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 |
Change destination for cargo ${cargo.trackingId}
Current destination 20 | ${cargo.finalDestination} 21 |
New destination 26 | 31 |
38 | 39 |
43 |
44 |
45 | 46 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/domain/model/voyage/VoyageNumber.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.voyage; 2 | 3 | import org.apache.commons.lang.Validate; 4 | import se.citerus.dddsample.domain.shared.ValueObject; 5 | 6 | /** 7 | * Identifies a voyage. 8 | * 9 | */ 10 | public class VoyageNumber implements ValueObject { 11 | 12 | private String number; 13 | 14 | public VoyageNumber(String number) { 15 | Validate.notNull(number); 16 | 17 | this.number = number; 18 | } 19 | 20 | @Override 21 | public boolean equals(Object o) { 22 | if (this == o) return true; 23 | if (o == null) return false; 24 | if (!(o instanceof VoyageNumber)) return false; 25 | 26 | final VoyageNumber other = (VoyageNumber) o; 27 | 28 | return sameValueAs(other); 29 | } 30 | 31 | @Override 32 | public int hashCode() { 33 | return number.hashCode(); 34 | } 35 | 36 | @Override 37 | public boolean sameValueAs(VoyageNumber other) { 38 | return other != null && this.number.equals(other.number); 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return number; 44 | } 45 | 46 | public String idString() { 47 | return number; 48 | } 49 | 50 | VoyageNumber() { 51 | // Needed by Hibernate 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /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 Map> eventMap = new HashMap>(); 13 | 14 | @Override 15 | public void store(HandlingEvent event) { 16 | final TrackingId trackingId = event.cargo().trackingId(); 17 | List list = eventMap.get(trackingId); 18 | if (list == null) { 19 | list = new ArrayList(); 20 | eventMap.put(trackingId, list); 21 | } 22 | list.add(event); 23 | } 24 | 25 | @Override 26 | public HandlingHistory lookupHandlingHistoryOfCargo(TrackingId trackingId) { 27 | List events = eventMap.get(trackingId); 28 | if (events == null) events = Collections.emptyList(); 29 | 30 | return new HandlingHistory(events); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /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.util.Date; 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 Date loadTime; 15 | private final Date 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, Date loadTime, Date 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 Date getLoadTime() { 47 | return loadTime; 48 | } 49 | 50 | public Date getUnloadTime() { 51 | return unloadTime; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/interfaces/tracking/TrackCommandValidatorTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.interfaces.tracking; 2 | 3 | import junit.framework.TestCase; 4 | import org.springframework.validation.BeanPropertyBindingResult; 5 | import org.springframework.validation.BindingResult; 6 | import org.springframework.validation.FieldError; 7 | 8 | public class TrackCommandValidatorTest extends TestCase { 9 | 10 | TrackCommandValidator validator; 11 | TrackCommand command; 12 | BindingResult errors; 13 | 14 | protected void setUp() throws Exception { 15 | validator = new TrackCommandValidator(); 16 | command = new TrackCommand(); 17 | errors = new BeanPropertyBindingResult(command, "command"); 18 | } 19 | 20 | public void testValidateIllegalId() throws Exception { 21 | validator.validate(command, errors); 22 | 23 | assertEquals(1, errors.getErrorCount()); 24 | FieldError error = errors.getFieldError("trackingId"); 25 | assertNotNull(error); 26 | assertNull(error.getRejectedValue()); 27 | assertEquals("error.required", error.getCode()); 28 | } 29 | 30 | public void testValidateSuccess() throws Exception { 31 | command.setTrackingId("non-empty"); 32 | validator.validate(command, errors); 33 | 34 | assertFalse(errors.hasErrors()); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/aggregator/HandlingReportService.java: -------------------------------------------------------------------------------- 1 | 2 | package com.aggregator; 3 | 4 | import javax.jws.WebMethod; 5 | import javax.jws.WebParam; 6 | import javax.jws.WebService; 7 | import javax.xml.bind.annotation.XmlSeeAlso; 8 | import javax.xml.ws.RequestWrapper; 9 | import javax.xml.ws.ResponseWrapper; 10 | 11 | 12 | /** 13 | * This class was generated by the JAX-WS RI. 14 | * JAX-WS RI 2.1.1 in JDK 6 15 | * Generated source version: 2.1 16 | * 17 | */ 18 | @WebService(name = "HandlingReportService", targetNamespace = "http://ws.handling.interfaces.dddsample.citerus.se/") 19 | @XmlSeeAlso({ 20 | ObjectFactory.class 21 | }) 22 | public interface HandlingReportService { 23 | 24 | 25 | /** 26 | * 27 | * @param arg0 28 | * @throws HandlingReportErrors_Exception 29 | */ 30 | @WebMethod 31 | @RequestWrapper(localName = "submitReport", targetNamespace = "http://ws.handling.interfaces.dddsample.citerus.se/", className = "com.aggregator.SubmitReport") 32 | @ResponseWrapper(localName = "submitReportResponse", targetNamespace = "http://ws.handling.interfaces.dddsample.citerus.se/", className = "com.aggregator.SubmitReportResponse") 33 | public void submitReport( 34 | @WebParam(name = "arg0", targetNamespace = "") 35 | HandlingReport arg0) 36 | throws HandlingReportErrors_Exception 37 | ; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /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/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | <% 2 | //response.sendRedirect("public/track"); 3 | %> 4 | 5 | 6 | 7 | 8 | 9 | DDDSample 10 | 11 | 12 |

13 |

Welcome to the DDDSample application.

14 |

There are two web interfaces available:

15 | 19 |

The Incident Logging application, that is used to register handling events, is a stand-alone application and a separate download.

20 |

Please visit the project website for more information and a screencast demonstration of how the application works.

21 |

This project is a joint effort by Eric Evans' company Domain Language 22 | and the Swedish software consulting company Citerus.

23 | 24 | 25 | -------------------------------------------------------------------------------- /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.Remote; 8 | import java.rmi.RemoteException; 9 | import java.util.Date; 10 | import java.util.List; 11 | 12 | /** 13 | * This facade shields the domain layer - model, services, repositories - 14 | * from concerns about such things as the user interface and remoting. 15 | */ 16 | public interface BookingServiceFacade extends Remote { 17 | 18 | String bookNewCargo(String origin, String destination, Date arrivalDeadline) throws RemoteException; 19 | 20 | CargoRoutingDTO loadCargoForRouting(String trackingId) throws RemoteException; 21 | 22 | void assignCargoToRoute(String trackingId, RouteCandidateDTO route) throws RemoteException; 23 | 24 | void changeDestination(String trackingId, String destinationUnLocode) throws RemoteException; 25 | 26 | List requestPossibleRoutesForCargo(String trackingId) throws RemoteException; 27 | 28 | List listShippingLocations() throws RemoteException; 29 | 30 | List listAllCargos() throws RemoteException; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/se/citerus/dddsample/infrastructure/persistence/hibernate/HandlingEvent.hbm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | se.citerus.dddsample.domain.model.handling.HandlingEvent$Type 20 | 12 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /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.lang.Validate; 4 | import se.citerus.dddsample.domain.shared.ValueObject; 5 | 6 | /** 7 | * Uniquely identifies a particular cargo. Automatically generated by the application. 8 | * 9 | */ 10 | public final class TrackingId implements ValueObject { 11 | 12 | private String id; 13 | 14 | /** 15 | * Constructor. 16 | * 17 | * @param id Id string. 18 | */ 19 | public TrackingId(final String id) { 20 | Validate.notNull(id); 21 | this.id = id; 22 | } 23 | 24 | /** 25 | * @return String representation of this tracking id. 26 | */ 27 | public String idString() { 28 | return id; 29 | } 30 | 31 | @Override 32 | public boolean equals(Object o) { 33 | if (this == o) return true; 34 | if (o == null || getClass() != o.getClass()) return false; 35 | 36 | TrackingId other = (TrackingId) o; 37 | 38 | return sameValueAs(other); 39 | } 40 | 41 | @Override 42 | public int hashCode() { 43 | return id.hashCode(); 44 | } 45 | 46 | @Override 47 | public boolean sameValueAs(TrackingId other) { 48 | return other != null && this.id.equals(other.id); 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return id; 54 | } 55 | 56 | TrackingId() { 57 | // Needed by Hibernate 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /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/com/aggregator/HandlingReportErrors_Exception.java: -------------------------------------------------------------------------------- 1 | 2 | package com.aggregator; 3 | 4 | import javax.xml.ws.WebFault; 5 | 6 | 7 | /** 8 | * This class was generated by the JAX-WS RI. 9 | * JAX-WS RI 2.1.1 in JDK 6 10 | * Generated source version: 2.1 11 | * 12 | */ 13 | @WebFault(name = "HandlingReportErrors", targetNamespace = "http://ws.handling.interfaces.dddsample.citerus.se/") 14 | public class HandlingReportErrors_Exception 15 | extends Exception 16 | { 17 | 18 | /** 19 | * Java type that goes as soapenv:Fault detail element. 20 | * 21 | */ 22 | private HandlingReportErrors faultInfo; 23 | 24 | /** 25 | * 26 | * @param message 27 | * @param faultInfo 28 | */ 29 | public HandlingReportErrors_Exception(String message, HandlingReportErrors faultInfo) { 30 | super(message); 31 | this.faultInfo = faultInfo; 32 | } 33 | 34 | /** 35 | * 36 | * @param message 37 | * @param faultInfo 38 | * @param cause 39 | */ 40 | public HandlingReportErrors_Exception(String message, HandlingReportErrors faultInfo, Throwable cause) { 41 | super(message, cause); 42 | this.faultInfo = faultInfo; 43 | } 44 | 45 | /** 46 | * 47 | * @return 48 | * returns fault bean: com.aggregator.HandlingReportErrors 49 | */ 50 | public HandlingReportErrors getFaultInfo() { 51 | return faultInfo; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/site/site.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | DDDSample 4 | images/banner-left.png 5 | http://dddsample.sf.net 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /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/infrastructure/messaging/jms/CargoHandledConsumer.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.infrastructure.messaging.jms; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import se.citerus.dddsample.application.CargoInspectionService; 6 | import se.citerus.dddsample.domain.model.cargo.TrackingId; 7 | 8 | import javax.jms.Message; 9 | import javax.jms.MessageListener; 10 | import javax.jms.TextMessage; 11 | 12 | /** 13 | * Consumes JMS messages and delegates notification of misdirected 14 | * cargo to the tracking service. 15 | * 16 | * This is a programmatic hook into the JMS infrastructure to 17 | * make cargo inspection message-driven. 18 | */ 19 | public class CargoHandledConsumer implements MessageListener { 20 | 21 | private CargoInspectionService cargoInspectionService; 22 | private final Log logger = LogFactory.getLog(getClass()); 23 | 24 | @Override 25 | public void onMessage(final Message message) { 26 | try { 27 | final TextMessage textMessage = (TextMessage) message; 28 | final String trackingidString = textMessage.getText(); 29 | 30 | cargoInspectionService.inspectCargo(new TrackingId(trackingidString)); 31 | } catch (Exception e) { 32 | logger.error(e, e); 33 | } 34 | } 35 | 36 | public void setCargoInspectionService(CargoInspectionService cargoInspectionService) { 37 | this.cargoInspectionService = cargoInspectionService; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/pathfinder/api/TransitEdge.java: -------------------------------------------------------------------------------- 1 | package com.pathfinder.api; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | /** 7 | * Represents an edge in a path through a graph, 8 | * describing the route of a cargo. 9 | * 10 | */ 11 | public final class TransitEdge implements Serializable { 12 | 13 | private final String voyageNumber; 14 | private final String fromUnLocode; 15 | private final String toUnLocode; 16 | private final Date fromDate; 17 | private final Date toDate; 18 | 19 | /** 20 | * Constructor. 21 | * 22 | * @param voyageNumber 23 | * @param fromUnLocode 24 | * @param toUnLocode 25 | * @param fromDate 26 | * @param toDate 27 | */ 28 | public TransitEdge(final String voyageNumber, 29 | final String fromUnLocode, 30 | final String toUnLocode, 31 | final Date fromDate, 32 | final Date toDate) { 33 | this.voyageNumber = voyageNumber; 34 | this.fromUnLocode = fromUnLocode; 35 | this.toUnLocode = toUnLocode; 36 | this.fromDate = fromDate; 37 | this.toDate = toDate; 38 | } 39 | 40 | public String getVoyageNumber() { 41 | return voyageNumber; 42 | } 43 | 44 | public String getFromUnLocode() { 45 | return fromUnLocode; 46 | } 47 | 48 | public String getToUnLocode() { 49 | return toUnLocode; 50 | } 51 | 52 | public Date getFromDate() { 53 | return fromDate; 54 | } 55 | 56 | public Date getToDate() { 57 | return toDate; 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/infrastructure/persistence/hibernate/CargoRepositoryHibernate.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.infrastructure.persistence.hibernate; 2 | 3 | import org.springframework.stereotype.Repository; 4 | import se.citerus.dddsample.domain.model.cargo.Cargo; 5 | import se.citerus.dddsample.domain.model.cargo.CargoRepository; 6 | import se.citerus.dddsample.domain.model.cargo.TrackingId; 7 | 8 | import java.util.List; 9 | import java.util.UUID; 10 | 11 | /** 12 | * Hibernate implementation of CargoRepository. 13 | */ 14 | @Repository 15 | public class CargoRepositoryHibernate extends HibernateRepository implements CargoRepository { 16 | 17 | public Cargo find(TrackingId tid) { 18 | return (Cargo) getSession(). 19 | createQuery("from Cargo where trackingId = :tid"). 20 | setParameter("tid", tid). 21 | uniqueResult(); 22 | } 23 | 24 | public void store(Cargo cargo) { 25 | getSession().saveOrUpdate(cargo); 26 | // Delete-orphan does not seem to work correctly when the parent is a component 27 | getSession().createSQLQuery("delete from Leg where cargo_id = null").executeUpdate(); 28 | } 29 | 30 | public TrackingId nextTrackingId() { 31 | // TODO use an actual DB sequence here, UUID is for in-mem 32 | final String random = UUID.randomUUID().toString().toUpperCase(); 33 | return new TrackingId( 34 | random.substring(0, random.indexOf("-")) 35 | ); 36 | } 37 | 38 | public List findAll() { 39 | return getSession().createQuery("from Cargo").list(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /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/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.util.Date; 10 | 11 | /** 12 | * Handling event service. 13 | */ 14 | public interface HandlingEventService { 15 | 16 | /** 17 | * Registers a handling event in the system, and notifies interested 18 | * parties that a cargo has been handled. 19 | * 20 | * @param completionTime when the event was completed 21 | * @param trackingId cargo tracking id 22 | * @param voyageNumber voyage number 23 | * @param unLocode UN locode for the location where the event occurred 24 | * @param type type of event 25 | * @throws se.citerus.dddsample.domain.model.handling.CannotCreateHandlingEventException 26 | * if a handling event that represents an actual event that's relevant to a cargo we're tracking 27 | * can't be created from the parameters 28 | */ 29 | void registerHandlingEvent(Date completionTime, 30 | TrackingId trackingId, 31 | VoyageNumber voyageNumber, 32 | UnLocode unLocode, 33 | HandlingEvent.Type type) throws CannotCreateHandlingEventException; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /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.util.Date; 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, Date 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/webapp/WEB-INF/booking-servlet.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/infrastructure/messaging/jms/HandlingEventRegistrationAttemptConsumer.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.infrastructure.messaging.jms; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import se.citerus.dddsample.application.HandlingEventService; 6 | import se.citerus.dddsample.interfaces.handling.HandlingEventRegistrationAttempt; 7 | 8 | import javax.jms.Message; 9 | import javax.jms.MessageListener; 10 | import javax.jms.ObjectMessage; 11 | 12 | /** 13 | * Consumes handling event registration attempt messages and delegates to 14 | * proper registration. 15 | * 16 | */ 17 | public class HandlingEventRegistrationAttemptConsumer implements MessageListener { 18 | 19 | private HandlingEventService handlingEventService; 20 | private static final Log logger = LogFactory.getLog(HandlingEventRegistrationAttemptConsumer.class); 21 | 22 | @Override 23 | public void onMessage(final Message message) { 24 | try { 25 | final ObjectMessage om = (ObjectMessage) message; 26 | HandlingEventRegistrationAttempt attempt = (HandlingEventRegistrationAttempt) om.getObject(); 27 | handlingEventService.registerHandlingEvent( 28 | attempt.getCompletionTime(), 29 | attempt.getTrackingId(), 30 | attempt.getVoyageNumber(), 31 | attempt.getUnLocode(), 32 | attempt.getType() 33 | ); 34 | } catch (Exception e) { 35 | logger.error(e, e); 36 | } 37 | } 38 | 39 | public void setHandlingEventService(HandlingEventService handlingEventService) { 40 | this.handlingEventService = handlingEventService; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/aggregator/SubmitReport.java: -------------------------------------------------------------------------------- 1 | 2 | package com.aggregator; 3 | 4 | import javax.xml.bind.annotation.XmlAccessType; 5 | import javax.xml.bind.annotation.XmlAccessorType; 6 | import javax.xml.bind.annotation.XmlType; 7 | 8 | 9 | /** 10 | *

Java class for submitReport complex type. 11 | * 12 | *

The following schema fragment specifies the expected content contained within this class. 13 | * 14 | *

15 |  * <complexType name="submitReport">
16 |  *   <complexContent>
17 |  *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
18 |  *       <sequence>
19 |  *         <element name="arg0" type="{http://ws.handling.interfaces.dddsample.citerus.se/}handlingReport" minOccurs="0"/>
20 |  *       </sequence>
21 |  *     </restriction>
22 |  *   </complexContent>
23 |  * </complexType>
24 |  * 
25 | * 26 | * 27 | */ 28 | @XmlAccessorType(XmlAccessType.FIELD) 29 | @XmlType(name = "submitReport", propOrder = { 30 | "arg0" 31 | }) 32 | public class SubmitReport { 33 | 34 | protected HandlingReport arg0; 35 | 36 | /** 37 | * Gets the value of the arg0 property. 38 | * 39 | * @return 40 | * possible object is 41 | * {@link HandlingReport } 42 | * 43 | */ 44 | public HandlingReport getArg0() { 45 | return arg0; 46 | } 47 | 48 | /** 49 | * Sets the value of the arg0 property. 50 | * 51 | * @param value 52 | * allowed object is 53 | * {@link HandlingReport } 54 | * 55 | */ 56 | public void setArg0(HandlingReport value) { 57 | this.arg0 = value; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /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/main/java/se/citerus/dddsample/domain/model/voyage/Schedule.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.voyage; 2 | 3 | import org.apache.commons.lang.Validate; 4 | import org.apache.commons.lang.builder.HashCodeBuilder; 5 | import se.citerus.dddsample.domain.shared.ValueObject; 6 | 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | /** 11 | * A voyage schedule. 12 | * 13 | */ 14 | public class Schedule implements ValueObject { 15 | 16 | private List carrierMovements = Collections.EMPTY_LIST; 17 | 18 | public static final Schedule EMPTY = new Schedule(); 19 | 20 | Schedule(final List carrierMovements) { 21 | Validate.notNull(carrierMovements); 22 | Validate.noNullElements(carrierMovements); 23 | Validate.notEmpty(carrierMovements); 24 | 25 | this.carrierMovements = carrierMovements; 26 | } 27 | 28 | /** 29 | * @return Carrier movements. 30 | */ 31 | public List carrierMovements() { 32 | return Collections.unmodifiableList(carrierMovements); 33 | } 34 | 35 | @Override 36 | public boolean sameValueAs(final Schedule other) { 37 | return other != null && this.carrierMovements.equals(other.carrierMovements); 38 | } 39 | 40 | @Override 41 | public boolean equals(final Object o) { 42 | if (this == o) return true; 43 | if (o == null || getClass() != o.getClass()) return false; 44 | 45 | final Schedule that = (Schedule) o; 46 | 47 | return sameValueAs(that); 48 | } 49 | 50 | @Override 51 | public int hashCode() { 52 | return new HashCodeBuilder().append(this.carrierMovements).toHashCode(); 53 | } 54 | 55 | Schedule() { 56 | // Needed by Hibernate 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/domain/model/location/UnLocodeTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.location; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class UnLocodeTest extends TestCase { 6 | 7 | public void testNew() throws Exception { 8 | assertValid("AA234"); 9 | assertValid("AAA9B"); 10 | assertValid("AAAAA"); 11 | 12 | assertInvalid("AAAA"); 13 | assertInvalid("AAAAAA"); 14 | assertInvalid("AAAA"); 15 | assertInvalid("AAAAAA"); 16 | assertInvalid("22AAA"); 17 | assertInvalid("AA111"); 18 | assertInvalid(null); 19 | } 20 | 21 | public void testIdString() throws Exception { 22 | assertEquals("ABCDE", new UnLocode("AbcDe").idString()); 23 | } 24 | 25 | public void testEquals() throws Exception { 26 | UnLocode allCaps = new UnLocode("ABCDE"); 27 | UnLocode mixedCase = new UnLocode("aBcDe"); 28 | 29 | assertTrue(allCaps.equals(mixedCase)); 30 | assertTrue(mixedCase.equals(allCaps)); 31 | assertTrue(allCaps.equals(allCaps)); 32 | 33 | assertFalse(allCaps.equals(null)); 34 | assertFalse(allCaps.equals(new UnLocode("FGHIJ"))); 35 | } 36 | 37 | public void testHashCode() throws Exception { 38 | UnLocode allCaps = new UnLocode("ABCDE"); 39 | UnLocode mixedCase = new UnLocode("aBcDe"); 40 | 41 | assertEquals(allCaps.hashCode(), mixedCase.hashCode()); 42 | } 43 | 44 | private void assertValid(String unlocode) { 45 | new UnLocode(unlocode); 46 | } 47 | 48 | private void assertInvalid(String unlocode) { 49 | try { 50 | new UnLocode(unlocode); 51 | fail("The combination [" + unlocode + "] is not a valid UnLocode"); 52 | } catch (IllegalArgumentException expected) {} 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/interfaces/booking/web/ItinerarySelectionCommandTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.interfaces.booking.web; 2 | 3 | import junit.framework.TestCase; 4 | import org.springframework.mock.web.MockHttpServletRequest; 5 | import org.springframework.web.bind.ServletRequestDataBinder; 6 | 7 | import java.util.List; 8 | 9 | public class ItinerarySelectionCommandTest extends TestCase { 10 | 11 | RouteAssignmentCommand command; 12 | MockHttpServletRequest request; 13 | 14 | public void testBind() { 15 | command = new RouteAssignmentCommand(); 16 | request = new MockHttpServletRequest(); 17 | 18 | request.addParameter("legs[0].voyageNumber", "CM01"); 19 | request.addParameter("legs[0].fromUnLocode", "AAAAA"); 20 | request.addParameter("legs[0].toUnLocode", "BBBBB"); 21 | 22 | request.addParameter("legs[1].voyageNumber", "CM02"); 23 | request.addParameter("legs[1].fromUnLocode", "CCCCC"); 24 | request.addParameter("legs[1].toUnLocode", "DDDDD"); 25 | 26 | request.addParameter("trackingId", "XYZ"); 27 | 28 | ServletRequestDataBinder binder = new ServletRequestDataBinder(command); 29 | binder.bind(request); 30 | 31 | List legs = command.getLegs(); 32 | assertEquals(2, legs.size()); 33 | 34 | RouteAssignmentCommand.LegCommand leg = legs.get(0); 35 | assertEquals("CM01", leg.getVoyageNumber()); 36 | assertEquals("AAAAA", leg.getFromUnLocode()); 37 | assertEquals("BBBBB", leg.getToUnLocode()); 38 | 39 | leg = legs.get(1); 40 | assertEquals("CM02", leg.getVoyageNumber()); 41 | assertEquals("CCCCC", leg.getFromUnLocode()); 42 | assertEquals("DDDDD", leg.getToUnLocode()); 43 | 44 | assertEquals("XYZ", command.getTrackingId()); 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/webapp/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/webapp/WEB-INF/jsp/pub/track.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | Tracking cargo 4 | 5 | 6 |
7 | 31 | 32 |

Hint: try tracking "ABC123" or "JKL567".

33 |
34 | 35 | 36 |
37 |

Cargo ${cargo.trackingId} is now: ${cargo.statusText}

38 |

Estimated time of arrival in ${cargo.destination}: ${cargo.eta}

39 |

${cargo.nextExpectedActivity}

40 | 41 |

Cargo is misdirected

42 |
43 | 44 |

Handling History

45 |
    46 | 47 |
  • 48 |

    49 |  ${leg.description}

    50 |
  • 51 |
    52 |
53 |
54 |
55 |
56 | 57 |
58 | 63 | 64 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/domain/shared/experimental/ValueObjectSupport.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.shared.experimental; 2 | 3 | import org.apache.commons.lang.builder.EqualsBuilder; 4 | import org.apache.commons.lang.builder.HashCodeBuilder; 5 | import se.citerus.dddsample.domain.shared.ValueObject; 6 | 7 | /** 8 | * Base class for value objects. 9 | * 10 | * @param 11 | */ 12 | public abstract class ValueObjectSupport implements ValueObject { 13 | 14 | private transient int cachedHashCode = 0; 15 | 16 | /** 17 | * @param other The other value object. 18 | * @return True if all non-transient fields are equal. 19 | */ 20 | @Override 21 | public final boolean sameValueAs(final T other) { 22 | return other != null && EqualsBuilder.reflectionEquals(this, other, false); 23 | } 24 | 25 | /** 26 | * @return Hash code built from all non-transient fields. 27 | */ 28 | @Override 29 | public final int hashCode() { 30 | // Using a local variable to ensure that we only do a single read 31 | // of the cachedHashCode field, to avoid race conditions. 32 | // It doesn't matter if several threads compute the hash code and overwrite 33 | // each other, but it's important that we never return 0, which could happen 34 | // with multiple reads of the cachedHashCode field. 35 | // 36 | // See java.lang.String.hashCode() 37 | int h = cachedHashCode; 38 | if (h == 0) { 39 | // Lazy initialization of hash code. 40 | // Value objects are immutable, so the hash code never changes. 41 | h = HashCodeBuilder.reflectionHashCode(this, false); 42 | cachedHashCode = h; 43 | } 44 | 45 | return h; 46 | } 47 | 48 | /** 49 | * @param o other object 50 | * @return True if other object has the same value as this value object. 51 | */ 52 | @Override 53 | public final boolean equals(final Object o) { 54 | if (this == o) return true; 55 | if (o == null || getClass() != o.getClass()) return false; 56 | 57 | return sameValueAs((T) o); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/domain/model/handling/HandlingHistoryTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.handling; 2 | 3 | import junit.framework.TestCase; 4 | import static se.citerus.dddsample.application.util.DateTestUtil.toDate; 5 | import se.citerus.dddsample.domain.model.cargo.Cargo; 6 | import se.citerus.dddsample.domain.model.cargo.RouteSpecification; 7 | import se.citerus.dddsample.domain.model.cargo.TrackingId; 8 | import static se.citerus.dddsample.domain.model.location.SampleLocations.*; 9 | import se.citerus.dddsample.domain.model.voyage.Voyage; 10 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber; 11 | 12 | import static java.util.Arrays.asList; 13 | import java.util.Date; 14 | 15 | 16 | public class HandlingHistoryTest extends TestCase { 17 | Cargo cargo; 18 | Voyage voyage; 19 | HandlingEvent event1; 20 | HandlingEvent event1duplicate; 21 | HandlingEvent event2; 22 | HandlingHistory handlingHistory; 23 | 24 | protected void setUp() throws Exception { 25 | cargo = new Cargo(new TrackingId("ABC"), new RouteSpecification(SHANGHAI, DALLAS, toDate("2009-04-01"))); 26 | voyage = new Voyage.Builder(new VoyageNumber("X25"), HONGKONG). 27 | addMovement(SHANGHAI, new Date(), new Date()). 28 | addMovement(DALLAS, new Date(), new Date()). 29 | build(); 30 | event1 = new HandlingEvent(cargo, toDate("2009-03-05"), new Date(100), HandlingEvent.Type.LOAD, SHANGHAI, voyage); 31 | event1duplicate = new HandlingEvent(cargo, toDate("2009-03-05"), new Date(200), HandlingEvent.Type.LOAD, SHANGHAI, voyage); 32 | event2 = new HandlingEvent(cargo, toDate("2009-03-10"), new Date(150), HandlingEvent.Type.UNLOAD, DALLAS, voyage); 33 | 34 | handlingHistory = new HandlingHistory(asList(event2, event1, event1duplicate)); 35 | } 36 | 37 | public void testDistinctEventsByCompletionTime() { 38 | assertEquals(asList(event1, event2), handlingHistory.distinctEventsByCompletionTime()); 39 | } 40 | 41 | public void testMostRecentlyCompletedEvent() { 42 | assertEquals(event2, handlingHistory.mostRecentlyCompletedEvent()); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /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.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.Date; 7 | import java.util.List; 8 | 9 | /** 10 | * DTO for registering and routing a cargo. 11 | */ 12 | public final class CargoRoutingDTO implements Serializable { 13 | 14 | private final String trackingId; 15 | private final String origin; 16 | private final String finalDestination; 17 | private final Date arrivalDeadline; 18 | private final boolean misrouted; 19 | private final List legs; 20 | 21 | /** 22 | * Constructor. 23 | * 24 | * @param trackingId 25 | * @param origin 26 | * @param finalDestination 27 | * @param arrivalDeadline 28 | * @param misrouted 29 | */ 30 | public CargoRoutingDTO(String trackingId, String origin, String finalDestination, Date arrivalDeadline, boolean misrouted) { 31 | this.trackingId = trackingId; 32 | this.origin = origin; 33 | this.finalDestination = finalDestination; 34 | this.arrivalDeadline = arrivalDeadline; 35 | this.misrouted = misrouted; 36 | this.legs = new ArrayList(); 37 | } 38 | 39 | public String getTrackingId() { 40 | return trackingId; 41 | } 42 | 43 | public String getOrigin() { 44 | return origin; 45 | } 46 | 47 | public String getFinalDestination() { 48 | return finalDestination; 49 | } 50 | 51 | public void addLeg(String voyageNumber, String from, String to, Date loadTime, Date unloadTime) { 52 | legs.add(new LegDTO(voyageNumber, from, to, loadTime, unloadTime)); 53 | } 54 | 55 | /** 56 | * @return An unmodifiable list DTOs. 57 | */ 58 | public List getLegs() { 59 | return Collections.unmodifiableList(legs); 60 | } 61 | 62 | public boolean isMisrouted() { 63 | return misrouted; 64 | } 65 | 66 | public boolean isRouted() { 67 | return !legs.isEmpty(); 68 | } 69 | 70 | public Date getArrivalDeadline() { 71 | return arrivalDeadline; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /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/domain/model/voyage/CarrierMovementTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.voyage; 2 | 3 | import junit.framework.TestCase; 4 | import static se.citerus.dddsample.domain.model.location.SampleLocations.HAMBURG; 5 | import static se.citerus.dddsample.domain.model.location.SampleLocations.STOCKHOLM; 6 | 7 | import java.util.Date; 8 | 9 | public class CarrierMovementTest extends TestCase { 10 | 11 | public void testConstructor() throws Exception { 12 | try { 13 | new CarrierMovement(null, null, new Date(), new Date()); 14 | fail("Should not accept null constructor arguments"); 15 | } catch (IllegalArgumentException expected) {} 16 | 17 | try { 18 | new CarrierMovement(null, null, new Date(), new Date()); 19 | fail("Should not accept null constructor arguments"); 20 | } catch (IllegalArgumentException expected) {} 21 | 22 | try { 23 | new CarrierMovement(STOCKHOLM, null, new Date(), new Date()); 24 | fail("Should not accept null constructor arguments"); 25 | } catch (IllegalArgumentException expected) {} 26 | 27 | // Legal 28 | new CarrierMovement(STOCKHOLM, HAMBURG, new Date(), new Date()); 29 | } 30 | 31 | public void testSameValueAsEqualsHashCode() throws Exception { 32 | CarrierMovement cm1 = new CarrierMovement(STOCKHOLM, HAMBURG, new Date(), new Date()); 33 | CarrierMovement cm2 = new CarrierMovement(STOCKHOLM, HAMBURG, new Date(), new Date()); 34 | CarrierMovement cm3 = new CarrierMovement(HAMBURG, STOCKHOLM, new Date(), new Date()); 35 | CarrierMovement cm4 = new CarrierMovement(HAMBURG, STOCKHOLM, new Date(), new Date()); 36 | 37 | assertTrue(cm1.sameValueAs(cm2)); 38 | assertFalse(cm2.sameValueAs(cm3)); 39 | assertTrue(cm3.sameValueAs(cm4)); 40 | 41 | assertTrue(cm1.equals(cm2)); 42 | assertFalse(cm2.equals(cm3)); 43 | assertTrue(cm3.equals(cm4)); 44 | 45 | assertTrue(cm1.hashCode() == cm2.hashCode()); 46 | assertFalse(cm2.hashCode() == cm3.hashCode()); 47 | assertTrue(cm3.hashCode() == cm4.hashCode()); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /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.lang.Validate; 4 | import se.citerus.dddsample.domain.shared.ValueObject; 5 | 6 | import java.util.regex.Pattern; 7 | 8 | /** 9 | * United nations location code. 10 | *

11 | * http://www.unece.org/cefact/locode/ 12 | * http://www.unece.org/cefact/locode/DocColumnDescription.htm#LOCODE 13 | */ 14 | public final class UnLocode implements ValueObject { 15 | 16 | private String unlocode; 17 | 18 | // Country code is exactly two letters. 19 | // Location code is usually three letters, but may contain the numbers 2-9 as well 20 | private static final Pattern VALID_PATTERN = Pattern.compile("[a-zA-Z]{2}[a-zA-Z2-9]{3}"); 21 | 22 | /** 23 | * Constructor. 24 | * 25 | * @param countryAndLocation Location string. 26 | */ 27 | public UnLocode(final String countryAndLocation) { 28 | Validate.notNull(countryAndLocation, "Country and location may not be null"); 29 | Validate.isTrue(VALID_PATTERN.matcher(countryAndLocation).matches(), 30 | countryAndLocation + " is not a valid UN/LOCODE (does not match pattern)"); 31 | 32 | this.unlocode = countryAndLocation.toUpperCase(); 33 | } 34 | 35 | /** 36 | * @return country code and location code concatenated, always upper case. 37 | */ 38 | public String idString() { 39 | return unlocode; 40 | } 41 | 42 | @Override 43 | public boolean equals(final Object o) { 44 | if (this == o) return true; 45 | if (o == null || getClass() != o.getClass()) return false; 46 | 47 | UnLocode other = (UnLocode) o; 48 | 49 | return sameValueAs(other); 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | return unlocode.hashCode(); 55 | } 56 | 57 | @Override 58 | public boolean sameValueAs(UnLocode other) { 59 | return other != null && this.unlocode.equals(other.unlocode); 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return idString(); 65 | } 66 | 67 | UnLocode() { 68 | // Needed by Hibernate 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /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/test/java/se/citerus/dddsample/domain/shared/experimental/EntitySupportTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.shared.experimental; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class EntitySupportTest extends TestCase { 6 | 7 | public void testNoIdentityAnnotationFail() { 8 | NoAnnotationEntity entity = new NoAnnotationEntity(); 9 | 10 | try { 11 | entity.identity(); 12 | fail("Entity must have a unique identity"); 13 | } catch (IllegalStateException expected) { 14 | } 15 | } 16 | 17 | public void testTwoIdentityAnnotationsFail() { 18 | TwoAnnotationsEntity entity = new TwoAnnotationsEntity(); 19 | try { 20 | entity.identity(); 21 | fail("Entity must have a unique identity"); 22 | } catch (IllegalStateException expected) { 23 | } 24 | } 25 | 26 | public void testOneAnnotationSuccess() { 27 | OneAnnotationEntity entity = new OneAnnotationEntity("id"); 28 | assertEquals("id", entity.identity()); 29 | } 30 | 31 | public void testSameIdentityEqualsHashcode() { 32 | OneAnnotationEntity entity1 = new OneAnnotationEntity("A"); 33 | OneAnnotationEntity entity2 = new OneAnnotationEntity("A"); 34 | OneAnnotationEntity entity3 = new OneAnnotationEntity("B"); 35 | 36 | assertTrue(entity1.sameIdentityAs(entity2)); 37 | assertFalse(entity2.sameIdentityAs(entity3)); 38 | 39 | assertTrue(entity1.equals(entity2)); 40 | assertFalse(entity2.equals(entity3)); 41 | 42 | assertTrue(entity1.hashCode() == entity2.hashCode()); 43 | assertFalse(entity2.hashCode() == entity3.hashCode()); 44 | } 45 | 46 | class NoAnnotationEntity extends EntitySupport { 47 | } 48 | 49 | class OneAnnotationEntity extends EntitySupport { 50 | private @Identity String id; 51 | 52 | OneAnnotationEntity(String id) { 53 | this.id = id; 54 | } 55 | } 56 | 57 | class TwoAnnotationsEntity extends EntitySupport { 58 | private @Identity String id1 = "id1"; 59 | private @Identity String id2 = "id2"; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/application/BookingServiceTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.application; 2 | 3 | import junit.framework.TestCase; 4 | import static org.easymock.EasyMock.*; 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.CargoRepository; 8 | import se.citerus.dddsample.domain.model.cargo.TrackingId; 9 | import se.citerus.dddsample.domain.model.location.LocationRepository; 10 | import static se.citerus.dddsample.domain.model.location.SampleLocations.CHICAGO; 11 | import static se.citerus.dddsample.domain.model.location.SampleLocations.STOCKHOLM; 12 | import se.citerus.dddsample.domain.model.location.UnLocode; 13 | import se.citerus.dddsample.domain.service.RoutingService; 14 | 15 | import java.util.Date; 16 | 17 | public class BookingServiceTest extends TestCase { 18 | 19 | BookingServiceImpl bookingService; 20 | CargoRepository cargoRepository; 21 | LocationRepository locationRepository; 22 | RoutingService routingService; 23 | 24 | protected void setUp() throws Exception { 25 | cargoRepository = createMock(CargoRepository.class); 26 | locationRepository = createMock(LocationRepository.class); 27 | routingService = createMock(RoutingService.class); 28 | bookingService = new BookingServiceImpl(cargoRepository, locationRepository, routingService); 29 | } 30 | 31 | public void testRegisterNew() { 32 | TrackingId expectedTrackingId = new TrackingId("TRK1"); 33 | UnLocode fromUnlocode = new UnLocode("USCHI"); 34 | UnLocode toUnlocode = new UnLocode("SESTO"); 35 | 36 | expect(cargoRepository.nextTrackingId()).andReturn(expectedTrackingId); 37 | expect(locationRepository.find(fromUnlocode)).andReturn(CHICAGO); 38 | expect(locationRepository.find(toUnlocode)).andReturn(STOCKHOLM); 39 | 40 | cargoRepository.store(isA(Cargo.class)); 41 | 42 | replay(cargoRepository, locationRepository); 43 | 44 | TrackingId trackingId = bookingService.bookNewCargo(fromUnlocode, toUnlocode, new Date()); 45 | assertEquals(expectedTrackingId, trackingId); 46 | } 47 | 48 | protected void tearDown() throws Exception { 49 | verify(cargoRepository, locationRepository); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/interfaces/booking/web/RouteAssignmentCommand.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.interfaces.booking.web; 2 | 3 | import org.apache.commons.collections.Factory; 4 | import org.apache.commons.collections.ListUtils; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Date; 8 | import java.util.List; 9 | 10 | public class RouteAssignmentCommand { 11 | 12 | private String trackingId; 13 | private List legs = ListUtils.lazyList( 14 | new ArrayList(), LegCommand.factory() 15 | ); 16 | 17 | public String getTrackingId() { 18 | return trackingId; 19 | } 20 | 21 | public void setTrackingId(String trackingId) { 22 | this.trackingId = trackingId; 23 | } 24 | 25 | public List getLegs() { 26 | return legs; 27 | } 28 | 29 | public void setLegs(List legs) { 30 | this.legs = legs; 31 | } 32 | 33 | public static final class LegCommand { 34 | private String voyageNumber; 35 | private String fromUnLocode; 36 | private String toUnLocode; 37 | private Date fromDate; 38 | private Date toDate; 39 | 40 | public String getVoyageNumber() { 41 | return voyageNumber; 42 | } 43 | 44 | public void setVoyageNumber(final String voyageNumber) { 45 | this.voyageNumber = voyageNumber; 46 | } 47 | 48 | public String getFromUnLocode() { 49 | return fromUnLocode; 50 | } 51 | 52 | public void setFromUnLocode(final String fromUnLocode) { 53 | this.fromUnLocode = fromUnLocode; 54 | } 55 | 56 | public String getToUnLocode() { 57 | return toUnLocode; 58 | } 59 | 60 | public void setToUnLocode(final String toUnLocode) { 61 | this.toUnLocode = toUnLocode; 62 | } 63 | 64 | public Date getFromDate() { 65 | return fromDate; 66 | } 67 | 68 | public void setFromDate(Date fromDate) { 69 | this.fromDate = fromDate; 70 | } 71 | 72 | public Date getToDate() { 73 | return toDate; 74 | } 75 | 76 | public void setToDate(Date toDate) { 77 | this.toDate = toDate; 78 | } 79 | 80 | public static Factory factory() { 81 | return new Factory() { 82 | public Object create() { 83 | return new LegCommand(); 84 | } 85 | }; 86 | } 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/domain/model/location/SampleLocations.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.location; 2 | 3 | import java.lang.reflect.Field; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * Sample locations, for test purposes. 11 | * 12 | */ 13 | public class SampleLocations { 14 | 15 | public static final Location HONGKONG = new Location(new UnLocode("CNHKG"), "Hongkong"); 16 | public static final Location MELBOURNE = new Location(new UnLocode("AUMEL"), "Melbourne"); 17 | public static final Location STOCKHOLM = new Location(new UnLocode("SESTO"), "Stockholm"); 18 | public static final Location HELSINKI = new Location(new UnLocode("FIHEL"), "Helsinki"); 19 | public static final Location CHICAGO = new Location(new UnLocode("USCHI"), "Chicago"); 20 | public static final Location TOKYO = new Location(new UnLocode("JNTKO"), "Tokyo"); 21 | public static final Location HAMBURG = new Location(new UnLocode("DEHAM"), "Hamburg"); 22 | public static final Location SHANGHAI = new Location(new UnLocode("CNSHA"), "Shanghai"); 23 | public static final Location ROTTERDAM = new Location(new UnLocode("NLRTM"), "Rotterdam"); 24 | public static final Location GOTHENBURG = new Location(new UnLocode("SEGOT"), "Göteborg"); 25 | public static final Location HANGZOU = new Location(new UnLocode("CNHGH"), "Hangzhou"); 26 | public static final Location NEWYORK = new Location(new UnLocode("USNYC"), "New York"); 27 | public static final Location DALLAS = new Location(new UnLocode("USDAL"), "Dallas"); 28 | 29 | public static final Map ALL = new HashMap(); 30 | 31 | static { 32 | for (Field field : SampleLocations.class.getDeclaredFields()) { 33 | if (field.getType().equals(Location.class)) { 34 | try { 35 | Location location = (Location) field.get(null); 36 | ALL.put(location.unLocode(), location); 37 | } catch (IllegalAccessException e) { 38 | throw new RuntimeException(e); 39 | } 40 | } 41 | } 42 | } 43 | 44 | public static List getAll() { 45 | return new ArrayList(ALL.values()); 46 | } 47 | 48 | public static Location lookup(UnLocode unLocode) { 49 | return ALL.get(unLocode); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/domain/shared/experimental/EntitySupport.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.shared.experimental; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | /** 6 | * Base class for entities. 7 | * 8 | */ 9 | public abstract class EntitySupport implements Entity { 10 | 11 | private static Field identityField; 12 | 13 | @Override 14 | public final boolean sameIdentityAs(final T other) { 15 | return other != null && this.identity().equals(other.identity()); 16 | } 17 | 18 | @Override 19 | public final ID identity() { 20 | if (identityField == null) { 21 | identityField = identityFieldLazyDetermination(this.getClass()); 22 | } 23 | 24 | try { 25 | return (ID) identityField.get(this); 26 | } catch (IllegalAccessException e) { 27 | throw new RuntimeException(e); 28 | } 29 | } 30 | 31 | private static Field identityFieldLazyDetermination(final Class cls) { 32 | Field identityField = null; 33 | 34 | for (Field field : cls.getDeclaredFields()) { 35 | if (field.getAnnotation(Identity.class) != null) { 36 | field.setAccessible(true); 37 | if (identityField != null) { 38 | throw new IllegalStateException("Only one field can be annotated with " + Identity.class); 39 | } else { 40 | identityField = field; 41 | } 42 | } 43 | } 44 | 45 | if (identityField == null) { 46 | if (cls == Object.class) { 47 | throw new IllegalStateException( 48 | "This class, or one of its superclasses, " + 49 | "must have a unique field annotated with " + Identity.class); 50 | } else { 51 | return identityFieldLazyDetermination(cls.getSuperclass()); 52 | } 53 | } 54 | 55 | return identityField; 56 | } 57 | 58 | @Override 59 | public final int hashCode() { 60 | return identity().hashCode(); 61 | } 62 | 63 | @Override 64 | public final boolean equals(final Object o) { 65 | if (this == o) return true; 66 | if (o == null || getClass() != o.getClass()) return false; 67 | 68 | return sameIdentityAs((T) o); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/application/impl/CargoInspectionServiceImpl.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.application.impl; 2 | 3 | import org.apache.commons.lang.Validate; 4 | import org.apache.commons.logging.Log; 5 | import org.apache.commons.logging.LogFactory; 6 | import org.springframework.transaction.annotation.Transactional; 7 | import se.citerus.dddsample.application.ApplicationEvents; 8 | import se.citerus.dddsample.application.CargoInspectionService; 9 | import se.citerus.dddsample.domain.model.cargo.Cargo; 10 | import se.citerus.dddsample.domain.model.cargo.CargoRepository; 11 | import se.citerus.dddsample.domain.model.cargo.TrackingId; 12 | import se.citerus.dddsample.domain.model.handling.HandlingEventRepository; 13 | import se.citerus.dddsample.domain.model.handling.HandlingHistory; 14 | 15 | public class CargoInspectionServiceImpl implements CargoInspectionService { 16 | 17 | private final ApplicationEvents applicationEvents; 18 | private final CargoRepository cargoRepository; 19 | private final HandlingEventRepository handlingEventRepository; 20 | private final Log logger = LogFactory.getLog(getClass()); 21 | 22 | public CargoInspectionServiceImpl(final ApplicationEvents applicationEvents, 23 | final CargoRepository cargoRepository, 24 | final HandlingEventRepository handlingEventRepository) { 25 | this.applicationEvents = applicationEvents; 26 | this.cargoRepository = cargoRepository; 27 | this.handlingEventRepository = handlingEventRepository; 28 | } 29 | 30 | @Override 31 | @Transactional 32 | public void inspectCargo(final TrackingId trackingId) { 33 | Validate.notNull(trackingId, "Tracking ID is required"); 34 | 35 | final Cargo cargo = cargoRepository.find(trackingId); 36 | if (cargo == null) { 37 | logger.warn("Can't inspect non-existing cargo " + trackingId); 38 | return; 39 | } 40 | 41 | final HandlingHistory handlingHistory = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId); 42 | 43 | cargo.deriveDeliveryProgress(handlingHistory); 44 | 45 | if (cargo.delivery().isMisdirected()) { 46 | applicationEvents.cargoWasMisdirected(cargo); 47 | } 48 | 49 | if (cargo.delivery().isUnloadedAtDestination()) { 50 | applicationEvents.cargoHasArrived(cargo); 51 | } 52 | 53 | cargoRepository.store(cargo); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/interfaces/handling/HandlingEventRegistrationAttempt.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.interfaces.handling; 2 | 3 | import org.apache.commons.lang.builder.ToStringBuilder; 4 | import org.apache.commons.lang.builder.ToStringStyle; 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.location.UnLocode; 8 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber; 9 | 10 | import java.io.Serializable; 11 | import java.util.Date; 12 | 13 | /** 14 | * This is a simple transfer object for passing incoming handling event 15 | * registration attempts to proper the registration procedure. 16 | * 17 | * It is used as a message queue element. 18 | * 19 | */ 20 | public final class HandlingEventRegistrationAttempt implements Serializable { 21 | 22 | private final Date registrationTime; 23 | private final Date 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(final Date registrationDate, 30 | final Date completionDate, 31 | final TrackingId trackingId, 32 | final VoyageNumber voyageNumber, 33 | final HandlingEvent.Type type, 34 | final UnLocode unLocode) { 35 | this.registrationTime = registrationDate; 36 | this.completionTime = completionDate; 37 | this.trackingId = trackingId; 38 | this.voyageNumber = voyageNumber; 39 | this.type = type; 40 | this.unLocode = unLocode; 41 | } 42 | 43 | public Date getCompletionTime() { 44 | return new Date(completionTime.getTime()); 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 Date 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/domain/model/location/Location.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.location; 2 | 3 | import org.apache.commons.lang.Validate; 4 | import se.citerus.dddsample.domain.shared.Entity; 5 | 6 | /** 7 | * A location is our model is stops on a journey, such as cargo 8 | * origin or destination, or carrier movement endpoints. 9 | * 10 | * It is uniquely identified by a UN Locode. 11 | * 12 | */ 13 | public final class Location implements Entity { 14 | 15 | private UnLocode unLocode; 16 | private String name; 17 | 18 | /** 19 | * Special Location object that marks an unknown location. 20 | */ 21 | public static final Location UNKNOWN = new Location( 22 | new UnLocode("XXXXX"), "Unknown location" 23 | ); 24 | 25 | /** 26 | * Package-level constructor, visible for test only. 27 | * 28 | * @param unLocode UN Locode 29 | * @param name location name 30 | * @throws IllegalArgumentException if the UN Locode or name is null 31 | */ 32 | Location(final UnLocode unLocode, final String name) { 33 | Validate.notNull(unLocode); 34 | Validate.notNull(name); 35 | 36 | this.unLocode = unLocode; 37 | this.name = name; 38 | } 39 | 40 | /** 41 | * @return UN Locode for this location. 42 | */ 43 | public UnLocode unLocode() { 44 | return unLocode; 45 | } 46 | 47 | /** 48 | * @return Actual name of this location, e.g. "Stockholm". 49 | */ 50 | public String name() { 51 | return name; 52 | } 53 | 54 | /** 55 | * @param object to compare 56 | * @return Since this is an entiy this will be true iff UN locodes are equal. 57 | */ 58 | @Override 59 | public boolean equals(final Object object) { 60 | if (object == null) { 61 | return false; 62 | } 63 | if (this == object) { 64 | return true; 65 | } 66 | if (!(object instanceof Location)) { 67 | return false; 68 | } 69 | Location other = (Location) object; 70 | return sameIdentityAs(other); 71 | } 72 | 73 | @Override 74 | public boolean sameIdentityAs(final Location other) { 75 | return this.unLocode.sameValueAs(other.unLocode); 76 | } 77 | 78 | /** 79 | * @return Hash code of UN locode. 80 | */ 81 | @Override 82 | public int hashCode() { 83 | return unLocode.hashCode(); 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | return name + " [" + unLocode + "]"; 89 | } 90 | 91 | Location() { 92 | // Needed by Hibernate 93 | } 94 | 95 | private Long id; 96 | 97 | } 98 | -------------------------------------------------------------------------------- /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 junit.framework.TestCase; 4 | import se.citerus.dddsample.domain.model.cargo.*; 5 | import se.citerus.dddsample.domain.model.location.Location; 6 | import static se.citerus.dddsample.domain.model.location.SampleLocations.*; 7 | import static se.citerus.dddsample.domain.model.voyage.SampleVoyages.CM001; 8 | import se.citerus.dddsample.interfaces.booking.facade.dto.CargoRoutingDTO; 9 | import se.citerus.dddsample.interfaces.booking.facade.dto.LegDTO; 10 | 11 | import java.util.Arrays; 12 | import java.util.Date; 13 | 14 | public class CargoRoutingDTOAssemblerTest extends TestCase { 15 | 16 | public void testToDTO() throws Exception { 17 | final CargoRoutingDTOAssembler assembler = new CargoRoutingDTOAssembler(); 18 | 19 | final Location origin = STOCKHOLM; 20 | final Location destination = MELBOURNE; 21 | final Cargo cargo = new Cargo(new TrackingId("XYZ"), new RouteSpecification(origin, destination, new Date())); 22 | 23 | final Itinerary itinerary = new Itinerary( 24 | Arrays.asList( 25 | new Leg(CM001, origin, SHANGHAI, new Date(), new Date()), 26 | new Leg(CM001, ROTTERDAM, destination, new Date(), new Date()) 27 | ) 28 | ); 29 | 30 | cargo.assignToRoute(itinerary); 31 | 32 | final CargoRoutingDTO dto = assembler.toDTO(cargo); 33 | 34 | assertEquals(2, dto.getLegs().size()); 35 | 36 | LegDTO legDTO = dto.getLegs().get(0); 37 | assertEquals("CM001", legDTO.getVoyageNumber()); 38 | assertEquals("SESTO", legDTO.getFrom()); 39 | assertEquals("CNSHA", legDTO.getTo()); 40 | 41 | legDTO = dto.getLegs().get(1); 42 | assertEquals("CM001", legDTO.getVoyageNumber()); 43 | assertEquals("NLRTM", legDTO.getFrom()); 44 | assertEquals("AUMEL", legDTO.getTo()); 45 | } 46 | 47 | public void testToDTO_NoItinerary() throws Exception { 48 | final CargoRoutingDTOAssembler assembler = new CargoRoutingDTOAssembler(); 49 | 50 | final Cargo cargo = new Cargo(new TrackingId("XYZ"), new RouteSpecification(STOCKHOLM, MELBOURNE, new Date())); 51 | final CargoRoutingDTO dto = assembler.toDTO(cargo); 52 | 53 | assertEquals("XYZ", dto.getTrackingId()); 54 | assertEquals("SESTO", dto.getOrigin()); 55 | assertEquals("AUMEL", dto.getFinalDestination()); 56 | assertTrue(dto.getLegs().isEmpty()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/admin/show.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cargo Administration 4 | 5 | 6 |

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
Details for cargo ${cargo.trackingId}
Origin${cargo.origin}
Destination 17 | ${cargo.finalDestination} 18 |
23 | 24 | 25 | 26 | Change destination 27 |
Arrival deadline
35 |

36 | 37 | 38 | 39 | 40 | 41 | 42 |

Cargo is misrouted - reroute this cargo

43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
Itinerary
Voyage numberLoadUnload
${leg.voyageNumber}${leg.from}()${leg.to}()
65 |
66 | 67 |

68 | 69 | 70 | 71 | Not routed - Route this cargo 72 |

73 |
74 |
75 |
76 | 77 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/domain/model/handling/HandlingHistory.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.handling; 2 | 3 | import org.apache.commons.lang.Validate; 4 | import se.citerus.dddsample.domain.shared.ValueObject; 5 | 6 | import java.util.*; 7 | import static java.util.Collections.sort; 8 | 9 | /** 10 | * The handling history of a cargo. 11 | * 12 | */ 13 | public class HandlingHistory implements ValueObject { 14 | 15 | private final List handlingEvents; 16 | 17 | public static final HandlingHistory EMPTY = new HandlingHistory(Collections.emptyList()); 18 | 19 | public HandlingHistory(Collection handlingEvents) { 20 | Validate.notNull(handlingEvents, "Handling events are required"); 21 | 22 | this.handlingEvents = new ArrayList(handlingEvents); 23 | } 24 | 25 | /** 26 | * @return A distinct list (no duplicate registrations) of handling events, ordered by completion time. 27 | */ 28 | public List distinctEventsByCompletionTime() { 29 | final List ordered = new ArrayList( 30 | new HashSet(handlingEvents) 31 | ); 32 | sort(ordered, BY_COMPLETION_TIME_COMPARATOR); 33 | return Collections.unmodifiableList(ordered); 34 | } 35 | 36 | /** 37 | * @return Most recently completed event, or null if the delivery history is empty. 38 | */ 39 | public HandlingEvent mostRecentlyCompletedEvent() { 40 | final List distinctEvents = distinctEventsByCompletionTime(); 41 | if (distinctEvents.isEmpty()) { 42 | return null; 43 | } else { 44 | return distinctEvents.get(distinctEvents.size() - 1); 45 | } 46 | } 47 | 48 | @Override 49 | public boolean sameValueAs(HandlingHistory other) { 50 | return other != null && this.handlingEvents.equals(other.handlingEvents); 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) return true; 56 | if (o == null || getClass() != o.getClass()) return false; 57 | 58 | final HandlingHistory other = (HandlingHistory) o; 59 | return sameValueAs(other); 60 | } 61 | 62 | @Override 63 | public int hashCode() { 64 | return handlingEvents.hashCode(); 65 | } 66 | 67 | private static final Comparator BY_COMPLETION_TIME_COMPARATOR = 68 | new Comparator() { 69 | public int compare(final HandlingEvent he1, final HandlingEvent he2) { 70 | return he1.completionTime().compareTo(he2.completionTime()); 71 | } 72 | }; 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/infrastructure/persistence/hibernate/HandlingEventRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.infrastructure.persistence.hibernate; 2 | 3 | import se.citerus.dddsample.domain.model.cargo.Cargo; 4 | import se.citerus.dddsample.domain.model.cargo.CargoRepository; 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.location.Location; 9 | import se.citerus.dddsample.domain.model.location.LocationRepository; 10 | import se.citerus.dddsample.domain.model.location.UnLocode; 11 | 12 | import java.util.Date; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | public class HandlingEventRepositoryTest extends AbstractRepositoryTest { 17 | 18 | HandlingEventRepository handlingEventRepository; 19 | CargoRepository cargoRepository; 20 | LocationRepository locationRepository; 21 | 22 | public void setHandlingEventRepository(HandlingEventRepository handlingEventRepository) { 23 | this.handlingEventRepository = handlingEventRepository; 24 | } 25 | 26 | public void setCargoRepository(CargoRepository cargoRepository) { 27 | this.cargoRepository = cargoRepository; 28 | } 29 | 30 | public void setLocationRepository(LocationRepository locationRepository) { 31 | this.locationRepository = locationRepository; 32 | } 33 | 34 | public void testSave() { 35 | Location location = locationRepository.find(new UnLocode("SESTO")); 36 | 37 | Cargo cargo = cargoRepository.find(new TrackingId("XYZ")); 38 | Date completionTime = new Date(10); 39 | Date registrationTime = new Date(20); 40 | HandlingEvent event = new HandlingEvent(cargo, completionTime, registrationTime, HandlingEvent.Type.CLAIM, location); 41 | 42 | handlingEventRepository.store(event); 43 | 44 | flush(); 45 | 46 | Map result = sjt.queryForMap("select * from HandlingEvent where id = ?", getLongId(event)); 47 | assertEquals(1L, result.get("CARGO_ID")); 48 | assertEquals(new Date(10), result.get("COMPLETIONTIME")); 49 | assertEquals(new Date(20), result.get("REGISTRATIONTIME")); 50 | assertEquals("CLAIM", result.get("TYPE")); 51 | // TODO: the rest of the columns 52 | } 53 | 54 | public void testFindEventsForCargo() throws Exception { 55 | TrackingId trackingId = new TrackingId("XYZ"); 56 | List handlingEvents = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId).distinctEventsByCompletionTime(); 57 | assertEquals(12, handlingEvents.size()); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/main/java/com/aggregator/HandlingReportServiceService.java: -------------------------------------------------------------------------------- 1 | 2 | package com.aggregator; 3 | 4 | import javax.xml.namespace.QName; 5 | import javax.xml.ws.Service; 6 | import javax.xml.ws.WebEndpoint; 7 | import javax.xml.ws.WebServiceClient; 8 | import javax.xml.ws.WebServiceFeature; 9 | import java.net.MalformedURLException; 10 | import java.net.URL; 11 | 12 | 13 | /** 14 | * This class was generated by the JAX-WS RI. 15 | * JAX-WS RI 2.1.1 in JDK 6 16 | * Generated source version: 2.1 17 | * 18 | */ 19 | @WebServiceClient(name = "HandlingReportServiceService", targetNamespace = "http://ws.handling.interfaces.dddsample.citerus.se/", wsdlLocation = "file:///Users/peter/src/dddsample/src/main/resources/HandlingReportService.wsdl") 20 | public class HandlingReportServiceService 21 | extends Service 22 | { 23 | 24 | private final static URL HANDLINGREPORTSERVICESERVICE_WSDL_LOCATION; 25 | 26 | static { 27 | URL url = null; 28 | try { 29 | url = new URL("file:/Users/peter/src/dddsample/src/main/resources/HandlingReportService.wsdl"); 30 | } catch (MalformedURLException e) { 31 | e.printStackTrace(); 32 | } 33 | HANDLINGREPORTSERVICESERVICE_WSDL_LOCATION = url; 34 | } 35 | 36 | public HandlingReportServiceService(URL wsdlLocation, QName serviceName) { 37 | super(wsdlLocation, serviceName); 38 | } 39 | 40 | public HandlingReportServiceService() { 41 | super(HANDLINGREPORTSERVICESERVICE_WSDL_LOCATION, new QName("http://ws.handling.interfaces.dddsample.citerus.se/", "HandlingReportServiceService")); 42 | } 43 | 44 | /** 45 | * 46 | * @return 47 | * returns HandlingReportService 48 | */ 49 | @WebEndpoint(name = "HandlingReportServicePort") 50 | public HandlingReportService getHandlingReportServicePort() { 51 | return (HandlingReportService)super.getPort(new QName("http://ws.handling.interfaces.dddsample.citerus.se/", "HandlingReportServicePort"), HandlingReportService.class); 52 | } 53 | 54 | /** 55 | * 56 | * @param features 57 | * A list of {@link javax.xml.ws.WebServiceFeature} to configure on the proxy. Supported features not in the features parameter will have their default values. 58 | * @return 59 | * returns HandlingReportService 60 | */ 61 | @WebEndpoint(name = "HandlingReportServicePort") 62 | public HandlingReportService getHandlingReportServicePort(WebServiceFeature... features) { 63 | return (HandlingReportService)super.getPort(new QName("http://ws.handling.interfaces.dddsample.citerus.se/", "HandlingReportServicePort"), HandlingReportService.class, features); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/domain/model/cargo/Leg.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.cargo; 2 | 3 | import org.apache.commons.lang.Validate; 4 | import org.apache.commons.lang.builder.EqualsBuilder; 5 | import org.apache.commons.lang.builder.HashCodeBuilder; 6 | import se.citerus.dddsample.domain.model.location.Location; 7 | import se.citerus.dddsample.domain.model.voyage.Voyage; 8 | import se.citerus.dddsample.domain.shared.ValueObject; 9 | 10 | import java.util.Date; 11 | 12 | /** 13 | * An itinerary consists of one or more legs. 14 | */ 15 | public class Leg implements ValueObject { 16 | 17 | private Voyage voyage; 18 | private Location loadLocation; 19 | private Location unloadLocation; 20 | private Date loadTime; 21 | private Date unloadTime; 22 | 23 | public Leg(Voyage voyage, Location loadLocation, Location unloadLocation, Date loadTime, Date unloadTime) { 24 | Validate.noNullElements(new Object[] {voyage, loadLocation, unloadLocation, loadTime, unloadTime}); 25 | 26 | this.voyage = voyage; 27 | this.loadLocation = loadLocation; 28 | this.unloadLocation = unloadLocation; 29 | this.loadTime = loadTime; 30 | this.unloadTime = unloadTime; 31 | } 32 | 33 | public Voyage voyage() { 34 | return voyage; 35 | } 36 | 37 | public Location loadLocation() { 38 | return loadLocation; 39 | } 40 | 41 | public Location unloadLocation() { 42 | return unloadLocation; 43 | } 44 | 45 | public Date loadTime() { 46 | return new Date(loadTime.getTime()); 47 | } 48 | 49 | public Date unloadTime() { 50 | return new Date(unloadTime.getTime()); 51 | } 52 | 53 | @Override 54 | public boolean sameValueAs(final Leg other) { 55 | return other != null && new EqualsBuilder(). 56 | append(this.voyage, other.voyage). 57 | append(this.loadLocation, other.loadLocation). 58 | append(this.unloadLocation, other.unloadLocation). 59 | append(this.loadTime, other.loadTime). 60 | append(this.unloadTime, other.unloadTime). 61 | isEquals(); 62 | } 63 | 64 | @Override 65 | public boolean equals(final Object o) { 66 | if (this == o) return true; 67 | if (o == null || getClass() != o.getClass()) return false; 68 | 69 | Leg leg = (Leg) o; 70 | 71 | return sameValueAs(leg); 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | return new HashCodeBuilder(). 77 | append(voyage). 78 | append(loadLocation). 79 | append(unloadLocation). 80 | append(loadTime). 81 | append(unloadTime). 82 | toHashCode(); 83 | } 84 | 85 | Leg() { 86 | // Needed by Hibernate 87 | } 88 | 89 | // Auto-generated surrogate key 90 | private Long id; 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/domain/model/cargo/RouteSpecificationTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.cargo; 2 | 3 | import junit.framework.TestCase; 4 | import static se.citerus.dddsample.application.util.DateTestUtil.toDate; 5 | import static se.citerus.dddsample.domain.model.location.SampleLocations.*; 6 | import se.citerus.dddsample.domain.model.voyage.Voyage; 7 | import se.citerus.dddsample.domain.model.voyage.VoyageNumber; 8 | 9 | import java.util.Arrays; 10 | 11 | public class RouteSpecificationTest extends TestCase { 12 | 13 | final Voyage hongKongTokyoNewYork = new Voyage.Builder( 14 | new VoyageNumber("V001"), HONGKONG). 15 | addMovement(TOKYO, toDate("2009-02-01"), toDate("2009-02-05")). 16 | addMovement(NEWYORK, toDate("2009-02-06"), toDate("2009-02-10")). 17 | addMovement(HONGKONG, toDate("2009-02-11"), toDate("2009-02-14")). 18 | build(); 19 | 20 | final Voyage dallasNewYorkChicago = new Voyage.Builder( 21 | new VoyageNumber("V002"), DALLAS). 22 | addMovement(NEWYORK, toDate("2009-02-06"), toDate("2009-02-07")). 23 | addMovement(CHICAGO, toDate("2009-02-12"), toDate("2009-02-20")). 24 | build(); 25 | 26 | // TODO: 27 | // it shouldn't be possible to create Legs that have load/unload locations 28 | // and/or dates that don't match the voyage's carrier movements. 29 | final Itinerary itinerary = new Itinerary(Arrays.asList( 30 | new Leg(hongKongTokyoNewYork, HONGKONG, NEWYORK, 31 | toDate("2009-02-01"), toDate("2009-02-10")), 32 | new Leg(dallasNewYorkChicago, NEWYORK, CHICAGO, 33 | toDate("2009-02-12"), toDate("2009-02-20"))) 34 | ); 35 | 36 | public void testIsSatisfiedBy_Success() { 37 | RouteSpecification routeSpecification = new RouteSpecification( 38 | HONGKONG, CHICAGO, toDate("2009-03-01") 39 | ); 40 | 41 | assertTrue(routeSpecification.isSatisfiedBy(itinerary)); 42 | } 43 | 44 | public void testIsSatisfiedBy_WrongOrigin() { 45 | RouteSpecification routeSpecification = new RouteSpecification( 46 | HANGZOU, CHICAGO, toDate("2009-03-01") 47 | ); 48 | 49 | assertFalse(routeSpecification.isSatisfiedBy(itinerary)); 50 | } 51 | 52 | public void testIsSatisfiedBy_WrongDestination() { 53 | RouteSpecification routeSpecification = new RouteSpecification( 54 | HONGKONG, DALLAS, toDate("2009-03-01") 55 | ); 56 | 57 | assertFalse(routeSpecification.isSatisfiedBy(itinerary)); 58 | } 59 | 60 | public void testIsSatisfiedBy_MissedDeadline() { 61 | RouteSpecification routeSpecification = new RouteSpecification( 62 | HONGKONG, CHICAGO, toDate("2009-02-15") 63 | ); 64 | 65 | assertFalse(routeSpecification.isSatisfiedBy(itinerary)); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/resources/context-infrastructure-persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 44 | 45 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/admin/selectItinerary.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cargo Administration 4 | 5 | 6 |
7 | 8 | 9 | 10 | 13 | 14 |
Select route
11 | Cargo ${cargo.trackingId} is going from ${cargo.origin} to ${cargo.finalDestination} 12 |
15 | 16 | 17 |

No routes found that satisfy the route specification. 18 | Try setting an arrival deadline futher into the future (a few weeks at least). 19 |

20 |
21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | "/> 42 | "/> 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 59 | 60 | 61 |
Route candidate ${itStatus.index + 1}
VoyageFromTo
${leg.voyageNumber}${leg.from}${leg.to}
55 |

56 | 57 |

58 |
62 |
63 |
64 | 65 |
66 | 67 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/domain/model/cargo/HandlingActivity.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.domain.model.cargo; 2 | 3 | import org.apache.commons.lang.Validate; 4 | import org.apache.commons.lang.builder.EqualsBuilder; 5 | import org.apache.commons.lang.builder.HashCodeBuilder; 6 | import 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 | /** 12 | * A handling activity represents how and where a cargo can be handled, 13 | * and can be used to express predictions about what is expected to 14 | * happen to a cargo in the future. 15 | * 16 | */ 17 | public class HandlingActivity implements ValueObject { 18 | 19 | // TODO make HandlingActivity a part of HandlingEvent too? There is some overlap. 20 | 21 | private HandlingEvent.Type type; 22 | private Location location; 23 | private Voyage voyage; 24 | 25 | public HandlingActivity(final HandlingEvent.Type type, final Location location) { 26 | Validate.notNull(type, "Handling event type is required"); 27 | Validate.notNull(location, "Location is required"); 28 | 29 | this.type = type; 30 | this.location = location; 31 | } 32 | 33 | public HandlingActivity(final HandlingEvent.Type type, final Location location, final Voyage voyage) { 34 | Validate.notNull(type, "Handling event type is required"); 35 | Validate.notNull(location, "Location is required"); 36 | Validate.notNull(location, "Voyage is required"); 37 | 38 | this.type = type; 39 | this.location = location; 40 | this.voyage = voyage; 41 | } 42 | 43 | public HandlingEvent.Type type() { 44 | return type; 45 | } 46 | 47 | public Location location() { 48 | return location; 49 | } 50 | 51 | public Voyage voyage() { 52 | return voyage; 53 | } 54 | 55 | @Override 56 | public boolean sameValueAs(final HandlingActivity other) { 57 | return other != null && new EqualsBuilder(). 58 | append(this.type, other.type). 59 | append(this.location, other.location). 60 | append(this.voyage, other.voyage). 61 | isEquals(); 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | return new HashCodeBuilder(). 67 | append(this.type). 68 | append(this.location). 69 | append(this.voyage). 70 | toHashCode(); 71 | } 72 | 73 | @Override 74 | public boolean equals(final Object obj) { 75 | if (obj == this) return true; 76 | if (obj == null) return false; 77 | if (obj.getClass() != this.getClass()) return false; 78 | 79 | HandlingActivity other = (HandlingActivity) obj; 80 | 81 | return sameValueAs(other); 82 | } 83 | 84 | HandlingActivity() { 85 | // Needed by Hibernate 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/admin/registrationForm.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cargo Administration 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 29 | 37 | 38 | 39 |
40 |
" method="post"> 41 | 42 | 43 | 44 | 45 | 46 | 53 | 54 | 55 | 56 | 63 | 64 | 65 | 66 | 69 | 70 | 71 | 72 | 75 | 76 | 77 | 78 | 79 | 80 | 83 | 84 | 85 |
Book new cargo
Origin 47 | 52 |
Destination 57 | 62 |
Arrival deadline: 67 |   68 |
73 |
74 |
81 | 82 |
86 |
87 |
88 | 93 | 94 | -------------------------------------------------------------------------------- /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/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/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | DDDSample 8 | 9 | 10 | org.springframework.web.util.Log4jConfigListener 11 | 12 | 13 | 14 | log4jConfigLocation 15 | classpath:log4j.properties 16 | 17 | 18 | 19 | org.springframework.web.context.ContextLoaderListener 20 | 21 | 22 | 23 | contextConfigLocation 24 | 25 | classpath:/com/pathfinder/internal/applicationContext.xml 26 | 27 | classpath:context-infrastructure.xml 28 | classpath:context-application.xml 29 | classpath:context-domain.xml 30 | classpath:context-interfaces.xml 31 | 32 | 33 | 34 | 35 | se.citerus.dddsample.application.util.SampleDataGenerator 36 | 37 | 38 | 39 | tracking 40 | org.springframework.web.servlet.DispatcherServlet 41 | 42 | 43 | 44 | tracking 45 | /public/* 46 | 47 | 48 | 49 | booking 50 | se.citerus.dddsample.interfaces.booking.web.BookingDispatcherServlet 51 | 52 | 53 | 54 | booking 55 | /admin/* 56 | 57 | 58 | 59 | jaxws 60 | org.apache.cxf.transport.servlet.CXFServlet 61 | 62 | 63 | 64 | jaxws 65 | /ws/* 66 | 67 | 68 | 69 | sitemesh 70 | com.opensymphony.module.sitemesh.filter.PageFilter 71 | 72 | 73 | 74 | sitemesh 75 | booking 76 | 77 | 78 | 79 | sitemesh 80 | tracking 81 | 82 | 83 | 84 | 85 | *.jsp 86 | /WEB-INF/jspf/include.jspf 87 | true 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/application/HandlingEventServiceTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.application; 2 | 3 | import junit.framework.TestCase; 4 | import static org.easymock.EasyMock.*; 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 static se.citerus.dddsample.domain.model.location.SampleLocations.*; 15 | import static se.citerus.dddsample.domain.model.voyage.SampleVoyages.CM001; 16 | import se.citerus.dddsample.domain.model.voyage.VoyageRepository; 17 | 18 | import java.util.Date; 19 | 20 | public class HandlingEventServiceTest extends TestCase { 21 | private HandlingEventServiceImpl service; 22 | private ApplicationEvents applicationEvents; 23 | private CargoRepository cargoRepository; 24 | private VoyageRepository voyageRepository; 25 | private HandlingEventRepository handlingEventRepository; 26 | private LocationRepository locationRepository; 27 | 28 | private final Cargo cargo = new Cargo(new TrackingId("ABC"), new RouteSpecification(HAMBURG, TOKYO, new Date())); 29 | 30 | protected void setUp() throws Exception{ 31 | cargoRepository = createMock(CargoRepository.class); 32 | voyageRepository = createMock(VoyageRepository.class); 33 | handlingEventRepository = createMock(HandlingEventRepository.class); 34 | locationRepository = createMock(LocationRepository.class); 35 | applicationEvents = createMock(ApplicationEvents.class); 36 | 37 | HandlingEventFactory handlingEventFactory = new HandlingEventFactory(cargoRepository, voyageRepository, locationRepository); 38 | service = new HandlingEventServiceImpl(handlingEventRepository, applicationEvents, handlingEventFactory); 39 | } 40 | 41 | protected void tearDown() throws Exception { 42 | verify(cargoRepository, voyageRepository, handlingEventRepository, applicationEvents); 43 | } 44 | 45 | public void testRegisterEvent() throws Exception { 46 | expect(cargoRepository.find(cargo.trackingId())).andReturn(cargo); 47 | expect(voyageRepository.find(CM001.voyageNumber())).andReturn(CM001); 48 | expect(locationRepository.find(STOCKHOLM.unLocode())).andReturn(STOCKHOLM); 49 | handlingEventRepository.store(isA(HandlingEvent.class)); 50 | applicationEvents.cargoWasHandled(isA(HandlingEvent.class)); 51 | 52 | replay(cargoRepository, voyageRepository, handlingEventRepository, locationRepository, applicationEvents); 53 | 54 | service.registerHandlingEvent(new Date(), cargo.trackingId(), CM001.voyageNumber(), STOCKHOLM.unLocode(), HandlingEvent.Type.LOAD); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/se/citerus/dddsample/infrastructure/persistence/hibernate/AbstractRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.infrastructure.persistence.hibernate; 2 | 3 | import org.hibernate.SessionFactory; 4 | import org.hibernate.classic.Session; 5 | import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; 6 | import org.springframework.orm.hibernate3.HibernateTransactionManager; 7 | import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests; 8 | import org.springframework.transaction.support.TransactionTemplate; 9 | import se.citerus.dddsample.application.util.SampleDataGenerator; 10 | import se.citerus.dddsample.domain.model.handling.HandlingEventFactory; 11 | import se.citerus.dddsample.domain.model.handling.HandlingEventRepository; 12 | 13 | import java.lang.reflect.Field; 14 | 15 | public abstract class AbstractRepositoryTest extends AbstractTransactionalDataSourceSpringContextTests { 16 | 17 | SessionFactory sessionFactory; 18 | SimpleJdbcTemplate sjt; 19 | HandlingEventFactory handlingEventFactory; 20 | HandlingEventRepository handlingEventRepository; 21 | 22 | public void setHandlingEventFactory(HandlingEventFactory handlingEventFactory) { 23 | this.handlingEventFactory = handlingEventFactory; 24 | } 25 | 26 | public void setHandlingEventRepository(HandlingEventRepository handlingEventRepository) { 27 | this.handlingEventRepository = handlingEventRepository; 28 | } 29 | 30 | protected AbstractRepositoryTest() { 31 | setAutowireMode(AUTOWIRE_BY_NAME); 32 | setDependencyCheck(false); 33 | } 34 | 35 | public void setSessionFactory(SessionFactory sessionFactory) { 36 | this.sessionFactory = sessionFactory; 37 | transactionManager = new HibernateTransactionManager(sessionFactory); 38 | } 39 | 40 | public SessionFactory getSessionFactory() { 41 | return sessionFactory; 42 | } 43 | 44 | protected void flush() { 45 | sessionFactory.getCurrentSession().flush(); 46 | } 47 | 48 | @Override 49 | protected String[] getConfigLocations() { 50 | return new String[] {"/context-infrastructure-persistence.xml", "context-domain.xml"}; 51 | } 52 | 53 | @Override 54 | protected void onSetUpInTransaction() throws Exception { 55 | // TODO store Sample* and object instances here instead of handwritten SQL 56 | SampleDataGenerator.loadSampleData(jdbcTemplate, new TransactionTemplate(transactionManager)); 57 | sjt = new SimpleJdbcTemplate(jdbcTemplate); 58 | } 59 | 60 | protected Session getSession() { 61 | return sessionFactory.getCurrentSession(); 62 | } 63 | 64 | // Instead of exposing a getId() on persistent classes 65 | protected Long getLongId(Object o) { 66 | if (getSession().contains(o)) { 67 | return (Long) getSession().getIdentifier(o); 68 | } else { 69 | try { 70 | Field id = o.getClass().getDeclaredField("id"); 71 | id.setAccessible(true); 72 | return (Long) id.get(o); 73 | } catch (Exception e) { 74 | throw new RuntimeException(e); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/se/citerus/dddsample/application/impl/HandlingEventServiceImpl.java: -------------------------------------------------------------------------------- 1 | package se.citerus.dddsample.application.impl; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 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.util.Date; 17 | 18 | public final class HandlingEventServiceImpl implements HandlingEventService { 19 | 20 | private final ApplicationEvents applicationEvents; 21 | private final HandlingEventRepository handlingEventRepository; 22 | private final HandlingEventFactory handlingEventFactory; 23 | private final Log logger = LogFactory.getLog(HandlingEventServiceImpl.class); 24 | 25 | public HandlingEventServiceImpl(final HandlingEventRepository handlingEventRepository, 26 | final ApplicationEvents applicationEvents, 27 | final HandlingEventFactory handlingEventFactory) { 28 | this.handlingEventRepository = handlingEventRepository; 29 | this.applicationEvents = applicationEvents; 30 | this.handlingEventFactory = handlingEventFactory; 31 | } 32 | 33 | @Override 34 | @Transactional(rollbackFor = CannotCreateHandlingEventException.class) 35 | public void registerHandlingEvent(final Date completionTime, 36 | final TrackingId trackingId, 37 | final VoyageNumber voyageNumber, 38 | final UnLocode unLocode, 39 | final HandlingEvent.Type type) throws CannotCreateHandlingEventException { 40 | final Date registrationTime = new Date(); 41 | /* Using a factory to create a HandlingEvent (aggregate). This is where 42 | it is determined wether the incoming data, the attempt, actually is capable 43 | of representing a real handling event. */ 44 | final HandlingEvent event = handlingEventFactory.createHandlingEvent( 45 | registrationTime, completionTime, trackingId, voyageNumber, unLocode, type 46 | ); 47 | 48 | /* Store the new handling event, which updates the persistent 49 | state of the handling event aggregate (but not the cargo aggregate - 50 | that happens asynchronously!) 51 | */ 52 | handlingEventRepository.store(event); 53 | 54 | /* Publish an event stating that a cargo has been handled. */ 55 | applicationEvents.cargoWasHandled(event); 56 | 57 | logger.info("Registered handling event"); 58 | } 59 | 60 | } 61 | --------------------------------------------------------------------------------