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 2023/04/24 12:28:56 UTC

[fineract] branch develop updated: FINERACT-1724: More batch support for datatables APIs

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 636b33196 FINERACT-1724: More batch support for datatables APIs
636b33196 is described below

commit 636b33196b5ca79baeec0339a25eec75f2d05e97
Author: Arnold Galovics <ga...@gmail.com>
AuthorDate: Mon Apr 24 13:18:18 2023 +0200

    FINERACT-1724: More batch support for datatables APIs
---
 .../batch/command/CommandStrategyProvider.java     |   5 +-
 ...yByAppTableIdAndDataTableIdCommandStrategy.java |  95 ++++++++++++
 .../batch/command/CommandStrategyProviderTest.java |   8 +-
 ...ppTableIdAndDataTableIdCommandStrategyTest.java | 165 +++++++++++++++++++++
 .../fineract/integrationtests/BatchApiTest.java    |  11 +-
 .../integrationtests/common/BatchHelper.java       |  32 ++++
 6 files changed, 313 insertions(+), 3 deletions(-)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java
index 24a2532f6..7b422f076 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java
@@ -50,7 +50,7 @@ public class CommandStrategyProvider {
     /**
      * Regex pattern for specifying query params
      */
-    private static final String MANDATORY_QUERY_PARAM_REGEX = "(\\?(\\w+(?:\\=[\\w,]+|&)+)+)";
+    private static final String MANDATORY_QUERY_PARAM_REGEX = "(\\?(\\w+(?:\\=[\\w\\-,]+|&)+)+)";
 
     /**
      * Regex pattern for specifying any query param that has key = 'command' or not specific anything.
@@ -202,6 +202,9 @@ public class CommandStrategyProvider {
         commandStrategies.put(CommandContext
                 .resource("datatables\\/" + ALPHANUMBERIC_WITH_UNDERSCORE_REGEX + "\\/" + NUMBER_REGEX + OPTIONAL_QUERY_PARAM_REGEX)
                 .method(GET).build(), "getDatatableEntryByAppTableIdCommandStrategy");
+        commandStrategies.put(CommandContext.resource("datatables\\/" + ALPHANUMBERIC_WITH_UNDERSCORE_REGEX + "\\/" + NUMBER_REGEX + "\\/"
+                + NUMBER_REGEX + OPTIONAL_QUERY_PARAM_REGEX).method(GET).build(),
+                "getDatatableEntryByAppTableIdAndDataTableIdCommandStrategy");
         commandStrategies.put(CommandContext.resource("loans\\/" + NUMBER_REGEX + OPTIONAL_COMMAND_PARAM_REGEX).method(PUT).build(),
                 "modifyLoanApplicationCommandStrategy");
         commandStrategies.put(
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategy.java
new file mode 100644
index 000000000..c1d3e4cad
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategy.java
@@ -0,0 +1,95 @@
+/**
+ * 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.batch.command.internal;
+
+import java.util.Map;
+import javax.ws.rs.core.UriInfo;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.batch.command.CommandStrategy;
+import org.apache.fineract.batch.command.CommandStrategyUtils;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.infrastructure.core.api.MutableUriInfo;
+import org.apache.fineract.infrastructure.dataqueries.api.DatatablesApiResource;
+import org.apache.http.HttpStatus;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implements {@link CommandStrategy} and get datatable by appTableId by data table id. It passes the contents of the
+ * body from the BatchRequest to {@link DatatablesApiResource} and gets back the response. This class will also catch
+ * any errors raised by {@link DatatablesApiResource} and map those errors to appropriate status codes in BatchResponse.
+ */
+@Component
+@RequiredArgsConstructor
+public class GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategy implements CommandStrategy {
+
+    /**
+     * Data table api resource {@link DatatablesApiResource}.
+     */
+    private final DatatablesApiResource dataTablesApiResource;
+
+    @Override
+    public BatchResponse execute(final BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) {
+        final MutableUriInfo parameterizedUriInfo = new MutableUriInfo(uriInfo);
+        final BatchResponse response = new BatchResponse();
+        final String responseBody;
+
+        response.setRequestId(request.getRequestId());
+        response.setHeaders(request.getHeaders());
+
+        final String relativeUrl = request.getRelativeUrl();
+        final String relativeUrlSubString = StringUtils.substringAfter(relativeUrl, "/");
+
+        // uriInfo will contain the query parameter value(s) that are sent in the actual batch uri.
+        // for example: batches?enclosingTransaction=true
+        // But the query parameters that are sent in the batch relative url has to be sent to
+        // datatablesApiResource.getDatatable
+        // To use the relative url query parameters
+        // - Parse and fetch the query parameters sent in the relative url
+        // (datatables/dt_123/1/2?genericResultSet=false)
+        // - Add them to the MutableUriInfo query parameters list
+        // - Call datatablesApiResource.getDatatableManyEntry(dataTable, appTableId, dataTableId, null, false, generic
+        // resultSet, uriInfo)
+        final String[] resources = StringUtils.split(relativeUrlSubString, "/");
+        final String dataTableName = resources[0];
+        final Long appTableId = Long.parseLong(resources[1]);
+        final Long dataTableId = Long.parseLong(StringUtils.substringBefore(resources[2], "?"));
+        boolean genericResultSet = false;
+        if (relativeUrl.indexOf('?') > 0) {
+            Map<String, String> queryParameters = CommandStrategyUtils.getQueryParameters(relativeUrl);
+            // Add the query parameters sent in the relative URL to MutableUriInfo
+            if (queryParameters.containsKey("genericResultSet")) {
+                genericResultSet = Boolean.parseBoolean(queryParameters.get("genericResultSet"));
+            }
+            CommandStrategyUtils.addQueryParametersToUriInfo(parameterizedUriInfo, queryParameters);
+        }
+        // Calls 'getDatatableManyEntry' function from 'DatatablesApiResource' to
+        // get the datatable details based on the appTableId
+        responseBody = dataTablesApiResource.getDatatableManyEntry(dataTableName, appTableId, dataTableId, null, genericResultSet,
+                parameterizedUriInfo);
+
+        response.setStatusCode(HttpStatus.SC_OK);
+
+        // Sets the response after retrieving the datatable
+        response.setBody(responseBody);
+
+        return response;
+    }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java b/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java
index cd9cff795..a4c7f9bf0 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java
@@ -45,6 +45,7 @@ import org.apache.fineract.batch.command.internal.CreateTransactionLoanCommandSt
 import org.apache.fineract.batch.command.internal.DisburseLoanCommandStrategy;
 import org.apache.fineract.batch.command.internal.GetChargeByChargeExternalIdCommandStrategy;
 import org.apache.fineract.batch.command.internal.GetChargeByIdCommandStrategy;
+import org.apache.fineract.batch.command.internal.GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategy;
 import org.apache.fineract.batch.command.internal.GetDatatableEntryByAppTableIdCommandStrategy;
 import org.apache.fineract.batch.command.internal.GetDatatableEntryByQueryCommandStrategy;
 import org.apache.fineract.batch.command.internal.GetLoanByExternalIdCommandStrategy;
@@ -174,6 +175,9 @@ public class CommandStrategyProviderTest {
                         mock(GetDatatableEntryByAppTableIdCommandStrategy.class)),
                 Arguments.of("datatables/test_dt_table/123?genericResultSet=true", HttpMethod.GET,
                         "getDatatableEntryByAppTableIdCommandStrategy", mock(GetDatatableEntryByAppTableIdCommandStrategy.class)),
+                Arguments.of("datatables/test_dt_table/123/1?genericResultSet=true", HttpMethod.GET,
+                        "getDatatableEntryByAppTableIdAndDataTableIdCommandStrategy",
+                        mock(GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategy.class)),
                 Arguments.of("datatables/test_dt_table/123", HttpMethod.POST, "createDatatableEntryCommandStrategy",
                         mock(CreateDatatableEntryCommandStrategy.class)),
                 Arguments.of("datatables/test_dt_table/123/1", HttpMethod.PUT, "updateDatatableEntryOneToManyCommandStrategy",
@@ -185,7 +189,9 @@ public class CommandStrategyProviderTest {
                 Arguments.of("loans/123", HttpMethod.PUT, "modifyLoanApplicationCommandStrategy",
                         mock(ModifyLoanApplicationCommandStrategy.class)),
                 Arguments.of("datatables/test_dt_table/query?columnFilter=id&valueFilter=12&resultColumns=id", HttpMethod.GET,
-                        "getDatatableEntryByQueryCommandStrategy", mock(GetDatatableEntryByQueryCommandStrategy.class)));
+                        "getDatatableEntryByQueryCommandStrategy", mock(GetDatatableEntryByQueryCommandStrategy.class)),
+                Arguments.of("datatables/test_dt_table/query?columnFilter=custom_id&valueFilter=10a62-d438-2319&resultColumns=id",
+                        HttpMethod.GET, "getDatatableEntryByQueryCommandStrategy", mock(GetDatatableEntryByQueryCommandStrategy.class)));
     }
 
     /**
diff --git a/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategyTest.java b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategyTest.java
new file mode 100644
index 000000000..c068b3a8c
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategyTest.java
@@ -0,0 +1,165 @@
+/**
+ * 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.batch.command.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.verify;
+
+import java.util.List;
+import java.util.stream.Stream;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.UriInfo;
+import org.apache.commons.lang3.RandomUtils;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.infrastructure.core.api.MutableUriInfo;
+import org.apache.fineract.infrastructure.dataqueries.api.DatatablesApiResource;
+import org.apache.http.HttpStatus;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests {GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategy}.
+ */
+public class GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategyTest {
+
+    /**
+     * Query parameter provider.
+     *
+     * @return the test data stream
+     */
+    private static Stream<Arguments> provideQueryParameters() {
+        return Stream.of(Arguments.of(null, 0), Arguments.of("genericResultSet=true", 1));
+    }
+
+    /**
+     * Test {@link GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategy#execute} happy path scenario.
+     *
+     * @param queryParameter
+     *            the query parameter
+     * @param numberOfQueryParams
+     *            number of query params are provided
+     */
+    @ParameterizedTest
+    @MethodSource("provideQueryParameters")
+    public void testExecuteSuccessScenario(final String queryParameter, final int numberOfQueryParams) {
+        final TestContext testContext = new TestContext();
+
+        final Long loanId = RandomUtils.nextLong();
+        final Long datatableId = RandomUtils.nextLong();
+        final String datatableName = "dt_loan_xyz";
+        final BatchRequest request = getBatchRequest(loanId, datatableId, queryParameter, datatableName);
+        final String responseBody = "{\\\"columnHeaders\\\":[{}],\\\"data\\\":\\\"{}\\\"}";
+        Boolean genericResultSet = false;
+        if (queryParameter != null && queryParameter.contains("genericResultSet=true")) {
+            genericResultSet = Boolean.TRUE;
+        }
+        given(testContext.dataTableApiResource.getDatatableManyEntry(eq(datatableName), eq(loanId), eq(datatableId), eq(null),
+                eq(genericResultSet), any(UriInfo.class))).willReturn(responseBody);
+
+        final BatchResponse response = testContext.subjectToTest.execute(request, testContext.uriInfo);
+        assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        assertEquals(request.getRequestId(), response.getRequestId());
+        assertEquals(request.getHeaders(), response.getHeaders());
+        assertEquals(responseBody, response.getBody());
+
+        verify(testContext.dataTableApiResource).getDatatableManyEntry(eq(datatableName), eq(loanId), eq(datatableId), eq(null),
+                eq(genericResultSet), testContext.uriInfoCaptor.capture());
+        MutableUriInfo mutableUriInfo = testContext.uriInfoCaptor.getValue();
+        assertThat(mutableUriInfo.getAdditionalQueryParameters()).hasSize(numberOfQueryParams);
+        if (numberOfQueryParams > 0) {
+            List<String> param = mutableUriInfo.getAdditionalQueryParameters().get("genericResultSet");
+            assertEquals(param.get(0), "true");
+        }
+    }
+
+    /**
+     * Creates and returns a request with the given loan id.
+     *
+     * @param loanId
+     *            the loan id
+     * @param queryParameter
+     *            the query parameter
+     * @param datatableName
+     *            the datatable name
+     * @return the {@link BatchRequest}
+     */
+    private BatchRequest getBatchRequest(final Long loanId, final Long datatableId, final String queryParameter,
+            final String datatableName) {
+        final BatchRequest br = new BatchRequest();
+        String relativeUrl = String.format("datatables/%s/%s/%s", datatableName, loanId, datatableId);
+        if (queryParameter != null) {
+            relativeUrl = relativeUrl + "?" + queryParameter;
+        }
+
+        br.setRequestId(RandomUtils.nextLong());
+        br.setRelativeUrl(relativeUrl);
+        br.setMethod(HttpMethod.GET);
+        br.setReference(RandomUtils.nextLong());
+        br.setBody("{}");
+
+        return br;
+    }
+
+    /**
+     * Private test context class used since testng runs in parallel to avoid state between tests
+     */
+    private static final class TestContext {
+
+        /**
+         * The subject under test.
+         */
+        private final GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategy subjectToTest;
+
+        /**
+         * Mock of {@link UriInfo}
+         */
+        @Mock
+        private UriInfo uriInfo;
+
+        /**
+         * Captor of {@link MutableUriInfo}.
+         */
+        @Captor
+        private ArgumentCaptor<MutableUriInfo> uriInfoCaptor;
+
+        /**
+         * {@link DatatablesApiResource} mock.
+         */
+        @Mock
+        private DatatablesApiResource dataTableApiResource;
+
+        /**
+         * Constructor.
+         */
+        private TestContext() {
+            MockitoAnnotations.openMocks(this);
+            subjectToTest = new GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategy(dataTableApiResource);
+        }
+    }
+}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java
index 39789ddbb..77aa09b8b 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java
@@ -43,6 +43,7 @@ import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.RandomUtils;
 import org.apache.fineract.batch.command.internal.AdjustTransactionCommandStrategy;
 import org.apache.fineract.batch.command.internal.CreateTransactionLoanCommandStrategy;
+import org.apache.fineract.batch.command.internal.GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategy;
 import org.apache.fineract.batch.domain.BatchRequest;
 import org.apache.fineract.batch.domain.BatchResponse;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
@@ -1326,6 +1327,7 @@ public class BatchApiTest {
      * @see org.apache.fineract.batch.command.internal.CreateDatatableEntryCommandStrategy
      * @see org.apache.fineract.batch.command.internal.UpdateDatatableEntryOneToManyCommandStrategy
      * @see org.apache.fineract.batch.command.internal.GetDatatableEntryByAppTableIdCommandStrategy
+     * @see GetDatatableEntryByAppTableIdAndDataTableIdCommandStrategy
      */
     @Test
     public void shouldReturnOkStatusOnSuccessfulCreateDataTableEntry() {
@@ -1373,8 +1375,12 @@ public class BatchApiTest {
         final BatchRequest getDatatableEntriesRequest = BatchHelper.getDatatableByIdRequest(loanId, datatableName, null,
                 updateDatatableEntryByEntryIdRequest.getReference());
 
+        // Get datatable entry by app table id batch request
+        final BatchRequest getDatatableEntryByIdRequest = BatchHelper.getDatatableEntryByIdRequest(loanId, datatableName, "$.resourceId",
+                null, updateDatatableEntryByEntryIdRequest.getReference());
+
         final List<BatchRequest> batchRequestsDatatableEntries = Arrays.asList(createDatatableEntryRequest,
-                updateDatatableEntryByEntryIdRequest, getDatatableEntriesRequest);
+                updateDatatableEntryByEntryIdRequest, getDatatableEntriesRequest, getDatatableEntryByIdRequest);
         LOG.info("Batch Request : {}", BatchHelper.toJsonString(batchRequestsDatatableEntries));
 
         final List<BatchResponse> responseDatatableBatch = BatchHelper.postBatchRequestsWithEnclosingTransaction(this.requestSpec,
@@ -1385,9 +1391,12 @@ public class BatchApiTest {
         final BatchResponse batchResponse1 = responseDatatableBatch.get(0);
         final BatchResponse batchResponse2 = responseDatatableBatch.get(1);
         final BatchResponse batchResponse3 = responseDatatableBatch.get(2);
+        final BatchResponse batchResponse4 = responseDatatableBatch.get(3);
+
         Assertions.assertEquals(HttpStatus.SC_OK, batchResponse1.getStatusCode(), "Verify Status Code 200 for create datatable entry");
         Assertions.assertEquals(HttpStatus.SC_OK, batchResponse2.getStatusCode(), "Verify Status Code 200 for update datatable entry");
         Assertions.assertEquals(HttpStatus.SC_OK, batchResponse3.getStatusCode(), "Verify Status Code 200 for get datatable entries");
+        Assertions.assertEquals(HttpStatus.SC_OK, batchResponse4.getStatusCode(), "Verify Status Code 200 for get datatable entry by id");
 
         final String getDatatableEntriesResponse = batchResponse3.getBody();
 
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java
index 6aabe2372..faaa0091e 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java
@@ -1166,6 +1166,38 @@ public final class BatchHelper {
         return br;
     }
 
+    /**
+     * Creates and returns a batch request to get datatable entry.
+     *
+     * @param loanId
+     *            the loan id
+     * @param datatableName
+     *            the name of datatable
+     * @param appTableId
+     *            the app table id
+     * @param queryParameter
+     *            the query parameters
+     * @param referenceId
+     *            the reference id
+     * @return the {@link BatchRequest}
+     */
+    public static BatchRequest getDatatableEntryByIdRequest(final Long loanId, final String datatableName, final String appTableId,
+            final String queryParameter, final Long referenceId) {
+        final BatchRequest br = new BatchRequest();
+        String relativeUrl = String.format("datatables/%s/%s/%s", datatableName, loanId, appTableId);
+        if (queryParameter != null) {
+            relativeUrl = relativeUrl + "?" + queryParameter;
+        }
+
+        br.setRequestId(4572L);
+        br.setReference(referenceId);
+        br.setRelativeUrl(relativeUrl);
+        br.setMethod(HttpMethod.GET);
+        br.setBody("{}");
+
+        return br;
+    }
+
     /**
      * Creates and returns a batch request to create datatable entry.
      *