diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/api/ProductMixApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/api/ProductMixApiResource.java index 6302dd27794..33703272bcf 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/api/ProductMixApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/api/ProductMixApiResource.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.loanproduct.productmix.api; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; @@ -34,17 +35,21 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; +import java.util.function.Supplier; import lombok.RequiredArgsConstructor; -import org.apache.fineract.commands.domain.CommandWrapper; -import org.apache.fineract.commands.service.CommandWrapperBuilder; -import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.command.core.CommandPipeline; import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; +import org.apache.fineract.portfolio.loanproduct.productmix.command.CreateProductMixCommand; +import org.apache.fineract.portfolio.loanproduct.productmix.command.DeleteProductMixCommand; +import org.apache.fineract.portfolio.loanproduct.productmix.command.UpdateProductMixCommand; +import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixCommandRequest; import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixData; +import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixDeleteRequest; import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixRequest; import org.apache.fineract.portfolio.loanproduct.productmix.service.ProductMixReadPlatformService; import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; @@ -62,10 +67,9 @@ public class ProductMixApiResource { Arrays.asList("productId", "productName", "restrictedProducts", "allowedProducts", "productOptions")); private final PlatformSecurityContext context; - private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final CommandPipeline commandPipeline; private final ApiRequestParameterHelper apiRequestParameterHelper; private final DefaultToApiJsonSerializer toApiJsonSerializer; - private final ProductMixReadPlatformService productMixReadPlatformService; private final LoanProductReadPlatformService loanProductReadPlatformService; @@ -89,25 +93,29 @@ public String retrieveTemplate(@PathParam("productId") final Long productId, @Co @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public CommandProcessingResult createProductMix(@PathParam("productId") final Long productId, - final ProductMixRequest productMixRequest) { + public CommandProcessingResult createProductMix(@PathParam("productId") final Long productId, @Valid final ProductMixRequest request) { + + final var payload = new ProductMixCommandRequest(productId, request.restrictedProducts()); - final CommandWrapper commandRequest = new CommandWrapperBuilder().createProductMix(productId) - .withJson(toApiJsonSerializer.serialize(productMixRequest)).build(); + final var command = new CreateProductMixCommand(); + command.setPayload(payload); - return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + final Supplier response = commandPipeline.send(command); + return response.get(); } @PUT @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public CommandProcessingResult updateProductMix(@PathParam("productId") final Long productId, - final ProductMixRequest productMixRequest) { + public CommandProcessingResult updateProductMix(@PathParam("productId") final Long productId, @Valid final ProductMixRequest request) { - final CommandWrapper commandRequest = new CommandWrapperBuilder().updateProductMix(productId) - .withJson(toApiJsonSerializer.serialize(productMixRequest)).build(); + final var payload = new ProductMixCommandRequest(productId, request.restrictedProducts()); - return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + final var command = new UpdateProductMixCommand(); + command.setPayload(payload); + + final Supplier response = commandPipeline.send(command); + return response.get(); } @DELETE @@ -115,9 +123,10 @@ public CommandProcessingResult updateProductMix(@PathParam("productId") final Lo @Produces({ MediaType.APPLICATION_JSON }) public CommandProcessingResult deleteProductMix(@PathParam("productId") final Long productId) { - final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteProductMix(productId).build(); + final var command = new DeleteProductMixCommand(); + command.setPayload(ProductMixDeleteRequest.builder().productId(productId).build()); - return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + final Supplier response = commandPipeline.send(command); + return response.get(); } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/command/CreateProductMixCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/command/CreateProductMixCommand.java new file mode 100644 index 00000000000..3dca0a352f2 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/command/CreateProductMixCommand.java @@ -0,0 +1,28 @@ +/** + * 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.apache.fineract.portfolio.loanproduct.productmix.command; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixCommandRequest; + +@Data +@EqualsAndHashCode(callSuper = true) +public class CreateProductMixCommand extends Command {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/command/DeleteProductMixCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/command/DeleteProductMixCommand.java new file mode 100644 index 00000000000..39de5d93194 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/command/DeleteProductMixCommand.java @@ -0,0 +1,28 @@ +/** + * 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.apache.fineract.portfolio.loanproduct.productmix.command; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixDeleteRequest; + +@Data +@EqualsAndHashCode(callSuper = true) +public class DeleteProductMixCommand extends Command {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/command/UpdateProductMixCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/command/UpdateProductMixCommand.java new file mode 100644 index 00000000000..312c6f33da6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/command/UpdateProductMixCommand.java @@ -0,0 +1,28 @@ +/** + * 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.apache.fineract.portfolio.loanproduct.productmix.command; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixCommandRequest; + +@Data +@EqualsAndHashCode(callSuper = true) +public class UpdateProductMixCommand extends Command {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/data/ProductMixCommandRequest.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/data/ProductMixCommandRequest.java new file mode 100644 index 00000000000..9d2da4b4070 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/data/ProductMixCommandRequest.java @@ -0,0 +1,29 @@ +/** + * 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.apache.fineract.portfolio.loanproduct.productmix.data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +public record ProductMixCommandRequest(Long productId, List restrictedProducts) implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/data/ProductMixDeleteRequest.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/data/ProductMixDeleteRequest.java new file mode 100644 index 00000000000..2d846512e57 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/data/ProductMixDeleteRequest.java @@ -0,0 +1,30 @@ +/* + * 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.apache.fineract.portfolio.loanproduct.productmix.data; + +import java.io.Serial; +import java.io.Serializable; +import lombok.Builder; + +@Builder +public record ProductMixDeleteRequest(Long productId) implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/data/ProductMixRequest.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/data/ProductMixRequest.java index d8858dc4faf..cc79b0622bb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/data/ProductMixRequest.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/data/ProductMixRequest.java @@ -18,11 +18,13 @@ */ package org.apache.fineract.portfolio.loanproduct.productmix.data; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; import java.io.Serial; import java.io.Serializable; import java.util.List; -public record ProductMixRequest(List restrictedProducts) implements Serializable { +public record ProductMixRequest(@NotNull List<@NotNull @Positive Long> restrictedProducts) implements Serializable { @Serial private static final long serialVersionUID = 1L; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/CreateProductMixCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/CreateProductMixCommandHandler.java index 425af7d3548..0d86e656fe5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/CreateProductMixCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/CreateProductMixCommandHandler.java @@ -18,32 +18,33 @@ */ package org.apache.fineract.portfolio.loanproduct.productmix.handler; -import org.apache.fineract.commands.annotation.CommandType; -import org.apache.fineract.commands.handler.NewCommandSourceHandler; -import org.apache.fineract.infrastructure.core.api.JsonCommand; +import io.github.resilience4j.retry.annotation.Retry; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.command.core.CommandHandler; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixCommandRequest; import org.apache.fineract.portfolio.loanproduct.productmix.service.ProductMixWritePlatformService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -@Service -@CommandType(entity = "PRODUCTMIX", action = "CREATE") -public class CreateProductMixCommandHandler implements NewCommandSourceHandler { +@Slf4j +@Component +@RequiredArgsConstructor +public class CreateProductMixCommandHandler implements CommandHandler { - private final ProductMixWritePlatformService productMixWritePlatformService; + private final ProductMixWritePlatformService writePlatformService; - @Autowired - public CreateProductMixCommandHandler(final ProductMixWritePlatformService productMixWritePlatformService) { - - this.productMixWritePlatformService = productMixWritePlatformService; + @Retry(name = "commandCreateProductMix", fallbackMethod = "fallback") + @Override + @Transactional + public CommandProcessingResult handle(Command command) { + return writePlatformService.createProductMix(command.getPayload()); } - @Transactional @Override - public CommandProcessingResult processCommand(final JsonCommand command) { - - return this.productMixWritePlatformService.createProductMix(command.getProductId(), command); + public CommandProcessingResult fallback(Command command, Throwable t) { + return CommandHandler.super.fallback(command, t); } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/DeleteProductMixCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/DeleteProductMixCommandHandler.java index 6fc544e665b..abe90de5f61 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/DeleteProductMixCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/DeleteProductMixCommandHandler.java @@ -18,32 +18,33 @@ */ package org.apache.fineract.portfolio.loanproduct.productmix.handler; -import org.apache.fineract.commands.annotation.CommandType; -import org.apache.fineract.commands.handler.NewCommandSourceHandler; -import org.apache.fineract.infrastructure.core.api.JsonCommand; +import io.github.resilience4j.retry.annotation.Retry; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.command.core.CommandHandler; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixDeleteRequest; import org.apache.fineract.portfolio.loanproduct.productmix.service.ProductMixWritePlatformService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -@Service -@CommandType(entity = "PRODUCTMIX", action = "DELETE") -public class DeleteProductMixCommandHandler implements NewCommandSourceHandler { +@Slf4j +@Component +@RequiredArgsConstructor +public class DeleteProductMixCommandHandler implements CommandHandler { - private final ProductMixWritePlatformService productMixWritePlatformService; + private final ProductMixWritePlatformService writePlatformService; - @Autowired - public DeleteProductMixCommandHandler(final ProductMixWritePlatformService productMixWritePlatformService) { - - this.productMixWritePlatformService = productMixWritePlatformService; + @Retry(name = "commandDeleteProductMix", fallbackMethod = "fallback") + @Override + @Transactional + public CommandProcessingResult handle(Command command) { + return writePlatformService.deleteProductMix(command.getPayload()); } - @Transactional @Override - public CommandProcessingResult processCommand(final JsonCommand command) { - - return this.productMixWritePlatformService.deleteProductMix(command.getProductId()); + public CommandProcessingResult fallback(Command command, Throwable t) { + return CommandHandler.super.fallback(command, t); } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/UpdateProductMixCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/UpdateProductMixCommandHandler.java index 784b4e12265..e78ae1c44ac 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/UpdateProductMixCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/UpdateProductMixCommandHandler.java @@ -18,32 +18,33 @@ */ package org.apache.fineract.portfolio.loanproduct.productmix.handler; -import org.apache.fineract.commands.annotation.CommandType; -import org.apache.fineract.commands.handler.NewCommandSourceHandler; -import org.apache.fineract.infrastructure.core.api.JsonCommand; +import io.github.resilience4j.retry.annotation.Retry; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.command.core.CommandHandler; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixCommandRequest; import org.apache.fineract.portfolio.loanproduct.productmix.service.ProductMixWritePlatformService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -@Service -@CommandType(entity = "PRODUCTMIX", action = "UPDATE") -public class UpdateProductMixCommandHandler implements NewCommandSourceHandler { +@Slf4j +@Component +@RequiredArgsConstructor +public class UpdateProductMixCommandHandler implements CommandHandler { - private final ProductMixWritePlatformService productMixWritePlatformService; + private final ProductMixWritePlatformService writePlatformService; - @Autowired - public UpdateProductMixCommandHandler(final ProductMixWritePlatformService productMixWritePlatformService) { - - this.productMixWritePlatformService = productMixWritePlatformService; + @Retry(name = "commandUpdateProductMix", fallbackMethod = "fallback") + @Override + @Transactional + public CommandProcessingResult handle(Command command) { + return writePlatformService.updateProductMix(command.getPayload()); } - @Transactional @Override - public CommandProcessingResult processCommand(final JsonCommand command) { - - return this.productMixWritePlatformService.updateProductMix(command.getProductId(), command); + public CommandProcessingResult fallback(Command command, Throwable t) { + return CommandHandler.super.fallback(command, t); } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/serialization/ProductMixDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/serialization/ProductMixDataValidator.java index ae3fae2a337..35c99089e5e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/serialization/ProductMixDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/serialization/ProductMixDataValidator.java @@ -18,56 +18,42 @@ */ package org.apache.fineract.portfolio.loanproduct.productmix.serialization; -import com.google.gson.JsonElement; -import com.google.gson.reflect.TypeToken; -import java.lang.reflect.Type; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Set; -import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; -import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; -import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public final class ProductMixDataValidator { - public static final String RESTRICTED_PRODUCTS = "restrictedProducts"; - /** - * The parameters supported for this command. - */ - private static final Set SUPPORTED_PARAMETERS = new HashSet<>(List.of(RESTRICTED_PRODUCTS)); public static final String PRODUCTMIX = "productmix"; + public static final String RESTRICTED_PRODUCTS = "restrictedProducts"; public static final String RESTRICTED_PRODUCT = "restrictedProduct"; - private final FromJsonHelper fromApiJsonHelper; + public void validateRestrictedProductsForCreate(final List restrictedProducts) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(PRODUCTMIX); - @Autowired - public ProductMixDataValidator(final FromJsonHelper fromApiJsonHelper) { - this.fromApiJsonHelper = fromApiJsonHelper; - } + baseDataValidator.reset().parameter(RESTRICTED_PRODUCTS).value(restrictedProducts).notNull(); - public void validateForCreate(final String json) { - if (StringUtils.isBlank(json)) { - throw new InvalidJsonException(); + if (restrictedProducts == null) { + // notNull() sopra ha giĆ  registrato l'errore corretto + } else if (restrictedProducts.isEmpty()) { + baseDataValidator.reset().parameter(RESTRICTED_PRODUCTS).failWithCode("must.not.be.empty"); + } else { + validateRestrictedProducts(restrictedProducts, baseDataValidator); } - final Type typeOfMap = new TypeToken>() {}.getType(); - this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS); + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + public void validateRestrictedProductsForUpdate(final List restrictedProducts) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(PRODUCTMIX); - final JsonElement element = this.fromApiJsonHelper.parse(json); - - final String[] restrictedProducts = this.fromApiJsonHelper.extractArrayNamed(RESTRICTED_PRODUCTS, element); - baseDataValidator.reset().parameter(RESTRICTED_PRODUCTS).value(restrictedProducts).arrayNotEmpty(); + baseDataValidator.reset().parameter(RESTRICTED_PRODUCTS).value(restrictedProducts).notNull(); if (restrictedProducts != null) { validateRestrictedProducts(restrictedProducts, baseDataValidator); } @@ -75,9 +61,9 @@ public void validateForCreate(final String json) { throwExceptionIfValidationWarningsExist(dataValidationErrors); } - private void validateRestrictedProducts(final String[] restrictedProducts, final DataValidatorBuilder baseDataValidator) { - for (final String restrictedId : restrictedProducts) { - baseDataValidator.reset().parameter(RESTRICTED_PRODUCT).value(restrictedId).notBlank().longGreaterThanZero(); + private void validateRestrictedProducts(final List restrictedProducts, final DataValidatorBuilder baseDataValidator) { + for (final Long restrictedId : restrictedProducts) { + baseDataValidator.reset().parameter(RESTRICTED_PRODUCT).value(restrictedId).notNull().longGreaterThanZero(); } } @@ -87,24 +73,4 @@ private void throwExceptionIfValidationWarningsExist(final List>() {}.getType(); - this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS); - - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(PRODUCTMIX); - - final JsonElement element = this.fromApiJsonHelper.parse(json); - final String[] restrictedProducts = this.fromApiJsonHelper.extractArrayNamed(RESTRICTED_PRODUCTS, element); - validateRestrictedProducts(restrictedProducts, baseDataValidator); - - throwExceptionIfValidationWarningsExist(dataValidationErrors); - } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixWritePlatformService.java index a5926d3d8e2..9aabb70c740 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixWritePlatformService.java @@ -18,15 +18,15 @@ */ package org.apache.fineract.portfolio.loanproduct.productmix.service; -import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixCommandRequest; +import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixDeleteRequest; public interface ProductMixWritePlatformService { - CommandProcessingResult createProductMix(Long productId, JsonCommand command); + CommandProcessingResult createProductMix(ProductMixCommandRequest request); - CommandProcessingResult updateProductMix(Long productId, JsonCommand command); - - CommandProcessingResult deleteProductMix(Long productId); + CommandProcessingResult updateProductMix(ProductMixCommandRequest request); + CommandProcessingResult deleteProductMix(ProductMixDeleteRequest request); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixWritePlatformServiceJpaRepositoryImpl.java index b61538e74b3..ae96be226d6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixWritePlatformServiceJpaRepositoryImpl.java @@ -19,14 +19,11 @@ package org.apache.fineract.portfolio.loanproduct.productmix.service; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; -import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.exception.ErrorHandler; @@ -34,6 +31,8 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException; +import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixCommandRequest; +import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixDeleteRequest; import org.apache.fineract.portfolio.loanproduct.productmix.domain.ProductMix; import org.apache.fineract.portfolio.loanproduct.productmix.domain.ProductMixRepository; import org.apache.fineract.portfolio.loanproduct.productmix.exception.ProductMixNotFoundException; @@ -52,109 +51,81 @@ public class ProductMixWritePlatformServiceJpaRepositoryImpl implements ProductMixWritePlatformService { private static final Logger LOG = LoggerFactory.getLogger(ProductMixWritePlatformServiceJpaRepositoryImpl.class); + private final PlatformSecurityContext context; - private final ProductMixDataValidator fromApiJsonDeserializer; + private final ProductMixDataValidator productMixDataValidator; private final ProductMixRepository productMixRepository; private final LoanProductRepository productRepository; @Autowired public ProductMixWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, - final ProductMixDataValidator fromApiJsonDeserializer, final ProductMixRepository productMixRepository, + final ProductMixDataValidator productMixDataValidator, final ProductMixRepository productMixRepository, final LoanProductRepository productRepository) { this.context = context; - this.fromApiJsonDeserializer = fromApiJsonDeserializer; + this.productMixDataValidator = productMixDataValidator; this.productMixRepository = productMixRepository; this.productRepository = productRepository; } @Transactional @Override - public CommandProcessingResult createProductMix(final Long productId, final JsonCommand command) { - + public CommandProcessingResult createProductMix(final ProductMixCommandRequest request) { try { - this.context.authenticatedUser(); - this.fromApiJsonDeserializer.validateForCreate(command.json()); + final Long productId = request.productId(); + final List restrictedProducts = request.restrictedProducts(); + + this.productMixDataValidator.validateRestrictedProductsForCreate(restrictedProducts); - final Set restrictedIds = new HashSet<>(Arrays.asList(command.arrayValueOfParameterNamed("restrictedProducts"))); + final Set restrictedIds = Set.copyOf(restrictedProducts); - // remove the existed restriction if it is not exists in - // restrictedIds. final List removedRestrictions = updateRestrictionsForProduct(productId, restrictedIds); final Map restrictedProductsAsMap = getRestrictedProducts(restrictedIds); final List productMixes = new ArrayList<>(); createNewProductMix(restrictedProductsAsMap, productId, productMixes); - this.productMixRepository.saveAll(productMixes); - final Map changes = new LinkedHashMap<>(); - changes.put("restrictedProductsForMix", restrictedProductsAsMap.keySet()); - changes.put("removedProductsForMix", removedRestrictions); - return new CommandProcessingResultBuilder().withProductId(productId).with(changes).withCommandId(command.commandId()).build(); - } catch (final JpaSystemException | DataIntegrityViolationException dve) { - - handleDataIntegrityIssues(dve); - return CommandProcessingResult.empty(); - } - } - - private List updateRestrictionsForProduct(final Long productId, final Set restrictedIds) { - - final List removedRestrictions = new ArrayList<>(); - final List mixesToRemove = new ArrayList<>(); + final List removedRestrictedProductIds = removedRestrictions.isEmpty() ? List.of() + : this.productMixRepository.findAllById(removedRestrictions).stream().map(ProductMix::getRestrictedProductId).toList(); - final List existedProductMixes = this.productMixRepository.findRestrictedProducts(productId); - for (final ProductMix productMix : existedProductMixes) { - if (!restrictedIds.contains(productMix.getProductId().toString())) { - mixesToRemove.add(productMix); - removedRestrictions.add(productMix.getId()); - } - } - if (!CollectionUtils.isEmpty(mixesToRemove)) { - this.productMixRepository.deleteAll(mixesToRemove); - } - return removedRestrictions; - } - - private void createNewProductMix(final Map restrictedProductsAsMap, final Long productId, - final List productMixes) { + final Map changes = new LinkedHashMap<>(); + changes.put("restrictedProductsForMix", new ArrayList<>(restrictedProductsAsMap.keySet())); + changes.put("removedProductsForMix", removedRestrictedProductIds); - final LoanProduct productMixInstance = findByProductIdIfProvided(productId); - for (final LoanProduct restrictedProduct : restrictedProductsAsMap.values()) { - final ProductMix productMix = ProductMix.createNew(productMixInstance, restrictedProduct); - productMixes.add(productMix); + return new CommandProcessingResultBuilder().withProductId(productId).with(changes).build(); + } catch (final JpaSystemException | DataIntegrityViolationException dve) { + throw handleDataIntegrityIssues(dve); } } + @Transactional @Override - public CommandProcessingResult updateProductMix(final Long productId, final JsonCommand command) { - + public CommandProcessingResult updateProductMix(final ProductMixCommandRequest request) { try { this.context.authenticatedUser(); - this.fromApiJsonDeserializer.validateForUpdate(command.json()); + + final Long productId = request.productId(); + final List restrictedProducts = request.restrictedProducts(); final Map changes = new LinkedHashMap<>(); final List existedProductMixes = new ArrayList<>(this.productMixRepository.findByProductId(productId)); if (CollectionUtils.isEmpty(existedProductMixes)) { throw new ProductMixNotFoundException(productId); } - final Set restrictedIds = new HashSet<>(Arrays.asList(command.arrayValueOfParameterNamed("restrictedProducts"))); - // updating with empty array means deleting the existed records. - if (restrictedIds.isEmpty()) { + this.productMixDataValidator.validateRestrictedProductsForUpdate(restrictedProducts); + + if (restrictedProducts.isEmpty()) { final List removedRestrictedProductIds = this.productMixRepository.findRestrictedProductIdsByProductId(productId); this.productMixRepository.deleteAll(existedProductMixes); changes.put("removedProductsForMix", removedRestrictedProductIds); - return new CommandProcessingResultBuilder().with(changes).withProductId(productId).withCommandId(command.commandId()) - .build(); + + return new CommandProcessingResultBuilder().withProductId(productId).with(changes).build(); } - /* - * if restrictedProducts array is not empty delete the duplicate ids which are already exists and update - * existedProductMixes - */ + final Set restrictedIds = Set.copyOf(restrictedProducts); final List productMixesToRemove = updateRestrictedIds(restrictedIds, existedProductMixes); final Map restrictedProductsAsMap = getRestrictedProducts(restrictedIds); createNewProductMix(restrictedProductsAsMap, productId, existedProductMixes); @@ -166,11 +137,61 @@ public CommandProcessingResult updateProductMix(final Long productId, final Json this.productMixRepository.deleteAll(productMixesToRemove); changes.put("removedProductsForMix", getProductIdsFromCollection(productMixesToRemove)); } - return new CommandProcessingResultBuilder().with(changes).withProductId(productId).withCommandId(command.commandId()).build(); + + return new CommandProcessingResultBuilder().withProductId(productId).with(changes).build(); } catch (final JpaSystemException | DataIntegrityViolationException dve) { + throw handleDataIntegrityIssues(dve); + } + } + + @Transactional + @Override + public CommandProcessingResult deleteProductMix(final ProductMixDeleteRequest request) { + try { + this.context.authenticatedUser(); - handleDataIntegrityIssues(dve); - return CommandProcessingResult.empty(); + final Long productId = request.productId(); + final Map changes = new LinkedHashMap<>(); + + final List existedProductMixes = this.productMixRepository.findByProductId(productId); + if (CollectionUtils.isEmpty(existedProductMixes)) { + throw new ProductMixNotFoundException(productId); + } + + changes.put("removedProductsForMix", getProductIdsFromCollection(existedProductMixes)); + this.productMixRepository.deleteAll(existedProductMixes); + + return new CommandProcessingResultBuilder().withProductId(productId).with(changes).build(); + } catch (final JpaSystemException | DataIntegrityViolationException dve) { + throw handleDataIntegrityIssues(dve); + } + } + + private List updateRestrictionsForProduct(final Long productId, final Set restrictedIds) { + final List removedRestrictions = new ArrayList<>(); + final List mixesToRemove = new ArrayList<>(); + + final List existedProductMixes = this.productMixRepository.findRestrictedProducts(productId); + for (final ProductMix productMix : existedProductMixes) { + if (!restrictedIds.contains(productMix.getRestrictedProductId())) { + mixesToRemove.add(productMix); + removedRestrictions.add(productMix.getId()); + } + } + + if (!CollectionUtils.isEmpty(mixesToRemove)) { + this.productMixRepository.deleteAll(mixesToRemove); + } + + return removedRestrictions; + } + + private void createNewProductMix(final Map restrictedProductsAsMap, final Long productId, + final List productMixes) { + final LoanProduct productMixInstance = findByProductIdIfProvided(productId); + for (final LoanProduct restrictedProduct : restrictedProductsAsMap.values()) { + final ProductMix productMix = ProductMix.createNew(productMixInstance, restrictedProduct); + productMixes.add(productMix); } } @@ -178,58 +199,39 @@ private LoanProduct findByProductIdIfProvided(final Long productId) { return this.productRepository.findById(productId).orElseThrow(() -> new LoanProductNotFoundException(productId)); } - private Map getRestrictedProducts(final Set restrictedIds) { - + private Map getRestrictedProducts(final Set restrictedIds) { final Map restrictedProducts = new HashMap<>(); - for (final String restrictedId : restrictedIds) { - final Long restrictedIdAsLong = Long.valueOf(restrictedId); - final LoanProduct restrictedProduct = findByProductIdIfProvided(Long.valueOf(restrictedId)); - restrictedProducts.put(restrictedIdAsLong, restrictedProduct); + for (final Long restrictedId : restrictedIds) { + final LoanProduct restrictedProduct = findByProductIdIfProvided(restrictedId); + restrictedProducts.put(restrictedId, restrictedProduct); } + return restrictedProducts; } - private void handleDataIntegrityIssues(final NonTransientDataAccessException dve) { + private RuntimeException handleDataIntegrityIssues(final NonTransientDataAccessException dve) { LOG.error("Error occurred.", dve); - throw ErrorHandler.getMappable(dve, "error.msg.product.loan.unknown.data.integrity.issue", + return ErrorHandler.getMappable(dve, "error.msg.product.loan.unknown.data.integrity.issue", "Unknown data integrity issue with resource."); } - private List updateRestrictedIds(final Set restrictedIds, final List existedProductMixes) { - + private List updateRestrictedIds(final Set restrictedIds, final List existedProductMixes) { final List productMixesToRemove = new ArrayList<>(); + for (final ProductMix productMix : existedProductMixes) { - final String currentMixId = productMix.getRestrictedProductId().toString(); + final Long currentMixId = productMix.getRestrictedProductId(); if (restrictedIds.contains(currentMixId)) { restrictedIds.remove(currentMixId); } else { productMixesToRemove.add(productMix); } } + existedProductMixes.removeAll(productMixesToRemove); return productMixesToRemove; } - @Override - public CommandProcessingResult deleteProductMix(final Long productId) { - try { - this.context.authenticatedUser(); - final Map changes = new LinkedHashMap<>(); - - final List existedProductMixes = this.productMixRepository.findByProductId(productId); - if (CollectionUtils.isEmpty(existedProductMixes)) { - throw new ProductMixNotFoundException(productId); - } - this.productMixRepository.deleteAll(existedProductMixes); - changes.put("removedProductsForMix", getProductIdsFromCollection(existedProductMixes)); - return new CommandProcessingResultBuilder().with(changes).withProductId(productId).build(); - } catch (final JpaSystemException | DataIntegrityViolationException dve) { - handleDataIntegrityIssues(dve); - return CommandProcessingResult.empty(); - } - } - private List getProductIdsFromCollection(final List collection) { final List productIds = new ArrayList<>(); for (final ProductMix productMix : collection) { @@ -237,5 +239,4 @@ private List getProductIdsFromCollection(final List collection } return productIds; } - }