MIFOSX-97: refactor into command handling logic, prepare for sql based approach for change detection.

This commit is contained in:
Keith Woodlock
2012-11-21 14:23:16 +00:00
parent b8961d7914
commit 9aa9b870f0
16 changed files with 2731 additions and 2920 deletions
@@ -68,8 +68,7 @@ public interface PortfolioApiDataConversionService {
LoanTransactionCommand convertJsonToLoanTransactionCommand(Long resourceIdentifier, String json); LoanTransactionCommand convertJsonToLoanTransactionCommand(Long resourceIdentifier, String json);
AdjustLoanTransactionCommand convertJsonToAdjustLoanTransactionCommand( AdjustLoanTransactionCommand convertJsonToAdjustLoanTransactionCommand(Long loanId, Long transactionId, String json);
Long loanId, Long transactionId, String json);
OrganisationCurrencyCommand convertJsonToOrganisationCurrencyCommand(String json); OrganisationCurrencyCommand convertJsonToOrganisationCurrencyCommand(String json);
@@ -66,7 +66,8 @@ public interface PortfolioApiJsonSerializerService {
String serializeOfficeDataToJson(boolean prettyPrint, Set<String> responseParameters, OfficeData office); String serializeOfficeDataToJson(boolean prettyPrint, Set<String> responseParameters, OfficeData office);
String serializeOfficeTransactionDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<OfficeTransactionData> officeTransactions); String serializeOfficeTransactionDataToJson(boolean prettyPrint, Set<String> responseParameters,
Collection<OfficeTransactionData> officeTransactions);
String serializeOfficeTransactionDataToJson(boolean prettyPrint, Set<String> responseParameters, OfficeTransactionData officeTransaction); String serializeOfficeTransactionDataToJson(boolean prettyPrint, Set<String> responseParameters, OfficeTransactionData officeTransaction);
@@ -100,9 +101,11 @@ public interface PortfolioApiJsonSerializerService {
String serializeClientDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<ClientData> clients); String serializeClientDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<ClientData> clients);
String serializeClientAccountSummaryCollectionDataToJson(boolean prettyPrint, Set<String> responseParameters, ClientAccountSummaryCollectionData clientAccount); String serializeClientAccountSummaryCollectionDataToJson(boolean prettyPrint, Set<String> responseParameters,
ClientAccountSummaryCollectionData clientAccount);
String serializeGroupAccountSummaryCollectionDataToJson(boolean prettyPrint, Set<String> responseParameters, GroupAccountSummaryCollectionData groupAccount); String serializeGroupAccountSummaryCollectionDataToJson(boolean prettyPrint, Set<String> responseParameters,
GroupAccountSummaryCollectionData groupAccount);
String serializeGroupDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<GroupData> groups); String serializeGroupDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<GroupData> groups);
@@ -118,7 +121,8 @@ public interface PortfolioApiJsonSerializerService {
String serializeLoanTransactionDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanTransactionData newTransactionData); String serializeLoanTransactionDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanTransactionData newTransactionData);
String serializeLoanReassignmentDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanReassignmentData loanReassignmentData); String serializeLoanReassignmentDataToJson(boolean prettyPrint, Set<String> responseParameters,
LoanReassignmentData loanReassignmentData);
String serializeChargeDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<ChargeData> charges); String serializeChargeDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<ChargeData> charges);
@@ -132,9 +136,11 @@ public interface PortfolioApiJsonSerializerService {
String serializeEntityIdentifier(EntityIdentifier identifier); String serializeEntityIdentifier(EntityIdentifier identifier);
String serializeClientIdentifierDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<ClientIdentifierData> clientIdentifiers); String serializeClientIdentifierDataToJson(boolean prettyPrint, Set<String> responseParameters,
Collection<ClientIdentifierData> clientIdentifiers);
String serializeClientIdentifierDataToJson(boolean prettyPrint, Set<String> responseParameters, ClientIdentifierData clientIdentifierData); String serializeClientIdentifierDataToJson(boolean prettyPrint, Set<String> responseParameters,
ClientIdentifierData clientIdentifierData);
String serializeDocumentDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<DocumentData> documentDatas); String serializeDocumentDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<DocumentData> documentDatas);
@@ -150,6 +156,7 @@ public interface PortfolioApiJsonSerializerService {
String serializeSavingAccountsDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<SavingAccountData> accounts); String serializeSavingAccountsDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<SavingAccountData> accounts);
// TODO - KW - might devide serialize up into portfolio and account to reduce interface // TODO - KW - might devide serialize up into portfolio and account to
// reduce interface
String serializeChartOfAccountDataToJson(boolean prettyPrint, Set<String> responseParameters, ChartOfAccountsData chartOfAccounts); String serializeChartOfAccountDataToJson(boolean prettyPrint, Set<String> responseParameters, ChartOfAccountsData chartOfAccounts);
} }
@@ -49,17 +49,14 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
private final NoteRepository noteRepository; private final NoteRepository noteRepository;
private final CodeValueRepository codeValueRepository; private final CodeValueRepository codeValueRepository;
// FIXME - kw - this value is hardcoded for now but will be retrieved from configuration // FIXME - kw - this value is hardcoded for now but will be retrieved from
// configuration
private boolean makerCheckerEnabledForTask = false; private boolean makerCheckerEnabledForTask = false;
@Autowired @Autowired
public ClientWritePlatformServiceJpaRepositoryImpl( public ClientWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final ClientRepository clientRepository,
final PlatformSecurityContext context, final ClientIdentifierRepository clientIdentifierRepository, final OfficeRepository officeRepository,
final ClientRepository clientRepository, NoteRepository noteRepository, final CodeValueRepository codeValueRepository) {
final ClientIdentifierRepository clientIdentifierRepository,
final OfficeRepository officeRepository,
NoteRepository noteRepository,
final CodeValueRepository codeValueRepository) {
this.context = context; this.context = context;
this.clientRepository = clientRepository; this.clientRepository = clientRepository;
this.clientIdentifierRepository = clientIdentifierRepository; this.clientIdentifierRepository = clientIdentifierRepository;
@@ -74,16 +71,12 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
final Long clientId = command.getId(); final Long clientId = command.getId();
final Client client = this.clientRepository.findOne(clientId); final Client client = this.clientRepository.findOne(clientId);
if (client == null || client.isDeleted()) { if (client == null || client.isDeleted()) { throw new ClientNotFoundException(clientId); }
throw new ClientNotFoundException(clientId);
}
client.delete(); client.delete();
this.clientRepository.save(client); this.clientRepository.save(client);
if (this.makerCheckerEnabledForTask && !command.isApprovedByChecker()) { if (this.makerCheckerEnabledForTask && !command.isApprovedByChecker()) { throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException(); }
throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException();
}
return new EntityIdentifier(client.getId()); return new EntityIdentifier(client.getId());
} }
@@ -92,21 +85,15 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
* Guaranteed to throw an exception no matter what the data integrity issue * Guaranteed to throw an exception no matter what the data integrity issue
* is. * is.
*/ */
private void handleDataIntegrityIssues(final ClientCommand command, private void handleDataIntegrityIssues(final ClientCommand command, final DataIntegrityViolationException dve) {
final DataIntegrityViolationException dve) {
Throwable realCause = dve.getMostSpecificCause(); Throwable realCause = dve.getMostSpecificCause();
if (realCause.getMessage().contains("external_id")) { if (realCause.getMessage().contains("external_id")) { throw new PlatformDataIntegrityException(
throw new PlatformDataIntegrityException( "error.msg.client.duplicate.externalId", "Client with externalId `" + command.getExternalId() + "` already exists",
"error.msg.client.duplicate.externalId", "externalId", command.getExternalId()); }
"Client with externalId `" + command.getExternalId()
+ "` already exists", "externalId",
command.getExternalId());
}
logger.error(dve.getMessage(), dve); logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException( throw new PlatformDataIntegrityException("error.msg.client.unknown.data.integrity.issue",
"error.msg.client.unknown.data.integrity.issue",
"Unknown data integrity issue with resource."); "Unknown data integrity issue with resource.");
} }
@@ -121,9 +108,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
validator.validateForCreate(); validator.validateForCreate();
final Office clientOffice = this.officeRepository.findOne(command.getOfficeId()); final Office clientOffice = this.officeRepository.findOne(command.getOfficeId());
if (clientOffice == null) { if (clientOffice == null) { throw new OfficeNotFoundException(command.getOfficeId()); }
throw new OfficeNotFoundException(command.getOfficeId());
}
String firstname = command.getFirstname(); String firstname = command.getFirstname();
String lastname = command.getLastname(); String lastname = command.getLastname();
@@ -132,15 +117,11 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
firstname = null; firstname = null;
} }
final Client newClient = Client final Client newClient = Client.newClient(clientOffice, firstname, lastname, command.getJoiningDate(), command.getExternalId());
.newClient(clientOffice, firstname, lastname,
command.getJoiningDate(), command.getExternalId());
this.clientRepository.save(newClient); this.clientRepository.save(newClient);
if (this.makerCheckerEnabledForTask && !command.isApprovedByChecker()) { if (this.makerCheckerEnabledForTask && !command.isApprovedByChecker()) { throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException(); }
throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException();
}
return newClient.getId(); return newClient.getId();
} catch (DataIntegrityViolationException dve) { } catch (DataIntegrityViolationException dve) {
@@ -163,22 +144,16 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
Long officeId = command.getOfficeId(); Long officeId = command.getOfficeId();
if (command.isOfficeChanged() && officeId != null) { if (command.isOfficeChanged() && officeId != null) {
clientOffice = this.officeRepository.findOne(officeId); clientOffice = this.officeRepository.findOne(officeId);
if (clientOffice == null) { if (clientOffice == null) { throw new OfficeNotFoundException(command.getOfficeId()); }
throw new OfficeNotFoundException(command.getOfficeId());
}
} }
final Client clientForUpdate = this.clientRepository.findOne(command.getId()); final Client clientForUpdate = this.clientRepository.findOne(command.getId());
if (clientForUpdate == null || clientForUpdate.isDeleted()) { if (clientForUpdate == null || clientForUpdate.isDeleted()) { throw new ClientNotFoundException(command.getId()); }
throw new ClientNotFoundException(command.getId());
}
clientForUpdate.update(clientOffice, command); clientForUpdate.update(clientOffice, command);
this.clientRepository.saveAndFlush(clientForUpdate); this.clientRepository.saveAndFlush(clientForUpdate);
if (this.makerCheckerEnabledForTask && !command.isApprovedByChecker()) { if (this.makerCheckerEnabledForTask && !command.isApprovedByChecker()) { throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException(); }
throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException();
}
return new EntityIdentifier(clientForUpdate.getId()); return new EntityIdentifier(clientForUpdate.getId());
} catch (DataIntegrityViolationException dve) { } catch (DataIntegrityViolationException dve) {
@@ -194,9 +169,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
context.authenticatedUser(); context.authenticatedUser();
final Client clientForUpdate = this.clientRepository.findOne(command.getClientId()); final Client clientForUpdate = this.clientRepository.findOne(command.getClientId());
if (clientForUpdate == null) { if (clientForUpdate == null) { throw new ClientNotFoundException(command.getClientId()); }
throw new ClientNotFoundException(command.getClientId());
}
final Note note = Note.clientNote(clientForUpdate, command.getNote()); final Note note = Note.clientNote(clientForUpdate, command.getNote());
@@ -212,10 +185,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
context.authenticatedUser(); context.authenticatedUser();
final Note noteForUpdate = this.noteRepository.findOne(command.getId()); final Note noteForUpdate = this.noteRepository.findOne(command.getId());
if (noteForUpdate == null if (noteForUpdate == null || noteForUpdate.isNotAgainstClientWithIdOf(command.getClientId())) { throw new NoteNotFoundException(
|| noteForUpdate.isNotAgainstClientWithIdOf(command.getClientId())) { command.getId(), command.getClientId(), "client"); }
throw new NoteNotFoundException(command.getId(),command.getClientId(), "client");
}
noteForUpdate.update(command.getNote()); noteForUpdate.update(command.getNote());
@@ -237,18 +208,15 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
validator.validateForCreate(); validator.validateForCreate();
final Client client = this.clientRepository.findOne(command.getClientId()); final Client client = this.clientRepository.findOne(command.getClientId());
if (client == null || client.isDeleted()) { if (client == null || client.isDeleted()) { throw new ClientNotFoundException(command.getClientId()); }
throw new ClientNotFoundException(command.getClientId());
}
final CodeValue documentType = this.codeValueRepository.findOne(command.getDocumentTypeId()); final CodeValue documentType = this.codeValueRepository.findOne(command.getDocumentTypeId());
if (documentType == null) { if (documentType == null) { throw new CodeValueNotFoundException(command.getDocumentTypeId()); }
throw new CodeValueNotFoundException(command.getDocumentTypeId());
}
documentTypeId = documentType.getId(); documentTypeId = documentType.getId();
documentTypeLabel = documentType.getLabel(); documentTypeLabel = documentType.getLabel();
final ClientIdentifier clientIdentifier = ClientIdentifier.createNew(client, documentType, documentKey, command.getDescription()); final ClientIdentifier clientIdentifier = ClientIdentifier.createNew(client, documentType, documentKey,
command.getDescription());
this.clientIdentifierRepository.save(clientIdentifier); this.clientIdentifierRepository.save(clientIdentifier);
@@ -277,19 +245,16 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
documentTypeId = command.getDocumentTypeId(); documentTypeId = command.getDocumentTypeId();
if (command.isDocumentTypeChanged() && documentTypeId != null) { if (command.isDocumentTypeChanged() && documentTypeId != null) {
documentType = this.codeValueRepository.findOne(documentTypeId); documentType = this.codeValueRepository.findOne(documentTypeId);
if (documentType == null) { if (documentType == null) { throw new CodeValueNotFoundException(command.getDocumentTypeId()); }
throw new CodeValueNotFoundException(command.getDocumentTypeId());
}
documentTypeId = documentType.getId(); documentTypeId = documentType.getId();
documentTypeLabel = documentType.getLabel(); documentTypeLabel = documentType.getLabel();
} }
final ClientIdentifier clientIdentifierForUpdate = this.clientIdentifierRepository.findOne(command.getId()); final ClientIdentifier clientIdentifierForUpdate = this.clientIdentifierRepository.findOne(command.getId());
if (clientIdentifierForUpdate == null) { if (clientIdentifierForUpdate == null) { throw new ClientIdentifierNotFoundException(command.getId()); }
throw new ClientIdentifierNotFoundException(command.getId());
}
// TODO - KW - why need to check what changed when integrity violation occurs? // TODO - KW - why need to check what changed when integrity
// violation occurs?
if (command.isDocumentTypeChanged() && command.isDocumentKeyChanged()) { if (command.isDocumentTypeChanged() && command.isDocumentKeyChanged()) {
documentTypeId = command.getDocumentTypeId(); documentTypeId = command.getDocumentTypeId();
documentKey = command.getDocumentKey(); documentKey = command.getDocumentKey();
@@ -312,22 +277,17 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
} }
} }
private void handleClientIdentifierDataIntegrityViolation( private void handleClientIdentifierDataIntegrityViolation(final String documentTypeLabel, final Long documentTypeId,
final String documentTypeLabel, final String documentKey, final DataIntegrityViolationException dve) {
final Long documentTypeId,
final String documentKey,
final DataIntegrityViolationException dve) {
if (dve.getMostSpecificCause().getMessage().contains("unique_client_identifier")) { if (dve.getMostSpecificCause().getMessage().contains("unique_client_identifier")) {
throw new DuplicateClientIdentifierException(documentTypeLabel); throw new DuplicateClientIdentifierException(documentTypeLabel);
} else if (dve.getMostSpecificCause().getMessage().contains("unique_identifier_key")) { } else if (dve.getMostSpecificCause().getMessage().contains("unique_identifier_key")) { throw new DuplicateClientIdentifierException(
throw new DuplicateClientIdentifierException(documentTypeId, documentTypeLabel, documentKey); documentTypeId, documentTypeLabel, documentKey); }
}
// only log as error if unexpected data integrity violation. // only log as error if unexpected data integrity violation.
logger.error(dve.getMessage(), dve); logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException( throw new PlatformDataIntegrityException("error.msg.clientIdentifier.unknown.data.integrity.issue",
"error.msg.clientIdentifier.unknown.data.integrity.issue",
"Unknown data integrity issue with resource."); "Unknown data integrity issue with resource.");
} }
@@ -335,9 +295,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
@Override @Override
public EntityIdentifier deleteClientIdentifier(final Long clientIdentifierId) { public EntityIdentifier deleteClientIdentifier(final Long clientIdentifierId) {
final ClientIdentifier clientIdentifier = this.clientIdentifierRepository.findOne(clientIdentifierId); final ClientIdentifier clientIdentifier = this.clientIdentifierRepository.findOne(clientIdentifierId);
if (clientIdentifier == null) { if (clientIdentifier == null) { throw new ClientIdentifierNotFoundException(clientIdentifierId); }
throw new ClientIdentifierNotFoundException(clientIdentifierId);
}
this.clientIdentifierRepository.delete(clientIdentifier); this.clientIdentifierRepository.delete(clientIdentifier);
return new EntityIdentifier(clientIdentifierId); return new EntityIdentifier(clientIdentifierId);
@@ -345,16 +303,12 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
@Transactional @Transactional
@Override @Override
public EntityIdentifier saveOrUpdateClientImage(Long clientId, public EntityIdentifier saveOrUpdateClientImage(Long clientId, String imageName, InputStream inputStream) {
String imageName, InputStream inputStream) {
try { try {
final Client client = this.clientRepository.findOne(clientId); final Client client = this.clientRepository.findOne(clientId);
String imageUploadLocation = setupForClientImageUpdate(clientId, String imageUploadLocation = setupForClientImageUpdate(clientId, client);
client);
String imageLocation = FileUtils.saveToFileSystem(inputStream,
imageUploadLocation, imageName);
String imageLocation = FileUtils.saveToFileSystem(inputStream, imageUploadLocation, imageName);
return updateClientImage(clientId, client, imageLocation); return updateClientImage(clientId, client, imageLocation);
} catch (IOException ioe) { } catch (IOException ioe) {
@@ -368,9 +322,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
public EntityIdentifier deleteClientImage(final Long clientId) { public EntityIdentifier deleteClientImage(final Long clientId) {
final Client client = this.clientRepository.findOne(clientId); final Client client = this.clientRepository.findOne(clientId);
if (client == null || client.isDeleted()) { if (client == null || client.isDeleted()) { throw new ClientNotFoundException(clientId); }
throw new ClientNotFoundException(clientId);
}
// delete image from the file system // delete image from the file system
if (StringUtils.isNotEmpty(client.getImageKey())) { if (StringUtils.isNotEmpty(client.getImageKey())) {
@@ -395,9 +347,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
} }
private String setupForClientImageUpdate(final Long clientId, final Client client) { private String setupForClientImageUpdate(final Long clientId, final Client client) {
if (client == null || client.isDeleted()) { if (client == null || client.isDeleted()) { throw new ClientNotFoundException(clientId); }
throw new ClientNotFoundException(clientId);
}
final String imageUploadLocation = FileUtils.generateClientImageParentDirectory(clientId); final String imageUploadLocation = FileUtils.generateClientImageParentDirectory(clientId);
// delete previous image from the file system // delete previous image from the file system
@@ -0,0 +1,19 @@
package org.mifosng.platform.infrastructure.errorhandling;
/**
* A {@link RuntimeException} that is thrown in the case where an invalid or unknown
* command is attempted to be processed by platform.
*/
public class UnsupportedCommandException extends RuntimeException {
private final String unsupportedCommandName;
public UnsupportedCommandException(final String unsupportedCommandName) {
this.unsupportedCommandName = unsupportedCommandName;
}
public String getUnsupportedCommandName() {
return unsupportedCommandName;
}
}
@@ -0,0 +1,42 @@
package org.mifosng.platform.infrastructure.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 UnsupportedCommandException} thrown by platform into a HTTP API friendly format.
*/
@Provider
@Component
@Scope("singleton")
public class UnsupportedCommandExceptionMapper implements ExceptionMapper<UnsupportedCommandException> {
@Override
public Response toResponse(final UnsupportedCommandException exception) {
List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
StringBuilder validationErrorCode = new StringBuilder("error.msg.command.unsupported");
StringBuilder defaultEnglishMessage = new StringBuilder("The command ").append(exception.getUnsupportedCommandName()).append(" is not supported.");
ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), exception.getUnsupportedCommandName(), exception.getUnsupportedCommandName());
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();
}
}
@@ -31,13 +31,10 @@ public class CommandSource extends AbstractPersistable<Long> {
@Column(name = "command_as_json", length = 1000) @Column(name = "command_as_json", length = 1000)
private String commandAsJson; private String commandAsJson;
// should maker and checker be a staff member as opposed to just any application user?
@SuppressWarnings("unused")
@ManyToOne @ManyToOne
@JoinColumn(name = "maker_id", nullable = false) @JoinColumn(name = "maker_id", nullable = false)
private AppUser maker; private AppUser maker;
@SuppressWarnings("unused")
@Column(name = "made_on_date", nullable = false) @Column(name = "made_on_date", nullable = false)
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
private Date madeOnDate; private Date madeOnDate;
@@ -52,11 +49,7 @@ public class CommandSource extends AbstractPersistable<Long> {
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
private Date checkedOnDate; private Date checkedOnDate;
public static CommandSource createdBy( public static CommandSource createdBy(final String apiOperation, final String resource, final Long resourceId, final AppUser maker,
final String apiOperation,
final String resource,
final Long resourceId,
final AppUser maker,
final LocalDate madeOnDate) { final LocalDate madeOnDate) {
return new CommandSource(apiOperation, resource, resourceId, maker, madeOnDate); return new CommandSource(apiOperation, resource, resourceId, maker, madeOnDate);
} }
@@ -65,11 +58,7 @@ public class CommandSource extends AbstractPersistable<Long> {
// //
} }
private CommandSource( private CommandSource(final String apiOperation, final String resource, final Long resourceId, final AppUser maker,
final String apiOperation,
final String resource,
final Long resourceId,
final AppUser maker,
final LocalDate madeOnDate) { final LocalDate madeOnDate) {
this.apiOperation = StringUtils.defaultIfEmpty(apiOperation, null); this.apiOperation = StringUtils.defaultIfEmpty(apiOperation, null);
this.resource = StringUtils.defaultIfEmpty(resource, null); this.resource = StringUtils.defaultIfEmpty(resource, null);
@@ -78,6 +67,14 @@ public class CommandSource extends AbstractPersistable<Long> {
this.madeOnDate = madeOnDate.toDate(); this.madeOnDate = madeOnDate.toDate();
} }
public CommandSource copy() {
LocalDate madeOnLocalDate = null;
if (this.madeOnDate != null) {
madeOnLocalDate = new LocalDate(this.madeOnDate);
}
return new CommandSource(this.apiOperation, this.resource, this.resourceId, this.maker, madeOnLocalDate);
}
public void markAsChecked(final AppUser checker, final LocalDate checkedOnDate) { public void markAsChecked(final AppUser checker, final LocalDate checkedOnDate) {
this.checker = checker; this.checker = checker;
this.checkedOnDate = checkedOnDate.toDate(); this.checkedOnDate = checkedOnDate.toDate();
@@ -99,6 +96,18 @@ public class CommandSource extends AbstractPersistable<Long> {
return this.commandAsJson; return this.commandAsJson;
} }
public String commandName() {
return this.apiOperation + '-' + this.resource;
}
public String resourceName() {
return this.resource;
}
public String operation() {
return this.apiOperation;
}
public boolean isClientResource() { public boolean isClientResource() {
return this.resource.equalsIgnoreCase("CLIENTS"); return this.resource.equalsIgnoreCase("CLIENTS");
} }
@@ -0,0 +1,7 @@
package org.mifosng.platform.makerchecker.service;
public interface ChangeDetectionService {
String detectChanges(String operation, String resourceName, Long resourceId, String commandSerializedAsJson);
}
@@ -0,0 +1,48 @@
package org.mifosng.platform.makerchecker.service;
import org.mifosng.platform.api.commands.ClientCommand;
import org.mifosng.platform.api.data.ClientData;
import org.mifosng.platform.api.infrastructure.PortfolioApiDataConversionService;
import org.mifosng.platform.api.infrastructure.PortfolioApiJsonSerializerService;
import org.mifosng.platform.client.service.ClientReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ChangeDetectionServiceJdbc implements ChangeDetectionService {
private final PortfolioApiDataConversionService apiDataConversionService;
private final PortfolioApiJsonSerializerService apiJsonSerializerService;
private final ClientReadPlatformService clientReadPlatformService;
@Autowired
public ChangeDetectionServiceJdbc(final PortfolioApiDataConversionService apiDataConversionService,
final PortfolioApiJsonSerializerService apiJsonSerializerService, final ClientReadPlatformService clientReadPlatformService) {
this.apiDataConversionService = apiDataConversionService;
this.apiJsonSerializerService = apiJsonSerializerService;
this.clientReadPlatformService = clientReadPlatformService;
}
@Override
public String detectChanges(final String operation, final String resourceName, final Long resourceId,
final String commandSerializedAsJson) {
String changesOnlyJson = "";
// FIXME - KW/JW - this change detection code for update of clients
// using ClientCommand can re placed when more suitable sql version is
// implemented.
final ClientData originalClient = this.clientReadPlatformService.retrieveIndividualClient(resourceId);
final ClientData changedClient = this.apiDataConversionService.convertInternalJsonFormatToClientDataChange(resourceId,
commandSerializedAsJson);
final String baseJson = this.apiJsonSerializerService.serializeClientDataToJson(originalClient);
final String workingJson = this.apiJsonSerializerService.serializeClientDataToJson(changedClient);
final ClientCommand changesOnly = this.apiDataConversionService.detectChanges(resourceId, baseJson, workingJson);
changesOnlyJson = this.apiJsonSerializerService.serializeClientCommandToJson(changesOnly);
return changesOnlyJson;
}
}
@@ -0,0 +1,113 @@
package org.mifosng.platform.makerchecker.service;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.commands.ClientCommand;
import org.mifosng.platform.api.data.EntityIdentifier;
import org.mifosng.platform.api.infrastructure.PortfolioApiDataConversionService;
import org.mifosng.platform.api.infrastructure.PortfolioApiJsonSerializerService;
import org.mifosng.platform.client.service.ClientWritePlatformService;
import org.mifosng.platform.client.service.RollbackTransactionAsCommandIsNotApprovedByCheckerException;
import org.mifosng.platform.makerchecker.domain.CommandSource;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ClientCommandHandler implements CommandSourceHandler {
private final PlatformSecurityContext context;
private final ChangeDetectionService changeDetectionService;
private final PortfolioApiDataConversionService apiDataConversionService;
private final PortfolioApiJsonSerializerService apiJsonSerializerService;
private final ClientWritePlatformService clientWritePlatformService;
@Autowired
public ClientCommandHandler(final PlatformSecurityContext context, final ChangeDetectionService changeDetectionService,
final PortfolioApiDataConversionService apiDataConversionService,
final PortfolioApiJsonSerializerService apiJsonSerializerService, final ClientWritePlatformService clientWritePlatformService) {
this.context = context;
this.changeDetectionService = changeDetectionService;
this.apiDataConversionService = apiDataConversionService;
this.apiJsonSerializerService = apiJsonSerializerService;
this.clientWritePlatformService = clientWritePlatformService;
}
public CommandSource handle(final CommandSource commandSource, final String apiRequestBodyInJson) {
final AppUser maker = context.authenticatedUser();
final LocalDate asToday = new LocalDate();
CommandSource commandSourceResult = commandSource.copy();
final Long resourceId = commandSource.resourceId();
Long newResourceId = null;
final ClientCommand command = this.apiDataConversionService.convertApiRequestJsonToClientCommand(resourceId, apiRequestBodyInJson);
final String commandSerializedAsJson = this.apiJsonSerializerService.serializeClientCommandToJson(command);
commandSourceResult.updateJsonTo(commandSerializedAsJson);
if (commandSource.isCreate()) {
try {
newResourceId = this.clientWritePlatformService.createClient(command);
commandSourceResult.markAsChecked(maker, asToday);
commandSourceResult.updateResourceId(newResourceId);
} catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
// swallow this rollback transaction by design
}
} else if (commandSource.isUpdate()) {
try {
final String jsonOfChangesOnly = this.changeDetectionService.detectChanges(commandSource.operation(),
commandSource.resourceName(), commandSource.resourceId(), commandSerializedAsJson);
commandSourceResult.updateJsonTo(jsonOfChangesOnly);
final ClientCommand changesOnly = this.apiDataConversionService.convertInternalJsonFormatToClientCommand(
commandSource.resourceId(), jsonOfChangesOnly, false);
EntityIdentifier result = this.clientWritePlatformService.updateClientDetails(changesOnly);
newResourceId = result.getEntityId();
commandSourceResult.markAsChecked(maker, asToday);
} catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
// swallow this rollback transaction by design
}
} else if (commandSource.isDelete()) {
try {
EntityIdentifier result = this.clientWritePlatformService.deleteClient(command);
newResourceId = result.getEntityId();
commandSourceResult.markAsChecked(maker, asToday);
} catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
// swallow this rollback transaction by design
}
}
return commandSourceResult;
}
public CommandSource handle(final CommandSource commandSourceResult) {
Long resourceId = null;
if (commandSourceResult.isClientResource()) {
final ClientCommand command = this.apiDataConversionService.convertInternalJsonFormatToClientCommand(
commandSourceResult.resourceId(), commandSourceResult.json(), true);
if (commandSourceResult.isCreate()) {
resourceId = this.clientWritePlatformService.createClient(command);
} else if (commandSourceResult.isUpdate()) {
// TODO - is there a case for doing change detection at this
// point also?
EntityIdentifier result = this.clientWritePlatformService.updateClientDetails(command);
resourceId = result.getEntityId();
} else if (commandSourceResult.isDelete()) {
EntityIdentifier result = this.clientWritePlatformService.deleteClient(command);
resourceId = result.getEntityId();
}
}
commandSourceResult.updateResourceId(resourceId);
return commandSourceResult;
}
}
@@ -0,0 +1,5 @@
package org.mifosng.platform.makerchecker.service;
public interface CommandSourceHandler {
}
@@ -0,0 +1,48 @@
package org.mifosng.platform.makerchecker.service;
import org.mifosng.platform.infrastructure.errorhandling.UnsupportedCommandException;
import org.mifosng.platform.makerchecker.domain.CommandSource;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* A class to delegate handling of commands to appropriate handler.
*/
@Service
public class CommandSourceHandlerDelegator {
private final PlatformSecurityContext context;
private final ClientCommandHandler clientCommandHandler;
@Autowired
public CommandSourceHandlerDelegator(final PlatformSecurityContext context, final ClientCommandHandler clientCommandHandler) {
this.context = context;
this.clientCommandHandler = clientCommandHandler;
}
public CommandSource handle(final CommandSource commandSource, final String apiRequestBodyInJson) {
context.authenticatedUser();
CommandSource commandSourceResult = null;
if (commandSource.isClientResource()) {
commandSourceResult = clientCommandHandler.handle(commandSource, apiRequestBodyInJson);
} else {
throw new UnsupportedCommandException(commandSource.commandName());
}
return commandSourceResult;
}
public CommandSource handleExistingCommand(final CommandSource commandSource) {
CommandSource commandSourceResult = null;
if (commandSource.isClientResource()) {
commandSourceResult = clientCommandHandler.handle(commandSource);
} else {
throw new UnsupportedCommandException(commandSource.commandName());
}
return commandSourceResult;
}
}
@@ -1,14 +1,7 @@
package org.mifosng.platform.makerchecker.service; package org.mifosng.platform.makerchecker.service;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosng.platform.api.commands.ClientCommand;
import org.mifosng.platform.api.data.ClientData;
import org.mifosng.platform.api.data.EntityIdentifier; import org.mifosng.platform.api.data.EntityIdentifier;
import org.mifosng.platform.api.infrastructure.PortfolioApiDataConversionService;
import org.mifosng.platform.api.infrastructure.PortfolioApiJsonSerializerService;
import org.mifosng.platform.client.service.ClientReadPlatformService;
import org.mifosng.platform.client.service.ClientWritePlatformService;
import org.mifosng.platform.client.service.RollbackTransactionAsCommandIsNotApprovedByCheckerException;
import org.mifosng.platform.makerchecker.domain.CommandSource; import org.mifosng.platform.makerchecker.domain.CommandSource;
import org.mifosng.platform.makerchecker.domain.CommandSourceRepository; import org.mifosng.platform.makerchecker.domain.CommandSourceRepository;
import org.mifosng.platform.security.PlatformSecurityContext; import org.mifosng.platform.security.PlatformSecurityContext;
@@ -22,86 +15,30 @@ public class PortfolioCommandSourceWriteServiceImpl implements PortfolioCommandS
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final CommandSourceRepository commandSourceRepository; private final CommandSourceRepository commandSourceRepository;
private final PortfolioApiDataConversionService apiDataConversionService; private final CommandSourceHandlerDelegator commandSourceHandlerDelegator;
private final PortfolioApiJsonSerializerService apiJsonSerializerService;
private final ClientWritePlatformService clientWritePlatformService;
private final ClientReadPlatformService clientReadPlatformService;
@Autowired @Autowired
public PortfolioCommandSourceWriteServiceImpl(final PlatformSecurityContext context, final CommandSourceRepository makerCheckerRepository, public PortfolioCommandSourceWriteServiceImpl(final PlatformSecurityContext context,
final PortfolioApiDataConversionService apiDataConversionService, final CommandSourceRepository makerCheckerRepository,
final PortfolioApiJsonSerializerService apiJsonSerializerService, final CommandSourceHandlerDelegator commandSourceHandlerDelegator) {
final ClientWritePlatformService clientWritePlatformService,
final ClientReadPlatformService clientReadPlatformService) {
this.context = context; this.context = context;
this.commandSourceRepository = makerCheckerRepository; this.commandSourceRepository = makerCheckerRepository;
this.apiDataConversionService = apiDataConversionService; this.commandSourceHandlerDelegator = commandSourceHandlerDelegator;
this.apiJsonSerializerService = apiJsonSerializerService;
this.clientWritePlatformService = clientWritePlatformService;
this.clientReadPlatformService = clientReadPlatformService;
} }
// NOT TRANSACTIONAL BY DESIGN FOR NOW // NOT TRANSACTIONAL BY DESIGN FOR NOW
@Override @Override
public EntityIdentifier logCommandSource(final String apiOperation, final String resource, final Long resourceId, final String jsonRequestBody) { public EntityIdentifier logCommandSource(final String apiOperation, final String resource, final Long resourceId,
final String apiRequestBodyInJson) {
final AppUser maker = context.authenticatedUser(); final AppUser maker = context.authenticatedUser();
final LocalDate asToday = new LocalDate(); final LocalDate asToday = new LocalDate();
final CommandSource commandSource = CommandSource.createdBy(apiOperation, resource, resourceId, maker, asToday); final CommandSource commandSourceInput = CommandSource.createdBy(apiOperation, resource, resourceId, maker, asToday);
final CommandSource commandSourceResult = this.commandSourceHandlerDelegator.handle(commandSourceInput, apiRequestBodyInJson);
commandSourceRepository.save(commandSourceResult);
Long newResourceId = null; return EntityIdentifier.makerChecker(commandSourceResult.resourceId(), commandSourceResult.getId());
if (commandSource.isClientResource()) {
// translate incoming api request json into java object catering for local and dateFormat parts of api
final ClientCommand command = this.apiDataConversionService.convertApiRequestJsonToClientCommand(resourceId, jsonRequestBody);
// produce serialized json of internal java object representation for persistence.
final String internalCommandSerializedAsJson = this.apiJsonSerializerService.serializeClientCommandToJson(command);
commandSource.updateJsonTo(internalCommandSerializedAsJson);
if (commandSource.isCreate()) {
try {
newResourceId = this.clientWritePlatformService.createClient(command);
commandSource.markAsChecked(maker, asToday);
commandSource.updateResourceId(newResourceId);
} catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
// swallow this rollback transaction by design
}
} else if (commandSource.isUpdate()) {
try {
// useful to employ change detection on update scenario to only store what details have changed
final ClientData originalClient = this.clientReadPlatformService.retrieveIndividualClient(resourceId);
final ClientData changedClient = this.apiDataConversionService.convertInternalJsonFormatToClientDataChange(resourceId, internalCommandSerializedAsJson);
final String baseJson = this.apiJsonSerializerService.serializeClientDataToJson(originalClient);
final String workingJson = this.apiJsonSerializerService.serializeClientDataToJson(changedClient);
final ClientCommand changesOnly = this.apiDataConversionService.detectChanges(resourceId, baseJson, workingJson);
final String changesOnlyJson = this.apiJsonSerializerService.serializeClientCommandToJson(changesOnly);
commandSource.updateJsonTo(changesOnlyJson);
EntityIdentifier result = this.clientWritePlatformService.updateClientDetails(changesOnly);
newResourceId = result.getEntityId();
commandSource.markAsChecked(maker, asToday);
} catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
// swallow this rollback transaction by design
}
} else if (commandSource.isDelete()) {
try {
EntityIdentifier result = this.clientWritePlatformService.deleteClient(command);
newResourceId = result.getEntityId();
commandSource.markAsChecked(maker, asToday);
} catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
// swallow this rollback transaction by design
}
}
}
commandSourceRepository.save(commandSource);
return EntityIdentifier.makerChecker(newResourceId, commandSource.getId());
} }
@Transactional @Transactional
@@ -110,28 +47,14 @@ public class PortfolioCommandSourceWriteServiceImpl implements PortfolioCommandS
final AppUser checker = context.authenticatedUser(); final AppUser checker = context.authenticatedUser();
final CommandSource entity = this.commandSourceRepository.findOne(id); final CommandSource commandSourceInput = this.commandSourceRepository.findOne(id);
entity.markAsChecked(checker, new LocalDate()); commandSourceInput.markAsChecked(checker, new LocalDate());
Long resourceId = null; final CommandSource commandSourceResult = this.commandSourceHandlerDelegator.handleExistingCommand(commandSourceInput);
if (entity.isClientResource()) {
final ClientCommand command = this.apiDataConversionService.convertInternalJsonFormatToClientCommand(entity.resourceId(), entity.json(), true);
if (entity.isCreate()) {
resourceId = this.clientWritePlatformService.createClient(command);
} else if (entity.isUpdate()) {
EntityIdentifier result = this.clientWritePlatformService.updateClientDetails(command);
resourceId = result.getEntityId();
} else if (entity.isDelete()) {
EntityIdentifier result = this.clientWritePlatformService.deleteClient(command);
resourceId = result.getEntityId();
}
}
entity.updateResourceId(resourceId); commandSourceRepository.save(commandSourceResult);
commandSourceRepository.save(entity); return EntityIdentifier.makerChecker(commandSourceResult.resourceId(), commandSourceResult.getId());
return EntityIdentifier.makerChecker(resourceId, entity.getId());
} }
@Transactional @Transactional