use google gson to support translating json request body into a map. This is towards supporting more robust update of attributes wheter they be optional or mandatory.

This commit is contained in:
Keith Woodlock
2012-07-17 19:17:58 +01:00
parent 5e6e52eebe
commit ae7d78a16e
18 changed files with 633 additions and 329 deletions
+2
View File
@@ -89,6 +89,8 @@ dependencies {
[group: 'com.sun.jersey.contribs', name: 'jersey-spring', version: jerseyVersion],
[group: 'com.sun.jersey.contribs', name: 'jersey-multipart', version: jerseyVersion],
[group: 'com.google.code.gson', name: 'gson', version: '2.2.2'],
[group: 'org.springframework.data', name: 'spring-data-jpa', version: springDataJpaVersion],
[group: 'joda-time', name: 'joda-time', version: '2.1'],
@@ -1,6 +1,8 @@
package org.mifosng.platform.api;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@@ -24,6 +26,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
@Path("/funds")
@Component
@Scope("singleton")
@@ -53,9 +58,25 @@ public class FundsApiResource {
}
@POST
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON })
public Response createFund(final FundCommand command) {
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public Response createFund(final String jsonRequestBody) {
Gson gson = new Gson();
Type typeOfMap = new TypeToken<Map<String, String>>(){}.getType();
Map<String, String> requestMap = gson.fromJson(jsonRequestBody, typeOfMap);
String name = null;
if (requestMap.containsKey("name")) {
name = requestMap.get("name");
}
String externalId = null;
if (requestMap.containsKey("externalId")) {
externalId = requestMap.get("externalId");
}
FundCommand command = new FundCommand(name, externalId);
Long fundId = this.writePlatformService.createFund(command);
@@ -77,10 +98,25 @@ public class FundsApiResource {
@PUT
@Path("{fundId}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON })
public Response updateFund(@PathParam("fundId") final Long fundId, final FundCommand command) {
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public Response updateFund(@PathParam("fundId") final Long fundId, final String jsonRequestBody) {
Gson gson = new Gson();
Type typeOfMap = new TypeToken<Map<String, String>>(){}.getType();
Map<String, String> requestMap = gson.fromJson(jsonRequestBody, typeOfMap);
String name = null;
if (requestMap.containsKey("name")) {
name = requestMap.get("name");
}
String externalId = null;
if (requestMap.containsKey("externalId")) {
externalId = requestMap.get("externalId");
}
FundCommand command = new FundCommand(name, externalId);
command.setId(fundId);
Long entityId = this.writePlatformService.updateFund(command);
@@ -1,8 +1,6 @@
package org.mifosng.platform.api;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Locale;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@@ -49,40 +47,23 @@ public class LoanProductApiResource {
private ApiJSONFormattingService jsonFormattingService;
@POST
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({ MediaType.APPLICATION_JSON })
public Response createLoanProduct(LoanProductCommand command) {
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public Response createLoanProduct(final String jsonRequestBody) {
Locale clientApplicationLocale = this.apiDataConversionService.localeFromString(command.getLocale());
final LoanProductCommand command = this.apiDataConversionService.convertJsonToCommand(null, jsonRequestBody);
BigDecimal principalValue = this.apiDataConversionService.convertFrom(command.getPrincipal(), "principal", clientApplicationLocale);
BigDecimal inArrearsToleranceValue = this.apiDataConversionService.convertFrom(command.getInArrearsTolerance(), "inArrearsTolerance", clientApplicationLocale);
BigDecimal interestRatePerPeriodValue = this.apiDataConversionService.convertFrom(command.getInterestRatePerPeriod(),"interestRatePerPeriod", clientApplicationLocale);
Integer digitsAfterDecimalValue = this.apiDataConversionService.convertToInteger(command.getDigitsAfterDecimal(), "digitsAfterDecimal", clientApplicationLocale);
Integer repaymentEveryValue = this.apiDataConversionService.convertToInteger(command.getRepaymentEvery(), "repaymentEvery", clientApplicationLocale);
Integer numberOfRepaymentsValue = this.apiDataConversionService.convertToInteger(command.getNumberOfRepayments(), "numberOfRepayments", clientApplicationLocale);
command.setPrincipalValue(principalValue);
command.setInArrearsToleranceValue(inArrearsToleranceValue);
command.setInterestRatePerPeriodValue(interestRatePerPeriodValue);
command.setDigitsAfterDecimalValue(digitsAfterDecimalValue);
command.setRepaymentEveryValue(repaymentEveryValue);
command.setNumberOfRepaymentsValue(numberOfRepaymentsValue);
EntityIdentifier entityIdentifier = this.loanProductWritePlatformService
.createLoanProduct(command);
EntityIdentifier entityIdentifier = this.loanProductWritePlatformService.createLoanProduct(command);
return Response.ok().entity(entityIdentifier).build();
}
@GET
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAllLoanProducts(@Context UriInfo uriInfo) {
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public String retrieveAllLoanProducts(@Context final UriInfo uriInfo) {
Collection<LoanProductData> products = this.loanProductReadPlatformService
.retrieveAllLoanProducts();
Collection<LoanProductData> products = this.loanProductReadPlatformService.retrieveAllLoanProducts();
String selectedFields = "";
return this.jsonFormattingService.convertRequest(products, filterName,
@@ -96,12 +77,11 @@ public class LoanProductApiResource {
*/
@GET
@Path("template")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveNewLoanProductDetails(@Context UriInfo uriInfo) {
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public String retrieveNewLoanProductDetails(@Context final UriInfo uriInfo) {
LoanProductData loanProduct = this.loanProductReadPlatformService
.retrieveNewLoanProductDetails();
LoanProductData loanProduct = this.loanProductReadPlatformService.retrieveNewLoanProductDetails();
String selectedFields = defaultFieldList + "," + allowedFieldList;
return this.jsonFormattingService.convertRequest(loanProduct,
@@ -111,14 +91,11 @@ public class LoanProductApiResource {
@GET
@Path("{productId}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveLoanProductDetails(
@PathParam("productId") final Long productId,
@Context UriInfo uriInfo) {
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public String retrieveLoanProductDetails(@PathParam("productId") final Long productId, final UriInfo uriInfo) {
LoanProductData loanProduct = this.loanProductReadPlatformService
.retrieveLoanProduct(productId);
LoanProductData loanProduct = this.loanProductReadPlatformService.retrieveLoanProduct(productId);
String selectedFields = "";
return this.jsonFormattingService.convertRequest(loanProduct,
@@ -128,28 +105,12 @@ public class LoanProductApiResource {
@PUT
@Path("{productId}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({ MediaType.APPLICATION_JSON })
public Response updateLoanProduct(@PathParam("productId") final Long productId, final LoanProductCommand command) {
Locale clientApplicationLocale = this.apiDataConversionService.localeFromString(command.getLocale());
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public Response updateLoanProduct(@PathParam("productId") final Long productId, final String jsonRequestBody) {
BigDecimal principalValue = this.apiDataConversionService.convertFrom(command.getPrincipal(), "principal", clientApplicationLocale);
BigDecimal inArrearsToleranceValue = this.apiDataConversionService.convertFrom(command.getInArrearsTolerance(), "inArrearsTolerance", clientApplicationLocale);
BigDecimal interestRatePerPeriodValue = this.apiDataConversionService.convertFrom(command.getInterestRatePerPeriod(),"interestRatePerPeriod", clientApplicationLocale);
LoanProductCommand command = this.apiDataConversionService.convertJsonToCommand(productId, jsonRequestBody);
Integer digitsAfterDecimalValue = this.apiDataConversionService.convertToInteger(command.getDigitsAfterDecimal(), "digitsAfterDecimal", clientApplicationLocale);
Integer repaymentEveryValue = this.apiDataConversionService.convertToInteger(command.getRepaymentEvery(), "repaymentEvery", clientApplicationLocale);
Integer numberOfRepaymentsValue = this.apiDataConversionService.convertToInteger(command.getNumberOfRepayments(), "numberOfRepayments", clientApplicationLocale);
command.setPrincipalValue(principalValue);
command.setInArrearsToleranceValue(inArrearsToleranceValue);
command.setInterestRatePerPeriodValue(interestRatePerPeriodValue);
command.setDigitsAfterDecimalValue(digitsAfterDecimalValue);
command.setRepaymentEveryValue(repaymentEveryValue);
command.setNumberOfRepaymentsValue(numberOfRepaymentsValue);
command.setId(productId);
EntityIdentifier entityIdentifier = this.loanProductWritePlatformService.updateLoanProduct(command);
return Response.ok().entity(entityIdentifier).build();
@@ -1,6 +1,8 @@
package org.mifosng.platform.api;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@@ -27,6 +29,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
@Path("/offices")
@Component
@Scope("singleton")
@@ -76,14 +81,42 @@ public class OfficeApiResource {
}
@POST
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON })
public Response createOffice(final OfficeCommand command) {
LocalDate openingLocalDate = apiDataConversionService.convertFrom(
command.getOpeningDate(), "openingDate",
command.getDateFormat());
command.setOpeningLocalDate(openingLocalDate);
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public Response createOffice(final String jsonRequestBody) {
Gson gson = new Gson();
Type typeOfMap = new TypeToken<Map<String, String>>(){}.getType();
Map<String, String> requestMap = gson.fromJson(jsonRequestBody, typeOfMap);
String name = null;
if (requestMap.containsKey("name")) {
name = requestMap.get("name");
}
String externalId = null;
if (requestMap.containsKey("externalId")) {
externalId = requestMap.get("externalId");
}
Long parentId = null;
if (requestMap.containsKey("parentId")) {
parentId = Long.valueOf(requestMap.get("parentId"));
}
String openingDate = null;
if (requestMap.containsKey("openingDate")) {
openingDate = requestMap.get("openingDate");
}
String dateFormat = null;
if (requestMap.containsKey("dateFormat")) {
dateFormat = requestMap.get("dateFormat");
}
LocalDate openingLocalDate = apiDataConversionService.convertFrom(openingDate, "openingDate", dateFormat);
OfficeCommand command = new OfficeCommand(name, externalId, parentId, openingLocalDate);
Long officeId = this.writePlatformService.createOffice(command);
@@ -112,15 +145,41 @@ public class OfficeApiResource {
@Path("{officeId}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON })
public Response updateOffice(@PathParam("officeId") final Long officeId,
final OfficeCommand command) {
LocalDate openingLocalDate = apiDataConversionService.convertFrom(
command.getOpeningDate(), "openingDate",
command.getDateFormat());
command.setOpeningLocalDate(openingLocalDate);
command.setId(officeId);
public Response updateOffice(@PathParam("officeId") final Long officeId, final String jsonRequestBody) {
Gson gson = new Gson();
Type typeOfMap = new TypeToken<Map<String, String>>(){}.getType();
Map<String, String> requestMap = gson.fromJson(jsonRequestBody, typeOfMap);
String name = null;
if (requestMap.containsKey("name")) {
name = requestMap.get("name");
}
String externalId = null;
if (requestMap.containsKey("externalId")) {
externalId = requestMap.get("externalId");
}
Long parentId = null;
if (requestMap.containsKey("parentId")) {
parentId = Long.valueOf(requestMap.get("parentId"));
}
String openingDate = null;
if (requestMap.containsKey("openingDate")) {
openingDate = requestMap.get("openingDate");
}
String dateFormat = null;
if (requestMap.containsKey("dateFormat")) {
dateFormat = requestMap.get("dateFormat");
}
LocalDate openingLocalDate = apiDataConversionService.convertFrom(openingDate, "openingDate", dateFormat);
OfficeCommand command = new OfficeCommand(officeId, name, externalId, parentId, openingLocalDate);
Long entityId = this.writePlatformService.updateOffice(command);
return Response.ok().entity(new EntityIdentifier(entityId)).build();
@@ -1,236 +1,189 @@
package org.mifosng.platform.api.commands;
import java.math.BigDecimal;
import java.util.Set;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
/**
* Immutable command for creating or updating details of a loan product.
*/
public class LoanProductCommand {
private Long id;
private String name;
private String description;
private String externalId;
private final Long id;
private final String name;
private final String description;
private final String externalId;
private Long fundId;
private final Long fundId;
private String locale;
private String currencyCode;
private String digitsAfterDecimal;
private Integer digitsAfterDecimalValue;
private BigDecimal principalValue;
private String principal;
private BigDecimal inArrearsToleranceValue;
private String inArrearsTolerance;
private final String currencyCode;
private final Integer digitsAfterDecimal;
private final BigDecimal principal;
private final BigDecimal inArrearsTolerance;
private String numberOfRepayments;
private Integer numberOfRepaymentsValue;
private String repaymentEvery;
private Integer repaymentEveryValue;
private final Integer numberOfRepayments;
private final Integer repaymentEvery;
private String interestRatePerPeriod;
private BigDecimal interestRatePerPeriodValue;
private final BigDecimal interestRatePerPeriod;
private Integer repaymentFrequencyType;
private Integer interestRateFrequencyType;
private Integer amortizationType;
private Integer interestType;
private Integer interestCalculationPeriodType;
private final Integer repaymentFrequencyType;
private final Integer interestRateFrequencyType;
private final Integer amortizationType;
private final Integer interestType;
private final Integer interestCalculationPeriodType;
protected LoanProductCommand() {
//
private final Set<String> modifiedParameters;
public LoanProductCommand(Set<String> modifiedParameters, final Long id, final String name, final String description, final String externalId, final Long fundId,
final String currencyCode, final Integer digitsAfterDecimal,
final BigDecimal principal, final BigDecimal inArrearsTolerance, final Integer numberOfRepayments,
final Integer repaymentEvery, final BigDecimal interestRatePerPeriod,
final Integer repaymentFrequencyType, final Integer interestRateFrequencyType, final Integer amortizationType, final Integer interestType,
final Integer interestCalculationPeriodType) {
this.modifiedParameters = modifiedParameters;
this.id = id;
this.name = name;
this.description = description;
this.externalId = externalId;
this.fundId = fundId;
this.currencyCode = currencyCode;
this.digitsAfterDecimal = digitsAfterDecimal;
this.principal = principal;
this.inArrearsTolerance = inArrearsTolerance;
this.numberOfRepayments = numberOfRepayments;
this.repaymentEvery = repaymentEvery;
this.interestRatePerPeriod = interestRatePerPeriod;
this.repaymentFrequencyType = repaymentFrequencyType;
this.interestRateFrequencyType = interestRateFrequencyType;
this.amortizationType = amortizationType;
this.interestType = interestType;
this.interestCalculationPeriodType = interestCalculationPeriodType;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getExternalId() {
return externalId;
}
public void setExternalId(String externalId) {
this.externalId = externalId;
}
public String getCurrencyCode() {
return currencyCode;
}
public void setCurrencyCode(String currencyCode) {
this.currencyCode = currencyCode;
}
public String getLocale() {
return locale;
}
public void setLocale(String locale) {
this.locale = locale;
}
public String getDigitsAfterDecimal() {
return digitsAfterDecimal;
}
public void setDigitsAfterDecimal(String digitsAfterDecimal) {
this.digitsAfterDecimal = digitsAfterDecimal;
}
public Integer getDigitsAfterDecimalValue() {
return digitsAfterDecimalValue;
}
public void setDigitsAfterDecimalValue(Integer digitsAfterDecimalValue) {
this.digitsAfterDecimalValue = digitsAfterDecimalValue;
}
public BigDecimal getPrincipalValue() {
return principalValue;
}
public void setPrincipalValue(BigDecimal principalValue) {
this.principalValue = principalValue;
}
public String getPrincipal() {
return principal;
}
public void setPrincipal(String principal) {
this.principal = principal;
}
public BigDecimal getInArrearsToleranceValue() {
return inArrearsToleranceValue;
}
public void setInArrearsToleranceValue(BigDecimal inArrearsToleranceValue) {
this.inArrearsToleranceValue = inArrearsToleranceValue;
}
public String getInArrearsTolerance() {
return inArrearsTolerance;
}
public void setInArrearsTolerance(String inArrearsTolerance) {
this.inArrearsTolerance = inArrearsTolerance;
}
public String getNumberOfRepayments() {
return numberOfRepayments;
}
public void setNumberOfRepayments(String numberOfRepayments) {
this.numberOfRepayments = numberOfRepayments;
}
public Integer getNumberOfRepaymentsValue() {
return numberOfRepaymentsValue;
}
public void setNumberOfRepaymentsValue(Integer numberOfRepaymentsValue) {
this.numberOfRepaymentsValue = numberOfRepaymentsValue;
}
public String getRepaymentEvery() {
return repaymentEvery;
}
public void setRepaymentEvery(String repaymentEvery) {
this.repaymentEvery = repaymentEvery;
}
public Integer getRepaymentEveryValue() {
return repaymentEveryValue;
}
public void setRepaymentEveryValue(Integer repaymentEveryValue) {
this.repaymentEveryValue = repaymentEveryValue;
}
public String getInterestRatePerPeriod() {
return interestRatePerPeriod;
}
public void setInterestRatePerPeriod(String interestRatePerPeriod) {
this.interestRatePerPeriod = interestRatePerPeriod;
}
public BigDecimal getInterestRatePerPeriodValue() {
return interestRatePerPeriodValue;
}
public void setInterestRatePerPeriodValue(BigDecimal interestRatePerPeriodValue) {
this.interestRatePerPeriodValue = interestRatePerPeriodValue;
}
public Integer getRepaymentFrequencyType() {
return repaymentFrequencyType;
}
public void setRepaymentFrequencyType(Integer repaymentFrequencyType) {
this.repaymentFrequencyType = repaymentFrequencyType;
}
public Integer getInterestRateFrequencyType() {
return interestRateFrequencyType;
}
public void setInterestRateFrequencyType(Integer interestRateFrequencyType) {
this.interestRateFrequencyType = interestRateFrequencyType;
}
public Integer getAmortizationType() {
return amortizationType;
}
public void setAmortizationType(Integer amortizationType) {
this.amortizationType = amortizationType;
}
public Integer getInterestType() {
return interestType;
}
public void setInterestType(Integer interestType) {
this.interestType = interestType;
}
public Integer getInterestCalculationPeriodType() {
return interestCalculationPeriodType;
}
public void setInterestCalculationPeriodType(
Integer interestCalculationPeriodType) {
this.interestCalculationPeriodType = interestCalculationPeriodType;
}
public Long getFundId() {
return fundId;
}
public void setFundId(Long fundId) {
this.fundId = fundId;
public String getCurrencyCode() {
return currencyCode;
}
public Integer getDigitsAfterDecimal() {
return digitsAfterDecimal;
}
public BigDecimal getPrincipal() {
return principal;
}
public BigDecimal getInArrearsTolerance() {
return inArrearsTolerance;
}
public Integer getNumberOfRepayments() {
return numberOfRepayments;
}
public Integer getRepaymentEvery() {
return repaymentEvery;
}
public BigDecimal getInterestRatePerPeriod() {
return interestRatePerPeriod;
}
public Integer getRepaymentFrequencyType() {
return repaymentFrequencyType;
}
public Integer getInterestRateFrequencyType() {
return interestRateFrequencyType;
}
public Integer getAmortizationType() {
return amortizationType;
}
public Integer getInterestType() {
return interestType;
}
public Integer getInterestCalculationPeriodType() {
return interestCalculationPeriodType;
}
public boolean isNameChanged() {
return this.modifiedParameters.contains("name");
}
public boolean isDescriptionChanged() {
return this.modifiedParameters.contains("description");
}
public boolean isFundChanged() {
return this.modifiedParameters.contains("fundId");
}
public boolean isCurrencyCodeChanged() {
return this.modifiedParameters.contains("currencyCode");
}
public boolean isDigitsAfterDecimalChanged() {
return this.modifiedParameters.contains("digitsAfterDecimal");
}
public boolean isPrincipalChanged() {
return this.modifiedParameters.contains("principal");
}
public boolean isInArrearsToleranceChanged() {
return this.modifiedParameters.contains("inArrearsTolerance");
}
public boolean isNumberOfRepaymentsChanged() {
return this.modifiedParameters.contains("numberOfRepayments");
}
public boolean isRepaymentEveryChanged() {
return this.modifiedParameters.contains("repaymentEvery");
}
public boolean isRepaymentFrequencyTypeChanged() {
return this.modifiedParameters.contains("repaymentFrequencyType");
}
public boolean isAmortizationTypeChanged() {
return this.modifiedParameters.contains("amortizationType");
}
public boolean isInterestTypeChanged() {
return this.modifiedParameters.contains("interestType");
}
public boolean isInterestRatePerPeriodChanged() {
return this.modifiedParameters.contains("interestRatePerPeriod");
}
public boolean isInterestRateFrequencyTypeChanged() {
return this.modifiedParameters.contains("interestRateFrequencyType");
}
public boolean isInterestCalculationPeriodTypeChanged() {
return this.modifiedParameters.contains("interestCalculationPeriodType");
}
}
@@ -0,0 +1,9 @@
package org.mifosng.platform.api.errorhandling;
/**
* A {@link RuntimeException} that is thrown in the case where invalid JSON is sent in the body of the request to the platform api.
*/
public class InvalidJsonException extends RuntimeException {
//
}
@@ -0,0 +1,31 @@
package org.mifosng.platform.api.errorhandling;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.mifosng.platform.api.data.ApiParameterError;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* An {@link ExceptionMapper} to map {@link InvalidJsonException} thrown by platform into a HTTP API friendly format.
*/
@Provider
@Component
@Scope("singleton")
public class InvalidJsonExceptionMapper implements ExceptionMapper<InvalidJsonException> {
@Override
public Response toResponse(final InvalidJsonException exception) {
String globalisationMessageCode = "error.msg.invalid.request.body";
String defaultUserMessage = "The JSON provided in the body of the request is invalid or missing.";
ApiParameterError error = ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage);
return Response.status(Status.BAD_REQUEST).entity(error).type(MediaType.APPLICATION_JSON).build();
}
}
@@ -0,0 +1,33 @@
package org.mifosng.platform.api.errorhandling;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.mifosng.platform.api.data.ApiParameterError;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.google.gson.stream.MalformedJsonException;
/**
* An {@link ExceptionMapper} to map {@link MalformedJsonException} thrown by platform into a HTTP API friendly format.
*/
@Provider
@Component
@Scope("singleton")
public class MalformedJsonExceptionMapper implements ExceptionMapper<MalformedJsonException> {
@Override
public Response toResponse(final MalformedJsonException exception) {
String globalisationMessageCode = "error.msg.invalid.request.body";
String defaultUserMessage = "The JSON provided in the body of the request is invalid or missing.";
ApiParameterError error = ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage);
return Response.status(Status.BAD_REQUEST).entity(error).type(MediaType.APPLICATION_JSON).build();
}
}
@@ -0,0 +1,20 @@
package org.mifosng.platform.api.errorhandling;
import java.util.List;
/**
* A {@link RuntimeException} that is thrown in the case where invalid
* parameters are sent in the body of the request to the platform api.
*/
public class UnsupportedParameterException extends RuntimeException {
private final List<String> unsupportedParameters;
public UnsupportedParameterException(List<String> unsupportedParameters) {
this.unsupportedParameters = unsupportedParameters;
}
public List<String> getUnsupportedParameters() {
return unsupportedParameters;
}
}
@@ -0,0 +1,44 @@
package org.mifosng.platform.api.errorhandling;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.mifosng.platform.api.data.ApiGlobalErrorResponse;
import org.mifosng.platform.api.data.ApiParameterError;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* An {@link ExceptionMapper} to map {@link UnsupportedParameterException} thrown by platform into a HTTP API friendly format.
*/
@Provider
@Component
@Scope("singleton")
public class UnsupportedParameterExceptionMapper implements ExceptionMapper<UnsupportedParameterException> {
@Override
public Response toResponse(final UnsupportedParameterException exception) {
List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
for (String parameterName : exception.getUnsupportedParameters()) {
StringBuilder validationErrorCode = new StringBuilder("error.msg.parameter.unsupported");
StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameterName).append(" is not supported.");
ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), parameterName, parameterName);
errors.add(error);
}
ApiGlobalErrorResponse invalidParameterError = ApiGlobalErrorResponse.badClientRequest(
"validation.msg.validation.errors.exist",
"Validation errors exist.", errors);
return Response.status(Status.BAD_REQUEST).entity(invalidParameterError).type(MediaType.APPLICATION_JSON).build();
}
}
@@ -4,6 +4,7 @@ import java.math.BigDecimal;
import java.util.Locale;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.commands.LoanProductCommand;
public interface ApiDataConversionService {
@@ -18,4 +19,6 @@ public interface ApiDataConversionService {
Locale localeFromString(String locale);
Locale localeFrom(String languageCode, String courntryCode, String variantCode);
LoanProductCommand convertJsonToCommand(Long productId, String jsonRequestBody);
}
@@ -1,5 +1,6 @@
package org.mifosng.platform.api.infrastructure;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
@@ -7,21 +8,36 @@ import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.mifosng.platform.api.commands.LoanProductCommand;
import org.mifosng.platform.api.data.ApiParameterError;
import org.mifosng.platform.api.errorhandling.InvalidJsonException;
import org.mifosng.platform.api.errorhandling.UnsupportedParameterException;
import org.mifosng.platform.exceptions.PlatformApiDataValidationException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.format.number.NumberFormatter;
import org.springframework.stereotype.Service;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
@Service
public class ApiDataConversionServiceImpl implements ApiDataConversionService {
private final Gson gsonConverter;
public ApiDataConversionServiceImpl() {
gsonConverter = new Gson();
}
@Override
public LocalDate convertFrom(String dateAsString, String parameterName,
String dateFormat) {
@@ -110,6 +126,18 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
@Override
public BigDecimal convertFrom(final String numericalValueFormatted, final String parameterName, final Locale clientApplicationLocale) {
if (clientApplicationLocale == null) {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
String defaultMessage = new StringBuilder("The parameter '" + parameterName + "' requires a 'locale' parameter to be passed with it.").toString();
ApiParameterError error = ApiParameterError.parameterError("validation.msg.missing.locale.parameter", defaultMessage, parameterName);
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors);
}
try {
BigDecimal number = null;
@@ -213,4 +241,111 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
return new Locale(languageCode.toLowerCase(), courntryCode.toUpperCase(), variantCode);
}
@Override
public LoanProductCommand convertJsonToCommand(final Long resourceIdentifier, final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
Type typeOfMap = new TypeToken<Map<String, String>>(){}.getType();
Map<String, String> requestMap = gsonConverter.fromJson(json, typeOfMap);
Set<String> supportedParams = new HashSet<String>(
Arrays.asList("name", "description", "externalId", "fundId", "currencyCode", "digitsAfterDecimalValue",
"principal", "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments",
"repaymentFrequencyType", "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType")
);
checkForUnsupportedParameters(requestMap, supportedParams);
Set<String> modifiedParameters = new HashSet<String>();
String name = extractStringParameter("name", requestMap, modifiedParameters);
String description = extractStringParameter("description", requestMap, modifiedParameters);
String externalId = extractStringParameter("externalId", requestMap, modifiedParameters);
Long fundId = extractLongParameter("fundId", requestMap, modifiedParameters);
String currencyCode = extractStringParameter("currencyCode", requestMap, modifiedParameters);
Integer digitsAfterDecimalValue = extractIntegerParameter("digitsAfterDecimal", requestMap, modifiedParameters);
BigDecimal principalValue = extractBigDecimalParameter("principal", requestMap, modifiedParameters);
BigDecimal inArrearsToleranceValue = extractBigDecimalParameter("inArrearsTolerance", requestMap, modifiedParameters);
BigDecimal interestRatePerPeriodValue = extractBigDecimalParameter("interestRatePerPeriod", requestMap, modifiedParameters);
Integer repaymentEveryValue = extractIntegerParameter("repaymentEvery", requestMap, modifiedParameters);
Integer numberOfRepaymentsValue = extractIntegerParameter("numberOfRepayments", requestMap, modifiedParameters);
Integer repaymentFrequencyTypeValue = extractIntegerParameter("repaymentFrequencyType", requestMap, modifiedParameters);
Integer interestRateFrequencyTypeValue = extractIntegerParameter("interestRateFrequencyType", requestMap, modifiedParameters);
Integer amortizationTypeValue = extractIntegerParameter("amortizationType", requestMap, modifiedParameters);
Integer interestTypeValue = extractIntegerParameter("interestType", requestMap, modifiedParameters);
Integer interestCalculationPeriodTypeValue = extractIntegerParameter("interestCalculationPeriodType", requestMap, modifiedParameters);
return new LoanProductCommand(modifiedParameters, resourceIdentifier, name, description, externalId, fundId,
currencyCode, digitsAfterDecimalValue, principalValue, inArrearsToleranceValue, numberOfRepaymentsValue, repaymentEveryValue, interestRatePerPeriodValue,
repaymentFrequencyTypeValue, interestRateFrequencyTypeValue, amortizationTypeValue, interestTypeValue, interestCalculationPeriodTypeValue);
}
private void checkForUnsupportedParameters(Map<String, String> requestMap, Set<String> supportedParams) {
List<String> unsupportedParameterList = new ArrayList<String>();
for (String providedParameter : requestMap.keySet()) {
if (!supportedParams.contains(providedParameter)) {
unsupportedParameterList.add(providedParameter);
}
}
if (!unsupportedParameterList.isEmpty()) {
throw new UnsupportedParameterException(unsupportedParameterList);
}
}
private String extractStringParameter(final String paramName, final Map<String, String> requestMap, final Set<String> modifiedParameters) {
String paramValue = null;
if (requestMap.containsKey(paramName)) {
paramValue = requestMap.get(paramName);
modifiedParameters.add(paramName);
}
return paramValue;
}
private Integer extractIntegerParameter(final String paramName, final Map<String, String> requestMap, final Set<String> modifiedParameters) {
Integer paramValue = null;
if (requestMap.containsKey(paramName)) {
paramValue = convertToInteger(requestMap.get(paramName), paramName, extractLocaleValue(requestMap));
modifiedParameters.add(paramName);
}
return paramValue;
}
private BigDecimal extractBigDecimalParameter(final String paramName, final Map<String, String> requestMap, final Set<String> modifiedParameters) {
BigDecimal paramValue = null;
if (requestMap.containsKey(paramName)) {
paramValue = convertFrom(requestMap.get(paramName), paramName, extractLocaleValue(requestMap));
modifiedParameters.add(paramName);
}
return paramValue;
}
private Long extractLongParameter(final String paramName, final Map<String, String> requestMap, final Set<String> modifiedParameters) {
Long paramValue = null;
if (requestMap.containsKey(paramName)) {
String valueAsString = requestMap.get(paramName);
if (StringUtils.isNotBlank(valueAsString)) {
paramValue = Long.valueOf(valueAsString);
}
modifiedParameters.add(paramName);
}
return paramValue;
}
private Locale extractLocaleValue(Map<String, String> requestMap) {
Locale clientApplicationLocale = null;
String locale = null;
if (requestMap.containsKey("locale")) {
locale = requestMap.get("locale");
clientApplicationLocale = localeFromString(locale);
}
return clientApplicationLocale;
}
}
@@ -5,6 +5,7 @@ import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.apache.commons.lang.StringUtils;
import org.mifosng.platform.api.commands.FundCommand;
import org.mifosng.platform.infrastructure.AbstractAuditableCustom;
import org.mifosng.platform.user.domain.AppUser;
@@ -35,9 +36,13 @@ public class Fund extends AbstractAuditableCustom<AppUser, Long> {
this.name = fundName;
}
public void update(FundCommand command) {
if (command.getName() != null) {
public void update(final FundCommand command) {
if (StringUtils.isNotBlank(command.getName())) {
this.name = command.getName();
}
if (command.getExternalId() != null) {
this.externalId = StringUtils.defaultIfEmpty(command.getExternalId(), null);
}
}
}
@@ -133,13 +133,16 @@ public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
}
public void update(final LoanProductCommand command, final Fund fund) {
if (command.getName() != null) {
if (command.isNameChanged()) {
this.name = command.getName();
}
if (command.getDescription() != null) {
if (command.isDescriptionChanged()) {
this.description = command.getDescription();
}
if (fund != null) {
if (command.isFundChanged()) {
this.fund = fund;
}
@@ -171,39 +171,50 @@ public class LoanProductRelatedDetail implements
return interestRebateAllowed;
}
public void update(LoanProductCommand command) {
public void update(final LoanProductCommand command) {
if (command.getCurrencyCode() != null && command.getDigitsAfterDecimal() != null) {
this.currency = new MonetaryCurrency(command.getCurrencyCode(), command.getDigitsAfterDecimalValue());
Integer digitsAfterDecimalChanged = this.currency.getDigitsAfterDecimal();
if (command.isDigitsAfterDecimalChanged()) {
digitsAfterDecimalChanged = command.getDigitsAfterDecimal();
}
if (command.getPrincipal() != null) {
this.principal = command.getPrincipalValue();
String currencyCodeChanged = this.currency.getCode();
if (command.isCurrencyCodeChanged()) {
currencyCodeChanged = command.getCurrencyCode();
}
if (command.getRepaymentEvery() != null) {
this.repayEvery = command.getRepaymentEveryValue();
if (command.isDigitsAfterDecimalChanged() || command.isCurrencyCodeChanged()) {
this.currency = new MonetaryCurrency(currencyCodeChanged, digitsAfterDecimalChanged);
}
if (command.getRepaymentFrequencyType() != null) {
if (command.isPrincipalChanged()) {
this.principal = command.getPrincipal();
}
if (command.isRepaymentEveryChanged()) {
this.repayEvery = command.getRepaymentEvery();
}
if (command.isRepaymentFrequencyTypeChanged()) {
this.repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequencyType());
}
if (command.getNumberOfRepayments() != null) {
this.numberOfRepayments = command.getNumberOfRepaymentsValue();
if (command.isNumberOfRepaymentsChanged()) {
this.numberOfRepayments = command.getNumberOfRepayments();
}
if (command.getAmortizationType() != null) {
if (command.isAmortizationTypeChanged()) {
this.amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationType());
}
if (command.getInArrearsToleranceValue() != null) {
this.inArrearsTolerance = command.getInArrearsToleranceValue();
if (command.isInArrearsToleranceChanged()) {
this.inArrearsTolerance = command.getInArrearsTolerance();
}
if (command.getInterestRatePerPeriod() != null) {
this.nominalInterestRatePerPeriod = command.getInterestRatePerPeriodValue();
if (command.isInterestRatePerPeriodChanged()) {
this.nominalInterestRatePerPeriod = command.getInterestRatePerPeriod();
}
if (command.getInterestRateFrequencyType() != null) {
if (command.isInterestRateFrequencyTypeChanged()) {
this.interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyType());
}
if (command.getInterestType() != null) {
if (command.isInterestTypeChanged()) {
this.interestMethod = InterestMethod.fromInt(command.getInterestType());
}
if (command.getInterestCalculationPeriodType() != null) {
if (command.isInterestCalculationPeriodTypeChanged()) {
this.interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodType());
}
}
@@ -27,17 +27,17 @@ public class LoanProductCommandValidator {
baseDataValidator.reset().parameter("description").value(command.getDescription()).ignoreIfNull().notExceedingLengthOf(500);
baseDataValidator.reset().parameter("externalId").value(command.getExternalId()).ignoreIfNull().notExceedingLengthOf(100);
baseDataValidator.reset().parameter("currencyCode").value(command.getCurrencyCode()).ignoreIfNull().notBlank();
baseDataValidator.reset().parameter("digitsAfterDecimal").value(command.getDigitsAfterDecimalValue()).ignoreIfNull().notNull().inMinMaxRange(0, 6);
baseDataValidator.reset().parameter("digitsAfterDecimal").value(command.getDigitsAfterDecimal()).ignoreIfNull().notNull().inMinMaxRange(0, 6);
baseDataValidator.reset().parameter("principal").value(command.getPrincipalValue()).ignoreIfNull().notNull().positiveAmount();
baseDataValidator.reset().parameter("inArrearsTolerance").value(command.getInArrearsToleranceValue()).ignoreIfNull().notNull().zeroOrPositiveAmount();
baseDataValidator.reset().parameter("principal").value(command.getPrincipal()).ignoreIfNull().notNull().positiveAmount();
baseDataValidator.reset().parameter("inArrearsTolerance").value(command.getInArrearsTolerance()).ignoreIfNull().notNull().zeroOrPositiveAmount();
baseDataValidator.reset().parameter("repaymentFrequencyType").value(command.getRepaymentFrequencyType()).ignoreIfNull().notNull().inMinMaxRange(0, 3);
baseDataValidator.reset().parameter("repaymentEvery").value(command.getRepaymentEveryValue()).ignoreIfNull().notNull().greaterThanZero();
baseDataValidator.reset().parameter("numberOfRepayments").value(command.getNumberOfRepaymentsValue()).ignoreIfNull().notNull().greaterThanZero();
baseDataValidator.reset().parameter("repaymentEvery").value(command.getRepaymentEvery()).ignoreIfNull().notNull().greaterThanZero();
baseDataValidator.reset().parameter("numberOfRepayments").value(command.getNumberOfRepayments()).ignoreIfNull().notNull().greaterThanZero();
baseDataValidator.reset().parameter("interestRatePerPeriod").value(command.getInterestRatePerPeriodValue()).ignoreIfNull().notBlank();
baseDataValidator.reset().parameter("interestRatePerPeriod").value(command.getInterestRatePerPeriod()).ignoreIfNull().notBlank();
baseDataValidator.reset().parameter("interestRateFrequencyType").value(command.getInterestRateFrequencyType()).ignoreIfNull().notNull().inMinMaxRange(0, 3);
baseDataValidator.reset().parameter("amortizationType").value(command.getAmortizationType()).ignoreIfNull().notNull().inMinMaxRange(0, 1);
@@ -63,16 +63,16 @@ public class LoanProductCommandValidator {
baseDataValidator.reset().parameter("description").value(command.getDescription()).notExceedingLengthOf(500);
baseDataValidator.reset().parameter("externalId").value(command.getExternalId()).notExceedingLengthOf(100);
baseDataValidator.reset().parameter("currencyCode").value(command.getCurrencyCode()).notBlank();
baseDataValidator.reset().parameter("digitsAfterDecimal").value(command.getDigitsAfterDecimalValue()).notNull().inMinMaxRange(0, 6);
baseDataValidator.reset().parameter("digitsAfterDecimal").value(command.getDigitsAfterDecimal()).notNull().inMinMaxRange(0, 6);
baseDataValidator.reset().parameter("principal").value(command.getPrincipalValue()).notNull().positiveAmount();
baseDataValidator.reset().parameter("inArrearsTolerance").value(command.getInArrearsToleranceValue()).notNull().zeroOrPositiveAmount();
baseDataValidator.reset().parameter("principal").value(command.getPrincipal()).notNull().positiveAmount();
baseDataValidator.reset().parameter("inArrearsTolerance").value(command.getInArrearsTolerance()).notNull().zeroOrPositiveAmount();
baseDataValidator.reset().parameter("repaymentFrequencyType").value(command.getRepaymentFrequencyType()).notNull().inMinMaxRange(0, 3);
baseDataValidator.reset().parameter("repaymentEvery").value(command.getRepaymentEveryValue()).notNull().greaterThanZero();
baseDataValidator.reset().parameter("numberOfRepayments").value(command.getNumberOfRepaymentsValue()).notNull().greaterThanZero();
baseDataValidator.reset().parameter("repaymentEvery").value(command.getRepaymentEvery()).notNull().greaterThanZero();
baseDataValidator.reset().parameter("numberOfRepayments").value(command.getNumberOfRepayments()).notNull().greaterThanZero();
baseDataValidator.reset().parameter("interestRatePerPeriod").value(command.getInterestRatePerPeriodValue()).notNull();
baseDataValidator.reset().parameter("interestRatePerPeriod").value(command.getInterestRatePerPeriod()).notNull();
baseDataValidator.reset().parameter("interestRateFrequencyType").value(command.getInterestRateFrequencyType()).notNull().inMinMaxRange(0, 3);
baseDataValidator.reset().parameter("amortizationType").value(command.getAmortizationType()).notNull().inMinMaxRange(0, 1);
@@ -59,18 +59,18 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
PeriodFrequencyType interestFrequencyType = PeriodFrequencyType
.fromInt(command.getInterestRateFrequencyType());
MonetaryCurrency currency = new MonetaryCurrency(command.getCurrencyCode(), command.getDigitsAfterDecimalValue());
MonetaryCurrency currency = new MonetaryCurrency(command.getCurrencyCode(), command.getDigitsAfterDecimal());
BigDecimal annualInterestRate = this.aprCalculator.calculateFrom(interestFrequencyType, command.getInterestRatePerPeriodValue());
BigDecimal annualInterestRate = this.aprCalculator.calculateFrom(interestFrequencyType, command.getInterestRatePerPeriod());
// associating fund with loan product at creation is optional for now.
Fund fund = findFundByIdIfProvided(command.getFundId());
LoanProduct loanproduct = new LoanProduct(fund, command.getName(), command.getDescription(),
currency, command.getPrincipalValue(),
command.getInterestRatePerPeriodValue(), interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod,
command.getRepaymentEveryValue(), repaymentFrequencyType, command.getNumberOfRepaymentsValue(),
amortizationMethod, command.getInArrearsToleranceValue());
currency, command.getPrincipal(),
command.getInterestRatePerPeriod(), interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod,
command.getRepaymentEvery(), repaymentFrequencyType, command.getNumberOfRepayments(),
amortizationMethod, command.getInArrearsTolerance());
this.loanProductRepository.save(loanproduct);
@@ -90,7 +90,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
@Transactional
@Override
public EntityIdentifier updateLoanProduct(LoanProductCommand command) {
public EntityIdentifier updateLoanProduct(final LoanProductCommand command) {
this.context.authenticatedUser();
@@ -99,14 +99,13 @@ public class Office extends AbstractAuditableCustom<AppUser, Long> {
}
public void update(OfficeCommand command) {
// ignore nulls when updating
if (command.getName() != null) {
public void update(final OfficeCommand command) {
if (StringUtils.isNotBlank(command.getName())) {
this.name = command.getName().trim();
}
if (command.getExternalId() != null) {
this.externalId = command.getExternalId().trim();
this.externalId = StringUtils.defaultIfEmpty(command.getExternalId().trim(), null);
}
if (command.getOpeningDate() != null) {