Merge pull request #932 from rishy/Batch-API

Batch-API added with 'Create Client Command' and transaction scope
This commit is contained in:
Vishwas Babu A J
2014-06-21 22:45:35 -07:00
18 changed files with 1221 additions and 3 deletions
+2 -2
View File
@@ -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'
@@ -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<BatchResponse> 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<BatchResponse> 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<BatchRequest> batchRequests = new ArrayList<>();
batchRequests.add(br1);
batchRequests.add(br2);
batchRequests.add(br3);
final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
final List<BatchResponse> 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());
}
}
@@ -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<BatchRequest> batchRequests) {
return new Gson().toJson(batchRequests);
}
/**
* Returns the converted string response into JSON.
*
* @param json
* @return List<BatchResponse>
*/
private static List<BatchResponse> fromJsonString(final String json) {
return new Gson().fromJson(json, new TypeToken<List<BatchResponse>>(){}.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<BatchResponse> 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<BatchResponse> 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<BatchResponse>
*/
public static List<BatchResponse> createRequest(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec,final BatchRequest br) {
final List<BatchRequest> batchRequests = new ArrayList<>();
batchRequests.add(br);
final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
final List<BatchResponse> response = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(requestSpec,
responseSpec, jsonifiedRequest);
//Verifies that the response result is there
Assert.assertNotNull(response);
Assert.assertTrue(response.size() > 0);
return response;
}
}
@@ -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<BatchRequest> requestList = this.batchRequestJsonHelper.extractList(jsonRequestString);
//Gets back the consolidated BatchResponse from BatchApiservice
List<BatchResponse> 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);
}
}
@@ -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;
}
}
@@ -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);
}
@@ -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<CommandContext, String> 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");
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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<Header> 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<Header> 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<Header> 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<Header> 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;
}
}
@@ -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<Header> 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<Header> 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<Header> 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<Header> 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;
}
}
@@ -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;
}
}
@@ -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<BatchRequest>
*/
public List<BatchRequest> extractList(final String json) {
final Type listType = new TypeToken<List<BatchRequest>>(){}.getType();
final List<BatchRequest> requests = super.getGsonConverter().fromJson(json, listType);
return requests;
}
}
@@ -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<BatchResponse>
*/
List<BatchResponse> handleBatchRequestsWithoutEnclosingTransaction(List<BatchRequest> 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<BatchResponse>
*/
List<BatchResponse> handleBatchRequestsWithEnclosingTransaction(List<BatchRequest> requestList);
}
@@ -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<BatchResponse> handleBatchRequests(final List<BatchRequest> requestList) {
final List<BatchResponse> 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<BatchResponse> handleBatchRequestsWithoutEnclosingTransaction(final
List<BatchRequest> requestList) {
return handleBatchRequests(requestList);
}
@Transactional
@Override
public List<BatchResponse> handleBatchRequestsWithEnclosingTransaction( final
List<BatchRequest> requestList) {
return handleBatchRequests(requestList);
}
}
@@ -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;
}
}
@@ -34,11 +34,14 @@
org.mifosplatform.mix.*,
org.mifosplatform.template.*,
org.mifosplatform.template.service.*,
org.mifosplatform.useradministration.*">
org.mifosplatform.useradministration.*,
org.mifosplatform.batch.*">
<context:exclude-filter expression="org.springframework.stereotype.Controller"
type="annotation" />
</context:component-scan>
<bean id="CreateClientCommandStrategy"
class="org.mifosplatform.batch.command.internal.CreateClientCommandStrategy"/>
<bean id="auditorAware"
class="org.mifosplatform.infrastructure.core.domain.AuditorAwareImpl" />
<jpa:auditing auditor-aware-ref="auditorAware" />
@@ -52,6 +55,7 @@
<jpa:repositories base-package="org.mifosplatform.mix.domain" />
<jpa:repositories base-package="org.mifosplatform.scheduledjobs.domain" />
<jpa:repositories base-package="org.mifosplatform.template.domain" />
<jpa:repositories base-package="org.mifosplatform.batch.domain"/>
<import resource="infrastructure.xml" />
@@ -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<BatchRequest> requestList = new ArrayList<>();
requestList.add(batchTest);
//Call the BatchApiService using mocked objects
final List<BatchResponse> 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);
}
}