From 747305cfc9e6e43f83a903828c60afae12507844 Mon Sep 17 00:00:00 2001 From: rishy Date: Thu, 17 Jul 2014 12:19:45 +0530 Subject: [PATCH] api docs for Batch API, added Client activation command --- api-docs/apiLive.htm | 337 +++++++++++++++++- .../integrationtests/BatchApiTest.java | 55 ++- .../integrationtests/common/BatchHelper.java | 28 +- .../command/CommandStrategyProvider.java | 2 + .../ActivateClientCommandStrategy.java | 76 ++++ .../batch/service/BatchApiServiceImpl.java | 1 - .../batch/service/ResolutionHelper.java | 10 +- 7 files changed, 493 insertions(+), 16 deletions(-) create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/batch/command/internal/ActivateClientCommandStrategy.java 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 @@
  • Creating and Updating
  • Updating Dates and Numbers
  • Field Descriptions
  • -
  • Authentication Overview
  • +
  • Authentication Overview
  • Errors
  • +
  • Batch API
  • Full API Matrix
  • Beta API Matrix
  • Payment Application Logic / Transaction Processing Strategies
  • @@ -2654,6 +2655,340 @@ function executeAjaxRequest(url, verbType, jsonData, basicAuthKey, successFuncti +   +
    +
    +

    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}" + } +] + + +
    + +
    + +   +
    +
    +

    Batch API Errors

    + +

    + In Batch API without "enclosingTransaction=true", if one of the response is erroneous, then an appropriate status code will be set for that request and the error message will be returned in it's "body", while all other requests will return successful response with a status code of "200". +

    + +
    + +
     
    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); } } }