From 240ff2da3a8b1dfe86afe0bf4bfe3d4427cfb927 Mon Sep 17 00:00:00 2001 From: Kenny Bastani Date: Wed, 11 Jan 2017 22:14:45 -0800 Subject: [PATCH] Release inventory --- .gitignore | 1 + .../java/demo/config/StateMachineConfig.java | 22 +++++- .../reservation/action/ReleaseInventory.java | 76 +++++++++++++++++++ .../controller/ReservationController.java | 7 ++ .../demo/reservation/domain/Reservation.java | 8 ++ .../config/InventoryStateMachineConfig.java | 25 +++++- .../inventory/function/InventoryReleased.java | 32 ++++++++ 7 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 warehouse/warehouse-web/src/main/java/demo/reservation/action/ReleaseInventory.java create mode 100644 warehouse/warehouse-worker/src/main/java/demo/inventory/function/InventoryReleased.java diff --git a/.gitignore b/.gitignore index 8351106..c33d7f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.class +dump.rdb # Mobile Tools for Java (J2ME) .mtj.tmp/ diff --git a/order/order-worker/src/main/java/demo/config/StateMachineConfig.java b/order/order-worker/src/main/java/demo/config/StateMachineConfig.java index 53d0269..dcc1a7e 100644 --- a/order/order-worker/src/main/java/demo/config/StateMachineConfig.java +++ b/order/order-worker/src/main/java/demo/config/StateMachineConfig.java @@ -8,6 +8,9 @@ import demo.order.event.OrderEventProcessor; import demo.order.event.OrderEventType; import demo.order.event.OrderEvents; import demo.payment.domain.Payment; +import demo.reservation.domain.Reservation; +import demo.reservation.domain.ReservationStatus; +import demo.reservation.domain.Reservations; import org.apache.log4j.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -329,7 +332,7 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter reservationFailed() { return context -> applyEvent(context, - new ReservationSucceeded(context, event -> { + new ReservationFailed(context, event -> { log.info(event.getType() + ": " + event.getLink("order").getHref()); // Get the order resource for the event Traverson traverson = new Traverson( @@ -337,6 +340,22 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter r.getStatus() == ReservationStatus.RESERVATION_SUCCEEDED) + .forEach(r -> { + Traverson res = new Traverson( + URI.create(r.getLink("self").getHref()), + MediaTypes.HAL_JSON + ); + + res.follow("self", "commands", "releaseInventory") + .toObject(Reservation.class); + }); + return traverson.follow("self") .toEntity(Order.class) .getBody(); @@ -360,6 +379,5 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter { + private final Logger log = Logger.getLogger(this.getClass()); + private final InventoryService inventoryService; + + public ReleaseInventory(InventoryService inventoryService) { + this.inventoryService = inventoryService; + } + + public Function getFunction() { + return (reservation) -> { + Assert.isTrue(reservation.getStatus() == ReservationStatus.RESERVATION_SUCCEEDED, + "Reservation must be in a succeeded state"); + Assert.notNull(reservation.getInventory(), "The reservation has no connected inventory"); + + ReservationService reservationService = reservation.getModule(ReservationModule.class).getDefaultService(); + + Inventory inventory = reservation.getInventory(); + + try { + // Remove the inventory and set the reservation to failed + reservation.setInventory(null); + reservation.setStatus(ReservationStatus.RESERVATION_FAILED); + reservation = reservationService.update(reservation); + + // Trigger the reservation failed event + reservation.sendAsyncEvent(new ReservationEvent(RESERVATION_FAILED, reservation)); + } catch (Exception ex) { + log.error("Could not release the reservation's inventory", ex); + if (reservation.getStatus() == ReservationStatus.RESERVATION_FAILED) { + // Rollback the attempt + reservation.setInventory(inventory); + reservation.setStatus(ReservationStatus.RESERVATION_SUCCEEDED); + reservation = reservationService.update(reservation); + } + throw ex; + } finally { + // Release the inventory + inventory.setReservation(null); + inventory.setStatus(InventoryStatus.RESERVATION_PENDING); + inventory = inventoryService.update(inventory); + + // Trigger the inventory released event + inventory.sendAsyncEvent(new InventoryEvent(INVENTORY_RELEASED, inventory)); + } + + return reservation; + }; + } +} diff --git a/warehouse/warehouse-web/src/main/java/demo/reservation/controller/ReservationController.java b/warehouse/warehouse-web/src/main/java/demo/reservation/controller/ReservationController.java index 668b88d..1fd2ece 100644 --- a/warehouse/warehouse-web/src/main/java/demo/reservation/controller/ReservationController.java +++ b/warehouse/warehouse-web/src/main/java/demo/reservation/controller/ReservationController.java @@ -101,6 +101,13 @@ public class ReservationController { .orElseThrow(() -> new RuntimeException("The command could not be applied")); } + @RequestMapping(path = "/reservations/{id}/commands/releaseInventory") + public ResponseEntity releaseInventory(@PathVariable Long id) { + return Optional.ofNullable(reservationService.get(id)) + .map(e -> new ResponseEntity<>(getReservationResource(e.releaseInventory()), HttpStatus.OK)) + .orElseThrow(() -> new RuntimeException("The command could not be applied")); + } + @RequestMapping(path = "/reservations/{id}/commands/connectOrder") public ResponseEntity connectOrder(@PathVariable Long id, @RequestParam(value = "orderId") Long orderId) { return Optional.ofNullable(reservationService.get(id) diff --git a/warehouse/warehouse-web/src/main/java/demo/reservation/domain/Reservation.java b/warehouse/warehouse-web/src/main/java/demo/reservation/domain/Reservation.java index b9ed899..3fc2942 100644 --- a/warehouse/warehouse-web/src/main/java/demo/reservation/domain/Reservation.java +++ b/warehouse/warehouse-web/src/main/java/demo/reservation/domain/Reservation.java @@ -9,6 +9,7 @@ import demo.domain.Module; import demo.inventory.domain.Inventory; import demo.reservation.action.ConnectOrder; import demo.reservation.action.ConnectInventory; +import demo.reservation.action.ReleaseInventory; import demo.reservation.controller.ReservationController; import demo.reservation.event.ReservationEvent; import demo.warehouse.domain.Warehouse; @@ -114,6 +115,13 @@ public class Reservation extends AbstractEntity { .apply(this); } + @Command(method = "releaseInventory", controller = ReservationController.class) + public Reservation releaseInventory() { + return getAction(ReleaseInventory.class) + .getFunction() + .apply(this); + } + @Command(method = "connectOrder", controller = ReservationController.class) public Reservation connectOrder(Long orderId) { return getAction(ConnectOrder.class) diff --git a/warehouse/warehouse-worker/src/main/java/demo/inventory/config/InventoryStateMachineConfig.java b/warehouse/warehouse-worker/src/main/java/demo/inventory/config/InventoryStateMachineConfig.java index a1b6905..fe6240c 100644 --- a/warehouse/warehouse-worker/src/main/java/demo/inventory/config/InventoryStateMachineConfig.java +++ b/warehouse/warehouse-worker/src/main/java/demo/inventory/config/InventoryStateMachineConfig.java @@ -114,7 +114,13 @@ public class InventoryStateMachineConfig extends EnumStateMachineConfigurerAdapt .source(InventoryStatus.RESERVATION_CONNECTED) .target(InventoryStatus.INVENTORY_RESERVED) .event(InventoryEventType.INVENTORY_RESERVED) - .action(inventoryReserved()); + .action(inventoryReserved()) + .and() + .withExternal() + .source(InventoryStatus.INVENTORY_RESERVED) + .target(InventoryStatus.RESERVATION_PENDING) + .event(InventoryEventType.INVENTORY_RELEASED) + .action(inventoryReleased()); } catch (Exception e) { throw new RuntimeException("Could not configure state machine transitions", e); } @@ -173,5 +179,22 @@ public class InventoryStateMachineConfig extends EnumStateMachineConfigurerAdapt .getBody(); })); } + + @Bean + public Action inventoryReleased() { + return context -> applyEvent(context, + new InventoryReleased(context, event -> { + log.info(event.getType() + ": " + event.getLink("inventory").getHref()); + // Get the inventory resource for the event + Traverson traverson = new Traverson( + URI.create(event.getLink("inventory").getHref()), + MediaTypes.HAL_JSON + ); + + return traverson.follow("self") + .toEntity(Inventory.class) + .getBody(); + })); + } } diff --git a/warehouse/warehouse-worker/src/main/java/demo/inventory/function/InventoryReleased.java b/warehouse/warehouse-worker/src/main/java/demo/inventory/function/InventoryReleased.java new file mode 100644 index 0000000..9517fc6 --- /dev/null +++ b/warehouse/warehouse-worker/src/main/java/demo/inventory/function/InventoryReleased.java @@ -0,0 +1,32 @@ +package demo.inventory.function; + +import demo.inventory.domain.Inventory; +import demo.inventory.domain.InventoryStatus; +import demo.inventory.event.InventoryEvent; +import demo.inventory.event.InventoryEventType; +import org.apache.log4j.Logger; +import org.springframework.statemachine.StateContext; + +import java.util.function.Function; + +public class InventoryReleased extends InventoryFunction { + + final private Logger log = Logger.getLogger(InventoryReleased.class); + + public InventoryReleased(StateContext context, Function lambda) { + super(context, lambda); + } + + /** + * Apply an {@link InventoryEvent} to the lambda function that was provided through the + * constructor of this {@link InventoryFunction}. + * + * @param event is the {@link InventoryEvent} to apply to the lambda function + */ + @Override + public Inventory apply(InventoryEvent event) { + log.info("Executing workflow for inventory released..."); + return super.apply(event); + } +}