| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.samples.cargo.app1.model.cargo; |
| |
| import org.qi4j.api.common.Optional; |
| import org.qi4j.api.entity.EntityComposite; |
| import org.qi4j.api.entity.association.Association; |
| import org.qi4j.api.injection.scope.Service; |
| import org.qi4j.api.injection.scope.Structure; |
| import org.qi4j.api.injection.scope.This; |
| import org.qi4j.api.mixin.Mixins; |
| import org.qi4j.api.property.Property; |
| import org.qi4j.api.value.ValueBuilder; |
| import org.qi4j.api.value.ValueBuilderFactory; |
| import org.qi4j.samples.cargo.app1.model.handling.HandlingHistory; |
| import org.qi4j.samples.cargo.app1.model.location.Location; |
| import org.qi4j.samples.cargo.app1.system.factories.DeliveryFactory; |
| |
| /** |
| * A Cargo. This is the central class in the domain model, |
| * and it is the root of the Cargo-Itinerary-Leg-Delivery-RouteSpecification aggregate. |
| * <p> |
| * A cargo is identified by a unique tracking id, and it always has an origin |
| * and a route specification. The life cycle of a cargo begins with the booking procedure, |
| * when the tracking id is assigned. During a (short) period of time, between booking |
| * and initial routing, the cargo has no itinerary. |
| * </p> |
| * <p> |
| * The booking clerk requests a list of possible routes, matching the route specification, |
| * and assigns the cargo to one route. The route to which a cargo is assigned is described |
| * by an itinerary. |
| * </p> |
| * <p> |
| * A cargo can be re-routed during transport, on demand of the customer, in which case |
| * a new route is specified for the cargo and a new route is requested. The old itinerary, |
| * being a value object, is discarded and a new one is attached. |
| * </p> |
| * <p> |
| * It may also happen that a cargo is accidentally misrouted, which should notify the proper |
| * personnel and also trigger a re-routing procedure. |
| * </p> |
| * <p> |
| * When a cargo is handled, the status of the delivery changes. Everything about the delivery |
| * of the cargo is contained in the Delivery value object, which is replaced whenever a cargo |
| * is handled by an asynchronous event triggered by the registration of the handling event. |
| * </p> |
| * <p> |
| * The delivery can also be affected by routing changes, i.e. when a the route specification |
| * changes, or the cargo is assigned to a new route. In that case, the delivery update is performed |
| * synchronously within the cargo aggregate. |
| * </p> |
| * <p> |
| * The life cycle of a cargo ends when the cargo is claimed by the customer. |
| * </p> |
| * <p> |
| * The cargo aggregate, and the entre domain model, is built to solve the problem |
| * of booking and tracking cargo. All important business rules for determining whether |
| * or not a cargo is misdirected, what the current status of the cargo is (on board carrier, |
| * in port etc), are captured in this aggregate. |
| * </p> |
| */ |
| @Mixins( Cargo.CargoMixin.class ) |
| public interface Cargo |
| extends EntityComposite |
| { |
| |
| TrackingId trackingId(); |
| |
| Location origin(); |
| |
| RouteSpecification routeSpecification(); |
| |
| Itinerary itinerary(); |
| |
| Delivery delivery(); |
| |
| /** |
| * Specifies a new route for this cargo. |
| * |
| * @param routeSpecification route specification. |
| */ |
| void specifyNewRoute( final RouteSpecification routeSpecification ); |
| |
| /** |
| * Attach a new itinerary to this cargo. |
| * |
| * @param itinerary an itinerary. May not be null. |
| */ |
| void assignToRoute( final Itinerary itinerary ); |
| |
| /** |
| * Updates all aspects of the cargo aggregate status |
| * based on the current route specification, itinerary and handling of the cargo. |
| * <p> |
| * When either of those three changes, i.e. when a new route is specified for the cargo, |
| * the cargo is assigned to a route or when the cargo is handled, the status must be |
| * re-calculated. |
| * </p> |
| * <p> |
| * {@link RouteSpecification} and {@link Itinerary} are both inside the Cargo |
| * aggregate, so changes to them cause the status to be updated <b>synchronously</b>, |
| * but changes to the delivery history (when a cargo is handled) cause the status update |
| * to happen <b>asynchronously</b> since {@link org.qi4j.samples.cargo.app1.model.handling.HandlingEvent} |
| * is in a different aggregate. |
| * </p> |
| * |
| * @param handlingHistory handling history |
| */ |
| void deriveDeliveryProgress( final HandlingHistory handlingHistory ); |
| |
| interface State |
| { |
| Association<Location> origin(); |
| |
| Property<RouteSpecification> routeSpecification(); |
| |
| @Optional |
| Property<Itinerary> itinerary(); |
| |
| @Optional |
| Property<Delivery> delivery(); |
| } |
| |
| public abstract class CargoMixin |
| implements Cargo |
| { |
| |
| public static final String TRACKING_ID_PREFIX = "TrackingId:"; |
| |
| @This |
| private State state; |
| |
| @Service |
| private DeliveryFactory deliveryFactory; |
| |
| @Structure |
| private ValueBuilderFactory vbf; |
| |
| private TrackingId trackingId; |
| |
| public TrackingId trackingId() |
| { |
| if( trackingId == null ) |
| { |
| final String identity = identity().get(); |
| String id = identity.substring( TRACKING_ID_PREFIX.length() ); |
| final ValueBuilder<TrackingId> builder = vbf.newValueBuilder( TrackingId.class ); |
| builder.prototype().id().set( id ); |
| trackingId = builder.newInstance(); |
| } |
| return trackingId; |
| } |
| |
| public Location origin() |
| { |
| return state.origin().get(); |
| } |
| |
| public RouteSpecification routeSpecification() |
| { |
| return state.routeSpecification().get(); |
| } |
| |
| public Itinerary itinerary() |
| { |
| return state.itinerary().get(); |
| } |
| |
| public Delivery delivery() |
| { |
| return state.delivery().get(); |
| } |
| |
| public void specifyNewRoute( final RouteSpecification routeSpecification ) |
| { |
| state.routeSpecification().set( routeSpecification ); |
| // Handling consistency within the Cargo aggregate synchronously |
| state.delivery().set( delivery().updateOnRouting( routeSpecification(), itinerary() ) ); |
| } |
| |
| public void assignToRoute( final Itinerary itinerary ) |
| { |
| state.itinerary().set( itinerary ); |
| // Handling consistency within the Cargo aggregate synchronously |
| state.delivery().set( delivery().updateOnRouting( routeSpecification(), itinerary() ) ); |
| } |
| |
| /** |
| * Updates all aspects of the cargo aggregate status |
| * based on the current route specification, itinerary and handling of the cargo. |
| * <p> |
| * When either of those three changes, i.e. when a new route is specified for the cargo, |
| * the cargo is assigned to a route or when the cargo is handled, the status must be |
| * re-calculated. |
| * </p> |
| * <p> |
| * {@link RouteSpecification} and {@link Itinerary} are both inside the Cargo |
| * aggregate, so changes to them cause the status to be updated <b>synchronously</b>, |
| * but changes to the delivery history (when a cargo is handled) cause the status update |
| * to happen <b>asynchronously</b> since |
| * {@link org.qi4j.samples.cargo.app1.model.handling.HandlingEvent} is in a different aggregate. |
| * </p> |
| * @param handlingHistory handling history |
| */ |
| public void deriveDeliveryProgress( final HandlingHistory handlingHistory ) |
| { |
| // TODO filter events on cargo (must be same as this cargo) |
| |
| // Delivery is a value object, so we can simply discard the old one |
| // and replace it with a new |
| state.delivery().set( deliveryFactory.derivedFrom( routeSpecification(), itinerary(), handlingHistory ) ); |
| } |
| } |
| } |