diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java index 60027871f..f2b34637b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java @@ -107,9 +107,13 @@ public class CommandSource extends AbstractPersistableCustom { @Column(name = "job_name") private String jobName; - public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker) { + @Column(name = "idempotency_key", length = 50) + private String idempotencyKey; + + public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker, + String idempotencyKey) { return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(), - command.json(), maker); + command.json(), maker, idempotencyKey); } protected CommandSource() { @@ -117,7 +121,7 @@ public class CommandSource extends AbstractPersistableCustom { } private CommandSource(final String actionName, final String entityName, final String href, final Long resourceId, - final Long subresourceId, final String commandSerializedAsJson, final AppUser maker) { + final Long subresourceId, final String commandSerializedAsJson, final AppUser maker, final String idempotencyKey) { this.actionName = actionName; this.entityName = entityName; this.resourceGetUrl = href; @@ -127,6 +131,7 @@ public class CommandSource extends AbstractPersistableCustom { this.maker = maker; this.madeOnDate = DateUtils.getOffsetDateTimeOfTenant(); this.processingResult = CommandProcessingResultType.PROCESSED.getValue(); + this.idempotencyKey = idempotencyKey; } public Long getCreditBureauId() { @@ -286,4 +291,11 @@ public class CommandSource extends AbstractPersistableCustom { this.transactionId = transactionId; } + public String getIdempotencyKey() { + return idempotencyKey; + } + + public void setIdempotencyKey(String idempotencyKey) { + this.idempotencyKey = idempotencyKey; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java index cb09f24f6..c28ac3466 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java @@ -42,6 +42,8 @@ public class CommandWrapper { private final Long organisationCreditBureauId; private final String jobName; + private final String idempotencyKey; + @SuppressWarnings("unused") private Long templateId; @@ -57,9 +59,9 @@ public class CommandWrapper { public static CommandWrapper fromExistingCommand(final Long commandId, final String actionName, final String entityName, final Long resourceId, final Long subresourceId, final String resourceGetUrl, final Long productId, final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String transactionId, - final Long creditBureauId, final Long organisationCreditBureauId) { + final Long creditBureauId, final Long organisationCreditBureauId, final String idempotencyKey) { return new CommandWrapper(commandId, actionName, entityName, resourceId, subresourceId, resourceGetUrl, productId, officeId, - groupId, clientId, loanId, savingsId, transactionId, creditBureauId, organisationCreditBureauId); + groupId, clientId, loanId, savingsId, transactionId, creditBureauId, organisationCreditBureauId, idempotencyKey); } private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId, @@ -82,12 +84,13 @@ public class CommandWrapper { this.creditBureauId = null; this.organisationCreditBureauId = null; this.jobName = null; + this.idempotencyKey = null; } public CommandWrapper(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String actionName, final String entityName, final Long entityId, final Long subentityId, final String href, final String json, final String transactionId, final Long productId, final Long templateId, final Long creditBureauId, - final Long organisationCreditBureauId, final String jobName) { + final Long organisationCreditBureauId, final String jobName, final String idempotencyKey) { this.commandId = null; this.officeId = officeId; @@ -108,12 +111,13 @@ public class CommandWrapper { this.creditBureauId = creditBureauId; this.organisationCreditBureauId = organisationCreditBureauId; this.jobName = jobName; + this.idempotencyKey = idempotencyKey; } private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId, final Long subresourceId, final String resourceGetUrl, final Long productId, final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String transactionId, final Long creditBureauId, - final Long organisationCreditBureauId) { + final Long organisationCreditBureauId, final String idempotencyKey) { this.commandId = commandId; this.officeId = officeId; @@ -133,6 +137,7 @@ public class CommandWrapper { this.creditBureauId = creditBureauId; this.organisationCreditBureauId = organisationCreditBureauId; this.jobName = null; + this.idempotencyKey = idempotencyKey; } public Long getCreditBureauId() { @@ -143,6 +148,10 @@ public class CommandWrapper { return this.organisationCreditBureauId; } + public String getIdempotencyKey() { + return idempotencyKey; + } + public String getHref() { return this.href; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index 56892ae07..a7327e7ca 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -45,11 +45,18 @@ public class CommandWrapperBuilder { private Long creditBureauId; private Long organisationCreditBureauId; private String jobName; + private String idempotencyKey; public CommandWrapper build() { return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName, this.entityName, this.entityId, this.subentityId, this.href, this.json, this.transactionId, this.productId, this.templateId, - this.creditBureauId, this.organisationCreditBureauId, this.jobName); + this.creditBureauId, this.organisationCreditBureauId, this.jobName, this.idempotencyKey); + } + + public CommandWrapper build(String idempotencyKey) { + return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName, this.entityName, + this.entityId, this.subentityId, this.href, this.json, this.transactionId, this.productId, this.templateId, + this.creditBureauId, this.organisationCreditBureauId, this.jobName, idempotencyKey); } public CommandWrapperBuilder updateCreditBureau() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/IdempotencyKeyGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/IdempotencyKeyGenerator.java new file mode 100644 index 000000000..a9e715507 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/IdempotencyKeyGenerator.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.commands.service; + +import java.util.UUID; +import org.springframework.stereotype.Component; + +@Component +public class IdempotencyKeyGenerator { + + public String create() { + return UUID.randomUUID().toString(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java index 0552172e7..c8ebab8c2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java @@ -130,7 +130,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId(), commandSourceInput.getOfficeId(), commandSourceInput.getGroupId(), commandSourceInput.getClientId(), commandSourceInput.getLoanId(), commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(), commandSourceInput.getCreditBureauId(), - commandSourceInput.getOrganisationCreditBureauId()); + commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getIdempotencyKey()); final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.json()); final JsonCommand command = JsonCommand.fromExistingCommand(makerCheckerId, commandSourceInput.json(), parsedCommand, this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(), diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java index c15376007..537c09ba0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java @@ -63,6 +63,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer private final ConfigurationDomainService configurationDomainService; private final CommandHandlerProvider commandHandlerProvider; + private final IdempotencyKeyGenerator idempotencyKeyGenerator; + @Transactional @Override public CommandProcessingResult executeCommand(final CommandWrapper wrapper, final JsonCommand command, @@ -88,7 +90,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer .orElseThrow(() -> new CommandNotFoundException(command.commandId())); commandSourceResult.markAsChecked(maker); } else { - commandSourceResult = CommandSource.fullEntryFrom(wrapper, command, maker); + commandSourceResult = CommandSource.fullEntryFrom(wrapper, command, maker, + wrapper.getIdempotencyKey() == null ? idempotencyKeyGenerator.create() : wrapper.getIdempotencyKey()); } commandSourceResult.updateResourceId(result.getResourceId()); commandSourceResult.updateForAudit(result.getOfficeId(), result.getGroupId(), result.getClientId(), result.getLoanId(), @@ -134,6 +137,9 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer public CommandProcessingResult logCommand(CommandSource commandSourceResult) { commandSourceResult.markAsAwaitingApproval(); + if (commandSourceResult.getIdempotencyKey() == null) { + commandSourceResult.setIdempotencyKey(idempotencyKeyGenerator.create()); + } commandSourceResult = commandSourceRepository.saveAndFlush(commandSourceResult); return new CommandProcessingResultBuilder().withCommandId(commandSourceResult.getId()) diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java index d38440250..e27555147 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java @@ -27,6 +27,7 @@ import java.util.Collection; import java.util.List; import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.commands.service.CommandWrapperBuilder; +import org.apache.fineract.commands.service.IdempotencyKeyGenerator; import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; import org.apache.fineract.infrastructure.bulkimport.constants.CenterConstants; import org.apache.fineract.infrastructure.bulkimport.constants.TemplatePopulateImportConstants; @@ -62,10 +63,13 @@ public class CenterImportHandler implements ImportHandler { private Workbook workbook; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final IdempotencyKeyGenerator idempotencyKeyGenerator; @Autowired - public CenterImportHandler(final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { + public CenterImportHandler(final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, + IdempotencyKeyGenerator idempotencyKeyGenerator) { this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + this.idempotencyKeyGenerator = idempotencyKeyGenerator; } @Override @@ -262,7 +266,7 @@ public class CenterImportHandler implements ImportHandler { String payload = gsonBuilder.create().toJson(calendarData); CommandWrapper commandWrapper = new CommandWrapper(result.getOfficeId(), result.getGroupId(), result.getClientId(), result.getLoanId(), result.getSavingsId(), null, null, null, null, null, payload, result.getTransactionId(), - result.getProductId(), null, null, null, null); + result.getProductId(), null, null, null, null, idempotencyKeyGenerator.create()); final CommandWrapper commandRequest = new CommandWrapperBuilder() // .createCalendar(commandWrapper, TemplatePopulateImportConstants.CENTER_ENTITY_TYPE, result.getGroupId()) // .withJson(payload) // diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java index b8835a9a3..b792fcb5b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java @@ -27,6 +27,7 @@ import java.util.Collection; import java.util.List; import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.commands.service.CommandWrapperBuilder; +import org.apache.fineract.commands.service.IdempotencyKeyGenerator; import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; import org.apache.fineract.infrastructure.bulkimport.constants.GroupConstants; import org.apache.fineract.infrastructure.bulkimport.constants.TemplatePopulateImportConstants; @@ -62,10 +63,13 @@ public class GroupImportHandler implements ImportHandler { private List statuses; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final IdempotencyKeyGenerator idempotencyKeyGenerator; @Autowired - public GroupImportHandler(final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { + public GroupImportHandler(final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, + IdempotencyKeyGenerator idempotencyKeyGenerator) { this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + this.idempotencyKeyGenerator = idempotencyKeyGenerator; } @Override @@ -241,7 +245,7 @@ public class GroupImportHandler implements ImportHandler { String payload = gsonBuilder.create().toJson(calendarData); CommandWrapper commandWrapper = new CommandWrapper(result.getOfficeId(), result.getGroupId(), result.getClientId(), result.getLoanId(), result.getSavingsId(), null, null, null, null, null, payload, result.getTransactionId(), - result.getProductId(), null, null, null, null); + result.getProductId(), null, null, null, null, idempotencyKeyGenerator.create()); final CommandWrapper commandRequest = new CommandWrapperBuilder() // .createCalendar(commandWrapper, TemplatePopulateImportConstants.CENTER_ENTITY_TYPE, result.getGroupId()) // .withJson(payload) // diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java index 0f847dabf..20a7c2bc5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java @@ -37,7 +37,7 @@ public class InteropWrapperBuilder { public CommandWrapper build() { return new CommandWrapper(null, null, null, null, null, actionName, entityName, null, null, href, json, null, null, null, null, - null, null); + null, null, null); } public InteropWrapperBuilder withJson(final String json) { diff --git a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml index 9f9c3b880..266a6ff77 100644 --- a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml +++ b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml @@ -25,6 +25,8 @@ + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 31941ab88..d66da7972 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -80,4 +80,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0061_add_idempotency_key_to_command_source.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0061_add_idempotency_key_to_command_source.xml new file mode 100644 index 000000000..1bb48450d --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0061_add_idempotency_key_to_command_source.xml @@ -0,0 +1,59 @@ + + + + + + CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + + + + + + + + + + idempotency_key is null + + + + + + + + + + + + + + + + ALTER TABLE m_portfolio_command_source MODIFY idempotency_key VARCHAR(50) not NULL + + +