Batch API read optimization + minor changes

This commit is contained in:
Arnold Galovics
2022-09-11 16:56:12 +02:00
committed by Arnold Gálovics
parent 130d9c0c3d
commit a0749ffe19
11 changed files with 192 additions and 1 deletions
@@ -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());
}
}
}
}
@@ -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 {
@@ -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();
}
}
@@ -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;
@@ -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;
}
}
@@ -5913,6 +5913,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return this.accountNumber;
}
public String getExternalId() {
return this.externalId;
}
public Client getClient() {
return this.client;
}
@@ -423,4 +423,8 @@ public final class LoanSchedulePeriodData {
public BigDecimal getTotalOverdue() {
return defaultToZeroIfNull(this.totalOverdue);
}
public BigDecimal totalOutstandingForPeriod() {
return defaultToZeroIfNull(this.totalOutstandingForPeriod);
}
}
@@ -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();
@@ -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 {
@@ -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());
}
}
@@ -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);
}
}