diff --git a/mifosng-provider/build.gradle b/mifosng-provider/build.gradle index aeee5bc0d..37b91e4f9 100644 --- a/mifosng-provider/build.gradle +++ b/mifosng-provider/build.gradle @@ -24,9 +24,9 @@ apply plugin: 'tomcat' apply plugin: 'project-report' /* define the valid syntax level for source files */ -sourceCompatibility = JavaVersion.VERSION_1_6 +sourceCompatibility = JavaVersion.VERSION_1_7 /* define binary compatiblity version */ -targetCompatibility = JavaVersion.VERSION_1_6 +targetCompatibility = JavaVersion.VERSION_1_7 project.ext.springVersion = '3.2.5.RELEASE' project.ext.springSecurityVersion = '3.1.4.RELEASE' diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/BatchApiTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/BatchApiTest.java new file mode 100644 index 000000000..bb72f4ee1 --- /dev/null +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/BatchApiTest.java @@ -0,0 +1,109 @@ +package org.mifosplatform.integrationtests; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mifosplatform.batch.domain.BatchRequest; +import org.mifosplatform.batch.domain.BatchResponse; +import org.mifosplatform.integrationtests.common.BatchHelper; +import org.mifosplatform.integrationtests.common.Utils; + +import com.jayway.restassured.builder.RequestSpecBuilder; +import com.jayway.restassured.builder.ResponseSpecBuilder; +import com.jayway.restassured.http.ContentType; +import com.jayway.restassured.specification.RequestSpecification; +import com.jayway.restassured.specification.ResponseSpecification; + +/** + * Test class for {@link org.mifosplatform.batch.command.CommandStrategyProvider}. + * This tests the response provided by commandStrategy by injecting it with + * a {@code BatchRequest}. + * + * @author RishabhShukla + * + * @see org.mifosplatform.integrationtests.common.BatchHelper + * @see org.mifosplatform.batch.domain.BatchRequest + */ +public class BatchApiTest { + + private ResponseSpecification responseSpec; + private RequestSpecification requestSpec; + + public BatchApiTest() { + super(); + } + + /** + * Sets up the essential settings for the TEST like contentType, expectedStatusCode. + * It uses the '@Before' annotation provided by jUnit. + */ + @Before + public void setup() { + + Utils.initializeRESTAssured(); + this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + } + + //Tests for the unimplemented command Strategies by returning 501 status code + @Test + public void shouldReturnStatusNotImplementedUnknownCommand() { + + final BatchRequest br = new BatchRequest(); + br.setRequestId(4711L); + br.setRelativeUrl("/nirvana"); + br.setMethod("POST"); + + final List response = BatchHelper.createRequest(this.requestSpec, this.responseSpec, br); + + //Verify that only 501 is returned as the status code + for(BatchResponse resp : response) { + Assert.assertEquals("Verifying Status code 501",(long) 501,(long) resp.getStatusCode()); + } + } + + //Tests for the successful response for a createClient request from createClientCommand + @Test + public void shouldReturnOkStatusForCreateClientCommand() { + + final BatchRequest br = BatchHelper.createClientRequest(4712L, ""); + + final List response = BatchHelper.createRequest(this.requestSpec, this.responseSpec, br); + + //Verify that a 200 response is returned as the status code + for(BatchResponse resp : response) { + Assert.assertEquals("Verifying Status code 200",(long) 200,(long) resp.getStatusCode()); + } + } + + //Tests for an erroneous response with statusCode 501 if transaction fails + @Test + public void shouldRollBackAllTransactionsOnFailure() { + + //Create first client + final BatchRequest br1 = BatchHelper.createClientRequest(4713L, "TestExtId1"); + + //Create second client + final BatchRequest br2 = BatchHelper.createClientRequest(4714L, "TestExtId2"); + + //Create third client, having same externalID as second client, hence cause of error + final BatchRequest br3 = BatchHelper.createClientRequest(4715L, "TestExtId2"); + + final List batchRequests = new ArrayList<>(); + + batchRequests.add(br1); + batchRequests.add(br2); + batchRequests.add(br3); + + final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests); + final List response = BatchHelper.postBatchRequestsWithEnclosingTransaction(this.requestSpec, + this.responseSpec, jsonifiedRequest); + + Assert.assertEquals(response.size(), 1); + Assert.assertEquals("Verifying Status code 400",(long) 400,(long) response.get(0).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 new file mode 100644 index 000000000..0d11923f1 --- /dev/null +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/BatchHelper.java @@ -0,0 +1,138 @@ +package org.mifosplatform.integrationtests.common; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.mifosplatform.batch.domain.BatchRequest; +import org.mifosplatform.batch.domain.BatchResponse; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.jayway.restassured.specification.RequestSpecification; +import com.jayway.restassured.specification.ResponseSpecification; + +/** + * Helper class for {@link org.mifosplatform.integrationtests.BatchApiTest}. + * It takes care of creation of {@code BatchRequest} list and posting this + * list to the server. + * + * @author Rishabh Shukla + * + * @see org.mifosplatform.integrationtests.BatchApiTest + */ +public class BatchHelper { + + private static final String BATCH_API_URL = "/mifosng-provider/api/v1/batches?tenantIdentifier=default"; + private static final String BATCH_API_URL_EXT = BATCH_API_URL + "&enclosingTransaction=true"; + + private BatchHelper() { + super(); + } + + /** + * Returns a JSON String for a list of {@code BatchRequest}s + * + * @param batchRequests + * @return JSON String of BatchRequest + */ + public static String toJsonString(final List batchRequests) { + return new Gson().toJson(batchRequests); + } + + /** + * Returns the converted string response into JSON. + * + * @param json + * @return List + */ + private static List fromJsonString(final String json) { + return new Gson().fromJson(json, new TypeToken>(){}.getType()); + } + + + /** + * Returns a list of BatchResponse with query paramater enclosing transaction set to + * false by posting the jsonified BatchRequest to the server. + * + * @param requestSpec + * @param responseSpec + * @param jsonifiedBatchRequests + * @return a list of BatchResponse + */ + public static List postBatchRequestsWithoutEnclosingTransaction(final RequestSpecification requestSpec, + final ResponseSpecification responseSpec, final String jsonifiedBatchRequests) { + final String response = Utils.performServerPost(requestSpec, responseSpec, BATCH_API_URL, jsonifiedBatchRequests, null); + return BatchHelper.fromJsonString(response); + } + + /** + * Returns a list of BatchResponse with query paramater enclosing transaction set to + * true by posting the jsonified BatchRequest to the server. + * + * @param requestSpec + * @param responseSpec + * @param jsonifiedBatchRequests + * @return a list of BatchResponse + */ + public static List postBatchRequestsWithEnclosingTransaction(final RequestSpecification requestSpec, + final ResponseSpecification responseSpec, final String jsonifiedBatchRequests) { + final String response = Utils.performServerPost(requestSpec, responseSpec, BATCH_API_URL_EXT, jsonifiedBatchRequests, null); + return BatchHelper.fromJsonString(response); + } + + /** + * Creates a new Client Request as one of the request in Batch. + * + * @param reqId + * @param externalId + * @return BatchRequest + */ + public static BatchRequest createClientRequest(final Long reqId, final String externalId) { + + final BatchRequest br = new BatchRequest(); + br.setRequestId(reqId); + br.setRelativeUrl("clients"); + br.setMethod("POST"); + + final String extId; + if(externalId.equals("")) { + extId = "ext" + String.valueOf((10000 * Math.random())) + String.valueOf((10000 * Math.random())); + } + else { + extId = externalId; + } + + final String body = "{ \"officeId\": 1, \"firstname\": \"Petra\", \"lastname\": \"Yton\"," + + "\"externalId\": " + extId + ", \"dateFormat\": \"dd MMMM yyyy\", \"locale\": \"en\"," + + "\"active\": false, \"activationDate\": \"04 March 2009\", \"submittedOnDate\": \"04 March 2009\"}"; + + br.setBody(body); + + return br; + } + + /** + * Returns a BatchResponse based on the given BatchRequest, by posting the request + * to the server. + * + * @param BatchRequest + * @return List + */ + public static List createRequest(final RequestSpecification requestSpec, + final ResponseSpecification responseSpec,final BatchRequest br) { + + final List batchRequests = new ArrayList<>(); + batchRequests.add(br); + + final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests); + final List response = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(requestSpec, + responseSpec, jsonifiedRequest); + + //Verifies that the response result is there + Assert.assertNotNull(response); + Assert.assertTrue(response.size() > 0); + + return response; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/batch/api/BatchApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/batch/api/BatchApiResource.java new file mode 100644 index 000000000..44323d73a --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/api/BatchApiResource.java @@ -0,0 +1,118 @@ +package org.mifosplatform.batch.api; + +import java.util.ArrayList; +import java.util.List; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +import org.mifosplatform.batch.domain.BatchRequest; +import org.mifosplatform.batch.domain.BatchResponse; +import org.mifosplatform.batch.serialization.BatchRequestJsonHelper; +import org.mifosplatform.batch.service.BatchApiService; +import org.mifosplatform.infrastructure.core.serialization.ToApiJsonSerializer; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * Provides a REST resource for Batch Requests. This class acts as a proxy to + * {@link org.mifosplatform.batch.service.BatchApiService} and de-serializes + * the incoming JSON string to a list of {@link org.mifosplatform.batch.domain + * .BatchRequest} type. This list is forwarded to BatchApiService which finally + * returns a list of {@link org.mifosplatform.batch.domain.BatchResponse} type + * which is then serialized into JSON response by this Resource class. + * + * @author Rishabh Shukla + * + * @see org.mifosplatform.batch.service.BatchApiService + * @see org.mifosplatform.batch.domain.BatchRequest + * @see org.mifosplatform.batch.domain.BatchResponse + */ +@Path("/batches") +@Component +@Scope("singleton") +public class BatchApiResource { + + private final PlatformSecurityContext context; + @SuppressWarnings("rawtypes") + private final ToApiJsonSerializer toApiJsonSerializer; + private final BatchApiService service; + private final BatchRequestJsonHelper batchRequestJsonHelper; + + /** + * Constructs a 'BatchApiService' with context, toApiJsonSerializer, service + * and batchRequestJsonHelper. + * + * @param context + * @param toApiJsonSerializer + * @param service + * @param batchRequestJsonHelper + */ + @Autowired + public BatchApiResource(final PlatformSecurityContext context, @SuppressWarnings("rawtypes") + final ToApiJsonSerializer toApiJsonSerializer, final BatchApiService service, + final BatchRequestJsonHelper batchRequestJsonHelper) { + + this.context = context; + this.toApiJsonSerializer = toApiJsonSerializer; + this.service = service; + this.batchRequestJsonHelper = batchRequestJsonHelper; + } + + /** + * Rest assured POST method to get {@link BatchRequest} and returns back the + * consolidated {@link BatchResponse} + * + * @param jsonRequestString + * @return serialized JSON + */ + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String handleBatchRequests(@DefaultValue("false") @QueryParam("enclosingTransaction") + final boolean enclosingTransaction, final String jsonRequestString) { + + //Handles user authentication + this.context.authenticatedUser(); + + //Converts request array into BatchRequest List + final List requestList = this.batchRequestJsonHelper.extractList(jsonRequestString); + + //Gets back the consolidated BatchResponse from BatchApiservice + List result = new ArrayList<>(); + + //if the request is to be handled as a Transaction. All requests will be rolled back on error + if(enclosingTransaction) { + + try { + result = service.handleBatchRequestsWithEnclosingTransaction(requestList); + } + catch(RuntimeException e) { + + BatchResponse response = new BatchResponse(); + + response.setStatusCode(400); + response.setBody("One or more of your requests couldn't be completed because of errors" + + " - Rolling back the whole transaction\n\n" + e.toString()); + result.add(response); + + return this.toApiJsonSerializer.serialize(result); + + } + + } + else { + result = service.handleBatchRequestsWithoutEnclosingTransaction(requestList); + } + + return this.toApiJsonSerializer.serialize(result); + + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/batch/command/CommandContext.java b/mifosng-provider/src/main/java/org/mifosplatform/batch/command/CommandContext.java new file mode 100644 index 000000000..31a782e0e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/command/CommandContext.java @@ -0,0 +1,87 @@ +package org.mifosplatform.batch.command; + +/** + * Provides an object to {@link org.mifosplatform.batch.service.BatchApiService} to + * get the proper commandStrategy for each request in BatchRequest. It uses Builder + * pattern to create object of this type. + * + * @author Rishabh Shukla + * + * @see org.mifosplatform.batch.service.BatchApiService + */ +public class CommandContext { + + /** + * Static Builder class to provide a Build method for CommandContext. + * + * @author Rishabh Shukla + */ + public static class Builder{ + + private String resource; + private String method; + + private Builder(final String resource) { + this.resource = resource; + } + + public Builder method(final String method) { + this.method = method; + return this; + } + + public CommandContext build() { + return new CommandContext(this.resource, this.method); + } + + } + + private final String resource; + private final String method; + + private CommandContext(final String resource, final String method) { + + this.resource = resource; + this.method = method; + } + + public static Builder resource(final String resource) { + return new Builder(resource); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((this.method == null) ? 0 : this.method.hashCode()); + result = prime * result + + ((this.resource == null) ? 0 : this.resource.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CommandContext other = (CommandContext) obj; + if (this.method == null) { + if (other.method != null) + return false; + } else if (!this.method.equals(other.method)) + return false; + if (this.resource == null) { + if (other.resource != null) + return false; + } else if (!this.resource.equals(other.resource)) + return false; + return true; + } + + + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/batch/command/CommandStrategy.java b/mifosng-provider/src/main/java/org/mifosplatform/batch/command/CommandStrategy.java new file mode 100644 index 000000000..80d60a431 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/command/CommandStrategy.java @@ -0,0 +1,25 @@ +package org.mifosplatform.batch.command; + +import org.mifosplatform.batch.domain.BatchRequest; +import org.mifosplatform.batch.domain.BatchResponse; + +/** + * An interface for various Command Strategies. It contains a single function + * which returns appropriate response from a particular command strategy. + * + * @author Rishabh Shukla + * + * @see org.mifosplatform.batch.command.internal.UnknownCommandStrategy + */ +public interface CommandStrategy { + + /** + * Returns an object of type {@link org.mifosplatform.batch.domain.BatchResponse}. + * This takes {@link org.mifosplatform.batch.domain.BatchRequest} as it's single + * argument and provides appropriate response. + * + * @param batchRequest + * @return BatchResponse + */ + public BatchResponse execute(BatchRequest batchRequest); +} 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 new file mode 100644 index 000000000..1190b7b58 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/command/CommandStrategyProvider.java @@ -0,0 +1,67 @@ +package org.mifosplatform.batch.command; + +import java.util.concurrent.ConcurrentHashMap; + +import org.mifosplatform.batch.command.internal.UnknownCommandStrategy; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +/** + * Provides an appropriate CommandStrategy using the 'method' and 'resourceUrl'. + * CommandStrategy bean is created using Spring Application Context. + * + * @author Rishabh Shukla + * + * @see org.mifosplatform.batch.command.internal.UnknownCommandStrategy + */ +@Component +public class CommandStrategyProvider { + + private final ApplicationContext applicationContext; + private final ConcurrentHashMap commandStrategies = new ConcurrentHashMap<>(); + + /** + * Constructs a CommandStrategyProvider with argument of ApplicationContext type. + * It also initialize commandStrategies using init() function by filling it + * with available CommandStrategies in {@link org.mifosplatform.batch.command.internal}. + * + * @param applicationContext + */ + @Autowired + public CommandStrategyProvider(final ApplicationContext applicationContext) { + + //calls init() function of this class. + init(); + + this.applicationContext = applicationContext; + } + + /** + * Returns an appropriate commandStrategy after determining it using + * the CommandContext of the request. If no such Strategy is found then + * a default strategy is returned back. + * + * @param commandContext + * @return CommandStrategy + * @see org.mifosplatform.batch.command.internal.UnknownCommandStrategy + */ + public CommandStrategy getCommandStrategy(final CommandContext commandContext) { + + if(this.commandStrategies.containsKey(commandContext)) { + return (CommandStrategy) this.applicationContext.getBean(this.commandStrategies.get(commandContext)); + } + + return new UnknownCommandStrategy(); + } + + /** + * Contains various available command strategies in {@link org.mifosplatform.batch.command.internal}. + * Any new command Strategy will have to be added within this function in order to initiate it + * within the constructor. + */ + private void init() { + this.commandStrategies.put(CommandContext.resource("clients").method("POST").build(), "CreateClientCommandStrategy"); + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/batch/command/internal/CreateClientCommandStrategy.java b/mifosng-provider/src/main/java/org/mifosplatform/batch/command/internal/CreateClientCommandStrategy.java new file mode 100644 index 000000000..bcf4c10ab --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/command/internal/CreateClientCommandStrategy.java @@ -0,0 +1,84 @@ +package org.mifosplatform.batch.command.internal; + +import org.mifosplatform.batch.command.CommandStrategy; +import org.mifosplatform.batch.domain.BatchRequest; +import org.mifosplatform.batch.domain.BatchResponse; +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; +import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; +import org.mifosplatform.infrastructure.core.exception.PlatformInternalServerException; +import org.mifosplatform.infrastructure.core.exception.UnsupportedParameterException; +import org.mifosplatform.portfolio.client.api.ClientsApiResource; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implements {@link org.mifosplatform.batch.command.CommandStrategy} to handle + * creation of a new 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 + */ +public class CreateClientCommandStrategy implements CommandStrategy{ + + private final ClientsApiResource clientsApiResource; + + @Autowired + public CreateClientCommandStrategy(final ClientsApiResource clientsApiResource) { + this.clientsApiResource = clientsApiResource; + } + + @Override + public BatchResponse execute(final BatchRequest request) { + + final BatchResponse response = new BatchResponse(); + final String responseBody; + + // try-catch blocks to map exceptions to appropriate status codes + try { + + //calls 'create' function from 'ClientsApiResource' to create a new client + responseBody = clientsApiResource.create(request.getBody()); + + response.setRequestId(request.getRequestId()); + response.setHeaders(request.getHeaders()); + response.setStatusCode(200); + //sets the body of the response after the successful creation of the client + response.setBody(responseBody); + + } catch (AbstractPlatformResourceNotFoundException e) { + + response.setStatusCode(404); + response.setBody("error : " + e.toString()); + + } catch (UnsupportedParameterException e) { + + response.setStatusCode(400); + response.setBody("error : " + e.toString()); + + } catch(PlatformApiDataValidationException e) { + + response.setStatusCode(400); + response.setBody("error : " + e.toString()); + + } catch (PlatformDataIntegrityException e) { + + response.setStatusCode(403); + response.setBody("error : " + e.toString()); + + } catch (PlatformInternalServerException e) { + + response.setStatusCode(500); + response.setBody("error : " + e.toString()); + } + + return response; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/batch/command/internal/UnknownCommandStrategy.java b/mifosng-provider/src/main/java/org/mifosplatform/batch/command/internal/UnknownCommandStrategy.java new file mode 100644 index 000000000..21d783d10 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/command/internal/UnknownCommandStrategy.java @@ -0,0 +1,29 @@ +package org.mifosplatform.batch.command.internal; + +import org.mifosplatform.batch.command.CommandStrategy; +import org.mifosplatform.batch.domain.BatchRequest; +import org.mifosplatform.batch.domain.BatchResponse; + +/** + * Provides a default CommandStrategy by implementing + * {@link org.mifosplatform.batch.command.CommandStrategy} + * in case there is no appropriate command strategy with requested + * 'method' and 'resoureUrl'. + * + * @author Rishabh Shukla + */ +public class UnknownCommandStrategy implements CommandStrategy { + + @Override + public BatchResponse execute(BatchRequest batchRequest) { + + final BatchResponse batchResponse = new BatchResponse(); + + batchResponse.setRequestId(batchRequest.getRequestId()); + batchResponse.setStatusCode(501); + + return batchResponse; + } + + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/batch/domain/BatchRequest.java b/mifosng-provider/src/main/java/org/mifosplatform/batch/domain/BatchRequest.java new file mode 100644 index 000000000..5132031bc --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/domain/BatchRequest.java @@ -0,0 +1,165 @@ +package org.mifosplatform.batch.domain; + +import java.util.Set; + +/** + * Provides an object for separate HTTP requests in the Batch Request for Batch API. + * A requestId is also included as data field which takes care of dependency issues + * among various requests. This class also provides getter and setter functions to + * access Batch Request data fields. + * + * @author Rishabh Shukla + * + * @see org.mifosplatform.batch.api.BatchApiResource + * @see Header + */ +public class BatchRequest { + + private Long requestId; + private String relativeUrl; + private String method; + private Set
headers; + private Long reference; + private String body; + + /** + * Constructs a 'BatchRequest' with requestId, relativeUrl, method, headers, reference and + * body of the incoming request. + * + * @param requestId of HTTP request. + * @param relativeUrl of HTTP request. + * @param method of HTTP request. + * @param headers of HTTP request. + * @param reference of HTTP request. + * @param body of HTTP request. + * + * @see Header + */ + public BatchRequest(Long requestId, String relativeUrl, String method, Set
headers, + Long reference, String body) { + + this.requestId = requestId; + this.relativeUrl = relativeUrl; + this.method = method; + this.headers = headers; + this.reference = reference; + this.body = body; + } + + /** + * Constructs a default constructor of 'BatchRequest' + */ + public BatchRequest() { + + } + + /** + * Returns the value of 'requestId' of an object of this class. + * + * @return requestId of the HTTP request. + */ + public Long getRequestId() { + return this.requestId; + } + + /** + * Sets the value of 'requestId' of an object of this class. + * + * @param requestId + */ + public void setRequestId(Long requestId) { + this.requestId = requestId; + } + + /** + * Returns the value of 'relativeUrl' of an object of this class. + * + * @return relativeUrl of the HTTP request. + */ + public String getRelativeUrl() { + return this.relativeUrl; + } + + /** + * Sets the value of 'relativeUrl' of an object of this class. + * + * @param relativeUrl + */ + public void setRelativeUrl(String relativeUrl) { + this.relativeUrl = relativeUrl; + } + + /** + * Returns the value of 'method' of an object of this class. + * + * @return method of the HTTP request. + */ + public String getMethod() { + return this.method; + } + + /** + * Sets the value of 'method' of the object of this class. + * + * @param method + */ + public void setMethod(String method) { + this.method = method; + } + + /** + * Returns the values of 'headers' of {@link Header} type of an object of this class. + * + * @return headers of the HTTP request. + * @see Header + */ + public Set
getHeaders() { + return this.headers; + } + + /** + * Sets the values of 'headers' of {@link Header} type of an object of this class. + * + * @param headers + * @see Header + */ + public void setHeaders(Set
headers) { + this.headers = headers; + } + + /** + * Returns the value of 'reference' of an object of this class + * + * @return reference of the HTTP request + */ + public Long getReference() { + return this.reference; + } + + /** + * Sets the value of 'reference' of an object of this class. + * + * @param reference + */ + public void setReference(Long reference) { + this.reference = reference; + } + + /** + * Returns the value of 'body' of an object of this class. + * + * @return body of the HTTP request. + */ + public String getBody() { + return this.body; + } + + /** + * Sets the value of 'body' of an object of this class. + * + * @param body + */ + public void setBody(String body) { + this.body = body; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/batch/domain/BatchResponse.java b/mifosng-provider/src/main/java/org/mifosplatform/batch/domain/BatchResponse.java new file mode 100644 index 000000000..74a1f845e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/domain/BatchResponse.java @@ -0,0 +1,120 @@ +package org.mifosplatform.batch.domain; + +import java.util.Set; + +/** + * Provides an object for separate HTTP responses in the Batch Response for Batch API. + * It contains all the information about a particular HTTP response in the Batch Response. + * Getter and Setter functions are also included to access response data fields. + * + * @author Rishabh Shukla + * + * @see org.mifosplatform.batch.api.BatchApiResource + * @see org.mifosplatform.batch.service.BatchApiService + * @see Header + */ +public class BatchResponse { + + private Long requestId; + private Integer statusCode; + private Set
headers; + private String body; + + /** + * Constructs a 'BatchResponse' with requestId, statusCode, headers + * and body of the HTTP requests. + * + * @param requestId + * @param statusCode + * @param headers + * @param body + * @see Header + */ + public BatchResponse(Long requestId, Integer statusCode, Set
headers, String body) { + this.requestId = requestId; + this.statusCode = statusCode; + this.headers = headers; + this.body = body; + } + + /** + * Constructs a default constructor of 'BatchResponse' + */ + public BatchResponse() { + + } + + /** + * Returns the 'requestId' of an object of this class. + * + * @return requestId of the HTTP request. + */ + public Long getRequestId() { + return this.requestId; + } + + /** + * Sets the value of 'requestId' of an object of this class. + * + * @param requestId + */ + public void setRequestId(Long requestId) { + this.requestId = requestId; + } + + /** + * Returns the 'statusCode' of an object of this class. + * + * @return statusCode of the HTTP request. + */ + public Integer getStatusCode() { + return this.statusCode; + } + + /** + * Sets the value of 'statusCode' of an object of this class. + * + * @param statusCode + */ + public void setStatusCode(Integer statusCode) { + this.statusCode = statusCode; + } + + /** + * Returns the 'headers' of {@link Header} type of an object of this class. + * + * @return headers of the HTTP request. + * @see Header + */ + public Set
getHeaders() { + return this.headers; + } + + /** + * Sets the value of 'headers' of {@link Header} type of an object of this class. + * + * @param headers of {@link Header} Type + * @see Header + */ + public void setHeaders(Set
headers) { + this.headers = headers; + } + + /** + * Returns the 'body' of an object of this class. + * + * @return body of the HTTP request. + */ + public String getBody() { + return this.body; + } + + /** + * Sets the value of 'body' of an object of this class. + * + * @param body + */ + public void setBody(String body) { + this.body = body; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/batch/domain/Header.java b/mifosng-provider/src/main/java/org/mifosplatform/batch/domain/Header.java new file mode 100644 index 000000000..ecd7824bf --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/domain/Header.java @@ -0,0 +1,72 @@ +package org.mifosplatform.batch.domain; + +/** + * Provides an object to handle HTTP headers as name and value pairs for Batch API. + * It is used in {@link BatchRequest} and {@link BatchResponse} to store the + * information regarding the headers in incoming and outgoing JSON Strings. + * + * @author Rishabh Shukla + * + * @see BatchRequest + * @see BatchResponse + */ +public class Header { + + private String name; + private String value; + + /** + * Constructs a 'Header' with the name and value of HTTP headers. + * + * @param name of the HTTP header. + * @param value of the HTTP header. + */ + public Header(String name, String value) { + this.name = name; + this.value = value; + } + + /** + * Constructs a default constructor of 'Header' + */ + public Header() { + + } + + /** + * Returns the 'name' data field of the object of this class. + * + * @return name data field of this class + */ + public String getName() { + return this.name; + } + + /** + * Sets 'name' data field of this class. + * + * @param name + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the 'value' data field of the object of this class. + * + * @return value data field of this class + */ + public String getValue() { + return this.value; + } + + /** + * Sets 'value' data field of this class. + * + * @param value + */ + public void setValue(String value) { + this.value = value; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/batch/serialization/BatchRequestJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/batch/serialization/BatchRequestJsonHelper.java new file mode 100644 index 000000000..1488e3ea2 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/serialization/BatchRequestJsonHelper.java @@ -0,0 +1,37 @@ +package org.mifosplatform.batch.serialization; + +import java.lang.reflect.Type; +import java.util.List; + +import org.mifosplatform.batch.domain.BatchRequest; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.springframework.stereotype.Component; + +import com.google.gson.reflect.TypeToken; + +/** + * Extends {@link org.mifosplatform.infrastructure.core.serialization.FromJsonHelper} to + * de-serialize the incoming String into a JSON List of type + * {@link org.mifosplatform.batch.domain.BatchRequest} + * + * @author Rishabh Shukla + * + * @see org.mifosplatform.batch.domain.BatchRequest + * @see org.mifosplatform.infrastructure.core.serialization.FromJsonHelper + */ +@Component +public class BatchRequestJsonHelper extends FromJsonHelper { + + /** + * Returns a list of batchRequests after de-serializing it from the input + * JSON string. + * + * @param json + * @return List + */ + public List extractList(final String json) { + final Type listType = new TypeToken>(){}.getType(); + final List requests = super.getGsonConverter().fromJson(json, listType); + return requests; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/batch/service/BatchApiService.java b/mifosng-provider/src/main/java/org/mifosplatform/batch/service/BatchApiService.java new file mode 100644 index 000000000..0d0c33d05 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/service/BatchApiService.java @@ -0,0 +1,41 @@ +package org.mifosplatform.batch.service; + +import java.util.List; + +import org.mifosplatform.batch.domain.BatchRequest; +import org.mifosplatform.batch.domain.BatchResponse; + +/** + * Provides an interface for service class, that implements the method to + * handle separate Batch Requests. + * + * @author Rishabh Shukla + * + * @see org.mifosplatform.batch.domain.BatchRequest + * @see org.mifosplatform.batch.domain.BatchResponse + * @see BatchApiServiceImpl + */ +public interface BatchApiService { + + /** + * Returns a list of {@link org.mifosplatform.batch.domain.BatchResponse}s by getting + * the appropriate CommandStrategy for every {@link org.mifosplatform.batch.domain.BatchRequest}. + * It will be used when the Query Parameter "enclosingTransaction "is set to 'false'. + * + * @param requestList + * @return List + */ + List handleBatchRequestsWithoutEnclosingTransaction(List requestList); + + /** + * returns a list of {@link org.mifosplatform.batch.domain.BatchResponse}s by getting + * the appropriate CommandStrategy for every {@link org.mifosplatform.batch.domain.BatchRequest}. + * It will be used when the Query Parameter "enclosingTransaction "is set to 'true'. If one or + * more of the requests are not completed properly then whole of the transaction will be rolled back + * properly. + * + * @param requestList + * @return List + */ + List handleBatchRequestsWithEnclosingTransaction(List requestList); +} 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 new file mode 100644 index 000000000..96604924f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/batch/service/BatchApiServiceImpl.java @@ -0,0 +1,72 @@ +package org.mifosplatform.batch.service; + +import java.util.ArrayList; +import java.util.List; + +import org.mifosplatform.batch.command.CommandContext; +import org.mifosplatform.batch.command.CommandStrategy; +import org.mifosplatform.batch.command.CommandStrategyProvider; +import org.mifosplatform.batch.domain.BatchRequest; +import org.mifosplatform.batch.domain.BatchResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * Implementation for {@link BatchApiService} to iterate through all the incoming + * requests and obtain the appropriate CommandStrategy from CommandStrategyProvider. + * + * @author Rishabh Shukla + * + * @see org.mifosplatform.batch.domain.BatchRequest + * @see org.mifosplatform.batch.domain.BatchResponse + * @see org.mifosplatform.batch.command.CommandStrategyProvider + */ +@Service +public class BatchApiServiceImpl implements BatchApiService{ + + private final CommandStrategyProvider strategyProvider; + + /** + * Constructs a 'BatchApiServiceImpl' with an argument of + * {@link org.mifosplatform.batch.command.CommandStrategyProvider} type. + * + * @param strategyProvider + */ + @Autowired + public BatchApiServiceImpl(final CommandStrategyProvider strategyProvider) { + this.strategyProvider = strategyProvider; + } + + private List handleBatchRequests(final List requestList) { + + final List responseList = new ArrayList<>(requestList.size()); + + for(final BatchRequest br: requestList) { + + final CommandStrategy commandStrategy = this.strategyProvider.getCommandStrategy(CommandContext. + resource(br.getRelativeUrl()).method(br.getMethod()).build()); + + final BatchResponse response = commandStrategy.execute(br); + + responseList.add(response); + } + + return responseList; + } + + @Override + public List handleBatchRequestsWithoutEnclosingTransaction(final + List requestList) { + + return handleBatchRequests(requestList); + } + + @Transactional + @Override + public List handleBatchRequestsWithEnclosingTransaction( final + List requestList) { + + return handleBatchRequests(requestList); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java index e1daac334..dfdfbb825 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java @@ -21,6 +21,7 @@ import org.joda.time.LocalDate; import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.UnsupportedParameterException; +import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import com.google.gson.Gson; @@ -29,6 +30,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +@Primary @Component public class FromJsonHelper { @@ -224,4 +226,9 @@ public class FromJsonHelper { public String extractMonthDayFormatParameter(final JsonObject element) { return this.helperDelegator.extractMonthDayFormatParameter(element); } + + public Gson getGsonConverter() { + return this.gsonConverter; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/resources/META-INF/spring/appContext.xml b/mifosng-provider/src/main/resources/META-INF/spring/appContext.xml index 157d7912c..9a9d760d1 100644 --- a/mifosng-provider/src/main/resources/META-INF/spring/appContext.xml +++ b/mifosng-provider/src/main/resources/META-INF/spring/appContext.xml @@ -34,11 +34,14 @@ org.mifosplatform.mix.*, org.mifosplatform.template.*, org.mifosplatform.template.service.*, - org.mifosplatform.useradministration.*"> + org.mifosplatform.useradministration.*, + org.mifosplatform.batch.*"> + @@ -52,6 +55,7 @@ + diff --git a/mifosng-provider/src/test/java/org/mifosplatform/batch/BatchBuilder.java b/mifosng-provider/src/test/java/org/mifosplatform/batch/BatchBuilder.java new file mode 100644 index 000000000..f7f2be124 --- /dev/null +++ b/mifosng-provider/src/test/java/org/mifosplatform/batch/BatchBuilder.java @@ -0,0 +1,43 @@ +package org.mifosplatform.batch; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.mifosplatform.batch.domain.BatchRequest; +import org.mifosplatform.batch.domain.BatchResponse; +import org.mifosplatform.batch.service.BatchApiService; +import org.mockito.Mockito; + +/** + * Runs a unit test for BatchApiResource by mocking the + * BatchRequest and BatchApiService objects. + * + * @author Rishabh Shukla + */ +public class BatchBuilder { + + //Verify a non-empty response by BatchApiResource. + @Test + public void batchApiTest() { + + //Mock a BatchRequest objects + final BatchRequest batchTest = Mockito.mock(BatchRequest.class); + + //Mock a BatchApiService object + final BatchApiService serviceTest = Mockito.mock(BatchApiService.class); + + final List requestList = new ArrayList<>(); + requestList.add(batchTest); + + //Call the BatchApiService using mocked objects + final List result = serviceTest.handleBatchRequestsWithoutEnclosingTransaction(requestList); + + //Verifies whether handleBatchRequests() function of BatchApiService was called + Mockito.verify(serviceTest).handleBatchRequestsWithoutEnclosingTransaction(requestList); + + //Verifies a non-empty response by the BatchApiResource + Assert.assertNotNull(result); + } +}