You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2022/09/11 14:56:18 UTC

[fineract] branch develop updated: Batch API read optimization + minor changes

This is an automated email from the ASF dual-hosted git repository.

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new a0749ffe1 Batch API read optimization + minor changes
a0749ffe1 is described below

commit a0749ffe19293cb4719e02873fc2020910b66306
Author: Arnold Galovics <ga...@gmail.com>
AuthorDate: Fri Sep 9 20:20:10 2022 +0200

    Batch API read optimization + minor changes
---
 .../fineract/batch/api/BatchApiResource.java       | 25 ++++++++++
 .../service/CodeValueReadPlatformServiceImpl.java  |  2 +
 .../InvalidInstanceTypeMethodExceptionMapper.java  | 46 ++++++++++++++++++
 .../filter/FineractInstanceModeApiFilter.java      |  5 +-
 .../InvalidInstanceTypeMethodException.java        |  5 ++
 .../portfolio/loanaccount/domain/Loan.java         |  4 ++
 .../loanschedule/data/LoanSchedulePeriodData.java  |  4 ++
 .../PaymentTypeReadPlatformServiceImpl.java        |  2 +
 .../service/PaymentTypeWriteServiceImpl.java       |  4 ++
 ...validInstanceTypeMethodExceptionMapperTest.java | 56 ++++++++++++++++++++++
 .../filter/FineractInstanceModeApiFilterTest.java  | 40 ++++++++++++++++
 11 files changed, 192 insertions(+), 1 deletion(-)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java
index f49003e41..663f70bd0 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java
@@ -29,8 +29,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DefaultValue;
+import javax.ws.rs.HttpMethod;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
@@ -43,7 +45,9 @@ import org.apache.fineract.batch.domain.BatchRequest;
 import org.apache.fineract.batch.domain.BatchResponse;
 import org.apache.fineract.batch.serialization.BatchRequestJsonHelper;
 import org.apache.fineract.batch.service.BatchApiService;
+import org.apache.fineract.infrastructure.core.config.FineractProperties;
 import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.exception.InvalidInstanceTypeMethodException;
 import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
@@ -78,6 +82,7 @@ public class BatchApiResource {
     private final ToApiJsonSerializer<BatchResponse> toApiJsonSerializer;
     private final BatchApiService service;
     private final BatchRequestJsonHelper batchRequestJsonHelper;
+    private final FineractProperties fineractProperties;
 
     /**
      * Rest assured POST method to get {@link BatchRequest} and returns back the consolidated {@link BatchResponse}
@@ -106,6 +111,8 @@ public class BatchApiResource {
         // Converts request array into BatchRequest List
         final List<BatchRequest> requestList = this.batchRequestJsonHelper.extractList(jsonRequestString);
 
+        validateRequestMethodsAllowedOnInstanceType(requestList);
+
         // Gets back the consolidated BatchResponse from BatchApiservice
         List<BatchResponse> result = new ArrayList<>();
 
@@ -120,4 +127,22 @@ public class BatchApiResource {
         return this.toApiJsonSerializer.serialize(result);
 
     }
+
+    /**
+     * Validates to make sure the request methods are allowed on currently running instance mode (type).
+     *
+     * @param requestList
+     *            the list of {@link BatchRequest}s
+     */
+    private void validateRequestMethodsAllowedOnInstanceType(final List<BatchRequest> requestList) {
+        // Throw exception if instance is read only and any of the batch requests are trying to write/update data.
+        if (fineractProperties.getMode().isReadOnlyMode()) {
+            final Optional<BatchRequest> nonGetRequest = requestList.stream()
+                    .filter(batchRequest -> !HttpMethod.GET.equals(batchRequest.getMethod())).findFirst();
+            if (nonGetRequest.isPresent()) {
+                throw new InvalidInstanceTypeMethodException(nonGetRequest.get().getMethod());
+            }
+        }
+    }
+
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueReadPlatformServiceImpl.java
index 4decf34b7..7821aa062 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueReadPlatformServiceImpl.java
@@ -65,6 +65,7 @@ public class CodeValueReadPlatformServiceImpl implements CodeValueReadPlatformSe
     }
 
     @Override
+    @Cacheable(value = "code_values", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#code+'cv')")
     public Collection<CodeValueData> retrieveCodeValuesByCode(final String code) {
 
         this.context.authenticatedUser();
@@ -88,6 +89,7 @@ public class CodeValueReadPlatformServiceImpl implements CodeValueReadPlatformSe
     }
 
     @Override
+    @Cacheable(value = "code_values", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#codeValueId+'cv_by_id')")
     public CodeValueData retrieveCodeValue(final Long codeValueId) {
 
         try {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/InvalidInstanceTypeMethodExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/InvalidInstanceTypeMethodExceptionMapper.java
new file mode 100644
index 000000000..77831d231
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/InvalidInstanceTypeMethodExceptionMapper.java
@@ -0,0 +1,46 @@
+/**
+ * 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.infrastructure.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.fineract.infrastructure.security.exception.InvalidInstanceTypeMethodException;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map {@link InvalidInstanceTypeMethodException} thrown by platform into an HTTP API
+ * friendly format.
+ */
+@Provider
+@Component
+@Slf4j
+public class InvalidInstanceTypeMethodExceptionMapper implements ExceptionMapper<InvalidInstanceTypeMethodException> {
+
+    @Override
+    public Response toResponse(final InvalidInstanceTypeMethodException exception) {
+        log.warn("Exception: {}, Message: {}", exception.getClass().getName(), exception.getMessage());
+        ApiGlobalErrorResponse errorResponse = ApiGlobalErrorResponse.invalidInstanceTypeMethod(exception.getMethod());
+        return Response.status(Status.METHOD_NOT_ALLOWED).entity(errorResponse).type(MediaType.APPLICATION_JSON).build();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/instancemode/filter/FineractInstanceModeApiFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/instancemode/filter/FineractInstanceModeApiFilter.java
index 4266de6b5..6b366d3f6 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/instancemode/filter/FineractInstanceModeApiFilter.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/instancemode/filter/FineractInstanceModeApiFilter.java
@@ -43,7 +43,10 @@ public class FineractInstanceModeApiFilter extends OncePerRequestFilter {
 
     private static final List<ExceptionListItem> EXCEPTION_LIST = List.of(
             item(FineractProperties.FineractModeProperties::isBatchManagerEnabled, pi -> pi.startsWith("/jobs")),
-            item(p -> true, pi -> pi.startsWith("/instance-mode")));
+            item(p -> true, pi -> pi.startsWith("/instance-mode")),
+            // Batches with all GET requests need to be allowed in read-only instances, hence this check will be moved
+            // under the Api Resource.
+            item(p -> true, pi -> pi.startsWith("/batches")));
 
     private final FineractProperties fineractProperties;
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/InvalidInstanceTypeMethodException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/InvalidInstanceTypeMethodException.java
index a69a0813b..85d44f8df 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/InvalidInstanceTypeMethodException.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/InvalidInstanceTypeMethodException.java
@@ -18,6 +18,7 @@
  */
 package org.apache.fineract.infrastructure.security.exception;
 
+import lombok.Getter;
 import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
 
 /**
@@ -25,9 +26,13 @@ import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainR
  *
  *
  */
+@Getter
 public class InvalidInstanceTypeMethodException extends AbstractPlatformDomainRuleException {
 
+    private final String method;
+
     public InvalidInstanceTypeMethodException(final String method) {
         super("error.msg.invalid.method.for.instance.type", "Method Not Allowed " + method + " for the instance type");
+        this.method = method;
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index dfd42a124..3acf93cc1 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -5913,6 +5913,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
         return this.accountNumber;
     }
 
+    public String getExternalId() {
+        return this.externalId;
+    }
+
     public Client getClient() {
         return this.client;
     }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java
index 0de3e8719..b03f44735 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java
@@ -423,4 +423,8 @@ public final class LoanSchedulePeriodData {
     public BigDecimal getTotalOverdue() {
         return defaultToZeroIfNull(this.totalOverdue);
     }
+
+    public BigDecimal totalOutstandingForPeriod() {
+        return defaultToZeroIfNull(this.totalOutstandingForPeriod);
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java
index 50ee62e35..65fd525b2 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java
@@ -24,6 +24,7 @@ import java.util.Collection;
 import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
 import org.springframework.stereotype.Service;
@@ -41,6 +42,7 @@ public class PaymentTypeReadPlatformServiceImpl implements PaymentTypeReadPlatfo
     }
 
     @Override
+    @Cacheable(value = "payment_types", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('payment_types')")
     public Collection<PaymentTypeData> retrieveAllPaymentTypes() {
         // TODO Auto-generated method stub
         this.context.authenticatedUser();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java
index 03955093d..0fcf4f26c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java
@@ -29,6 +29,7 @@ import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
 import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepository;
 import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.orm.jpa.JpaSystemException;
 import org.springframework.stereotype.Service;
@@ -50,6 +51,7 @@ public class PaymentTypeWriteServiceImpl implements PaymentTypeWriteService {
     }
 
     @Override
+    @CacheEvict(value = "payment_types", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('payment_types')")
     public CommandProcessingResult createPaymentType(JsonCommand command) {
         this.fromApiJsonDeserializer.validateForCreate(command.json());
         String name = command.stringValueOfParameterNamed(PaymentTypeApiResourceConstants.NAME);
@@ -63,6 +65,7 @@ public class PaymentTypeWriteServiceImpl implements PaymentTypeWriteService {
     }
 
     @Override
+    @CacheEvict(value = "payment_types", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('payment_types')")
     public CommandProcessingResult updatePaymentType(Long paymentTypeId, JsonCommand command) {
 
         this.fromApiJsonDeserializer.validateForUpdate(command.json());
@@ -77,6 +80,7 @@ public class PaymentTypeWriteServiceImpl implements PaymentTypeWriteService {
     }
 
     @Override
+    @CacheEvict(value = "payment_types", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('payment_types')")
     public CommandProcessingResult deletePaymentType(Long paymentTypeId) {
         final PaymentType paymentType = this.repositoryWrapper.findOneWithNotFoundDetection(paymentTypeId);
         try {
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/exceptionmapper/InvalidInstanceTypeMethodExceptionMapperTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/exceptionmapper/InvalidInstanceTypeMethodExceptionMapperTest.java
new file mode 100644
index 000000000..f52ee35d4
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/exceptionmapper/InvalidInstanceTypeMethodExceptionMapperTest.java
@@ -0,0 +1,56 @@
+/**
+ * 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.infrastructure.core.exceptionmapper;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.Response;
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.fineract.infrastructure.security.exception.InvalidInstanceTypeMethodException;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link InvalidInstanceTypeMethodExceptionMapper}.
+ */
+public class InvalidInstanceTypeMethodExceptionMapperTest {
+
+    /**
+     * Tests the {@link InvalidInstanceTypeMethodExceptionMapper#toResponse(InvalidInstanceTypeMethodException)} method
+     * for successful scenario.
+     */
+    @Test
+    public void testToResponse() {
+        final InvalidInstanceTypeMethodExceptionMapper exceptionMapper = new InvalidInstanceTypeMethodExceptionMapper();
+        final InvalidInstanceTypeMethodException exception = new InvalidInstanceTypeMethodException(HttpMethod.POST);
+
+        final Response response = exceptionMapper.toResponse(exception);
+
+        assertNotNull(response);
+        assertEquals(Response.Status.METHOD_NOT_ALLOWED.getStatusCode(), response.getStatus());
+        assertNotNull(response.getEntity());
+        final ApiGlobalErrorResponse errorResponse = (ApiGlobalErrorResponse) response.getEntity();
+        assertEquals(String.valueOf(Response.Status.METHOD_NOT_ALLOWED.getReasonPhrase()), errorResponse.getHttpStatusCode());
+        assertEquals("Invalid instance type called in api request for the method POST", errorResponse.getDeveloperMessage());
+        assertEquals("error.msg.invalid.instance.type", errorResponse.getUserMessageGlobalisationCode());
+        assertEquals("Invalid method POST used with request to this instance type.", errorResponse.getDefaultUserMessage());
+    }
+
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/instancemode/filter/FineractInstanceModeApiFilterTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/instancemode/filter/FineractInstanceModeApiFilterTest.java
index 86d14d8f7..a071dc5c8 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/instancemode/filter/FineractInstanceModeApiFilterTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/instancemode/filter/FineractInstanceModeApiFilterTest.java
@@ -244,4 +244,44 @@ class FineractInstanceModeApiFilterTest {
         // then
         verify(filterChain).doFilter(request, response);
     }
+
+    @Test
+    void testDoFilterInternal_ShouldLetBatchesApisThrough_WhenFineractIsInReadMode() throws ServletException, IOException {
+        // given
+        FineractProperties.FineractModeProperties modeProperties = InstanceModeMock.createModeProps(true, false, false, false);
+        given(fineractProperties.getMode()).willReturn(modeProperties);
+        given(request.getMethod()).willReturn(HttpMethod.POST.name());
+        given(request.getPathInfo()).willReturn("/batches");
+        // when
+        underTest.doFilterInternal(request, response, filterChain);
+        // then
+        verify(filterChain).doFilter(request, response);
+    }
+
+    @Test
+    void testDoFilterInternal_ShouldLetBatchesApisThrough_WhenFineractIsInWriteMode() throws ServletException, IOException {
+        // given
+        FineractProperties.FineractModeProperties modeProperties = InstanceModeMock.createModeProps(false, true, false, false);
+        given(fineractProperties.getMode()).willReturn(modeProperties);
+        given(request.getMethod()).willReturn(HttpMethod.POST.name());
+        given(request.getPathInfo()).willReturn("/batches");
+        // when
+        underTest.doFilterInternal(request, response, filterChain);
+        // then
+        verify(filterChain).doFilter(request, response);
+    }
+
+    @Test
+    void testDoFilterInternal_ShouldLetBatchesApisThrough_WhenFineractIsInBatchMode() throws ServletException, IOException {
+        // given
+        FineractProperties.FineractModeProperties modeProperties = InstanceModeMock.createModeProps(false, false, true, true);
+        given(fineractProperties.getMode()).willReturn(modeProperties);
+        given(request.getMethod()).willReturn(HttpMethod.POST.name());
+        given(request.getPathInfo()).willReturn("/batches");
+        // when
+        underTest.doFilterInternal(request, response, filterChain);
+        // then
+        verify(filterChain).doFilter(request, response);
+    }
+
 }