work through cleaner implementation of error handling support for http api for the office resource.

This commit is contained in:
Keith Woodlock
2012-05-09 23:33:33 +01:00
parent 7ad484bc2e
commit 008ec86c9a
32 changed files with 1071 additions and 297 deletions
@@ -0,0 +1,32 @@
package org.mifosng.data;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="messageArg")
public class ApiErrorMessageArg {
/**
* The actual value of the parameter (if any) as passed to API.
*/
private Object value;
public static ApiErrorMessageArg from(Object object) {
return new ApiErrorMessageArg(object);
}
protected ApiErrorMessageArg() {
//
}
public ApiErrorMessageArg(Object object) {
this.value = object;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
@@ -0,0 +1,174 @@
package org.mifosng.data;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
/**
*
*/
@XmlRootElement
public class ApiGlobalErrorResponse {
/**
* A developer friendly plain English description of why the HTTP error response was returned from the API.
*/
private String developerMessage;
/**
* A link to a URL which provides more information on the details of this type of error.
*/
private String developerDocLink;
/**
* The HTTP status code of the response.
*/
private String httpStatusCode;
/**
* A user friendly plain English description of why the HTTP error response was returned from the API that can be presented to end users.
*/
private String defaultUserMessage;
/**
* A code that can be used for globalisation support by client applications of the API.
*/
private String userMessageGlobalisationCode;
/**
* A list of zero or more of the actual reasons for the HTTP error should they be needed. Typically used to express data validation errors with parameters passed to API.
*/
private List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
public static ApiGlobalErrorResponse unAuthenticated() {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("401");
globalErrorResponse.setDeveloperMessage("No authentication details were passed in api request.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.not.authenticated");
globalErrorResponse.setDefaultUserMessage("Unauthenticated. Please login.");
return globalErrorResponse;
}
public static ApiGlobalErrorResponse unAuthorized() {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("403");
globalErrorResponse.setDeveloperMessage("The user associated with credentials passed on this request does not have sufficient privileges to perform this action.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.not.authorized");
globalErrorResponse.setDefaultUserMessage("Insufficient priviledges to perform this action.");
return globalErrorResponse;
}
public static ApiGlobalErrorResponse notFound(String globalisationMessageCode, String defaultUserMessage, Object... defaultUserMessageArgs) {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("404");
globalErrorResponse.setDeveloperMessage("The requested resource is not available.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.resource.not.found");
globalErrorResponse.setDefaultUserMessage("The requested resource is not available.");
List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
errors.add(ApiParameterError.resourceIdentifierNotFound(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
globalErrorResponse.setErrors(errors);
return globalErrorResponse;
}
public static ApiGlobalErrorResponse dataIntegrityError(final String globalisationMessageCode, final String defaultUserMessage, final String parameterName, final Object... defaultUserMessageArgs) {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("400");
globalErrorResponse.setDeveloperMessage("The request caused a data integrity issue to be fired by the database.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode);
globalErrorResponse.setDefaultUserMessage(defaultUserMessage);
List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
errors.add(ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage, parameterName, defaultUserMessageArgs));
globalErrorResponse.setErrors(errors);
return globalErrorResponse;
}
public static ApiGlobalErrorResponse badClientRequest(final String globalisationMessageCode, final String defaultUserMessage,
List<ApiParameterError> errors) {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("400");
globalErrorResponse.setDeveloperMessage("The request was invalid. This typically will happen due to validation errors which are provided.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode);
globalErrorResponse.setDefaultUserMessage(defaultUserMessage);
globalErrorResponse.setErrors(errors);
return globalErrorResponse;
}
protected ApiGlobalErrorResponse() {
//
}
public ApiGlobalErrorResponse(final List<ApiParameterError> errors) {
this.errors = errors;
}
@XmlElementWrapper(name="errors")
@XmlElement(name="errorResponse")
public List<ApiParameterError> getErrors() {
return errors;
}
public void setErrors(List<ApiParameterError> errors) {
this.errors = errors;
}
public String getDeveloperMessage() {
return developerMessage;
}
public void setDeveloperMessage(String developerMessage) {
this.developerMessage = developerMessage;
}
public String getHttpStatusCode() {
return httpStatusCode;
}
public void setHttpStatusCode(String httpStatusCode) {
this.httpStatusCode = httpStatusCode;
}
public String getDefaultUserMessage() {
return defaultUserMessage;
}
public void setDefaultUserMessage(String defaultUserMessage) {
this.defaultUserMessage = defaultUserMessage;
}
public String getUserMessageGlobalisationCode() {
return userMessageGlobalisationCode;
}
public void setUserMessageGlobalisationCode(String userMessageGlobalisationCode) {
this.userMessageGlobalisationCode = userMessageGlobalisationCode;
}
public String getDeveloperDocLink() {
return developerDocLink;
}
public void setDeveloperDocLink(String developerDocLink) {
this.developerDocLink = developerDocLink;
}
}
@@ -0,0 +1,120 @@
package org.mifosng.data;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="apiParameterError")
public class ApiParameterError {
/**
* A developer friendly plain English description of why the HTTP error response was returned from the API.
*/
private String developerMessage;
/**
* A user friendly plain English description of why the HTTP error response was returned from the API that can be presented to end users.
*/
private String defaultUserMessage;
/**
* A code that can be used for globalisation support by client applications of the API.
*/
private String userMessageGlobalisationCode;
/**
* The name of the field or parameter passed to the API that this error relates to.
*/
private String parameterName;
/**
* The actual value of the parameter (if any) as passed to API.
*/
private Object value;
/**
* Arguments related to the user error message.
*/
private List<ApiErrorMessageArg> args = new ArrayList<ApiErrorMessageArg>();
public static ApiParameterError resourceIdentifierNotFound(String globalisationMessageCode, String defaultUserMessage, Object... defaultUserMessageArgs) {
return new ApiParameterError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
}
public static ApiParameterError parameterError(final String globalisationMessageCode, final String defaultUserMessage,
final String parameterName, final Object... defaultUserMessageArgs) {
ApiParameterError error = new ApiParameterError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
error.setParameterName(parameterName);
return error;
}
protected ApiParameterError() {
//
}
private ApiParameterError(final String globalisationMessageCode, final String defaultUserMessage, final Object[] defaultUserMessageArgs) {
this.userMessageGlobalisationCode = globalisationMessageCode;
this.developerMessage = defaultUserMessage;
this.defaultUserMessage = defaultUserMessage;
List<ApiErrorMessageArg> messageArgs = new ArrayList<ApiErrorMessageArg>();
if (defaultUserMessageArgs != null) {
for (Object object : defaultUserMessageArgs) {
messageArgs.add(ApiErrorMessageArg.from(object));
}
}
this.args = messageArgs;
this.parameterName="identifier";
}
public String getDeveloperMessage() {
return developerMessage;
}
public void setDeveloperMessage(String developerMessage) {
this.developerMessage = developerMessage;
}
public String getDefaultUserMessage() {
return defaultUserMessage;
}
public void setDefaultUserMessage(String defaultUserMessage) {
this.defaultUserMessage = defaultUserMessage;
}
public String getUserMessageGlobalisationCode() {
return userMessageGlobalisationCode;
}
public void setUserMessageGlobalisationCode(String userMessageGlobalisationCode) {
this.userMessageGlobalisationCode = userMessageGlobalisationCode;
}
public String getParameterName() {
return parameterName;
}
public void setParameterName(String parameterName) {
this.parameterName = parameterName;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public List<ApiErrorMessageArg> getArgs() {
return args;
}
public void setArgs(List<ApiErrorMessageArg> args) {
this.args = args;
}
}
@@ -33,6 +33,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
repaymentEvery, repaymentFrequency, numberOfRepayments, amortizationMethod, toleranceAmount, flexibleRepaymentSchedule, interestRebateAllowed);
}
@Override
public String getName() {
return this.name;
}
@@ -41,6 +42,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
this.name = name;
}
@Override
public String getDescription() {
return this.description;
}
@@ -49,6 +51,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
this.description = description;
}
@Override
public String getCurrencyCode() {
return this.commonLoanProperties.getCurrencyCode();
}
@@ -57,6 +60,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
this.commonLoanProperties.setCurrencyCode(currencyCode);
}
@Override
public Integer getDigitsAfterDecimal() {
return this.commonLoanProperties.getDigitsAfterDecimal();
}
@@ -65,6 +69,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
this.commonLoanProperties.setDigitsAfterDecimal(digitsAfterDecimal);
}
@Override
public Integer getRepaymentEvery() {
return this.commonLoanProperties.getRepaymentEvery();
}
@@ -73,6 +78,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
this.commonLoanProperties.setRepaymentEvery(repaymentEvery);
}
@Override
public Integer getRepaymentFrequency() {
return this.commonLoanProperties.getRepaymentFrequency();
}
@@ -81,6 +87,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
this.commonLoanProperties.setRepaymentFrequency(repaymentFrequency);
}
@Override
public Boolean isFlexibleRepaymentSchedule() {
return this.commonLoanProperties.isFlexibleRepaymentSchedule();
}
@@ -90,6 +97,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
this.commonLoanProperties.setFlexibleRepaymentSchedule(flexibleRepaymentSchedule);
}
@Override
public Boolean isInterestRebateAllowed() {
return this.commonLoanProperties.isInterestRebateAllowed();
}
@@ -98,6 +106,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
this.commonLoanProperties.setInterestRebateAllowed(interestRebateAllowed);
}
@Override
public BigDecimal getPrincipal() {
return this.commonLoanProperties.getPrincipal();
}
@@ -114,6 +123,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
// return this.commonLoanProperties.getInterestRebateAllowed();
// }
@Override
public Integer getInterestRateFrequencyMethod() {
return commonLoanProperties.getInterestRateFrequencyMethod();
}
@@ -122,6 +132,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
this.commonLoanProperties.setInterestRateFrequencyMethod(interestRateFrequencyMethod);
}
@Override
public Integer getInterestMethod() {
return commonLoanProperties.getInterestMethod();
}
@@ -130,6 +141,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
this.commonLoanProperties.setInterestMethod(interestMethod);
}
@Override
public Integer getAmortizationMethod() {
return commonLoanProperties.getAmortizationMethod();
}
@@ -138,6 +150,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
this.commonLoanProperties.setAmortizationMethod(amortizationMethod);
}
@Override
public Integer getNumberOfRepayments() {
return commonLoanProperties.getNumberOfRepayments();
}
@@ -146,6 +159,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
this.commonLoanProperties.setNumberOfRepayments(numberOfRepayments);
}
@Override
public BigDecimal getInterestRatePerPeriod() {
return commonLoanProperties.getInterestRatePerPeriod();
}
@@ -154,6 +168,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
this.commonLoanProperties.setInterestRatePerPeriod(interestRatePerPeriod);
}
@Override
public CommonLoanProperties getCommonLoanProperties() {
return commonLoanProperties;
}
@@ -162,6 +177,7 @@ public class UpdateLoanProductCommand implements LoanProductCommandData {
this.commonLoanProperties = commonLoanProperties;
}
@Override
public BigDecimal getInArrearsToleranceAmount() {
return this.commonLoanProperties.getInArrearsToleranceAmount();
}
@@ -56,6 +56,7 @@ public class CustomOAuthConsumerContextFilter extends OAuthConsumerContextFilter
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
@@ -40,7 +40,7 @@ error.msg.client.id.invalid=Client does not exist.
error.msg.office.id.invalid=Office does not exist.
error.msg.office.duplicate.externalId=An office with 'External Id' already exists.
error.msg.office.duplicate.name=An office with 'name' already exists.
error.msg.office.unknown.data.integretity.issue=An unknown data integrity issue occured.
error.msg.office.unknown.data.integrity.issue=An unknown data integrity issue occured.
dialog.title.confirmation.required=Confirmation required
text.confirmation.required=Are you sure?
@@ -40,7 +40,7 @@ error.msg.client.id.invalid=Client does not exist.(fr)
error.msg.office.id.invalid=Office does not exist.(fr)
error.msg.office.duplicate.externalId=An office with 'External Id' already exists.(fr)
error.msg.office.duplicate.name=An office with 'name' already exists.(fr)
error.msg.office.unknown.data.integretity.issue=An unknown data integrity issue occured.(fr)
error.msg.office.unknown.data.integrity.issue=An unknown data integrity issue occured.(fr)
dialog.title.confirmation.required=Confirmation required(fr)
text.confirmation.required=Are you sure?(fr)
@@ -28,6 +28,7 @@ import org.springframework.security.oauth.provider.token.OAuthProviderToken;
public class CustomProtectedResourceProcessingFilter extends
ProtectedResourceProcessingFilter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
@@ -1,56 +0,0 @@
package org.mifosng.platform;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosng.data.ErrorResponse;
import org.mifosng.platform.exceptions.NewDataValidationException;
public class OfficeValidator {
private final String name;
private final Long parentId;
private final LocalDate openingDate;
private final String externalId;
public OfficeValidator(String name, Long parentId, LocalDate openingDate, String externalId) {
this.name = name;
this.parentId = parentId;
this.openingDate = openingDate;
this.externalId = externalId;
}
public void validate() {
List<ErrorResponse> dataValidationErrors = new ArrayList<ErrorResponse>();
if (StringUtils.isBlank(this.name)) {
ErrorResponse error = new ErrorResponse("validation.msg.office.name.cannot.be.blank", "name");
dataValidationErrors.add(error);
}
if (this.parentId == null || parentId.equals(Long.valueOf(-1))) {
ErrorResponse error = new ErrorResponse("validation.msg.office.parent.cannot.be.blank", "parentId");
dataValidationErrors.add(error);
}
if (this.openingDate == null) {
ErrorResponse error = new ErrorResponse("validation.msg.office.opening.date.cannot.be.blank", "openingDate");
dataValidationErrors.add(error);
}
if (StringUtils.isNotBlank(this.externalId)) {
if (this.externalId.trim().length() > 100) {
ErrorResponse error = new ErrorResponse("validation.msg.office.externalId.exceeds.max.length", "externalId");
dataValidationErrors.add(error);
}
}
if (!dataValidationErrors.isEmpty()) {
throw new NewDataValidationException(dataValidationErrors, "Data validation errors exist.");
}
}
}
@@ -33,7 +33,6 @@ import org.mifosng.data.ClientDataWithAccountsData;
import org.mifosng.data.CurrencyData;
import org.mifosng.data.DerivedLoanData;
import org.mifosng.data.EnumOptionReadModel;
import org.mifosng.data.ErrorResponse;
import org.mifosng.data.ExtraDatasets;
import org.mifosng.data.LoanAccountData;
import org.mifosng.data.LoanProductData;
@@ -53,7 +52,7 @@ import org.mifosng.platform.client.domain.ClientRepository;
import org.mifosng.platform.currency.domain.ApplicationCurrency;
import org.mifosng.platform.currency.domain.ApplicationCurrencyRepository;
import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.exceptions.ApplicationDomainRuleException;
import org.mifosng.platform.exceptions.PlatformResourceNotFoundException;
import org.mifosng.platform.exceptions.UnAuthenticatedUserException;
import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.Loan;
@@ -200,11 +199,7 @@ public class ReadPlatformServiceImpl implements ReadPlatformService {
new Object[] { currentUser.getOrganisation().getId(),
clientId, noteId });
} catch (EmptyResultDataAccessException e) {
ErrorResponse errorResponse = new ErrorResponse(
"error.msg.client.id.invalid", "id");
throw new ApplicationDomainRuleException(
Arrays.asList(errorResponse), "Client with identifier "
+ clientId.toString() + " does not exist.");
throw new PlatformResourceNotFoundException("error.msg.client.id.invalid", "Client with identifier {0} does not exist", clientId);
}
}
@@ -403,11 +398,7 @@ public class ReadPlatformServiceImpl implements ReadPlatformService {
return selectedOffice;
} catch (EmptyResultDataAccessException e) {
ErrorResponse errorResponse = new ErrorResponse(
"error.msg.office.id.invalid", "id");
throw new ApplicationDomainRuleException(
Arrays.asList(errorResponse), "Office with identifier "
+ officeId.toString() + " does not exist.");
throw new PlatformResourceNotFoundException("error.msg.office.id.invalid", "Office with identifier {0} does not exist", officeId);
}
}
@@ -426,11 +417,7 @@ public class ReadPlatformServiceImpl implements ReadPlatformService {
return this.jdbcTemplate.queryForObject(sql, rm, new Object[] {
clientId, currentUser.getOrganisation().getId() });
} catch (EmptyResultDataAccessException e) {
ErrorResponse errorResponse = new ErrorResponse(
"error.msg.client.id.invalid", "id");
throw new ApplicationDomainRuleException(
Arrays.asList(errorResponse), "Client with identifier "
+ clientId.toString() + " does not exist.");
throw new PlatformResourceNotFoundException("error.msg.client.id.invalid", "Client with identifier {0} does not exist", clientId);
}
}
@@ -575,6 +562,7 @@ public class ReadPlatformServiceImpl implements ReadPlatformService {
return loanRepaymentData;
}
@Override
public NewLoanWorkflowStepOneData retrieveClientAndProductDetails(final Long clientId, final Long productId) {
AppUser currentUser = extractAuthenticatedUser();
@@ -1,6 +1,10 @@
package org.mifosng.platform;
import static org.mifosng.platform.Specifications.*;
import static org.mifosng.platform.Specifications.loansThatMatch;
import static org.mifosng.platform.Specifications.officesThatMatch;
import static org.mifosng.platform.Specifications.productThatMatches;
import static org.mifosng.platform.Specifications.rolesThatMatch;
import static org.mifosng.platform.Specifications.usersThatMatch;
import java.math.BigDecimal;
import java.util.ArrayList;
@@ -44,10 +48,11 @@ import org.mifosng.platform.currency.domain.ApplicationCurrencyRepository;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.exceptions.ApplicationDomainRuleException;
import org.mifosng.platform.exceptions.UnAuthenticatedUserException;
import org.mifosng.platform.exceptions.InvalidSignupException;
import org.mifosng.platform.exceptions.NewDataValidationException;
import org.mifosng.platform.exceptions.NoAuthorizationException;
import org.mifosng.platform.exceptions.PlatformDataIntegrityException;
import org.mifosng.platform.exceptions.UnAuthenticatedUserException;
import org.mifosng.platform.infrastructure.BasicPasswordEncodablePlatformUser;
import org.mifosng.platform.infrastructure.PlatformPasswordEncoder;
import org.mifosng.platform.infrastructure.PlatformUser;
@@ -74,6 +79,7 @@ import org.mifosng.platform.organisation.domain.OfficeRepository;
import org.mifosng.platform.organisation.domain.Organisation;
import org.mifosng.platform.organisation.domain.OrganisationCurrency;
import org.mifosng.platform.organisation.domain.OrganisationRepository;
import org.mifosng.platform.organisation.service.OfficeCommandValidator;
import org.mifosng.platform.user.domain.AppUser;
import org.mifosng.platform.user.domain.AppUserRepository;
import org.mifosng.platform.user.domain.Permission;
@@ -331,7 +337,7 @@ public class WritePlatformServiceJpaRepositoryImpl implements
try {
AppUser currentUser = extractAuthenticatedUser();
OfficeValidator validator = new OfficeValidator(command.getName(), command.getParentId(), command.getOpeningDate(), command.getExternalId());
OfficeCommandValidator validator = new OfficeCommandValidator(command.getName(), command.getParentId(), command.getOpeningDate(), command.getExternalId());
validator.validate();
Office parent = validateUserPriviledgeOnOfficeAndRetrieve(currentUser, command.getParentId());
@@ -347,8 +353,8 @@ public class WritePlatformServiceJpaRepositoryImpl implements
return office.getId();
} catch (DataIntegrityViolationException dve) {
List<ErrorResponse> dataValidationErrors = handleOfficeDataIntegrityIssues(command, dve);
throw new ApplicationDomainRuleException(dataValidationErrors, "Errors exist.");
handleOfficeDataIntegrityIssues(command, dve);
return Long.valueOf(-1);
}
}
@@ -359,7 +365,7 @@ public class WritePlatformServiceJpaRepositoryImpl implements
try {
AppUser currentUser = extractAuthenticatedUser();
OfficeValidator validator = new OfficeValidator(command.getName(), command.getParentId(), command.getOpeningDate(), command.getExternalId());
OfficeCommandValidator validator = new OfficeCommandValidator(command.getName(), command.getParentId(), command.getOpeningDate(), command.getExternalId());
validator.validate();
Office office = validateUserPriviledgeOnOfficeAndRetrieve(currentUser, command.getId());
@@ -370,27 +376,25 @@ public class WritePlatformServiceJpaRepositoryImpl implements
return office.getId();
} catch (DataIntegrityViolationException dve) {
List<ErrorResponse> dataValidationErrors = handleOfficeDataIntegrityIssues(command, dve);
throw new ApplicationDomainRuleException(dataValidationErrors, "Errors exist.");
handleOfficeDataIntegrityIssues(command, dve);
return Long.valueOf(-1);
}
}
private List<ErrorResponse> handleOfficeDataIntegrityIssues(final OfficeCommand command, DataIntegrityViolationException dve) {
/*
* Guaranteed to throw an exception no matter what the data integrity issue is.
*/
private void handleOfficeDataIntegrityIssues(final OfficeCommand command, DataIntegrityViolationException dve) {
List<ErrorResponse> dataValidationErrors = new ArrayList<ErrorResponse>();
Throwable realCause = dve.getMostSpecificCause();
if (realCause.getMessage().contains("externalid_org")) {
ErrorResponse error = new ErrorResponse("error.msg.office.duplicate.externalId", "externalId", command.getExternalId());
dataValidationErrors.add(error);
throw new PlatformDataIntegrityException("error.msg.office.duplicate.externalId", "Office with externalId {0} already exists", "externalId", command.getExternalId());
} else if (realCause.getMessage().contains("name_org")) {
ErrorResponse error = new ErrorResponse("error.msg.office.duplicate.name", "name", command.getName());
dataValidationErrors.add(error);
} else {
logger.error(dve.getMessage(), dve);
ErrorResponse error = new ErrorResponse("error.msg.office.unknown.data.integretity.issue", "id");
dataValidationErrors.add(error);
}
return dataValidationErrors;
throw new PlatformDataIntegrityException("error.msg.office.duplicate.name", "Office with name {0} already exists", "name", command.getName());
}
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.office.unknown.data.integrity.issue", "Unknown data integrity issue with resource.");
}
/*
@@ -1,31 +1,16 @@
package org.mifosng.platform.api;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import org.mifosng.data.EntityIdentifier;
import org.mifosng.data.ErrorResponse;
import org.mifosng.data.ErrorResponseList;
import org.mifosng.data.ExtraDatasets;
import org.mifosng.data.reports.GenericResultset;
import org.mifosng.platform.InvalidSqlException;
import org.mifosng.platform.ReadPlatformService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,8 +22,7 @@ import org.springframework.stereotype.Component;
@Scope("singleton")
public class ExtraDataApiResource {
private final static Logger logger = LoggerFactory
.getLogger(ExtraDataApiResource.class);
private final static Logger logger = LoggerFactory.getLogger(ExtraDataApiResource.class);
@Autowired
private ReadPlatformService readPlatformService;
@@ -1,6 +1,5 @@
package org.mifosng.platform.api;
import java.util.Arrays;
import java.util.Collection;
import javax.ws.rs.Consumes;
@@ -16,7 +15,6 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.mifosng.data.EntityIdentifier;
import org.mifosng.data.ErrorResponse;
import org.mifosng.data.ErrorResponseList;
import org.mifosng.data.LoanProductData;
import org.mifosng.data.LoanProductList;
@@ -30,7 +28,6 @@ import org.mifosng.platform.user.domain.AppUser;
import org.mifosng.platform.user.domain.AppUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
@@ -38,7 +35,6 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
/*
*
* @see http://viewfromthefringe.blogspot.com/2010/06/versioning-rest-services.html
* @see http://jersey.576304.n2.nabble.com/Restful-Service-Versioning-td6332833.html
*
@@ -57,7 +53,6 @@ import org.springframework.stereotype.Component;
* 8. @PUT /loanproducts/{productId} updates loan product of that identifier
* 9. Using /resource/template pattern for UI specific function of get 'data with defaults' for new resource.
*/
@Path("/v1/loanproducts")
@Component
@Scope("singleton")
@@ -113,10 +108,6 @@ public class LoanProductApiResource {
EntityIdentifier entityIdentifier = this.writePlatformService.createLoanProduct(command);
return Response.ok().entity(entityIdentifier).build();
} catch (AccessDeniedException e) {
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.permission", "id");
ErrorResponseList list = new ErrorResponseList(Arrays.asList(errorResponse));
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(list).build());
} catch (ApplicationDomainRuleException e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getErrors())).build());
} catch (NewDataValidationException e) {
@@ -135,10 +126,6 @@ public class LoanProductApiResource {
Collection<LoanProductData> products = this.readPlatformService.retrieveAllLoanProducts();
return Response.ok().entity(new LoanProductList(products)).build();
} catch (AccessDeniedException e) {
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.permission", "id");
ErrorResponseList list = new ErrorResponseList(Arrays.asList(errorResponse));
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(list).build());
} catch (ApplicationDomainRuleException e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getErrors())).build());
} catch (NewDataValidationException e) {
@@ -161,10 +148,6 @@ public class LoanProductApiResource {
LoanProductData loanProduct = this.readPlatformService.retrieveNewLoanProductDetails();
return Response.ok().entity(loanProduct).build();
} catch (AccessDeniedException e) {
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.permission", "id");
ErrorResponseList list = new ErrorResponseList(Arrays.asList(errorResponse));
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(list).build());
} catch (ApplicationDomainRuleException e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getErrors())).build());
} catch (NewDataValidationException e) {
@@ -184,10 +167,6 @@ public class LoanProductApiResource {
LoanProductData loanProduct = this.readPlatformService.retrieveLoanProduct(productId);
return Response.ok().entity(loanProduct).build();
} catch (AccessDeniedException e) {
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.permission", "id");
ErrorResponseList list = new ErrorResponseList(Arrays.asList(errorResponse));
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(list).build());
} catch (ApplicationDomainRuleException e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getErrors())).build());
} catch (NewDataValidationException e) {
@@ -230,10 +209,6 @@ public class LoanProductApiResource {
EntityIdentifier entityIdentifier = this.writePlatformService.updateLoanProduct(command);
return Response.ok().entity(entityIdentifier).build();
} catch (AccessDeniedException e) {
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.permission", "id");
ErrorResponseList list = new ErrorResponseList(Arrays.asList(errorResponse));
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(list).build());
} catch (ApplicationDomainRuleException e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getErrors())).build());
} catch (NewDataValidationException e) {
@@ -1,151 +0,0 @@
package org.mifosng.platform.api;
import java.util.Arrays;
import java.util.Collection;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.mifosng.data.EntityIdentifier;
import org.mifosng.data.ErrorResponse;
import org.mifosng.data.ErrorResponseList;
import org.mifosng.data.OfficeData;
import org.mifosng.data.OfficeList;
import org.mifosng.data.command.OfficeCommand;
import org.mifosng.platform.ReadPlatformService;
import org.mifosng.platform.WritePlatformService;
import org.mifosng.platform.exceptions.ApplicationDomainRuleException;
import org.mifosng.platform.exceptions.NewDataValidationException;
import org.mifosng.platform.user.domain.AppUser;
import org.mifosng.platform.user.domain.AppUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@Path("/v1/offices")
@Component
@Scope("singleton")
public class OfficeApiResource {
@Autowired
private ReadPlatformService readPlatformService;
@Autowired
private WritePlatformService writePlatformService;
@Autowired
private AppUserRepository appUserRepository;
private void hardcodeUserIntoSecurityContext() {
AppUser currentUser = this.appUserRepository.findOne(Long.valueOf(1));
Authentication auth = new UsernamePasswordAuthenticationToken(currentUser, currentUser, currentUser.getAuthorities());
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(auth);
}
@GET
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({MediaType.APPLICATION_JSON})
public Response retrieveOffices() {
try {
hardcodeUserIntoSecurityContext();
Collection<OfficeData> offices = this.readPlatformService.retrieveAllOffices();
return Response.ok().entity(new OfficeList(offices)).build();
} catch (AccessDeniedException e) {
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.permission", "id");
ErrorResponseList list = new ErrorResponseList(Arrays.asList(errorResponse));
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(list).build());
} catch (ApplicationDomainRuleException e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getErrors())).build());
} catch (NewDataValidationException e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getValidationErrors())).build());
}
}
@POST
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON})
public Response createOffice(final OfficeCommand command) {
try {
hardcodeUserIntoSecurityContext();
Long officeId = this.writePlatformService.createOffice(command);
return Response.ok().entity(new EntityIdentifier(officeId)).build();
} catch (AccessDeniedException e) {
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.permission", "id");
ErrorResponseList list = new ErrorResponseList(Arrays.asList(errorResponse));
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(list).build());
} catch (ApplicationDomainRuleException e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getErrors())).build());
} catch (NewDataValidationException e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getValidationErrors())).build());
}
}
@GET
@Path("{officeId}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON})
public Response retreiveOffice(@PathParam("officeId") final Long officeId) {
try {
hardcodeUserIntoSecurityContext();
OfficeData office = this.readPlatformService.retrieveOffice(officeId);
return Response.ok().entity(office).build();
} catch (AccessDeniedException e) {
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.permission", "id");
ErrorResponseList list = new ErrorResponseList(Arrays.asList(errorResponse));
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(list).build());
} catch (ApplicationDomainRuleException e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getErrors())).build());
} catch (NewDataValidationException e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getValidationErrors())).build());
}
}
@PUT
@Path("{officeId}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON})
public Response updateOffice(@PathParam("officeId") final Long officeId, final OfficeCommand command) {
try {
hardcodeUserIntoSecurityContext();
command.setId(officeId);
Long entityId = this.writePlatformService.updateOffice(command);
return Response.ok().entity(new EntityIdentifier(entityId)).build();
} catch (AccessDeniedException e) {
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.permission", "id");
ErrorResponseList list = new ErrorResponseList(Arrays.asList(errorResponse));
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(list).build());
} catch (ApplicationDomainRuleException e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getErrors())).build());
} catch (NewDataValidationException e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getValidationErrors())).build());
}
}
}
@@ -0,0 +1,29 @@
package org.mifosng.platform.api.errorhandling;
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.data.ApiGlobalErrorResponse;
import org.springframework.context.annotation.Scope;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
/**
* An {@link ExceptionMapper} to map {@link AccessDeniedException} thrown by platform into a HTTP API friendly format.
*
* The {@link AccessDeniedException} is thrown by spring security on platform when an attempt is made to use functionality
* for which the user does have sufficient privileges.
*/
@Provider
@Component
@Scope("singleton")
public class AccessDeniedExceptionMapper implements ExceptionMapper<AccessDeniedException> {
@Override
public Response toResponse(AccessDeniedException exception) {
// Status code 403 really reads as: "Authenticated - but not authorized":
return Response.status(Status.FORBIDDEN).entity(ApiGlobalErrorResponse.unAuthorized()).build();
}
}
@@ -0,0 +1,29 @@
package org.mifosng.platform.api.errorhandling;
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.data.ApiGlobalErrorResponse;
import org.mifosng.platform.exceptions.NoAuthorizationException;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* An {@link ExceptionMapper} to map {@link NoAuthorizationException} thrown by platform into a HTTP API friendly format.
*
* The {@link NoAuthorizationException} is thrown on platform when an attempt is made to use functionality
* for which the user does have sufficient privileges.
*/
@Provider
@Component
@Scope("singleton")
public class NoAuthorizationExceptionMapper implements ExceptionMapper<NoAuthorizationException> {
@Override
public Response toResponse(NoAuthorizationException exception) {
// Status code 403 really reads as: "Authenticated - but not authorized":
return Response.status(Status.FORBIDDEN).entity(ApiGlobalErrorResponse.unAuthorized()).build();
}
}
@@ -0,0 +1,31 @@
package org.mifosng.platform.api.errorhandling;
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.data.ApiGlobalErrorResponse;
import org.mifosng.platform.exceptions.PlatformApiDataValidationException;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* An {@link ExceptionMapper} to map {@link PlatformApiDataValidationException} thrown by platform into a HTTP API friendly format.
*
* The {@link PlatformApiDataValidationException} is typically thrown in data validation of the parameters passed in with an api request.
*/
@Provider
@Component
@Scope("singleton")
public class PlatformApiDataValidationExceptionMapper implements ExceptionMapper<PlatformApiDataValidationException> {
@Override
public Response toResponse(PlatformApiDataValidationException exception) {
ApiGlobalErrorResponse dataIntegrityError = ApiGlobalErrorResponse.badClientRequest(exception.getGlobalisationMessageCode(),
exception.getDefaultUserMessage(), exception.getErrors());
return Response.status(Status.BAD_REQUEST).entity(dataIntegrityError).build();
}
}
@@ -0,0 +1,31 @@
package org.mifosng.platform.api.errorhandling;
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.data.ApiGlobalErrorResponse;
import org.mifosng.platform.exceptions.PlatformDataIntegrityException;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* An {@link ExceptionMapper} to map {@link PlatformDataIntegrityException} thrown by platform into a HTTP API friendly format.
*
* The {@link PlatformDataIntegrityException} is thrown when modifying api call result in data integrity checks to be fired.
*/
@Provider
@Component
@Scope("singleton")
public class PlatformDataIntegrityExceptionMapper implements ExceptionMapper<PlatformDataIntegrityException> {
@Override
public Response toResponse(PlatformDataIntegrityException exception) {
ApiGlobalErrorResponse dataIntegrityError = ApiGlobalErrorResponse.dataIntegrityError(exception.getGlobalisationMessageCode(), exception.getDefaultUserMessage(),
exception.getParameterName(), exception.getDefaultUserMessageArgs());
return Response.status(Status.BAD_REQUEST).entity(dataIntegrityError).build();
}
}
@@ -0,0 +1,29 @@
package org.mifosng.platform.api.errorhandling;
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.data.ApiGlobalErrorResponse;
import org.mifosng.platform.exceptions.PlatformResourceNotFoundException;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* An {@link ExceptionMapper} to map {@link PlatformResourceNotFoundException} thrown by platform into a HTTP API friendly format.
*
* The {@link PlatformResourceNotFoundException} is thrown when an api call for a resource that is expected to exist does not.
*/
@Provider
@Component
@Scope("singleton")
public class PlatformResourceNotFoundExceptionMapper implements ExceptionMapper<PlatformResourceNotFoundException> {
@Override
public Response toResponse(PlatformResourceNotFoundException exception) {
ApiGlobalErrorResponse notFoundErrorResponse = ApiGlobalErrorResponse.notFound(exception.getGlobalisationMessageCode(), exception.getDefaultUserMessage(), exception.getDefaultUserMessageArgs());
return Response.status(Status.NOT_FOUND).entity(notFoundErrorResponse).build();
}
}
@@ -1,14 +1,11 @@
package org.mifosng.platform.api.errorhandling;
import java.util.Arrays;
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.data.ErrorResponse;
import org.mifosng.data.ErrorResponseList;
import org.mifosng.data.ApiGlobalErrorResponse;
import org.mifosng.platform.exceptions.UnAuthenticatedUserException;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -23,9 +20,7 @@ public class UnAuthenticatedUserExceptionMapper implements ExceptionMapper<UnAut
@Override
public Response toResponse(UnAuthenticatedUserException exception) {
ErrorResponse errorResponse = new ErrorResponse("error.msg.not.authorized", "id");
ErrorResponseList list = new ErrorResponseList(Arrays.asList(errorResponse));
return Response.status(Status.UNAUTHORIZED).entity(list).build();
// Status code 401 really reads as: "Unauthenticated":
return Response.status(Status.UNAUTHORIZED).entity(ApiGlobalErrorResponse.unAuthenticated()).build();
}
}
@@ -0,0 +1,99 @@
package org.mifosng.platform.api.office;
import java.util.Collection;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.mifosng.data.EntityIdentifier;
import org.mifosng.data.OfficeData;
import org.mifosng.data.OfficeList;
import org.mifosng.data.command.OfficeCommand;
import org.mifosng.platform.WritePlatformService;
import org.mifosng.platform.organisation.service.OfficeReadPlatformService;
import org.mifosng.platform.user.domain.AppUser;
import org.mifosng.platform.user.domain.AppUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@Path("/v1/offices")
@Component
@Scope("singleton")
public class OfficeApiResource {
@Autowired
private OfficeReadPlatformService readPlatformService;
@Autowired
private WritePlatformService writePlatformService;
@Autowired
private AppUserRepository appUserRepository;
private void hardcodeUserIntoSecurityContext() {
AppUser currentUser = this.appUserRepository.findOne(Long.valueOf(1));
Authentication auth = new UsernamePasswordAuthenticationToken(currentUser, currentUser, currentUser.getAuthorities());
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(auth);
}
@GET
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({MediaType.APPLICATION_JSON})
public Response retrieveOffices() {
hardcodeUserIntoSecurityContext();
Collection<OfficeData> offices = this.readPlatformService.retrieveAllOffices();
return Response.ok().entity(new OfficeList(offices)).build();
}
@POST
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON})
public Response createOffice(final OfficeCommand command) {
hardcodeUserIntoSecurityContext();
Long officeId = this.writePlatformService.createOffice(command);
return Response.ok().entity(new EntityIdentifier(officeId)).build();
}
@GET
@Path("{officeId}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON})
public Response retreiveOffice(@PathParam("officeId") final Long officeId) {
hardcodeUserIntoSecurityContext();
OfficeData office = this.readPlatformService.retrieveOffice(officeId);
return Response.ok().entity(office).build();
}
@PUT
@Path("{officeId}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON})
public Response updateOffice(@PathParam("officeId") final Long officeId, final OfficeCommand command) {
hardcodeUserIntoSecurityContext();
command.setId(officeId);
Long entityId = this.writePlatformService.updateOffice(command);
return Response.ok().entity(new EntityIdentifier(entityId)).build();
}
}
@@ -0,0 +1,30 @@
package org.mifosng.platform.exceptions;
import java.util.List;
import org.mifosng.data.ApiParameterError;
public class PlatformApiDataValidationException extends RuntimeException {
private final String globalisationMessageCode;
private final String defaultUserMessage;
private final List<ApiParameterError> errors;
public PlatformApiDataValidationException(final String globalisationMessageCode, final String defaultUserMessage, List<ApiParameterError> errors) {
this.globalisationMessageCode = globalisationMessageCode;
this.defaultUserMessage = defaultUserMessage;
this.errors = errors;
}
public String getGlobalisationMessageCode() {
return globalisationMessageCode;
}
public String getDefaultUserMessage() {
return defaultUserMessage;
}
public List<ApiParameterError> getErrors() {
return errors;
}
}
@@ -0,0 +1,42 @@
package org.mifosng.platform.exceptions;
/**
* A {@link RuntimeException} thrown when data integrity problems happen due to state modifying actions.
*/
public class PlatformDataIntegrityException extends RuntimeException {
private final String globalisationMessageCode;
private final String defaultUserMessage;
private final String parameterName;
private final Object[] defaultUserMessageArgs;
public PlatformDataIntegrityException(final String globalisationMessageCode, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
this.globalisationMessageCode = globalisationMessageCode;
this.defaultUserMessage = defaultUserMessage;
this.parameterName = "";
this.defaultUserMessageArgs = defaultUserMessageArgs;
}
public PlatformDataIntegrityException(final String globalisationMessageCode, final String defaultUserMessage, final String parameterName, final Object... defaultUserMessageArgs) {
this.globalisationMessageCode = globalisationMessageCode;
this.defaultUserMessage = defaultUserMessage;
this.parameterName = parameterName;
this.defaultUserMessageArgs = defaultUserMessageArgs;
}
public String getGlobalisationMessageCode() {
return globalisationMessageCode;
}
public String getDefaultUserMessage() {
return defaultUserMessage;
}
public Object[] getDefaultUserMessageArgs() {
return defaultUserMessageArgs;
}
public String getParameterName() {
return parameterName;
}
}
@@ -0,0 +1,29 @@
package org.mifosng.platform.exceptions;
/**
* A {@link RuntimeException} thrown when resources that are queried for are not found.
*/
public class PlatformResourceNotFoundException extends RuntimeException {
private final String globalisationMessageCode;
private final String defaultUserMessage;
private final Object[] defaultUserMessageArgs;
public PlatformResourceNotFoundException(String globalisationMessageCode, String defaultUserMessage, Object... defaultUserMessageArgs) {
this.globalisationMessageCode = globalisationMessageCode;
this.defaultUserMessage = defaultUserMessage;
this.defaultUserMessageArgs = defaultUserMessageArgs;
}
public String getGlobalisationMessageCode() {
return globalisationMessageCode;
}
public String getDefaultUserMessage() {
return defaultUserMessage;
}
public Object[] getDefaultUserMessageArgs() {
return defaultUserMessageArgs;
}
}
@@ -35,7 +35,8 @@ public class CustomAuthenticationFailureHandler implements AuthenticationFailure
* If redirecting or forwarding, {@code saveException} will be called to cache the exception for use in
* the target view.
*/
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (defaultFailureUrl == null) {
@@ -0,0 +1,55 @@
package org.mifosng.platform.organisation.service;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosng.data.ApiParameterError;
import org.mifosng.platform.exceptions.PlatformApiDataValidationException;
public class OfficeCommandValidator {
private final String name;
private final Long parentId;
private final LocalDate openingDate;
private final String externalId;
public OfficeCommandValidator(String name, Long parentId, LocalDate openingDate, String externalId) {
this.name = name;
this.parentId = parentId;
this.openingDate = openingDate;
this.externalId = externalId;
}
public void validate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
if (StringUtils.isBlank(this.name)) {
ApiParameterError error = ApiParameterError.parameterError("validation.msg.office.name.cannot.be.blank", "The parameter name cannot be blank.", "name");
dataValidationErrors.add(error);
}
if (this.parentId == null || parentId.intValue() <= 0) {
ApiParameterError error = ApiParameterError.parameterError("validation.msg.office.parent.cannot.be.blank", "A valid parentId must be provided.", "parentId");
dataValidationErrors.add(error);
}
if (this.openingDate == null) {
ApiParameterError error = ApiParameterError.parameterError("validation.msg.office.opening.date.cannot.be.blank", "The parameter openingDate cannot be blank", "openingDate");
dataValidationErrors.add(error);
}
if (StringUtils.isNotBlank(this.externalId)) {
if (this.externalId.trim().length() > 100) {
ApiParameterError error = ApiParameterError.parameterError("validation.msg.office.externalId.exceeds.max.length", "The parameter externalId exceeds max length of {0}", "externalId", 100);
dataValidationErrors.add(error);
}
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
}
}
}
@@ -0,0 +1,117 @@
package org.mifosng.platform.organisation.service;
import static org.mifosng.platform.Specifications.officesThatMatch;
import org.mifosng.data.command.OfficeCommand;
import org.mifosng.platform.exceptions.NoAuthorizationException;
import org.mifosng.platform.exceptions.PlatformDataIntegrityException;
import org.mifosng.platform.organisation.domain.Office;
import org.mifosng.platform.organisation.domain.OfficeRepository;
import org.mifosng.platform.user.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OfficePlatformServiceJpaRepositoryImpl implements OfficeWritePlatformService {
private final static Logger logger = LoggerFactory.getLogger(OfficePlatformServiceJpaRepositoryImpl.class);
private final PlatformSecurityContext context;
private final OfficeRepository officeRepository;
@Autowired
public OfficePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final OfficeRepository officeRepository) {
this.context = context;
this.officeRepository = officeRepository;
}
@Transactional
@Override
public Long createOffice(final OfficeCommand command) {
try {
AppUser currentUser = context.authenticatedUser();
OfficeCommandValidator validator = new OfficeCommandValidator(command.getName(), command.getParentId(), command.getOpeningDate(), command.getExternalId());
validator.validate();
Office parent = validateUserPriviledgeOnOfficeAndRetrieve(currentUser, command.getParentId());
Office office = Office.createNew(currentUser.getOrganisation(), parent, command.getName(), command.getOpeningDate(), command.getExternalId());
// pre save to generate id for use in office hierarchy
this.officeRepository.save(office);
office.generateHierarchy();
this.officeRepository.save(office);
return office.getId();
} catch (DataIntegrityViolationException dve) {
handleOfficeDataIntegrityIssues(command, dve);
return Long.valueOf(-1);
}
}
@Transactional
@Override
public Long updateOffice(final OfficeCommand command) {
try {
AppUser currentUser = context.authenticatedUser();
OfficeCommandValidator validator = new OfficeCommandValidator(command.getName(), command.getParentId(), command.getOpeningDate(), command.getExternalId());
validator.validate();
Office office = validateUserPriviledgeOnOfficeAndRetrieve(currentUser, command.getId());
office.update(command.getName(), command.getExternalId(), command.getOpeningDate());
this.officeRepository.save(office);
return office.getId();
} catch (DataIntegrityViolationException dve) {
handleOfficeDataIntegrityIssues(command, dve);
return Long.valueOf(-1);
}
}
/*
* Guaranteed to throw an exception no matter what the data integrity issue is.
*/
private void handleOfficeDataIntegrityIssues(final OfficeCommand command, DataIntegrityViolationException dve) {
Throwable realCause = dve.getMostSpecificCause();
if (realCause.getMessage().contains("externalid_org")) {
throw new PlatformDataIntegrityException("error.msg.office.duplicate.externalId", "Office with externalId {0} already exists", "externalId", command.getExternalId());
} else if (realCause.getMessage().contains("name_org")) {
throw new PlatformDataIntegrityException("error.msg.office.duplicate.name", "Office with name {0} already exists", "name", command.getName());
}
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.office.unknown.data.integrity.issue", "Unknown data integrity issue with resource.");
}
/*
* used to restrict modifying operations to office that are either the users office or lower (child) in the office hierarchy
*/
private Office validateUserPriviledgeOnOfficeAndRetrieve(AppUser currentUser, Long officeId) {
Office userOffice = this.officeRepository.findOne(officesThatMatch(currentUser.getOrganisation(), currentUser.getOffice().getId()));
if (userOffice.doesNotHaveAnOfficeInHierarchyWithId(officeId)) {
throw new NoAuthorizationException("User does not have sufficient priviledges to act on the provided office.");
}
Office officeToReturn = userOffice;
if (!userOffice.identifiedBy(officeId)) {
officeToReturn = this.officeRepository.findOne(officesThatMatch(currentUser.getOrganisation(), officeId));
}
return officeToReturn;
}
}
@@ -0,0 +1,12 @@
package org.mifosng.platform.organisation.service;
import java.util.Collection;
import org.mifosng.data.OfficeData;
public interface OfficeReadPlatformService {
Collection<OfficeData> retrieveAllOffices();
OfficeData retrieveOffice(Long officeId);
}
@@ -0,0 +1,105 @@
package org.mifosng.platform.organisation.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosng.data.OfficeData;
import org.mifosng.platform.exceptions.PlatformResourceNotFoundException;
import org.mifosng.platform.user.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService {
private final SimpleJdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
@Autowired
public OfficeReadPlatformServiceImpl(final PlatformSecurityContext context, final DataSource dataSource) {
this.context = context;
this.jdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
private static final class OfficeMapper implements RowMapper<OfficeData> {
public String officeSchema() {
return " o.id as id, o.name as name, o.external_id as externalId, o.opening_date as openingDate, o.hierarchy as hierarchy, parent.id as parentId, parent.name as parentName "
+ "from org_office o LEFT JOIN org_office AS parent ON parent.id = o.parent_id ";
}
@Override
public OfficeData mapRow(final ResultSet rs, final int rowNum)
throws SQLException {
Long id = rs.getLong("id");
String name = rs.getString("name");
String externalId = rs.getString("externalId");
LocalDate openingDate = new LocalDate(rs.getDate("openingDate"));
String hierarchy = rs.getString("hierarchy");
Long parentId = rs.getLong("parentId");
String parentName = rs.getString("parentName");
return new OfficeData(id, name, externalId, openingDate, hierarchy, parentId, parentName);
}
}
@Override
public Collection<OfficeData> retrieveAllOffices() {
AppUser currentUser = context.authenticatedUser();
String hierarchy = currentUser.getOffice().getHierarchy();
String hierarchySearchString = hierarchy + "%";
OfficeMapper rm = new OfficeMapper();
String sql = "select " + rm.officeSchema() + "where o.org_id = ? and o.hierarchy like ? order by o.hierarchy";
return this.jdbcTemplate.query(sql, rm, new Object[] {currentUser.getOrganisation().getId(), hierarchySearchString});
}
@Override
public OfficeData retrieveOffice(final Long officeId) {
try {
AppUser currentUser = context.authenticatedUser();
OfficeMapper rm = new OfficeMapper();
String sql = "select " + rm.officeSchema()
+ " where o.org_id = ? and o.id = ?";
OfficeData selectedOffice = this.jdbcTemplate.queryForObject(sql,
rm, new Object[] { currentUser.getOrganisation().getId(),
officeId });
List<OfficeData> allowedParents = new ArrayList<OfficeData>();
if (StringUtils.isNotBlank(selectedOffice.getParentName())) {
Collection<OfficeData> allOffices = retrieveAllOffices();
for (OfficeData office : allOffices) {
if (!office.getId().equals(selectedOffice.getId())) {
allowedParents.add(office);
}
}
}
selectedOffice.setAllowedParents(allowedParents);
return selectedOffice;
} catch (EmptyResultDataAccessException e) {
throw new PlatformResourceNotFoundException("error.msg.office.id.invalid", "Office with identifier {0} does not exist", officeId);
}
}
}
@@ -0,0 +1,10 @@
package org.mifosng.platform.organisation.service;
import org.mifosng.data.command.OfficeCommand;
public interface OfficeWritePlatformService {
Long createOffice(OfficeCommand command);
Long updateOffice(OfficeCommand command);
}
@@ -0,0 +1,9 @@
package org.mifosng.platform.organisation.service;
import org.mifosng.platform.user.domain.AppUser;
public interface PlatformSecurityContext {
AppUser authenticatedUser();
}
@@ -0,0 +1,33 @@
package org.mifosng.platform.organisation.service;
import org.mifosng.platform.exceptions.UnAuthenticatedUserException;
import org.mifosng.platform.user.domain.AppUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
/**
* Responsible for returning the current authenticated user that is currently set in the {@link SecurityContext}.
*/
@Service
public class PlatformSecurityContextThroughSpringSecurity implements PlatformSecurityContext {
@Override
public AppUser authenticatedUser() {
AppUser currentUser = null;
SecurityContext context = SecurityContextHolder.getContext();
if (context != null) {
Authentication auth = context.getAuthentication();
if (auth != null) {
currentUser = (AppUser) auth.getPrincipal();
}
}
if (currentUser == null) {
throw new UnAuthenticatedUserException();
}
return currentUser;
}
}