Automatically rebuild the state after a crash / restart
[demos/kafka/demos-kafka-payment-system-transfer] / src / main / java / de / juplo / kafka / payment / transfer / adapter / TransferController.java
1 package de.juplo.kafka.payment.transfer.adapter;
2
3
4 import de.juplo.kafka.payment.transfer.domain.Transfer;
5 import de.juplo.kafka.payment.transfer.ports.GetTransferUseCase;
6 import de.juplo.kafka.payment.transfer.ports.MessagingService;
7 import lombok.RequiredArgsConstructor;
8 import lombok.extern.slf4j.Slf4j;
9 import org.springframework.http.HttpStatus;
10 import org.springframework.http.MediaType;
11 import org.springframework.http.ResponseEntity;
12 import org.springframework.validation.FieldError;
13 import org.springframework.web.bind.MethodArgumentNotValidException;
14 import org.springframework.web.bind.annotation.*;
15 import org.springframework.web.context.request.async.DeferredResult;
16
17 import javax.servlet.http.HttpServletRequest;
18 import javax.validation.Valid;
19 import java.net.URI;
20 import java.util.Date;
21 import java.util.HashMap;
22 import java.util.Map;
23 import java.util.Optional;
24 import java.util.concurrent.CompletableFuture;
25
26
27 @RequestMapping(TransferController.PATH)
28 @ResponseBody
29 @RequiredArgsConstructor
30 @Slf4j
31  public class TransferController
32 {
33   public final static String PATH = "/transfers";
34
35   private final GetTransferUseCase getTransferUseCase;
36   private final MessagingService messagingService;
37
38
39   @PostMapping(
40       path = "",
41       consumes = MediaType.APPLICATION_JSON_VALUE,
42       produces = MediaType.APPLICATION_JSON_VALUE)
43   public DeferredResult<ResponseEntity<?>> transfer(
44       HttpServletRequest request,
45       @Valid @RequestBody TransferDTO transferDTO)
46   {
47     DeferredResult<ResponseEntity<?>> result = new DeferredResult<>();
48
49     getTransferUseCase
50         .get(transferDTO.getId())
51         .map(transfer ->
52             CompletableFuture.completedFuture(
53                 ResponseEntity
54                     .ok()
55                     .location(location(transferDTO))
56                     .build()))
57         .or(() ->
58             Optional.of(
59                 messagingService
60                     .send(
61                         Transfer
62                             .builder()
63                             .id(transferDTO.getId())
64                             .payer(transferDTO.getPayer())
65                             .payee(transferDTO.getPayee())
66                             .amount(transferDTO.getAmount())
67                             .build())
68                     .thenApply($ ->
69                         ResponseEntity
70                             .created(location(transferDTO))
71                             .build())))
72         .get()
73         .thenAccept(responseEntity -> result.setResult(responseEntity))
74         .exceptionally(e ->
75         {
76           result.setErrorResult(e);
77           return null;
78         });
79
80     return result;
81   }
82
83   private URI location(TransferDTO transferDTO)
84   {
85     return URI.create(PATH + "/" + transferDTO.getId());
86   }
87
88   @GetMapping(
89       path = "/{id}",
90       produces = MediaType.APPLICATION_JSON_VALUE)
91   public ResponseEntity<TransferDTO> get(@PathVariable Long id)
92   {
93     return
94         getTransferUseCase
95             .get(id)
96             .map(transfer -> ResponseEntity.ok(TransferDTO.of(transfer)))
97             .orElse(ResponseEntity.notFound().build());
98   }
99
100   @ResponseStatus(HttpStatus.BAD_REQUEST)
101   @ExceptionHandler(MethodArgumentNotValidException.class)
102   public Map<String, Object> handleValidationExceptions(
103       HttpServletRequest request,
104       MethodArgumentNotValidException e)
105   {
106     Map<String, Object> errorAttributes = new HashMap<>();
107     errorAttributes.put("status", HttpStatus.BAD_REQUEST.value());
108     errorAttributes.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
109     errorAttributes.put("path", request.getRequestURI());
110     errorAttributes.put("method", request.getMethod());
111     errorAttributes.put("timestamp", new Date());
112     Map<String, String> errors = new HashMap<>();
113     e.getBindingResult().getAllErrors().forEach((error) -> {
114       String fieldName = ((FieldError) error).getField();
115       String errorMessage = error.getDefaultMessage();
116       errors.put(fieldName, errorMessage);
117     });
118     errorAttributes.put("errors", errors);
119     errorAttributes.put("message", "Validation failed: Invalid message format, error count: " + errors.size());
120     return errorAttributes;
121   }
122 }