/* * Copyright 2011 Marc Grue. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. * * See the License for the specific language governing permissions and * limitations under the License. */ package org.qi4j.sample.dcicargo.sample_a.context.shipping.booking; import java.util.Date; import java.util.List; import org.joda.time.DateMidnight; import org.qi4j.api.common.Optional; import org.qi4j.api.injection.scope.Service; import org.qi4j.api.injection.scope.This; import org.qi4j.api.mixin.Mixins; import org.qi4j.api.value.ValueBuilder; import org.qi4j.api.value.ValueBuilderFactory; import org.qi4j.sample.dcicargo.sample_a.context.support.FoundNoRoutesException; import org.qi4j.sample.dcicargo.sample_a.context.support.RoutingService; import org.qi4j.sample.dcicargo.sample_a.data.entity.CargoEntity; import org.qi4j.sample.dcicargo.sample_a.data.entity.CargosEntity; import org.qi4j.sample.dcicargo.sample_a.data.entity.LocationEntity; import org.qi4j.sample.dcicargo.sample_a.data.shipping.cargo.Cargo; import org.qi4j.sample.dcicargo.sample_a.data.shipping.cargo.Cargos; import org.qi4j.sample.dcicargo.sample_a.data.shipping.cargo.RouteSpecification; import org.qi4j.sample.dcicargo.sample_a.data.shipping.cargo.TrackingId; import org.qi4j.sample.dcicargo.sample_a.data.shipping.delivery.Delivery; import org.qi4j.sample.dcicargo.sample_a.data.shipping.itinerary.Itinerary; import org.qi4j.sample.dcicargo.sample_a.data.shipping.location.Location; import org.qi4j.sample.dcicargo.sample_a.infrastructure.dci.Context; import org.qi4j.sample.dcicargo.sample_a.infrastructure.dci.RoleMixin; /** * Book New Cargo use case */ public class BookNewCargo extends Context { // ROLES --------------------------------------------------------------------- // Methodful Roles private CargoFactoryRole cargoFactory; private RoutingFacadeRole routingFacade; // Methodless Roles private Location origin; private Location destination; private Date arrivalDeadline; private Itinerary itinerary; // CONTEXT CONSTRUCTORS ------------------------------------------------------ public BookNewCargo( Cargos cargos, Location origin, Location destination, Date arrivalDeadline ) throws Exception { cargoFactory = rolePlayer( CargoFactoryRole.class, cargos ); this.origin = origin; this.destination = destination; this.arrivalDeadline = arrivalDeadline; } public BookNewCargo( Cargo cargo ) { routingFacade = rolePlayer( RoutingFacadeRole.class, cargo ); } public BookNewCargo( Cargo cargo, Itinerary itinerary ) { routingFacade = rolePlayer( RoutingFacadeRole.class, cargo ); this.itinerary = itinerary; } // Constructor proxies for communication layer public BookNewCargo( String originId, String destinationId, Date deadline ) throws Exception { this( loadEntity( CargosEntity.class, CargosEntity.CARGOS_ID ), loadEntity( Location.class, originId ), loadEntity( Location.class, destinationId ), deadline ); } public BookNewCargo( String trackingIdString ) { this( loadEntity( CargoEntity.class, trackingIdString ) ); } public BookNewCargo( String trackingIdString, Itinerary itinerary ) { this( loadEntity( Cargo.class, trackingIdString ), itinerary ); } // INTERACTIONS -------------------------------------------------------------- public TrackingId book() { return cargoFactory.createCargo( null ); } public TrackingId createCargo( String trackingIdString ) { return cargoFactory.createCargo( trackingIdString ); } public void changeDestination( String destination ) { routingFacade.changeDestination( loadEntity( LocationEntity.class, destination ) ); } public List<Itinerary> routeCandidates() throws FoundNoRoutesException { return routingFacade.routeCandidates(); } public void assignCargoToRoute() { routingFacade.assignCargoToRoute(); } // METHODFUL ROLE IMPLEMENTATIONS -------------------------------------------- @Mixins( CargoFactoryRole.Mixin.class ) public interface CargoFactoryRole { void setContext( BookNewCargo context ); TrackingId createCargo( @Optional String trackingIdString ); class Mixin extends RoleMixin<BookNewCargo> implements CargoFactoryRole { @This Cargos cargos; public TrackingId createCargo( String trackingIdString ) { // New route specification RouteSpecification routeSpec = context.buildRouteSpecification( vbf, context.origin, context.destination, context.arrivalDeadline ); // Build delivery snapshot from route specification Delivery delivery = new BuildDeliverySnapshot( routeSpec ).get(); // Create cargo Cargo cargo = cargos.createCargo( routeSpec, delivery, trackingIdString ); return cargo.trackingId().get(); } } } @Mixins( RoutingFacadeRole.Mixin.class ) public interface RoutingFacadeRole { void setContext( BookNewCargo context ); List<Itinerary> routeCandidates() throws FoundNoRoutesException; void assignCargoToRoute(); void changeDestination( Location destination ); class Mixin extends RoleMixin<BookNewCargo> implements RoutingFacadeRole { @This Cargo cargo; @Service RoutingService routingService; // Use case step 3 - system calculates possible routes public List<Itinerary> routeCandidates() throws FoundNoRoutesException // Deviation 3a { return routingService.fetchRoutesForSpecification( cargo.routeSpecification().get() ); } public void assignCargoToRoute() { cargo.itinerary().set( context.itinerary ); if( cargo.delivery().get().lastHandlingEvent().get() != null ) { // We treat subsequent route assignments as reroutes of misdirected cargo cargo.delivery().get().lastHandlingEvent().get().wasUnexpected().set( true ); } // Build delivery snapshot with updated itinerary cargo.delivery().set( new BuildDeliverySnapshot( cargo ).get() ); } public void changeDestination( Location newDestination ) { Location currentOrigin = cargo.routeSpecification().get().origin().get(); Date currentDeadline = cargo.routeSpecification().get().arrivalDeadline().get(); RouteSpecification newRouteSpecification = context.buildRouteSpecification( vbf, currentOrigin, newDestination, currentDeadline ); cargo.routeSpecification().set( newRouteSpecification ); // Build new delivery snapshot with updated route specification cargo.delivery().set( new BuildDeliverySnapshot( cargo ).get() ); } } } public RouteSpecification buildRouteSpecification( ValueBuilderFactory vbf, Location origin, Location destination, Date deadline ) { if( origin == destination ) { throw new RuntimeException( "Origin location can't be same as destination location." ); } if( deadline == null ) { throw new RuntimeException( "Arrival deadline cannot be null." ); } Date endOfToday = new DateMidnight().plusDays( 1 ).toDate(); if( deadline.before( endOfToday ) ) { throw new RuntimeException( "Arrival deadline is in the past or Today." + "\nDeadline " + deadline + "\nToday (midnight) " + endOfToday ); } ValueBuilder<RouteSpecification> routeSpec = vbf.newValueBuilder( RouteSpecification.class ); routeSpec.prototype().origin().set( origin ); routeSpec.prototype().destination().set( destination ); routeSpec.prototype().arrivalDeadline().set( deadline ); return routeSpec.newInstance(); } }