diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm
index 23f9b6231..aac4d98c8 100644
--- a/api-docs/apiLive.htm
+++ b/api-docs/apiLive.htm
@@ -103,8 +103,9 @@
+
+
Batch API
+
+
+ The Mifos X Batch API enables a consumer to access significant amounts of data in a single call or to make changes to several objects at once.
+ Batching allows a consumer to pass instructions for several operations in a single HTTP request. A consumer can also specify dependencies between related operations. Once all operations have been completed, a consolidated response will be passed back and the HTTP connection will be closed.
+
+
+
+ The Batch API takes in an array of logical HTTP requests represented as JSON arrays - each request has a requestId (the id of a request used to specify the sequence and as a dependency between requests), a method (corresponding to HTTP method GET/PUT/POST/DELETE etc.), a relativeUrl (the portion of the URL after https://example.org/api/v2/), optional headers array (corresponding to HTTP headers), optional reference parameter if a request is dependent on another request and an optional body (for POST and PUT requests).
+ The Batch API returns an array of logical HTTP responses represented as JSON arrays - each response has a requestId, a status code, an optional headers array and an optional body (which is a JSON encoded string).
+
+
+
+ Batch API uses Json Path to handle dependent
+ parameters. For example, if request '2' is referencing request '1' and in the "body" or in "relativeUrl" of request '2', there is a dependent parameter (which will look like "$.parameter_name"), then Batch API will internally substitute this dependent parameter from the response body of request '1'.
+
+
+
+ Batch API is able to handle deeply nested dependent requests as well nested parameters. As shown in the example, requests are dependent on each other as, 1<--2<--6, i.e a nested dependency, where request '6' is not directly dependent on request '1' but still it is one of the nested child of request '1'.
+ In the same way Batch API could handle a deeply nested dependent value, such as {..[..{..,$.parameter_name,..}..]}.
+
+
+
+
+
+POST https://DomainName/api/v1/batches
+
+
+POST batches
+Content-Type: application/json Request Body:
+[
+ {
+ "requestId":1,
+ "relativeUrl":"clients",
+ "method":"POST",
+ "headers":[
+ {
+ "name":"Content-type",
+ "value":"text/html"
+ }
+ ],
+ "body":"{
+ \"officeId\": 1,
+ \"firstname\": \"Petra\",
+ \"lastname\": \"Yton\",
+ \"externalId\": \"ex_externalId1\",
+ \"dateFormat\": \"dd MMMM yyyy\",
+ \"locale\": \"en\",
+ \"active\": true,
+ \"activationDate\": \"04 March 2009\",
+ \"submittedOnDate\": \"04 March 2009\"
+ }"
+ },
+ {
+ "requestId":2,
+ "relativeUrl":"loans",
+ "method":"POST",
+ "headers":[
+ {
+ "name":"Content-type",
+ "value":"text/html"
+ }
+ ],
+ "reference":1,
+ "body":"{ \"dateFormat\": \"dd MMMM yyyy\",
+ \"locale\": \"en_GB\",
+ \"clientId\": \"$.clientId\",
+ \"productId\": 26,
+ \"principal\": \"10,000.00\",
+ \"loanTermFrequency\": 12,
+ \"loanTermFrequencyType\": 2,
+ \"loanType\": \"individual\",
+ \"numberOfRepayments\": 10,
+ \"repaymentEvery\": 1,
+ \"repaymentFrequencyType\": 2,
+ \"interestRatePerPeriod\": 10,
+ \"amortizationType\": 1,
+ \"interestType\": 0,
+ \"interestCalculationPeriodType\": 1,
+ \"transactionProcessingStrategyId\": 1,
+ \"expectedDisbursementDate\": \"10 Jun 2013\",
+ \"submittedOnDate\": \"10 Jun 2013\"
+ }"
+ },
+ {
+ "requestId":3,
+ "relativeUrl":"loans/$.loanId/charges",
+ "method":"POST",
+ "reference":2,
+ "headers":[
+ {
+ "name":"Content-type",
+ "value":"text/html"
+ }
+ ],
+ "body":"{
+ \"chargeId\": \"2\",
+ \"locale\": \"en\",
+ \"amount\": \"100\",
+ \"dateFormat\": \"dd MMMM yyyy\",
+ \"dueDate\": \"29 April 2013\"
+ }"
+ },
+ {
+ "requestId":4,
+ "relativeUrl":"loans/$.loanId/charges",
+ "method":"GET",
+ "reference":2,
+ "headers":[
+ {
+ "name":"Content-type",
+ "value":"text/html"
+ }
+ ],
+ "body":"{}"
+ }
+]
+
+
+[
+ {
+ "requestId":1,
+ "statusCode":200,
+ "headers":[
+ {
+ "name":"Content-type",
+ "value":"text/html"
+ },
+ {
+ "name":"X-Mifos-Platform-TenantId",
+ "value":"text/html"
+ }
+ ],
+ "body":"{\"officeId\":1,\"clientId\":909,\"resourceId\":909}"
+ },
+ {
+ "requestId":2,
+ "statusCode":200,
+ "headers":[
+ {
+ "name":"Content-type",
+ "value":"text/html"
+ }
+ ],
+ "body":"{\"officeId\":1,\"clientId\":909,\"loanId\":212,\"resourceId\":212}"
+ },
+ {
+ "requestId":3,
+ "statusCode":200,
+ "headers":[
+ {
+ "name":"Content-type",
+ "value":"text/html"
+ }
+ ],
+ "body":"{\"officeId\":1,\"clientId\":909,\"loanId\":212,\"resourceId\":155}"
+ },
+ {
+ "requestId":4,
+ "statusCode":200,
+ "headers":[
+ {
+ "name":"Content-type",
+ "value":"text/html"
+ }
+ ],
+ "body":"[
+ {
+ \"id\":155,
+ \"chargeId\":2,
+ \"name\":\"Charge_Loans_GEQJC5\",
+ \"chargeTimeType\":{
+ \"id\":1,
+ \"code\":\"chargeTimeType.disbursement\",
+ \"value\":\"Disbursement\"
+ },
+ \"chargeCalculationType\":{
+ \"id\":2,
+ \"code\":\"chargeCalculationType.percent.of.amount\",
+ \"value\":\"% Amount\"
+ },
+ \"percentage\":100.000000,
+ \"amountPercentageAppliedTo\":10000.000000,
+ \"currency\":{
+ \"code\":\"USD\",
+ \"name\":\"USDollar\",
+ \"decimalPlaces\":2,
+ \"displaySymbol\":\"$\",
+ \"nameCode\":\"currency.USD\",
+ \"displayLabel\":\"US Dollar ($)\"
+ },
+ \"amount\":10000.000000,
+ \"amountPaid\":0,
+ \"amountWaived\":0,
+ \"amountWrittenOff\":0,
+ \"amountOutstanding\":10000.000000,
+ \"amountOrPercentage\":100.000000,
+ \"penalty\":false,
+ \"chargePaymentMode\":{
+ \"id\":0,
+ \"code\":\"chargepaymentmode.regular\",
+ \"value\":\"Regular\"
+ },
+ \"paid\":false,
+ \"waived\":false,
+ \"chargePayable\":false
+ }
+ ]"
+ }
+]
+
+
+
+
+
+
+
+
Batch requests in a single transaction
+
+
+ The Mifos X Batch API is also capable of executing all the requests in a single transaction, by setting a Query Parameter, "enclosingTransaction=true". So, if one or more of the requests in a batch returns an erroneous response all of the Data base transactions made by other successful requests will be rolled back.
+
+
+
+ If there has been a rollback in a transaction then a single response will be provided, with a '400' status code and a body consisting of the error details of the first failed request.
+
+
+
+
+
+POST https://DomainName/api/v1/batches?enclosingTransaction=true
+
+
+POST batches
+Content-Type: application/json Request Body:
+[
+ {
+ "requestId":1,
+ "relativeUrl":"clients",
+ "method":"POST",
+ "headers":[
+ {
+ "name":"Content-type",
+ "value":"text/html"
+ },
+ {
+ "name":"X-Mifos-Platform-TenantId",
+ "value":"text/html"
+ }
+ ],
+ "body":"{
+ \"officeId\": 1,
+ \"firstname\": \"Petra\",
+ \"lastname\": \"Yton\",
+ \"externalId\": \"externalId_4\",
+ \"dateFormat\": \"dd MMMM yyyy\",
+ \"locale\": \"en\", \"active\": true,
+ \"activationDate\": \"04 March 2009\",
+ \"submittedOnDate\": \"04 March 2009\"
+ }"
+ },
+ {
+ "requestId":2,
+ "relativeUrl":"savingsaccounts",
+ "method":"POST",
+ "reference":1,
+ "headers":[
+ {
+ "name":"Content-type",
+ "value":"text/html"
+ }
+ ],
+ "body":"{
+ \"clientId\": \"$.clientId\",
+ \"productId\": 1,
+ \"locale\": \"en\",
+ \"dateFormat\": \"dd MMMM yyyy\",
+ \"submittedOnDate\": \"01 March 2011\"
+ }"
+ }
+]
+
+
+Successful transaction response:
+[
+ {
+ "requestId":1,
+ "statusCode":200,
+ "headers":[
+ {
+ "name":"Content-type",
+ "value":"text/html"
+ },
+ {
+ "name":"X-Mifos-Platform-TenantId",
+ "value":"text/html"
+ }
+ ],
+ "body":"{\"officeId\":1,\"clientId\":922,\"resourceId\":922}"
+ },
+ {
+ "requestId":2,
+ "statusCode":200,
+ "headers":[
+ {
+ "name":"Content-type",
+ "value":"text/html"
+ }
+ ],
+ "body":"{\"officeId\":1,\"clientId\":922,\"savingsId\":116,\"resourceId\":116}"
+ }
+]
+
+
+
+
+
+
+
diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/BatchApiTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/BatchApiTest.java
index 5c4ea5579..409bca722 100644
--- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/BatchApiTest.java
+++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/BatchApiTest.java
@@ -203,13 +203,17 @@ public class BatchApiTest {
// Create a createClient Request
final BatchRequest br1 = BatchHelper.createClientRequest(4718L, "");
+ // Create a activateClient Request
+ final BatchRequest br2 = BatchHelper.activateClientRequest(4719L, 4718L);
+
// Create a ApplyLoan Request
- final BatchRequest br2 = BatchHelper.applyLoanRequest(4719L, 4718L, productId);
+ final BatchRequest br3 = BatchHelper.applyLoanRequest(4720L, 4719L, productId);
final List batchRequests = new ArrayList<>();
batchRequests.add(br1);
batchRequests.add(br2);
+ batchRequests.add(br3);
final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
@@ -244,13 +248,17 @@ public class BatchApiTest {
// Create a createClient Request
final BatchRequest br1 = BatchHelper.createClientRequest(4720L, "");
+ // Create a activateClient Request
+ final BatchRequest br2 = BatchHelper.activateClientRequest(4721L, 4720L);
+
// Create a applySavings Request
- final BatchRequest br2 = BatchHelper.applySavingsRequest(4721L, 4720L, productId);
+ final BatchRequest br3 = BatchHelper.applySavingsRequest(4722L, 4721L, productId);
final List batchRequests = new ArrayList<>();
batchRequests.add(br1);
batchRequests.add(br2);
+ batchRequests.add(br3);
final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
@@ -288,14 +296,14 @@ public class BatchApiTest {
// Create a createClient Request
final BatchRequest br1 = BatchHelper.createClientRequest(4722L, "");
- // Create a ApplyLoan Request
- final BatchRequest br2 = BatchHelper.applyLoanRequest(4723L, 4722L, productId);
+ // Create a activateClient Request
+ final BatchRequest br2 = BatchHelper.activateClientRequest(4723L, 4722L);
- // Create a createCharge Request
- final BatchRequest br3 = BatchHelper.createChargeRequest(4724L, 4723L);
+ // Create a ApplyLoan Request
+ final BatchRequest br3 = BatchHelper.applyLoanRequest(4724L, 4723L, productId);
// Create a Collect Charges Request
- final BatchRequest br4 = BatchHelper.collectChargesRequest(4725L, 4723L);
+ final BatchRequest br4 = BatchHelper.collectChargesRequest(4725L, 4724L);
final List batchRequests = new ArrayList<>();
@@ -309,7 +317,36 @@ public class BatchApiTest {
final List response = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, this.responseSpec,
jsonifiedRequest);
- Assert.assertEquals("Verify Status Code 200 for Create Charge", 200L, (long) response.get(2).getStatusCode());
- Assert.assertEquals("Verify Status Code 200 for Collect Charges", 200L, (long) response.get(3).getStatusCode());
+ Assert.assertEquals("Verify Status Code 200 for Create Loan Charge", 200L, (long) response.get(3).getStatusCode());
+ }
+
+ /**
+ * Test for the successful activation of a pending client using
+ * 'ActivateClientCommandStrategy'. A '200' status code is expected on
+ * successful activation.
+ *
+ * @see org.mifosplatform.batch.command.internal.ActivateClientCommandStrategy
+ */
+ @Test
+ public void shouldReturnOkStatusOnSuccessfulClientActivation() {
+
+ // Create a createClient Request
+ final BatchRequest br1 = BatchHelper.createClientRequest(4726L, "");
+
+ // Create a activateClient Request
+ final BatchRequest br2 = BatchHelper.activateClientRequest(4727L, 4726L);
+
+ final List batchRequests = new ArrayList<>();
+
+ batchRequests.add(br1);
+ batchRequests.add(br2);
+
+ final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
+
+ final List response = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, this.responseSpec,
+ jsonifiedRequest);
+
+ Assert.assertEquals("Verify Status Code 200 for Create Charge", 200L, (long) response.get(0).getStatusCode());
+ Assert.assertEquals("Verify Status Code 200 for Collect Charges", 200L, (long) response.get(1).getStatusCode());
}
}
\ No newline at end of file
diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/BatchHelper.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/BatchHelper.java
index 89e7bfafa..1219dce2d 100644
--- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/BatchHelper.java
+++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/BatchHelper.java
@@ -130,8 +130,7 @@ public class BatchHelper {
}
final String body = "{ \"officeId\": 1, \"firstname\": \"Petra\", \"lastname\": \"Yton\"," + "\"externalId\": " + extId
- + ", \"dateFormat\": \"dd MMMM yyyy\", \"locale\": \"en\","
- + "\"active\": true, \"activationDate\": \"04 March 2009\", \"submittedOnDate\": \"04 March 2009\"}";
+ + ", \"dateFormat\": \"dd MMMM yyyy\", \"locale\": \"en\"," + "\"active\": false, \"submittedOnDate\": \"04 March 2009\"}";
br.setBody(body);
@@ -244,7 +243,7 @@ public class BatchHelper {
/**
* Creates and returns a
* {@link org.mifosplatform.batch.command.internal.CollectChargesCommandStrategy}
- * Request with given requestId and reference
+ * Request with given requestId and reference.
*
* @param requestId
* @param reference
@@ -263,6 +262,29 @@ public class BatchHelper {
return br;
}
+ /**
+ * Creates and returns a
+ * {@link org.mifosplatform.batch.command.internal.ActivateClientCommandStrategy}
+ * Request with given requestId and reference.
+ *
+ *
+ * @param requestId
+ * @param reference
+ * @return BatchRequest
+ */
+ public static BatchRequest activateClientRequest(final Long requestId, final Long reference) {
+
+ final BatchRequest br = new BatchRequest();
+
+ br.setRequestId(requestId);
+ br.setRelativeUrl("clients/$.clientId?command=activate");
+ br.setReference(reference);
+ br.setMethod("POST");
+ br.setBody("{ \"locale\": \"en\", \"dateFormat\": \"dd MMMM yyyy\", \"activationDate\": \"01 March 2011\"}");
+
+ return br;
+ }
+
/**
* Checks that the client with given externalId is not created on the
* server.
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/batch/command/CommandStrategyProvider.java b/mifosng-provider/src/main/java/org/mifosplatform/batch/command/CommandStrategyProvider.java
index b864a0922..e2d05f28a 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/batch/command/CommandStrategyProvider.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/command/CommandStrategyProvider.java
@@ -74,6 +74,8 @@ public class CommandStrategyProvider {
this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/charges").method("POST").build(), "createChargeCommandStrategy");
this.commandStrategies
.put(CommandContext.resource("loans\\/\\d+\\/charges").method("GET").build(), "collectChargesCommandStrategy");
+ this.commandStrategies.put(CommandContext.resource("clients\\/\\d+\\?command=activate").method("POST").build(),
+ "activateClientCommandStrategy");
}
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/batch/command/internal/ActivateClientCommandStrategy.java b/mifosng-provider/src/main/java/org/mifosplatform/batch/command/internal/ActivateClientCommandStrategy.java
new file mode 100644
index 000000000..b501b7091
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/command/internal/ActivateClientCommandStrategy.java
@@ -0,0 +1,76 @@
+package org.mifosplatform.batch.command.internal;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.mifosplatform.batch.command.CommandStrategy;
+import org.mifosplatform.batch.domain.BatchRequest;
+import org.mifosplatform.batch.domain.BatchResponse;
+import org.mifosplatform.batch.exception.ErrorHandler;
+import org.mifosplatform.batch.exception.ErrorInfo;
+import org.mifosplatform.portfolio.client.api.ClientsApiResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implements {@link org.mifosplatform.batch.command.CommandStrategy} to handle
+ * activation of a pending client. It passes the contents of the body from the
+ * BatchRequest to
+ * {@link org.mifosplatform.portfolio.client.api.ClientsApiResource} and gets
+ * back the response. This class will also catch any errors raised by
+ * {@link org.mifosplatform.portfolio.client.api.ClientsApiResource} and map
+ * those errors to appropriate status codes in BatchResponse.
+ *
+ * @author Rishabh Shukla
+ *
+ * @see org.mifosplatform.batch.command.CommandStrategy
+ * @see org.mifosplatform.batch.domain.BatchRequest
+ * @see org.mifosplatform.batch.domain.BatchResponse
+ */
+@Component
+public class ActivateClientCommandStrategy implements CommandStrategy {
+
+ private final ClientsApiResource clientsApiResource;
+
+ @Autowired
+ public ActivateClientCommandStrategy(final ClientsApiResource clientsApiResource) {
+ this.clientsApiResource = clientsApiResource;
+ }
+
+ @Override
+ public BatchResponse execute(final BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) {
+
+ final BatchResponse response = new BatchResponse();
+ final String responseBody;
+
+ response.setRequestId(request.getRequestId());
+ response.setHeaders(request.getHeaders());
+
+ final String[] pathParameters = request.getRelativeUrl().split("/");
+ Long clientId = Long.parseLong(pathParameters[1].substring(0, pathParameters[1].indexOf("?")));
+
+ // Try-catch blocks to map exceptions to appropriate status codes
+ try {
+
+ // Calls 'activate' function from 'ClientsApiResource' to create a new
+ // client
+ responseBody = clientsApiResource.activate(clientId, "activate", request.getBody());
+
+ response.setStatusCode(200);
+ // Sets the body of the response after the successful activation of
+ // the client
+ response.setBody(responseBody);
+
+ } catch (RuntimeException e) {
+
+ // Gets an object of type ErrorInfo, containing information about
+ // raised exception
+ ErrorInfo ex = ErrorHandler.handler(e);
+
+ response.setStatusCode(ex.getStatusCode());
+ response.setBody(ex.getMessage());
+ }
+
+ return response;
+ }
+
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/batch/service/BatchApiServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/batch/service/BatchApiServiceImpl.java
index a5b7beef7..aa8775c0d 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/batch/service/BatchApiServiceImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/service/BatchApiServiceImpl.java
@@ -140,7 +140,6 @@ public class BatchApiServiceImpl implements BatchApiService {
childResponse.setRequestId(childRequest.getRequestId());
childResponse.setStatusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
childResponse.setBody(ex.getMessage());
-
}
childResponses.add(childResponse);
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/batch/service/ResolutionHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/batch/service/ResolutionHelper.java
index b060f0af9..5e2c38ef4 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/batch/service/ResolutionHelper.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/service/ResolutionHelper.java
@@ -141,13 +141,19 @@ public class ResolutionHelper {
if (relativeUrl.contains("$.")) {
+ String queryParams = "";
+ if(relativeUrl.contains("?")) {
+ queryParams = relativeUrl.substring(relativeUrl.indexOf("?"));
+ relativeUrl = relativeUrl.substring(0, relativeUrl.indexOf("?"));
+ }
+
final String[] parameters = relativeUrl.split("/");
-
+
for (String parameter : parameters) {
if (parameter.contains("$.")) {
final String resParamValue = responseJsonModel.get(parameter).toString();
relativeUrl = relativeUrl.replace(parameter, resParamValue);
- br.setRelativeUrl(relativeUrl);
+ br.setRelativeUrl(relativeUrl+queryParams);
}
}
}