diff --git a/mifosng-data/src/main/java/org/mifosng/data/ApiGlobalErrorResponse.java b/mifosng-data/src/main/java/org/mifosng/data/ApiGlobalErrorResponse.java index 0cfd9f7a0..fb300155c 100644 --- a/mifosng-data/src/main/java/org/mifosng/data/ApiGlobalErrorResponse.java +++ b/mifosng-data/src/main/java/org/mifosng/data/ApiGlobalErrorResponse.java @@ -66,6 +66,21 @@ public class ApiGlobalErrorResponse { return globalErrorResponse; } + public static ApiGlobalErrorResponse domainRuleViolation(String globalisationMessageCode, String defaultUserMessage, Object... defaultUserMessageArgs) { + ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); + globalErrorResponse.setHttpStatusCode("403"); + globalErrorResponse.setDeveloperMessage("Request was understoon but caused a domain rule violation."); + globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes"); + globalErrorResponse.setUserMessageGlobalisationCode("validation.msg.domain.rule.violation"); + globalErrorResponse.setDefaultUserMessage("Errors contain reason for domain rule violation."); + + List errors = new ArrayList(); + errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs)); + globalErrorResponse.setErrors(errors); + + return globalErrorResponse; + } + public static ApiGlobalErrorResponse notFound(String globalisationMessageCode, String defaultUserMessage, Object... defaultUserMessageArgs) { diff --git a/mifosng-data/src/main/java/org/mifosng/data/ApiParameterError.java b/mifosng-data/src/main/java/org/mifosng/data/ApiParameterError.java index ad083a08d..d959e99ba 100644 --- a/mifosng-data/src/main/java/org/mifosng/data/ApiParameterError.java +++ b/mifosng-data/src/main/java/org/mifosng/data/ApiParameterError.java @@ -70,7 +70,7 @@ public class ApiParameterError { } this.args = messageArgs; - this.parameterName="identifier"; + this.parameterName="id"; } public String getDeveloperMessage() { diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/errorhandling/PlatformDomainRuleExceptionMapper.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/errorhandling/PlatformDomainRuleExceptionMapper.java new file mode 100644 index 000000000..fcf782c33 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/errorhandling/PlatformDomainRuleExceptionMapper.java @@ -0,0 +1,30 @@ +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.AbstractPlatformDomainRuleException; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * An {@link ExceptionMapper} to map {@link AbstractPlatformDomainRuleException} thrown by platform into a HTTP API friendly format. + * + * The {@link AbstractPlatformDomainRuleException} is thrown when an api call results is some internal business/domain logic been violated. + */ +@Provider +@Component +@Scope("singleton") +public class PlatformDomainRuleExceptionMapper implements ExceptionMapper { + + @Override + public Response toResponse(AbstractPlatformDomainRuleException exception) { + + ApiGlobalErrorResponse notFoundErrorResponse = ApiGlobalErrorResponse.domainRuleViolation(exception.getGlobalisationMessageCode(), exception.getDefaultUserMessage(), exception.getDefaultUserMessageArgs()); + // request understood but not carried out due to it violating some domain/business logic + return Response.status(Status.FORBIDDEN).entity(notFoundErrorResponse).build(); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/PlatformDomainRuleException.java b/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/AbstractPlatformDomainRuleException.java similarity index 75% rename from mifosng-provider/src/main/java/org/mifosng/platform/exceptions/PlatformDomainRuleException.java rename to mifosng-provider/src/main/java/org/mifosng/platform/exceptions/AbstractPlatformDomainRuleException.java index 6cc5dc989..a99c2c48c 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/PlatformDomainRuleException.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/AbstractPlatformDomainRuleException.java @@ -3,13 +3,13 @@ package org.mifosng.platform.exceptions; /** * A {@link RuntimeException} thrown when valid api request end up violating some domain rule. */ -public class PlatformDomainRuleException extends RuntimeException { +public abstract class AbstractPlatformDomainRuleException extends RuntimeException { private final String globalisationMessageCode; private final String defaultUserMessage; private final Object[] defaultUserMessageArgs; - public PlatformDomainRuleException(String globalisationMessageCode, String defaultUserMessage, Object... defaultUserMessageArgs) { + public AbstractPlatformDomainRuleException(String globalisationMessageCode, String defaultUserMessage, Object... defaultUserMessageArgs) { this.globalisationMessageCode = globalisationMessageCode; this.defaultUserMessage = defaultUserMessage; this.defaultUserMessageArgs = defaultUserMessageArgs; diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/CannotUpdateOfficeWithParentOfficeSameAsSelf.java b/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/CannotUpdateOfficeWithParentOfficeSameAsSelf.java new file mode 100644 index 000000000..b22ae32ae --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/CannotUpdateOfficeWithParentOfficeSameAsSelf.java @@ -0,0 +1,12 @@ +package org.mifosng.platform.exceptions; + +/** + * Exception thrown when an attempt is made update the parent of a root office. + */ +public class CannotUpdateOfficeWithParentOfficeSameAsSelf extends + AbstractPlatformDomainRuleException { + + public CannotUpdateOfficeWithParentOfficeSameAsSelf(final Long officeId, final Long parentId) { + super("error.msg.office.parentId.same.as.id", "Cannot update office with parent same as self.", officeId, parentId); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted.java b/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted.java new file mode 100644 index 000000000..e4ee0cfc4 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted.java @@ -0,0 +1,13 @@ +package org.mifosng.platform.exceptions; + +/** + * {@link AbstractPlatformDomainRuleException} thrown when trying to delete a loan in an invalid state. + */ +public class LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted extends + AbstractPlatformDomainRuleException { + + public LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted(final Long id) { + super("error.msg.loan.cannot.delete.loan.in.its.present.state", "Loan with identifier " + id + " cannot be deleted in its current state.", id); + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/RootOfficeParentCannotBeUpdated.java b/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/RootOfficeParentCannotBeUpdated.java new file mode 100644 index 000000000..276d53439 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/RootOfficeParentCannotBeUpdated.java @@ -0,0 +1,12 @@ +package org.mifosng.platform.exceptions; + +/** + * Exception thrown when an attempt is made update the parent of a root office. + */ +public class RootOfficeParentCannotBeUpdated extends + AbstractPlatformDomainRuleException { + + public RootOfficeParentCannotBeUpdated() { + super("error.msg.office.cannot.update.parent.office.of.root.office", "The root office must not be set with a parent office."); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java index c82085dc9..f3905999a 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -21,9 +21,9 @@ import org.mifosng.platform.client.domain.NoteRepository; import org.mifosng.platform.currency.domain.MonetaryCurrency; import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.exceptions.LoanNotFoundException; +import org.mifosng.platform.exceptions.LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted; import org.mifosng.platform.exceptions.LoanTransactionNotFoundException; import org.mifosng.platform.exceptions.NoAuthorizationException; -import org.mifosng.platform.exceptions.PlatformDomainRuleException; import org.mifosng.platform.loan.domain.DefaultLoanLifecycleStateMachine; import org.mifosng.platform.loan.domain.Loan; import org.mifosng.platform.loan.domain.LoanLifecycleStateMachine; @@ -113,7 +113,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } if (loan.isNotSubmittedAndPendingApproval()) { - throw new PlatformDomainRuleException("error.msg.cannot.delete.loan.in.its.present.state", "Loan with identifier {0} cannot be deleted in its current state.", loanId); + throw new LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted(loanId); } Long clientId = loan.getClient().getId(); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/organisation/domain/Office.java b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/domain/Office.java index 0db44b0f3..a701db9ea 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/organisation/domain/Office.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/domain/Office.java @@ -18,6 +18,8 @@ import javax.persistence.UniqueConstraint; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; import org.mifosng.platform.api.commands.OfficeCommand; +import org.mifosng.platform.exceptions.CannotUpdateOfficeWithParentOfficeSameAsSelf; +import org.mifosng.platform.exceptions.RootOfficeParentCannotBeUpdated; import org.mifosng.platform.infrastructure.AbstractAuditableCustom; import org.mifosng.platform.user.domain.AppUser; @@ -120,6 +122,12 @@ public class Office extends AbstractAuditableCustom { } public void update(Office newParent) { + if (this.parent == null) { + throw new RootOfficeParentCannotBeUpdated(); + } + if (this.identifiedBy(newParent.getId())) { + throw new CannotUpdateOfficeWithParentOfficeSameAsSelf(this.getId(), newParent.getId()); + } this.parent = newParent; generateHierarchy(); } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/OfficeWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/OfficeWritePlatformServiceJpaRepositoryImpl.java index 39a9364b0..427f58e9d 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/OfficeWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/OfficeWritePlatformServiceJpaRepositoryImpl.java @@ -43,9 +43,6 @@ public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWriteP validator.validateForCreate(); Office parent = validateUserPriviledgeOnOfficeAndRetrieve(currentUser, command.getParentId()); - if (parent == null) { - throw new OfficeNotFoundException(command.getParentId()); - } Office office = Office.createNew(currentUser.getOrganisation(), parent, command.getName(), command.getOpeningLocalDate(), command.getExternalId()); @@ -74,17 +71,11 @@ public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWriteP validator.validateForUpdate(); Office office = validateUserPriviledgeOnOfficeAndRetrieve(currentUser, command.getId()); - if (office == null) { - throw new OfficeNotFoundException(command.getId()); - } office.update(command); - if (!command.isRootOffice() && command.getParentId() != null) { + if (command.getParentId() != null) { Office parent = validateUserPriviledgeOnOfficeAndRetrieve(currentUser, command.getParentId()); - if (parent == null) { - throw new OfficeNotFoundException(command.getParentId()); - } office.update(parent); } @@ -130,6 +121,9 @@ public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWriteP Office officeToReturn = userOffice; if (!userOffice.identifiedBy(officeId)) { officeToReturn = this.officeRepository.findOne(officesThatMatch(currentUser.getOrganisation(), officeId)); + if (officeToReturn == null) { + throw new OfficeNotFoundException(officeId); + } } return officeToReturn;