From b5e891afed10e912fc781bf36b977defe981993d Mon Sep 17 00:00:00 2001 From: Vusumuzi Khoza Date: Sun, 26 Oct 2025 23:14:24 +0200 Subject: [PATCH] improving workflow fault tolerance --- pom.xml | 1 - .../qantra/tm/configs/NflowConfiguration.java | 79 ------------------- .../domain/controllers/ConfigController.java | 4 + .../controllers/OnboardingController.java | 1 + .../tm/domain/controllers/TestController.java | 17 +++- .../controllers/TransactonController.java | 16 +++- .../qantra/tm/domain/models/BaseEntity.java | 3 +- .../qantra/tm/domain/models/Transaction.java | 4 +- .../repositories/TransactionRepository.java | 1 + .../domain/services/TransactionService.java | 4 +- .../EconetConfirmationProcessor.java | 3 +- .../handlers/RecipientsUpdateHandler.java | 55 ------------- .../handlers/ValidationHandler.java | 11 --- .../handlers/erp/SalesInvoiceHandler.java | 0 .../domain/workflows/TransactionWorkflow.java | 22 ++++-- .../workflows/handlers/ConfirmHandler.java | 18 ++--- .../handlers/IntegrationHandler.java | 1 - src/main/resources/application.properties | 1 - 18 files changed, 63 insertions(+), 178 deletions(-) delete mode 100644 src/main/java/zw/qantra/tm/configs/NflowConfiguration.java delete mode 100644 src/main/java/zw/qantra/tm/domain/services/processors/handlers/RecipientsUpdateHandler.java delete mode 100644 src/main/java/zw/qantra/tm/domain/services/processors/handlers/ValidationHandler.java delete mode 100644 src/main/java/zw/qantra/tm/domain/services/processors/handlers/erp/SalesInvoiceHandler.java diff --git a/pom.xml b/pom.xml index 08a2359..df0c150 100644 --- a/pom.xml +++ b/pom.xml @@ -169,7 +169,6 @@ spring-boot-starter-mail 3.1.5 - diff --git a/src/main/java/zw/qantra/tm/configs/NflowConfiguration.java b/src/main/java/zw/qantra/tm/configs/NflowConfiguration.java deleted file mode 100644 index 08d9ab0..0000000 --- a/src/main/java/zw/qantra/tm/configs/NflowConfiguration.java +++ /dev/null @@ -1,79 +0,0 @@ -package zw.qantra.tm.configs; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.joda.JodaModule; -import com.fasterxml.jackson.datatype.jsr310.JSR310Module; -import io.nflow.engine.config.EngineConfiguration; -import io.nflow.engine.config.NFlow; -import org.springframework.context.annotation.Bean; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; - -import java.util.List; - -@Configuration -public class NflowConfiguration { - - /** - * Creates the primary, application-wide ObjectMapper. - *

- * This bean is marked as {@code @Primary} to ensure it's the default for dependency injection - * throughout the application. It's configured using Spring Boot's {@link Jackson2ObjectMapperBuilder}, - * which automatically registers modules for Java 8 Time, Optional, etc., if they are on the classpath. - * It also includes any custom {@link Module} beans defined in the application context. - *

- * - * @param builder The default Jackson2ObjectMapperBuilder, pre-configured by Spring Boot. - * @param customModules A list of all custom Jackson {@link Module} beans found in the application context. - * @return A customized {@link com.fasterxml.jackson.databind.ObjectMapper}. - */ - @Primary - @Bean - public ObjectMapper primaryObjectMapper(Jackson2ObjectMapperBuilder builder, List customModules) { - builder.modules(customModules); - builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - return builder.build(); - } - - /** - * Overrides the default ObjectMapper used by the nFlow REST API. - *

- * This bean definition ensures that the nFlow REST endpoints use the same consistent, - * primary ObjectMapper as the rest of the application. - *

- */ - @Bean("nflowRestObjectMapper") - public ObjectMapper nflowRestObjectMapper(ObjectMapper primaryObjectMapper) { - return primaryObjectMapper; - } - - /** - * Overrides the default ObjectMapper used by the nFlow Engine for variable serialization. - *

- * This is the key fix for `InvalidDefinitionException` with `LocalDateTime` in workflow variables. - * It ensures the engine uses our fully configured primary ObjectMapper. - *

- */ - @Bean("nflowEngineObjectMapper") - public ObjectMapper nflowEngineObjectMapper(ObjectMapper primaryObjectMapper) { - return primaryObjectMapper; - } - - @Bean - @NFlow - public EngineConfiguration.EngineObjectMapperSupplier nflowObjectMapper() { - ObjectMapper mapper = new ObjectMapper(); - mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); - mapper.registerModule(new JodaModule()); - mapper.registerModule(new Jdk8Module()); - mapper.registerModule(new JSR310Module()); - mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - return () -> mapper; - } - -} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/controllers/ConfigController.java b/src/main/java/zw/qantra/tm/domain/controllers/ConfigController.java index 5e79c31..791ad77 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/ConfigController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/ConfigController.java @@ -107,6 +107,7 @@ public class ConfigController { .externalId("78f1f497-c9eb-401c-b22c-884756e68e40") .integrationProcessorLabel("STEWARD") .accountFieldName("Phone Number") + .percentageCommission(6) .build(), Provider.builder() .description("NetOne Airtime") @@ -117,6 +118,7 @@ public class ConfigController { .externalId("95d485a7-39c9-4559-8a27-6521969abe8f") .integrationProcessorLabel("STEWARD") .accountFieldName("Phone Number") + .percentageCommission(5.5) .build(), Provider.builder() .description("Prepaid Zesa") @@ -127,6 +129,7 @@ public class ConfigController { .externalId("0f67f7cc-b62b-4fb5-915f-6740b2afdba6") .integrationProcessorLabel("STEWARD") .accountFieldName("Meter Number") + .percentageCommission(1.5) .build(), Provider.builder() .description("Econet Broadband") @@ -137,6 +140,7 @@ public class ConfigController { .externalId("287863e2-a791-4106-b629-79dadc9a6779") .integrationProcessorLabel("STEWARD") .accountFieldName("Phone Number") + .percentageCommission(6) .build() ); providerService.getProviderRepository().saveAll(providers); diff --git a/src/main/java/zw/qantra/tm/domain/controllers/OnboardingController.java b/src/main/java/zw/qantra/tm/domain/controllers/OnboardingController.java index 404ecfd..e92cdef 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/OnboardingController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/OnboardingController.java @@ -65,6 +65,7 @@ public class OnboardingController { var instance = workflowInstanceFactory.newWorkflowInstanceBuilder() .setType(RegistrationWorkflow.TYPE) .setExternalId(request.getUsername() + "-" + new Instant().getMillis()) + .setBusinessKey(request.getUsername()) .putStateVariable("body", request) .setNextActivation(DateTime.now()) .build(); diff --git a/src/main/java/zw/qantra/tm/domain/controllers/TestController.java b/src/main/java/zw/qantra/tm/domain/controllers/TestController.java index feb067f..5e21db3 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/TestController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/TestController.java @@ -1,18 +1,31 @@ package zw.qantra.tm.domain.controllers; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; +import zw.qantra.tm.domain.enums.RequestType; +import zw.qantra.tm.domain.models.Transaction; import zw.qantra.tm.domain.services.EmailService; +import zw.qantra.tm.domain.services.TransactionService; @RestController @RequestMapping("/test") @CrossOrigin(origins = "*") +@RequiredArgsConstructor public class TestController { - @Autowired - private EmailService emailService; + private final EmailService emailService; + private final TransactionService transactionService; + + @PostMapping("/dbtest") + public ResponseEntity dbtest(@RequestBody Transaction transaction) { + transactionService.save(transaction); + transaction.setType(RequestType.REQUEST); + transactionService.save(transaction); + return ResponseEntity.ok(transaction); + } @GetMapping("/hello/{email}") public ResponseEntity helloEndpoint(@PathVariable String email) { diff --git a/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java b/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java index 5e99552..79178a6 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java @@ -1,6 +1,9 @@ package zw.qantra.tm.domain.controllers; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.Phonenumber; import io.nflow.engine.internal.workflow.ObjectStringMapper; import io.nflow.engine.service.WorkflowInstanceService; import io.nflow.engine.workflow.instance.WorkflowInstance; @@ -176,12 +179,21 @@ public class TransactonController { @PostMapping - public ResponseEntity confirm(@RequestBody Transaction transaction) throws ExecutionException, InterruptedException { + public ResponseEntity confirm(@RequestBody Transaction transaction) + throws ExecutionException, InterruptedException, NumberParseException { LogUtils.logPrettyJson(logger, "incoming transaction", transaction); + + // phone must begin with '+' + // need to format the debit phone number since it's the business key + PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); + Phonenumber.PhoneNumber number = phoneUtil.parse(transaction.getDebitPhone(), ""); + String formattedPhoneNumber = phoneUtil.format(number, PhoneNumberUtil.PhoneNumberFormat.E164); + var instance = workflowInstanceFactory.newWorkflowInstanceBuilder() .setType(TransactionWorkflow.TYPE) .setExternalId(transaction.getUserId() + "-" + new Instant().getMillis()) - .putStateVariable("confirmation", transaction) + .setBusinessKey(formattedPhoneNumber) + .putStateVariable("confirmation", Utils.toJson(transaction)) .setNextActivation(DateTime.now()) .build(); diff --git a/src/main/java/zw/qantra/tm/domain/models/BaseEntity.java b/src/main/java/zw/qantra/tm/domain/models/BaseEntity.java index 12f0251..6b5202a 100644 --- a/src/main/java/zw/qantra/tm/domain/models/BaseEntity.java +++ b/src/main/java/zw/qantra/tm/domain/models/BaseEntity.java @@ -40,5 +40,6 @@ public class BaseEntity { @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS") private LocalDateTime updatedAt; // Or Date, Timestamp - private Boolean deleted = false; + @Column(columnDefinition = "BOOLEAN DEFAULT FALSE") + private Boolean deleted; } diff --git a/src/main/java/zw/qantra/tm/domain/models/Transaction.java b/src/main/java/zw/qantra/tm/domain/models/Transaction.java index 580b0ea..ffc204b 100644 --- a/src/main/java/zw/qantra/tm/domain/models/Transaction.java +++ b/src/main/java/zw/qantra/tm/domain/models/Transaction.java @@ -4,12 +4,14 @@ import com.fasterxml.jackson.annotation.JsonInclude; import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.SQLRestriction; +import org.springframework.data.domain.Persistable; import zw.qantra.tm.domain.enums.AuthType; import zw.qantra.tm.domain.enums.CurrencyType; import zw.qantra.tm.domain.enums.RequestType; import zw.qantra.tm.domain.enums.Status; import java.math.BigDecimal; +import java.util.UUID; @Entity @Getter @@ -18,7 +20,6 @@ import java.math.BigDecimal; @AllArgsConstructor @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) -@SQLRestriction("deleted = false") public class Transaction extends BaseEntity { private String userId; private String trace; @@ -101,5 +102,4 @@ public class Transaction extends BaseEntity { @Transient private Object additionalData; - } \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/repositories/TransactionRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/TransactionRepository.java index 3a4be35..7c5b4ff 100644 --- a/src/main/java/zw/qantra/tm/domain/repositories/TransactionRepository.java +++ b/src/main/java/zw/qantra/tm/domain/repositories/TransactionRepository.java @@ -13,4 +13,5 @@ import java.util.UUID; @Repository public interface TransactionRepository extends JpaRepository, JpaSpecificationExecutor { Transaction findByTraceAndType(String trace, RequestType type); + Optional findByReference(String reference); } \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/services/TransactionService.java b/src/main/java/zw/qantra/tm/domain/services/TransactionService.java index 84b0219..a5d3d09 100644 --- a/src/main/java/zw/qantra/tm/domain/services/TransactionService.java +++ b/src/main/java/zw/qantra/tm/domain/services/TransactionService.java @@ -50,7 +50,7 @@ public class TransactionService { return transactionRepository.findAll(spec, pageable); } - public Transaction save(Transaction transaction){ + public Transaction save(Transaction transaction) { return transactionRepository.save(transaction); } -} \ No newline at end of file +} diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/EconetConfirmationProcessor.java b/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/EconetConfirmationProcessor.java index 8716ffc..39a8f5c 100644 --- a/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/EconetConfirmationProcessor.java +++ b/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/EconetConfirmationProcessor.java @@ -18,6 +18,7 @@ import zw.qantra.tm.rest.RestService; import zw.qantra.tm.utils.Utils; import java.util.LinkedHashMap; +import java.util.UUID; @Service("CONFIRM_ECONET") @RequiredArgsConstructor @@ -80,7 +81,7 @@ public class EconetConfirmationProcessor implements TransactionProcessorInterfac transaction.setErrorMessage((String) body.get("errorMessage")); } - return transactionService.save(transaction); + return transaction; } @Override diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/RecipientsUpdateHandler.java b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/RecipientsUpdateHandler.java deleted file mode 100644 index dcabe19..0000000 --- a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/RecipientsUpdateHandler.java +++ /dev/null @@ -1,55 +0,0 @@ -package zw.qantra.tm.domain.services.processors.handlers; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import zw.qantra.tm.domain.models.Transaction; -import zw.qantra.tm.domain.enums.Status; -import zw.qantra.tm.domain.models.Recipient; -import zw.qantra.tm.domain.services.RecipientService; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class RecipientsUpdateHandler implements LogicHandler { - - private final RecipientService recipientService; - - @Override - public Transaction process(Transaction transaction) { - System.out.println("processing recipients"); - - if(transaction.getStatus() == Status.SUCCESS){ - List recipients = recipientService.getRecipient(transaction.getCreditAccount(), transaction.getUserId()); - - Recipient recipient = null; - if(recipients.size() > 0){ - recipient = recipients.get(0); - }else{ - recipient = new Recipient(); - recipient.setAccount(transaction.getCreditAccount()); - recipient.setUserId(transaction.getUserId()); - } - - recipient.setLatestProviderLabel(transaction.getProviderLabel()); - if(transaction.getCreditName() != null && !transaction.getCreditName().isEmpty()){ - recipient.setName(transaction.getCreditName()); - } - if(transaction.getCreditEmail() != null && !transaction.getCreditEmail().isEmpty()){ - recipient.setEmail(transaction.getCreditEmail()); - } - if(transaction.getCreditPhone() != null && !transaction.getCreditPhone().isEmpty()){ - recipient.setPhoneNumber(transaction.getCreditPhone()); - } - - String stringToInitial = transaction.getCreditName() != null && !transaction.getCreditName().isEmpty() - ? transaction.getCreditName() : transaction.getBillName(); - - recipient.setInitials(stringToInitial.substring(0, 2).toUpperCase()); - recipientService.save(recipient); - - } - - return transaction; - } -} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/ValidationHandler.java b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/ValidationHandler.java deleted file mode 100644 index 9336040..0000000 --- a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/ValidationHandler.java +++ /dev/null @@ -1,11 +0,0 @@ -package zw.qantra.tm.domain.services.processors.handlers; - -import zw.qantra.tm.domain.models.Transaction; - -public class ValidationHandler implements LogicHandler { - @Override - public Transaction process(Transaction transaction) { - System.out.println("processing validation"); - return transaction; - } -} diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/erp/SalesInvoiceHandler.java b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/erp/SalesInvoiceHandler.java deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/zw/qantra/tm/domain/workflows/TransactionWorkflow.java b/src/main/java/zw/qantra/tm/domain/workflows/TransactionWorkflow.java index f148ac7..c9b4c1b 100644 --- a/src/main/java/zw/qantra/tm/domain/workflows/TransactionWorkflow.java +++ b/src/main/java/zw/qantra/tm/domain/workflows/TransactionWorkflow.java @@ -74,9 +74,12 @@ public class TransactionWorkflow extends WorkflowDefinition { public NextAction calculateCharges(StateExecution execution) { try { - Transaction transaction = execution.getVariable("confirmation", Transaction.class); + LinkedHashMap map = execution.getVariable("confirmation", LinkedHashMap.class); + byte[] json = objectMapper.writeValueAsBytes(map); + Transaction transaction = objectMapper.readValue(json, Transaction.class); + process(transaction, "calculateCharges"); - execution.setVariable("body", transaction); + execution.setVariable("body", Utils.toJson(transaction)); return NextAction.moveToState(CLEANUP, "Charge calculation complete"); } catch (Exception e) { @@ -87,9 +90,12 @@ public class TransactionWorkflow extends WorkflowDefinition { public NextAction cleanup(StateExecution execution) { try { - Transaction transaction = execution.getVariable("body", Transaction.class); + LinkedHashMap map = execution.getVariable("body", LinkedHashMap.class); + byte[] json = objectMapper.writeValueAsBytes(map); + Transaction transaction = objectMapper.readValue(json, Transaction.class); + process(transaction, "cleanup"); - execution.setVariable("body", transaction); + execution.setVariable("body", Utils.toJson(transaction)); return NextAction.moveToState(CONFIRM, "Cleanup complete"); } catch (Exception e) { @@ -100,7 +106,9 @@ public class TransactionWorkflow extends WorkflowDefinition { public NextAction confirm(StateExecution execution) { try { - Transaction transaction = execution.getVariable("body", Transaction.class); + LinkedHashMap map = execution.getVariable("body", LinkedHashMap.class); + byte[] json = objectMapper.writeValueAsBytes(map); + Transaction transaction = objectMapper.readValue(json, Transaction.class); transaction.setWorkflowId(execution.getWorkflowInstanceId()); transaction = process(transaction, "confirm"); @@ -117,9 +125,7 @@ public class TransactionWorkflow extends WorkflowDefinition { byte[] json = objectMapper.writeValueAsBytes(map); Transaction transaction = objectMapper.readValue(json, Transaction.class); - transaction.setWorkflowId(execution.getWorkflowInstanceId()); - transaction = process(transaction, "recipientsUpdate"); - execution.setVariable("body", Utils.toJson(transaction)); + process(transaction, "recipientsUpdate"); return NextAction.moveToState(CONFIRM_SUCCESS, "Transaction confirmed successfully"); } diff --git a/src/main/java/zw/qantra/tm/domain/workflows/handlers/ConfirmHandler.java b/src/main/java/zw/qantra/tm/domain/workflows/handlers/ConfirmHandler.java index 8c4ab87..cb39a77 100644 --- a/src/main/java/zw/qantra/tm/domain/workflows/handlers/ConfirmHandler.java +++ b/src/main/java/zw/qantra/tm/domain/workflows/handlers/ConfirmHandler.java @@ -17,6 +17,7 @@ import zw.qantra.tm.utils.Utils; import java.util.LinkedHashMap; import java.util.List; +import java.util.UUID; @Service("confirm") @RequiredArgsConstructor @@ -28,8 +29,8 @@ public class ConfirmHandler implements HandlerInterface { @Override public Object process(Transaction transaction) throws Exception { - transaction = transactionService.save(transaction); + transactionService.save(transaction); TransactionEvent transactionEvent = TransactionEvent.builder() .transactionId(transaction.getId().toString()) .event(transaction.getType().name()) @@ -43,7 +44,7 @@ public class ConfirmHandler implements HandlerInterface { try { // MAIN TRANSACTION LOGIC! - transactionProcessorInterface.process(transaction); + transaction = (Transaction) transactionProcessorInterface.process(transaction); transaction.setConfirmationStatus(transaction.getStatus()); }catch (Exception e){ @@ -60,15 +61,8 @@ public class ConfirmHandler implements HandlerInterface { transactionEvent.setMessage(transaction.getErrorMessage()); transactionEventService.save(transactionEvent); - transaction = transactionService.save(transaction); - // fetch additionalData - AdditionalData data = additionalDataService.getAdditionalDataRepository() - .findByTransactionIdAndRequestType( - transaction.getId().toString(), RequestType.CONFIRM).orElse(null); - if(data != null) { - transaction.setAdditionalData(Utils.fromJson(data.getJsonString(), List.class)); - } - - return transaction; + Transaction t1 = Utils.deepCopy(transaction, Transaction.class); + transactionService.save(transaction); + return t1; } } diff --git a/src/main/java/zw/qantra/tm/domain/workflows/handlers/IntegrationHandler.java b/src/main/java/zw/qantra/tm/domain/workflows/handlers/IntegrationHandler.java index 674fe10..1fb302a 100644 --- a/src/main/java/zw/qantra/tm/domain/workflows/handlers/IntegrationHandler.java +++ b/src/main/java/zw/qantra/tm/domain/workflows/handlers/IntegrationHandler.java @@ -13,7 +13,6 @@ import zw.qantra.tm.domain.services.*; import zw.qantra.tm.domain.services.factories.PaymentProcessorFactory; import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface; import zw.qantra.tm.domain.services.processors.handlers.LogicPipeline; -import zw.qantra.tm.domain.services.processors.handlers.ValidationHandler; import zw.qantra.tm.exceptions.ApiException; @Service("integration") diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index bb0365b..93d5e33 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,4 @@ spring.application.name=tm -spring.main.allow-bean-definition-overriding=true debug=false logging.level.org.springframework.boot.autoconfigure=INFO