FINERACT-2246: New command processing - document management

This commit is contained in:
Aleksandar Vidakovic
2026-02-16 14:56:40 +01:00
committed by Aleksandar Vidakovic
parent 8a61e30a45
commit 2ff0c62f40
172 changed files with 5928 additions and 3100 deletions
@@ -18,6 +18,8 @@
*/
package org.apache.fineract.infrastructure.bulkimport.api;
import static org.apache.fineract.util.StreamResponseUtil.DISPOSITION_TYPE_ATTACHMENT;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
@@ -30,6 +32,7 @@ import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.bulkimport.data.GlobalEntityType;
import org.apache.fineract.infrastructure.bulkimport.data.ImportData;
@@ -39,7 +42,9 @@ import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentData;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.documentmanagement.exception.DocumentNotFoundException;
import org.apache.fineract.infrastructure.documentmanagement.service.DocumentReadPlatformService;
import org.apache.fineract.util.StreamResponseUtil;
import org.springframework.stereotype.Component;
@Path("/v1/imports")
@@ -48,10 +53,8 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor
public class BulkImportApiResource {
private static final String RESOURCE_NAME_FOR_PERMISSION = "IMPORT";
private final PlatformSecurityContext context;
private final BulkImportWorkbookService bulkImportWorkbookService;
private final DocumentReadPlatformService documentReadPlatformService;
private final DefaultToApiJsonSerializer<ImportData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
@@ -59,23 +62,26 @@ public class BulkImportApiResource {
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveImportDocuments(@Context final UriInfo uriInfo, @QueryParam("entityType") final String entityType) {
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION);
Collection<ImportData> importData = new ArrayList<>();
if (entityType.equals(GlobalEntityType.CLIENT.getCode())) {
final Collection<ImportData> importForClientEntity = this.bulkImportWorkbookService.getImports(GlobalEntityType.CLIENTS_ENTITY);
final Collection<ImportData> importForClientPerson = this.bulkImportWorkbookService.getImports(GlobalEntityType.CLIENTS_PERSON);
final var importForClientEntity = this.bulkImportWorkbookService.getImports(GlobalEntityType.CLIENTS_ENTITY);
final var importForClientPerson = this.bulkImportWorkbookService.getImports(GlobalEntityType.CLIENTS_PERSON);
if (importForClientEntity != null) {
importData.addAll(importForClientEntity);
}
if (importForClientPerson != null) {
importData.addAll(importForClientPerson);
}
} else {
final GlobalEntityType type = GlobalEntityType.fromCode(entityType);
if (type == null) {
throw new ImportTypeNotFoundException(entityType);
}
importData = this.bulkImportWorkbookService.getImports(type);
}
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -84,17 +90,28 @@ public class BulkImportApiResource {
@GET
@Path("getOutputTemplateLocation")
public String retriveOutputTemplateLocation(@QueryParam("importDocumentId") final String importDocumentId) {
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION);
final DocumentData documentData = this.bulkImportWorkbookService.getOutputTemplateLocation(importDocumentId);
return this.toApiJsonSerializer.serialize(documentData.getLocation());
public String retriveOutputTemplateLocation(@QueryParam("importDocumentId") final Long importDocumentId) {
final var imporData = bulkImportWorkbookService.getImport(importDocumentId);
return Optional.ofNullable(imporData)
.flatMap(importData -> Optional.ofNullable(documentReadPlatformService.retrieveDocument(imporData.getDocumentId())))
.map(DocumentData::getLocation).orElse(null);
}
@GET
@Path("downloadOutputTemplate")
@Produces("application/vnd.ms-excel")
public Response getOutputTemplate(@QueryParam("importDocumentId") final String importDocumentId) {
return bulkImportWorkbookService.getOutputTemplate(importDocumentId);
}
public Response getOutputTemplate(@QueryParam("importDocumentId") final Long importDocumentId) {
// TODO: can we avoid 2 calls by introducing a new function? clean code more important now
final var docs = documentReadPlatformService.retrieveAllDocuments("IMPORT", importDocumentId);
final var docData = docs.stream().findFirst();
final var d = docData.orElseThrow(() -> new DocumentNotFoundException("IMPORT", importDocumentId, -1L));
final var content = documentReadPlatformService.retrieveDocumentContent(d.getParentEntityType(), d.getParentEntityId(), d.getId());
final var streamResponseData = StreamResponseUtil.StreamResponseData.builder().type(content.getContentType())
.fileName(content.getFileName()).stream(content.getStream()).dispositionType(DISPOSITION_TYPE_ATTACHMENT).build();
return StreamResponseUtil.ok(streamResponseData);
}
}
@@ -18,48 +18,38 @@
*/
package org.apache.fineract.infrastructure.bulkimport.data;
import lombok.Getter;
import org.apache.fineract.infrastructure.bulkimport.domain.ImportDocument;
import org.apache.fineract.infrastructure.core.domain.FineractContext;
import org.apache.fineract.infrastructure.core.domain.FineractEvent;
import org.apache.poi.ss.usermodel.Workbook;
@Getter
public final class BulkImportEvent extends FineractEvent {
private final Workbook workbook;
private final Long importId;
private final ImportDocument importDocument;
private final String fileName;
private final String fileType;
private final String locale;
private final String dateFormat;
private BulkImportEvent(final Object source, final Workbook workbook, final Long importId, final String locale, final String dateFormat,
FineractContext context) {
private final Long entityId;
public BulkImportEvent(final Object source, final Workbook workbook, final String fileName, String fileType,
final ImportDocument importDocument, final String locale, final String dateFormat, FineractContext context, Long entityId) {
super(source, context);
this.workbook = workbook;
this.importId = importId;
this.fileName = fileName;
this.fileType = fileType;
this.importDocument = importDocument;
this.locale = locale;
this.dateFormat = dateFormat;
this.entityId = entityId;
}
public static BulkImportEvent instance(final Object source, final Workbook workbook, final Long importId, final String locale,
final String dateFormat, FineractContext context) {
return new BulkImportEvent(source, workbook, importId, locale, dateFormat, context);
}
public Workbook getWorkbook() {
return workbook;
}
public Long getImportId() {
return importId;
}
public String getDateFormat() {
return dateFormat;
}
public String getLocale() {
return locale;
}
}
@@ -22,21 +22,26 @@ import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.documentmanagement.domain.Document;
import org.apache.fineract.useradministration.domain.AppUser;
@Entity
@Table(name = "m_import_document")
public class ImportDocument extends AbstractPersistableCustom<Long> {
@Getter
@Setter
@NoArgsConstructor
@Accessors(chain = true)
public final class ImportDocument extends AbstractPersistableCustom<Long> {
@OneToOne
@JoinColumn(name = "document_id")
private Document document;
@Column(name = "document_id")
private Long documentId;
@Column(name = "import_time")
private LocalDateTime importTime;
@@ -63,11 +68,7 @@ public class ImportDocument extends AbstractPersistableCustom<Long> {
@Column(name = "failure_count", nullable = true)
private Integer failureCount;
protected ImportDocument() {
}
public static ImportDocument instance(final Document document, final LocalDateTime importTime, final Integer entityType,
public static ImportDocument instance(final Long documentId, final LocalDateTime importTime, final Integer entityType,
final AppUser createdBy, final Integer totalRecords) {
final Boolean completed = Boolean.FALSE;
@@ -75,14 +76,14 @@ public class ImportDocument extends AbstractPersistableCustom<Long> {
final Integer failureCount = 0;
final LocalDateTime endTime = DateUtils.getLocalDateTimeOfTenant();
return new ImportDocument(document, importTime, endTime, completed, entityType, createdBy, totalRecords, successCount,
return new ImportDocument(documentId, importTime, endTime, completed, entityType, createdBy, totalRecords, successCount,
failureCount);
}
private ImportDocument(final Document document, final LocalDateTime importTime, final LocalDateTime endTime, Boolean completed,
private ImportDocument(final Long documentId, final LocalDateTime importTime, final LocalDateTime endTime, Boolean completed,
final Integer entityType, final AppUser createdBy, final Integer totalRecords, final Integer successCount,
final Integer failureCount) {
this.document = document;
this.documentId = documentId;
this.importTime = importTime;
this.endTime = endTime;
this.completed = completed;
@@ -100,13 +101,4 @@ public class ImportDocument extends AbstractPersistableCustom<Long> {
this.successCount = successCount;
this.failureCount = errorCount;
}
public Document getDocument() {
return this.document;
}
public Integer getEntityType() {
return this.entityType;
}
}
@@ -0,0 +1,44 @@
/**
* 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.infrastructure.bulkimport.mapping;
import static org.mapstruct.InjectionStrategy.CONSTRUCTOR;
import static org.mapstruct.MappingConstants.ComponentModel.SPRING;
import org.apache.fineract.infrastructure.bulkimport.data.ImportData;
import org.apache.fineract.infrastructure.bulkimport.domain.ImportDocument;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = SPRING, injectionStrategy = CONSTRUCTOR)
public interface ImportDocumentMapper {
@Mapping(source = "id", target = "importId")
@Mapping(source = "documentId", target = "documentId")
@Mapping(ignore = true, target = "name")
@Mapping(source = "entityType", target = "entityType")
@Mapping(source = "importTime", target = "importTime")
@Mapping(source = "completed", target = "completed")
@Mapping(source = "endTime", target = "endTime")
@Mapping(source = "createdBy.id", target = "createdBy")
@Mapping(source = "failureCount", target = "failureCount")
@Mapping(source = "successCount", target = "successCount")
@Mapping(source = "totalRecords", target = "totalRecords")
ImportData map(ImportDocument source);
}
@@ -18,27 +18,21 @@
*/
package org.apache.fineract.infrastructure.bulkimport.service;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URLConnection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.command.core.CommandPipeline;
import org.apache.fineract.infrastructure.bulkimport.data.BulkImportEvent;
import org.apache.fineract.infrastructure.bulkimport.data.Count;
import org.apache.fineract.infrastructure.bulkimport.data.GlobalEntityType;
import org.apache.fineract.infrastructure.bulkimport.domain.ImportDocument;
import org.apache.fineract.infrastructure.bulkimport.domain.ImportDocumentRepository;
import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandler;
import org.apache.fineract.infrastructure.contentstore.util.ContentPipe;
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.documentmanagement.command.DocumentCommand;
import org.apache.fineract.infrastructure.documentmanagement.domain.Document;
import org.apache.fineract.infrastructure.documentmanagement.service.DocumentWritePlatformService;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.fineract.infrastructure.documentmanagement.command.DocumentUpdateCommand;
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentCreateResponse;
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentUpdateRequest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;
@@ -50,14 +44,15 @@ public class BulkImportEventListener implements ApplicationListener<BulkImportEv
private final ApplicationContext applicationContext;
private final ImportDocumentRepository importRepository;
private final DocumentWritePlatformService documentService;
private final ContentPipe pipe;
private final CommandPipeline commandPipeline;
@Override
public void onApplicationEvent(final BulkImportEvent event) {
try {
ThreadLocalContextUtil.init(event.getContext());
final ImportDocument importDocument = this.importRepository.findById(event.getImportId()).orElse(null);
final GlobalEntityType entityType = GlobalEntityType.fromInt(importDocument.getEntityType());
final GlobalEntityType entityType = GlobalEntityType.fromInt(event.getImportDocument().getEntityType());
final ImportHandler importHandler = switch (entityType) {
case OFFICES -> this.applicationContext.getBean("officeImportHandler", ImportHandler.class);
@@ -85,38 +80,28 @@ public class BulkImportEventListener implements ApplicationListener<BulkImportEv
throw new GeneralPlatformDomainRuleException("error.msg.unable.to.find.resource", "Unable to find requested resource");
};
final Workbook workbook = event.getWorkbook();
final Count count = importHandler.process(workbook, event.getLocale(), event.getDateFormat());
importDocument.update(DateUtils.getLocalDateTimeOfTenant(), count.getSuccessCount(), count.getErrorCount());
this.importRepository.saveAndFlush(importDocument);
final var count = importHandler.process(event.getWorkbook(), event.getLocale(), event.getDateFormat());
final Set<String> modifiedParams = new HashSet<>();
modifiedParams.add("fileName");
modifiedParams.add("size");
modifiedParams.add("type");
modifiedParams.add("location");
Document document = importDocument.getDocument();
event.getImportDocument().update(DateUtils.getLocalDateTimeOfTenant(), count.getSuccessCount(), count.getErrorCount());
DocumentCommand documentCommand = new DocumentCommand(modifiedParams, document.getId(), entityType.name(), null,
document.getName(), document.getFileName(), document.getSize(),
URLConnection.guessContentTypeFromName(document.getFileName()), null, null);
final var pipedInputStream = pipe.pipe(output -> {
event.getWorkbook().write(output);
});
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
try {
workbook.write(bos);
} finally {
bos.close();
}
} catch (IOException io) {
log.error("Problem occurred in onApplicationEvent function", io);
}
byte[] bytes = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
this.documentService.updateDocument(documentCommand, bis);
final var command = new DocumentUpdateCommand();
command.setPayload(DocumentUpdateRequest.builder().id(event.getImportDocument().getDocumentId()).entityId(event.getEntityId())
.entityType("IMPORT").stream(pipedInputStream).build());
final Supplier<DocumentCreateResponse> response = commandPipeline.send(command);
response.get();
importRepository.saveAndFlush(event.getImportDocument());
} catch (Exception e) {
log.error("Bulk import error:", e);
} finally {
ThreadLocalContextUtil.reset();
}
}
}
@@ -18,70 +18,58 @@
*/
package org.apache.fineract.infrastructure.bulkimport.service;
import jakarta.ws.rs.core.Response;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.Collection;
import java.util.function.Supplier;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.fineract.command.core.CommandPipeline;
import org.apache.fineract.infrastructure.bulkimport.data.BulkImportEvent;
import org.apache.fineract.infrastructure.bulkimport.data.GlobalEntityType;
import org.apache.fineract.infrastructure.bulkimport.data.ImportData;
import org.apache.fineract.infrastructure.bulkimport.domain.ImportDocument;
import org.apache.fineract.infrastructure.bulkimport.domain.ImportDocumentRepository;
import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandlerUtils;
import org.apache.fineract.infrastructure.bulkimport.mapping.ImportDocumentMapper;
import org.apache.fineract.infrastructure.contentstore.util.ContentPipe;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentData;
import org.apache.fineract.infrastructure.documentmanagement.domain.Document;
import org.apache.fineract.infrastructure.documentmanagement.domain.DocumentRepository;
import org.apache.fineract.infrastructure.documentmanagement.service.DocumentWritePlatformService;
import org.apache.fineract.infrastructure.documentmanagement.service.DocumentWritePlatformServiceJpaRepositoryImpl;
import org.apache.fineract.infrastructure.documentmanagement.command.DocumentCreateCommand;
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentCreateRequest;
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentCreateResponse;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.tika.Tika;
import org.apache.tika.io.TikaInputStream;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Slf4j
@RequiredArgsConstructor
@Service
public class BulkImportWorkbookServiceImpl implements BulkImportWorkbookService {
private static final Logger LOG = LoggerFactory.getLogger(BulkImportWorkbookServiceImpl.class);
private final ApplicationContext applicationContext;
private final PlatformSecurityContext securityContext;
private final DocumentWritePlatformService documentWritePlatformService;
private final DocumentRepository documentRepository;
private final ImportDocumentRepository importDocumentRepository;
private final JdbcTemplate jdbcTemplate;
@Autowired
public BulkImportWorkbookServiceImpl(final ApplicationContext applicationContext, final PlatformSecurityContext securityContext,
final DocumentWritePlatformService documentWritePlatformService, final DocumentRepository documentRepository,
final ImportDocumentRepository importDocumentRepository, final JdbcTemplate jdbcTemplate) {
this.applicationContext = applicationContext;
this.securityContext = securityContext;
this.documentWritePlatformService = documentWritePlatformService;
this.documentRepository = documentRepository;
this.importDocumentRepository = importDocumentRepository;
this.jdbcTemplate = jdbcTemplate;
}
private final ContentPipe pipe;
private final CommandPipeline commandPipeline;
private final ImportDocumentMapper mapper;
@Override
public Long importWorkbook(String entity, InputStream inputStream, FormDataContentDisposition fileDetail, final String locale,
@@ -169,35 +157,56 @@ public class BulkImportWorkbookServiceImpl implements BulkImportWorkbookService
throw new GeneralPlatformDomainRuleException("error.msg.unable.to.find.resource", "Unable to find requested resource");
}
return publishEvent(primaryColumn, fileDetail, bis, entityType, workbook, locale, dateFormat);
return publishEvent(primaryColumn, fileDetail, fileType, entityType, workbook, locale, dateFormat);
}
throw new GeneralPlatformDomainRuleException("error.msg.null", "One or more of the given parameters not found");
} catch (IOException e) {
LOG.error("Problem occurred in importWorkbook function", e);
log.error("Problem occurred in importWorkbook function", e);
throw new GeneralPlatformDomainRuleException("error.msg.io.exception",
"IO exception occured with " + fileDetail.getFileName() + " " + e.getMessage(), e);
}
}
private Long publishEvent(final Integer primaryColumn, final FormDataContentDisposition fileDetail,
final InputStream clonedInputStreamWorkbook, final GlobalEntityType entityType, final Workbook workbook, final String locale,
final String dateFormat) {
private DocumentCreateResponse createDocument(final Workbook workbook, final FormDataContentDisposition fileDetail,
final String fileType) {
final var pipedInputStream = pipe.pipe(output -> {
workbook.write(output);
});
final String fileName = fileDetail.getFileName();
final var command = new DocumentCreateCommand();
final Long documentId = this.documentWritePlatformService.createInternalDocument(
DocumentWritePlatformServiceJpaRepositoryImpl.DocumentManagementEntity.IMPORT.name(),
this.securityContext.authenticatedUser().getId(), null, clonedInputStreamWorkbook,
URLConnection.guessContentTypeFromName(fileName), fileName, null, fileName);
final Document document = this.documentRepository.findById(documentId).orElse(null);
// TODO: does it really make sense to use the current user's ID?
// TODO: wouldn't it be better to use "entityType.name()" as entity name?
command.setPayload(DocumentCreateRequest.builder().entityId(this.securityContext.authenticatedUser().getId()).entityType("IMPORT")
.name(fileDetail.getFileName()).description(fileDetail.getFileName()).fileName(fileDetail.getFileName())
.size(fileDetail.getSize()).type(fileType).stream(pipedInputStream).build());
final Supplier<DocumentCreateResponse> response = commandPipeline.send(command);
return response.get();
}
private Long publishEvent(final Integer primaryColumn, final FormDataContentDisposition fileDetail, final String fileType,
final GlobalEntityType entityType, final Workbook workbook, final String locale, final String dateFormat) {
final var importDocument = ImportDocument.instance(null, DateUtils.getLocalDateTimeOfTenant(), entityType.getValue(),
securityContext.authenticatedUser(), ImportHandlerUtils.getNumberOfRows(workbook.getSheetAt(0), primaryColumn));
final var response = createDocument(workbook, fileDetail, fileType);
importDocument.setDocumentId(response.getResourceId());
// TODO: should we mark as "not yet processed by import handler"?
importDocumentRepository.saveAndFlush(importDocument);
final var event = new BulkImportEvent(this, workbook, fileDetail.getFileName(), fileType, importDocument, locale, dateFormat,
ThreadLocalContextUtil.getContext(), this.securityContext.authenticatedUser().getId());
final ImportDocument importDocument = ImportDocument.instance(document, DateUtils.getLocalDateTimeOfTenant(), entityType.getValue(),
this.securityContext.authenticatedUser(), ImportHandlerUtils.getNumberOfRows(workbook.getSheetAt(0), primaryColumn));
this.importDocumentRepository.saveAndFlush(importDocument);
BulkImportEvent event = BulkImportEvent.instance(this, workbook, importDocument.getId(), locale, dateFormat,
ThreadLocalContextUtil.getContext());
applicationContext.publishEvent(event);
// TODO: remove when done
log.warn("Import document ID: {} ({})", importDocument.getId(), importDocument.getDocumentId());
return importDocument.getId();
}
@@ -208,7 +217,12 @@ public class BulkImportWorkbookServiceImpl implements BulkImportWorkbookService
final ImportMapper rm = new ImportMapper();
final String sql = "select " + rm.schema() + " order by i.id desc";
return this.jdbcTemplate.query(sql, rm, new Object[] { type.getValue() }); // NOSONAR
return this.jdbcTemplate.query(sql, rm, type.getValue());
}
@Override
public ImportData getImport(Long id) {
return importDocumentRepository.findById(id).map(mapper::map).orElse(null);
}
private static final class ImportMapper implements RowMapper<ImportData> {
@@ -240,50 +254,4 @@ public class BulkImportWorkbookServiceImpl implements BulkImportWorkbookService
failureCount);
}
}
@Override
public DocumentData getOutputTemplateLocation(String importDocumentId) {
this.securityContext.authenticatedUser();
final ImportTemplateLocationMapper importTemplateLocationMapper = new ImportTemplateLocationMapper();
final String sql = "select " + importTemplateLocationMapper.schema();
return this.jdbcTemplate.queryForObject(sql, importTemplateLocationMapper, new Object[] { Long.parseLong(importDocumentId) }); // NOSONAR
}
@Override
public Response getOutputTemplate(String importDocumentId) {
this.securityContext.authenticatedUser();
final ImportTemplateLocationMapper importTemplateLocationMapper = new ImportTemplateLocationMapper();
final String sql = "select " + importTemplateLocationMapper.schema();
DocumentData documentData = this.jdbcTemplate.queryForObject(sql, importTemplateLocationMapper,
new Object[] { Long.parseLong(importDocumentId) }); // NOSONAR
return buildResponse(documentData);
}
private Response buildResponse(DocumentData documentData) {
String fileName = "Output" + documentData.getFileName();
String fileLocation = documentData.getLocation();
File file = new File(fileLocation);
final Response.ResponseBuilder response = Response.ok(file);
response.header("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
response.header("Content-Type", "application/vnd.ms-excel");
return response.build();
}
private static final class ImportTemplateLocationMapper implements RowMapper<DocumentData> {
public String schema() {
final StringBuilder sql = new StringBuilder();
sql.append("d.location,d.file_name ").append("from m_import_document i inner join m_document d on i.document_id=d.id ")
.append("where i.id= ? ");
return sql.toString();
}
@Override
public DocumentData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
final String location = rs.getString("location");
final String fileName = rs.getString("file_name");
return new DocumentData(null, null, null, null, fileName, null, null, location, null, null);
}
}
}
@@ -204,6 +204,92 @@ public class SecurityConfig {
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_SAVINGNOTE")
.requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/groups/*/notes"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_GROUPNOTE")
// document: clients
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/clients/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/clients/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "CREATE_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/clients/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/clients/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_DOCUMENT")
// document: client_identifiers
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/client_identifiers/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/client_identifiers/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "CREATE_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/client_identifiers/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/client_identifiers/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_DOCUMENT")
// document: staff
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/staff/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/staff/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "CREATE_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/staff/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/staff/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_DOCUMENT")
// document: loans
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/loans/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/loans/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "CREATE_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/loans/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/loans/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_DOCUMENT")
// document: savings
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/savings/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/savings/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "CREATE_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/savings/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/savings/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_DOCUMENT")
// document: groups
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/groups/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/groups/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "CREATE_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/groups/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/groups/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_DOCUMENT")
// document: import
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/import/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/import/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "CREATE_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/import/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/import/*/documents"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_DOCUMENT")
// image: clients
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/clients/*/images"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/clients/*/images"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "CREATE_CLIENTIMAGE")
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/clients/*/images"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_CLIENTIMAGE")
.requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/clients/*/images"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_CLIENTIMAGE")
// image: staff
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/staff/*/images"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_DOCUMENT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/staff/*/images"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "CREATE_STAFFIMAGE")
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/staff/*/images"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_STAFFIMAGE")
.requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/staff/*/images"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_STAFFIMAGE")
// bulk import
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/import"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_IMPORT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/import/*"))
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_IMPORT")
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/twofactor/validate")).fullyAuthenticated()
.requestMatchers(API_MATCHER.matcher("/api/*/twofactor")).fullyAuthenticated()
@@ -55,7 +55,7 @@ public class JerseyConfig extends ResourceConfig {
@PostConstruct
public void setup() {
appCtx.getBeansWithAnnotation(Path.class).values().forEach(component -> register(component.getClass()));
appCtx.getBeansWithAnnotation(Path.class).values().forEach(this::register);
appCtx.getBeansWithAnnotation(Provider.class).values().forEach(this::register);
}
@@ -1,110 +0,0 @@
/**
* 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.infrastructure.documentmanagement.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.documentmanagement.api.ImagesApiResource.EntityTypeForImages;
import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepository;
import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepositoryFactory;
import org.apache.fineract.infrastructure.documentmanagement.data.FileData;
import org.apache.fineract.infrastructure.documentmanagement.data.ImageData;
import org.apache.fineract.infrastructure.documentmanagement.domain.StorageType;
import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
import org.apache.fineract.portfolio.client.exception.ImageNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class ImageReadPlatformServiceImpl implements ImageReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final ContentRepositoryFactory contentRepositoryFactory;
private final ClientRepositoryWrapper clientRepositoryWrapper;
private final StaffRepositoryWrapper staffRepositoryWrapper;
@Autowired
public ImageReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, final ContentRepositoryFactory documentStoreFactory,
final ClientRepositoryWrapper clientRepositoryWrapper, StaffRepositoryWrapper staffRepositoryWrapper) {
this.staffRepositoryWrapper = staffRepositoryWrapper;
this.jdbcTemplate = jdbcTemplate;
this.contentRepositoryFactory = documentStoreFactory;
this.clientRepositoryWrapper = clientRepositoryWrapper;
}
private static final class ImageMapper implements RowMapper<ImageData> {
private final String entityDisplayName;
ImageMapper(final String entityDisplayName) {
this.entityDisplayName = entityDisplayName;
}
public String schema(String entityType) {
StringBuilder builder = new StringBuilder(
"image.id as id, image.location as location, image.storage_type_enum as storageType ");
if (EntityTypeForImages.CLIENTS.toString().equalsIgnoreCase(entityType)) {
builder.append(" from m_image image , m_client client " + " where client.image_id = image.id and client.id=?");
} else if (EntityTypeForImages.STAFF.toString().equalsIgnoreCase(entityType)) {
builder.append("from m_image image , m_staff staff " + " where staff.image_id = image.id and staff.id=?");
}
return builder.toString();
}
@Override
public ImageData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final String location = rs.getString("location");
final Integer storageTypeInt = JdbcSupport.getInteger(rs, "storageType");
StorageType storageType = storageTypeInt != null ? StorageType.fromInt(storageTypeInt) : null;
return new ImageData(location, storageType, this.entityDisplayName);
}
}
@Override
public FileData retrieveImage(String entityType, final Long entityId) {
try {
String displayName;
if (EntityTypeForImages.CLIENTS.toString().equalsIgnoreCase(entityType)) {
Client owner = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId);
displayName = owner.getDisplayName();
} else if (EntityTypeForImages.STAFF.toString().equalsIgnoreCase(entityType)) {
Staff owner = this.staffRepositoryWrapper.findOneWithNotFoundDetection(entityId);
displayName = owner.displayName();
} else {
displayName = "UnknownEntityType:" + entityType;
}
final ImageMapper imageMapper = new ImageMapper(displayName);
final String sql = "select " + imageMapper.schema(entityType);
final ImageData imageData = this.jdbcTemplate.queryForObject(sql, imageMapper, entityId); // NOSONAR
final ContentRepository contentRepository = this.contentRepositoryFactory.getRepository(imageData.storageType());
return contentRepository.fetchImage(imageData);
} catch (final EmptyResultDataAccessException e) {
throw new ImageNotFoundException("clients", entityId, e);
}
}
}
@@ -1,167 +0,0 @@
/**
* 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.infrastructure.documentmanagement.service;
import java.io.InputStream;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.domain.Base64EncodedImage;
import org.apache.fineract.infrastructure.documentmanagement.api.ImagesApiResource.EntityTypeForImages;
import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepository;
import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepositoryFactory;
import org.apache.fineract.infrastructure.documentmanagement.domain.Image;
import org.apache.fineract.infrastructure.documentmanagement.domain.ImageRepository;
import org.apache.fineract.infrastructure.documentmanagement.domain.StorageType;
import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ImageWritePlatformServiceJpaRepositoryImpl implements ImageWritePlatformService {
private final ContentRepositoryFactory contentRepositoryFactory;
private final ClientRepositoryWrapper clientRepositoryWrapper;
private final ImageRepository imageRepository;
private final StaffRepositoryWrapper staffRepositoryWrapper;
@Autowired
public ImageWritePlatformServiceJpaRepositoryImpl(final ContentRepositoryFactory documentStoreFactory,
final ClientRepositoryWrapper clientRepositoryWrapper, final ImageRepository imageRepository,
StaffRepositoryWrapper staffRepositoryWrapper) {
this.contentRepositoryFactory = documentStoreFactory;
this.clientRepositoryWrapper = clientRepositoryWrapper;
this.imageRepository = imageRepository;
this.staffRepositoryWrapper = staffRepositoryWrapper;
}
@Transactional
@Override
public CommandProcessingResult saveOrUpdateImage(String entityName, final Long clientId, final String imageName,
final InputStream inputStream, final Long fileSize) {
Object owner = deletePreviousImage(entityName, clientId);
final ContentRepository contentRepository = this.contentRepositoryFactory.getRepository();
final String imageLocation = contentRepository.saveImage(inputStream, clientId, imageName, fileSize);
return updateImage(owner, imageLocation, contentRepository.getStorageType());
}
@Transactional
@Override
public CommandProcessingResult saveOrUpdateImage(String entityName, final Long clientId, final Base64EncodedImage encodedImage) {
Object owner = deletePreviousImage(entityName, clientId);
final ContentRepository contenRepository = this.contentRepositoryFactory.getRepository();
final String imageLocation = contenRepository.saveImage(encodedImage, clientId, "image");
return updateImage(owner, imageLocation, contenRepository.getStorageType());
}
@Transactional
@Override
public CommandProcessingResult deleteImage(String entityName, final Long clientId) {
Object owner = null;
Image image = null;
if (EntityTypeForImages.CLIENTS.toString().equals(entityName)) {
owner = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
Client client = (Client) owner;
image = client.getImage();
client.setImage(null);
this.clientRepositoryWrapper.save(client);
} else if (EntityTypeForImages.STAFF.toString().equals(entityName)) {
owner = this.staffRepositoryWrapper.findOneWithNotFoundDetection(clientId);
Staff staff = (Staff) owner;
image = staff.getImage();
staff.setImage(null);
this.staffRepositoryWrapper.save(staff);
}
// delete image from the file system
if (image != null) {
final ContentRepository contentRepository = this.contentRepositoryFactory
.getRepository(StorageType.fromInt(image.getStorageType()));
contentRepository.deleteImage(image.getLocation());
this.imageRepository.delete(image);
}
return CommandProcessingResult.resourceResult(clientId);
}
/**
* @param entityName
* @param entityId
* @return
*/
private Object deletePreviousImage(String entityName, final Long entityId) {
Object owner = null;
Image image = null;
if (EntityTypeForImages.CLIENTS.toString().equals(entityName)) {
Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId);
image = client.getImage();
owner = client;
} else if (EntityTypeForImages.STAFF.toString().equals(entityName)) {
Staff staff = this.staffRepositoryWrapper.findOneWithNotFoundDetection(entityId);
image = staff.getImage();
owner = staff;
}
if (image != null) {
final ContentRepository contentRepository = this.contentRepositoryFactory
.getRepository(StorageType.fromInt(image.getStorageType()));
contentRepository.deleteImage(image.getLocation());
}
return owner;
}
private CommandProcessingResult updateImage(final Object owner, final String imageLocation, final StorageType storageType) {
Image image = null;
Long clientId = null;
if (owner instanceof Client client) {
image = client.getImage();
clientId = client.getId();
image = createImage(image, imageLocation, storageType);
client.setImage(image);
this.clientRepositoryWrapper.save(client);
} else if (owner instanceof Staff staff) {
image = staff.getImage();
clientId = staff.getId();
image = createImage(image, imageLocation, storageType);
staff.setImage(image);
this.staffRepositoryWrapper.save(staff);
}
if (image != null) {
this.imageRepository.save(image);
}
return CommandProcessingResult.resourceResult(clientId);
}
private Image createImage(Image image, final String imageLocation, final StorageType storageType) {
if (image == null) {
image = new Image(imageLocation, storageType);
} else {
image.setLocation(imageLocation);
image.setStorageType(storageType.getValue());
}
return image;
}
}
@@ -19,28 +19,23 @@
package org.apache.fineract.infrastructure.event.external.service.serialization.serializer.document;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.avro.generic.GenericContainer;
import org.apache.fineract.avro.document.v1.DocumentDataV1;
import org.apache.fineract.avro.generator.ByteBufferSerializable;
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentData;
import org.apache.fineract.infrastructure.documentmanagement.domain.Document;
import org.apache.fineract.infrastructure.documentmanagement.service.DocumentReadPlatformService;
import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.document.DocumentBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.document.DocumentCreatedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.document.DocumentDeletedBusinessEvent;
import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.document.DocumentDataMapper;
import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@RequiredArgsConstructor
public class DocumentBusinessEventSerializer implements BusinessEventSerializer {
private static final Logger log = LoggerFactory.getLogger(DocumentBusinessEventSerializer.class);
private final DocumentReadPlatformService service;
private final DocumentDataMapper mapper;
@Override
@@ -51,35 +46,9 @@ public class DocumentBusinessEventSerializer implements BusinessEventSerializer
@Override
public <T> ByteBufferSerializable toAvroDTO(BusinessEvent<T> rawEvent) {
DocumentBusinessEvent event = (DocumentBusinessEvent) rawEvent;
Document entity = event.get(); // domain entity
var event = (DocumentBusinessEvent) rawEvent;
DocumentData dto = null;
if (rawEvent instanceof DocumentCreatedBusinessEvent) {
try {
dto = service.retrieveDocument(entity.getParentEntityType(), entity.getParentEntityId(), entity.getId());
} catch (Exception ex) {
// log at DEBUG and fall back to entity mapping
log.debug("DocumentData not found, falling back to entity", ex);
}
}
// If we have the DTO, let MapStruct do the work. Otherwise, build from the entity
DocumentDataV1 avro;
if (dto != null) {
avro = mapper.map(dto);
} else {
avro = DocumentDataV1.newBuilder().setId(entity.getId()).setParentEntityType(entity.getParentEntityType())
.setParentEntityId(entity.getParentEntityId()).setName(entity.getName()).setFileName(entity.getFileName())
.setSize(entity.getSize()).setType(entity.getType()).setDescription(entity.getDescription()).build();
}
Integer storageTypeCode = (dto != null && dto.getStorageType() != null && dto.storageType() != null) ? dto.getStorageType()
: (entity.storageType() != null ? entity.storageType().getValue() : null);
avro.setStorageType(storageTypeCode);
return avro;
return mapper.map(event.get());
}
@Override
@@ -0,0 +1,66 @@
/**
* 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.organisation.staff.adapter;
import static java.util.Objects.nonNull;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.documentmanagement.adapter.EntityImageIdAdapter;
import org.apache.fineract.organisation.staff.domain.StaffRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@RequiredArgsConstructor
@Component
@Deprecated
class StaffImageIdAdapter implements EntityImageIdAdapter {
private static final String ENTITY_TYPE = "staff";
private final StaffRepository repository;
@Override
public boolean accept(String entityType) {
return ENTITY_TYPE.equalsIgnoreCase(entityType);
}
@Override
@Transactional(readOnly = true)
public Optional<ImageIdResult> get(Long entityId) {
return repository.findById(entityId).filter(staff -> nonNull(staff.getImageId()))
.map(staff -> ImageIdResult.builder().id(staff.getImageId()).displayName(staff.getDisplayName()).build());
}
@Override
@Transactional
public Optional<ImageIdResult> set(Long entityId, Long imageId) {
final var result = get(entityId);
if (imageId == null) {
repository.removeImageId(entityId);
} else {
repository.updateByIdAndImageId(entityId, imageId);
}
return result;
}
}
@@ -20,6 +20,7 @@ package org.apache.fineract.organisation.staff.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
@@ -27,10 +28,14 @@ public interface StaffRepository extends JpaRepository<Staff, Long>, JpaSpecific
String FIND_BY_OFFICE_QUERY = "select s from Staff s where s.id = :id AND s.office.id = :officeId";
/**
* Find staff by officeid.
*/
@Query(FIND_BY_OFFICE_QUERY)
Staff findByOffice(@Param("id") Long id, @Param("officeId") Long officeId);
@Modifying
@Query("UPDATE Staff staff SET staff.imageId = :imageId WHERE staff.id = :staffId")
void updateByIdAndImageId(@Param("staffId") Long staffId, @Param("imageId") Long imageId);
@Modifying
@Query("UPDATE Staff staff SET staff.imageId = null WHERE staff.id = :staffId")
void removeImageId(@Param("staffId") Long staffId);
}
@@ -34,22 +34,22 @@ import org.mapstruct.Named;
@Mapper(config = MapstructMapperConfig.class)
public interface ClientMapper {
@Mapping(target = "accountNo", source = "source.accountNumber")
@Mapping(target = "accountNo", source = "accountNumber")
@Mapping(target = "status", source = "source", qualifiedByName = "clientStatusEnum")
@Mapping(target = "subStatus", source = "source", qualifiedByName = "clientSubStatusCode")
@Mapping(target = "officeId", source = "source.office.id")
@Mapping(target = "officeName", source = "source.office.name")
@Mapping(target = "transferToOfficeId", source = "source.transferToOffice.id")
@Mapping(target = "transferToOfficeName", source = "source.transferToOffice.name")
@Mapping(target = "externalId", source = "source.externalId")
@Mapping(target = "officeId", source = "office.id")
@Mapping(target = "officeName", source = "office.name")
@Mapping(target = "transferToOfficeId", source = "transferToOffice.id")
@Mapping(target = "transferToOfficeName", source = "transferToOffice.name")
@Mapping(target = "externalId", source = "externalId")
@Mapping(target = "gender", source = "source", qualifiedByName = "clientGenderCode")
@Mapping(target = "imageId", source = "source.image.id")
@Mapping(target = "staffId", source = "source.staff.id")
@Mapping(target = "staffName", source = "source.staff.displayName")
@Mapping(target = "imageId", source = "imageId")
@Mapping(target = "staffId", source = "staff.id")
@Mapping(target = "staffName", source = "staff.displayName")
@Mapping(target = "timeline", source = "source", qualifiedByName = "clientTimelineData")
@Mapping(target = "savingsProductId", source = "source.savingsProductId")
@Mapping(target = "savingsProductName", source = "source.id")
@Mapping(target = "savingsAccountId", source = "source.savingsAccountId")
@Mapping(target = "savingsProductId", source = "savingsProductId")
@Mapping(target = "savingsProductName", source = "id")
@Mapping(target = "savingsAccountId", source = "savingsAccountId")
@Mapping(target = "clientType", source = "source", qualifiedByName = "clientTypeCode")
@Mapping(target = "clientClassification", source = "source", qualifiedByName = "clientClassificationCode")
@Mapping(target = "legalForm", source = "source", qualifiedByName = "clientLegalFormEnum")
@@ -18,6 +18,16 @@
*/
package org.apache.fineract.portfolio.self.client.api;
import static java.util.Objects.requireNonNull;
import static org.apache.fineract.infrastructure.contentstore.processor.DataUrlDecoderContentProcessor.DATA_URL_DECODE_RESULT_CONTENT_TYPE;
import static org.apache.fineract.infrastructure.contentstore.processor.DataUrlEncoderContentProcessor.DATA_URL_ENCODE_PARAM_CONTENT_TYPE;
import static org.apache.fineract.infrastructure.contentstore.processor.DataUrlEncoderContentProcessor.DATA_URL_ENCODE_PARAM_ENCODING;
import static org.apache.fineract.infrastructure.contentstore.processor.ImageResizeContentProcessor.IMAGE_RESIZE_PARAM_FORMAT;
import static org.apache.fineract.infrastructure.contentstore.processor.ImageResizeContentProcessor.IMAGE_RESIZE_PARAM_MAX_HEIGHT;
import static org.apache.fineract.infrastructure.contentstore.processor.ImageResizeContentProcessor.IMAGE_RESIZE_PARAM_MAX_WIDTH;
import static org.apache.fineract.infrastructure.contentstore.processor.SizeContentProcessor.SIZE_RESULT_VALUE;
import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
@@ -40,10 +50,28 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import java.io.InputStream;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.command.core.CommandPipeline;
import org.apache.fineract.infrastructure.contentstore.detector.ContentDetectorContext;
import org.apache.fineract.infrastructure.contentstore.detector.ContentDetectorManager;
import org.apache.fineract.infrastructure.contentstore.processor.Base64DecoderContentProcessor;
import org.apache.fineract.infrastructure.contentstore.processor.Base64EncoderContentProcessor;
import org.apache.fineract.infrastructure.contentstore.processor.ContentProcessorContext;
import org.apache.fineract.infrastructure.contentstore.processor.DataUrlDecoderContentProcessor;
import org.apache.fineract.infrastructure.contentstore.processor.DataUrlEncoderContentProcessor;
import org.apache.fineract.infrastructure.contentstore.processor.ImageResizeContentProcessor;
import org.apache.fineract.infrastructure.contentstore.processor.SizeContentProcessor;
import org.apache.fineract.infrastructure.core.data.UploadRequest;
import org.apache.fineract.infrastructure.documentmanagement.api.ImagesApiResource;
import org.apache.fineract.infrastructure.documentmanagement.command.ImageCreateCommand;
import org.apache.fineract.infrastructure.documentmanagement.command.ImageDeleteCommand;
import org.apache.fineract.infrastructure.documentmanagement.data.ImageCreateRequest;
import org.apache.fineract.infrastructure.documentmanagement.data.ImageCreateResponse;
import org.apache.fineract.infrastructure.documentmanagement.data.ImageDeleteRequest;
import org.apache.fineract.infrastructure.documentmanagement.data.ImageDeleteResponse;
import org.apache.fineract.infrastructure.documentmanagement.service.ImageReadPlatformService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.client.api.ClientApiConstants;
import org.apache.fineract.portfolio.client.api.ClientChargesApiResource;
@@ -54,6 +82,7 @@ import org.apache.fineract.portfolio.self.client.data.SelfClientDataValidator;
import org.apache.fineract.portfolio.self.client.service.AppuserClientMapperReadService;
import org.apache.fineract.portfolio.self.config.SelfServiceModuleIsEnabledCondition;
import org.apache.fineract.useradministration.domain.AppUser;
import org.apache.fineract.util.StreamResponseUtil;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
@@ -69,11 +98,19 @@ public class SelfClientsApiResource {
private final PlatformSecurityContext context;
private final ClientsApiResource clientApiResource;
private final ImagesApiResource imagesApiResource;
private final ClientChargesApiResource clientChargesApiResource;
private final ClientTransactionsApiResource clientTransactionsApiResource;
private final AppuserClientMapperReadService appUserClientMapperReadService;
private final SelfClientDataValidator dataValidator;
private final ImageReadPlatformService imageReadPlatformService;
private final CommandPipeline commandPipeline;
private final ImageResizeContentProcessor imageResizeContentProcessor;
private final Base64EncoderContentProcessor base64EncoderContentProcessor;
private final Base64DecoderContentProcessor base64DecoderContentProcessor;
private final DataUrlEncoderContentProcessor dataUrlEncoderContentProcessor;
private final DataUrlDecoderContentProcessor dataUrlDecoderContentProcessor;
private final SizeContentProcessor sizeContentProcessor;
private final ContentDetectorManager contentDetectorManager;
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@@ -149,7 +186,20 @@ public class SelfClientsApiResource {
validateAppuserClientsMapping(clientId);
return this.imagesApiResource.retrieveImage("clients", clientId, maxWidth, maxHeight, output, MediaType.TEXT_PLAIN);
final var content = imageReadPlatformService.retrieveImage(ClientApiConstants.clientEntityName, clientId);
// stream base64 encoded original format
final var detectorCtx = contentDetectorManager.detect(ContentDetectorContext.builder().fileName(content.getFileName()).build());
final var ctx = imageResizeContentProcessor.then(base64EncoderContentProcessor).then(dataUrlEncoderContentProcessor)
.process(new ContentProcessorContext(content.getStream(),
Map.of(IMAGE_RESIZE_PARAM_MAX_WIDTH, maxWidth, IMAGE_RESIZE_PARAM_MAX_HEIGHT, maxHeight, IMAGE_RESIZE_PARAM_FORMAT,
detectorCtx.getFormat(), DATA_URL_ENCODE_PARAM_CONTENT_TYPE, detectorCtx.getMimeType(),
DATA_URL_ENCODE_PARAM_ENCODING, "base64")));
final var streamResponseData = StreamResponseUtil.StreamResponseData.builder().fileName(content.getFileName())
.type(TEXT_PLAIN_VALUE).stream(ctx.getInputStream()).build();
return StreamResponseUtil.ok(streamResponseData);
}
@GET
@@ -233,36 +283,63 @@ public class SelfClientsApiResource {
@Produces({ MediaType.APPLICATION_JSON })
@RequestBody(description = "Add new client image", content = {
@Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(implementation = UploadRequest.class)) })
public CommandProcessingResult addNewClientImage(@PathParam("clientId") final Long clientId,
@HeaderParam("Content-Length") final Long fileSize, @FormDataParam("file") final InputStream inputStream,
@FormDataParam("file") final FormDataContentDisposition fileDetails, @FormDataParam("file") final FormDataBodyPart bodyPart) {
public ImageCreateResponse addNewClientImage(@PathParam("clientId") final Long clientId,
@HeaderParam("Content-Length") final Long fileSize, @FormDataParam("file") final InputStream is,
@FormDataParam("file") final FormDataContentDisposition fileDetails, @FormDataParam("file") final FormDataBodyPart filePart) {
validateAppuserClientsMapping(clientId);
return this.imagesApiResource.addNewClientImage(ClientApiConstants.clientEntityName, clientId, fileSize, inputStream, fileDetails,
bodyPart);
// TODO: add proper error messages
requireNonNull(fileDetails, "");
requireNonNull(filePart, "");
requireNonNull(is, "");
final var command = new ImageCreateCommand();
command.setPayload(ImageCreateRequest.builder().entityId(clientId).entityType(ClientApiConstants.clientEntityName)
.fileName(fileDetails.getFileName()).size(fileSize).type(filePart.getMediaType().toString()).stream(is).build());
final Supplier<ImageCreateResponse> response = commandPipeline.send(command);
return response.get();
}
@POST
@Path("{clientId}/images")
@Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public CommandProcessingResult addNewClientImage(@PathParam("entity") final String entityName,
@PathParam("clientId") final Long clientId, final String jsonRequestBody) {
public ImageCreateResponse addNewClientImage(@PathParam("clientId") final Long clientId, final InputStream body) {
validateAppuserClientsMapping(clientId);
return this.imagesApiResource.addNewClientImage(ClientApiConstants.clientEntityName, clientId, jsonRequestBody);
final var ctx = dataUrlDecoderContentProcessor.then(base64DecoderContentProcessor).then(sizeContentProcessor).process(body);
final String contentType = ctx.getResult(DATA_URL_DECODE_RESULT_CONTENT_TYPE);
Long size = ctx.getResult(SIZE_RESULT_VALUE);
final var command = new ImageCreateCommand();
command.setPayload(ImageCreateRequest.builder().entityId(clientId).entityType(ClientApiConstants.clientEntityName)
.fileName(UUID.randomUUID().toString()).size(size).type(contentType).stream(ctx.getInputStream()).build());
final Supplier<ImageCreateResponse> response = commandPipeline.send(command);
return response.get();
}
@DELETE
@Path("{clientId}/images")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public CommandProcessingResult deleteClientImage(@PathParam("clientId") final Long clientId) {
public ImageDeleteResponse deleteClientImage(@PathParam("clientId") final Long clientId) {
validateAppuserClientsMapping(clientId);
return this.imagesApiResource.deleteClientImage(ClientApiConstants.clientEntityName, clientId);
final var command = new ImageDeleteCommand();
command.setPayload(ImageDeleteRequest.builder().entityId(clientId).entityType(ClientApiConstants.clientEntityName).build());
final Supplier<ImageDeleteResponse> response = commandPipeline.send(command);
return response.get();
}
@GET
@@ -182,6 +182,7 @@ fineract.content.regex-whitelist-enabled=${FINERACT_CONTENT_REGEX_WHITELIST_ENAB
fineract.content.regex-whitelist=${FINERACT_CONTENT_REGEX_WHITELIST:.*\\.pdf$,.*\\.doc,.*\\.docx,.*\\.xls,.*\\.xlsx,.*\\.jpg,.*\\.jpeg,.*\\.png}
fineract.content.mime-whitelist-enabled=${FINERACT_CONTENT_MIME_WHITELIST_ENABLED:true}
fineract.content.mime-whitelist=${FINERACT_CONTENT_MIME_WHITELIST:application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,image/jpeg,image/png}
fineract.content.default-buffer-size=${FINERACT_CONTENT_DEFAULT_BUFFER_SIZE:8192}
fineract.content.filesystem.enabled=${FINERACT_CONTENT_FILESYSTEM_ENABLED:true}
fineract.content.filesystem.rootFolder=${FINERACT_CONTENT_FILESYSTEM_ROOT_FOLDER:${user.home}/.fineract}
fineract.content.s3.enabled=${FINERACT_CONTENT_S3_ENABLED:false}
@@ -192,6 +193,9 @@ fineract.content.s3.region=${FINERACT_CONTENT_S3_REGION:}
fineract.content.s3.endpoint=${FINERACT_CONTENT_S3_ENDPOINT:}
fineract.content.s3.path-style-addressing-enabled=${FINERACT_CONTENT_S3_PATH_STYLE_ADDRESSING_ENABLED:false}
spring.servlet.multipart.max-file-size=${FINERACT_MULTIPART_FILE_SIZE:5MB}
spring.servlet.multipart.max-request-size=${FINERACT_MULTIPART_REQUEST_SIZE:10MB}
fineract.template.regex-whitelist-enabled=${FINERACT_TEMPLATE_REGEX_WHITELIST_ENABLED:true}
fineract.template.regex-whitelist=${FINERACT_TEMPLATE_REGEX_WHITELIST:}
@@ -489,55 +493,89 @@ resilience4j.retry.instances.postInterest.retryExceptions=${FINERACT_PROCESS_POS
# business date
resilience4j.retry.instances.commandBusinessDateUpdate.max-attempts=${FINERACT_PROCESS_COMMAND_BUSINESS_DATE_UPDATE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandBusinessDateUpdate.wait-duration=${FINERACT_PROCESS_COMMAND_BUSINESS_DATE_UPDATE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandBusinessDateUpdate.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_BUSINESS_DATE_UPDATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandBusinessDateUpdate.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_BUSINESS_DATE_UPDATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandBusinessDateUpdate.retryExceptions=${FINERACT_PROCESS_COMMAND_BUSINESS_DATE_UPDATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandBusinessDateUpdate.max-attempts=${FINERACT_COMMAND_BUSINESS_DATE_UPDATE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandBusinessDateUpdate.wait-duration=${FINERACT_COMMAND_BUSINESS_DATE_UPDATE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandBusinessDateUpdate.enable-exponential-backoff=${FINERACT_COMMAND_BUSINESS_DATE_UPDATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandBusinessDateUpdate.exponential-backoff-multiplier=${FINERACT_COMMAND_BUSINESS_DATE_UPDATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandBusinessDateUpdate.retryExceptions=${FINERACT_COMMAND_BUSINESS_DATE_UPDATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
# cache
resilience4j.retry.instances.commandCacheSwitch.max-attempts=${FINERACT_PROCESS_COMMAND_CACHE_SWITCH_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandCacheSwitch.wait-duration=${FINERACT_PROCESS_COMMAND_CACHE_SWITCH_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandCacheSwitch.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_CACHE_SWITCH_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandCacheSwitch.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_CACHE_SWITCH_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandCacheSwitch.retryExceptions=${FINERACT_PROCESS_COMMAND_CACHE_SWITCH_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandCacheSwitch.max-attempts=${FINERACT_COMMAND_CACHE_SWITCH_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandCacheSwitch.wait-duration=${FINERACT_COMMAND_CACHE_SWITCH_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandCacheSwitch.enable-exponential-backoff=${FINERACT_COMMAND_CACHE_SWITCH_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandCacheSwitch.exponential-backoff-multiplier=${FINERACT_COMMAND_CACHE_SWITCH_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandCacheSwitch.retryExceptions=${FINERACT_COMMAND_CACHE_SWITCH_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
# currency
resilience4j.retry.instances.commandCurrencyUpdate.max-attempts=${FINERACT_PROCESS_COMMAND_CURRENCY_UPDATE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandCurrencyUpdate.wait-duration=${FINERACT_PROCESS_COMMAND_CURRENCY_UPDATE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandCurrencyUpdate.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_CURRENCY_UPDATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandCurrencyUpdate.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_CURRENCY_UPDATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandCurrencyUpdate.retryExceptions=${FINERACT_PROCESS_COMMAND_CURRENCY_UPDATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandCurrencyUpdate.max-attempts=${FINERACT_COMMAND_CURRENCY_UPDATE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandCurrencyUpdate.wait-duration=${FINERACT_COMMAND_CURRENCY_UPDATE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandCurrencyUpdate.enable-exponential-backoff=${FINERACT_COMMAND_CURRENCY_UPDATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandCurrencyUpdate.exponential-backoff-multiplier=${FINERACT_COMMAND_CURRENCY_UPDATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandCurrencyUpdate.retryExceptions=${FINERACT_COMMAND_CURRENCY_UPDATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
# external event configuration
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.max-attempts=${FINERACT_PROCESS_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.wait-duration=${FINERACT_PROCESS_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.retryExceptions=${FINERACT_PROCESS_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.max-attempts=${FINERACT_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.wait-duration=${FINERACT_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.enable-exponential-backoff=${FINERACT_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.exponential-backoff-multiplier=${FINERACT_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.retryExceptions=${FINERACT_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
# note
resilience4j.retry.instances.commandNoteCreate.max-attempts=${FINERACT_PROCESS_COMMAND_NOTE_CREATE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandNoteCreate.wait-duration=${FINERACT_PROCESS_COMMAND_NOTE_CREATE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandNoteCreate.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_NOTE_CREATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandNoteCreate.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_NOTE_CREATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandNoteCreate.retryExceptions=${FINERACT_PROCESS_COMMAND_NOTE_CREATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandNoteCreate.max-attempts=${FINERACT_COMMAND_NOTE_CREATE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandNoteCreate.wait-duration=${FINERACT_COMMAND_NOTE_CREATE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandNoteCreate.enable-exponential-backoff=${FINERACT_COMMAND_NOTE_CREATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandNoteCreate.exponential-backoff-multiplier=${FINERACT_COMMAND_NOTE_CREATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandNoteCreate.retryExceptions=${FINERACT_COMMAND_NOTE_CREATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandNoteUpdate.max-attempts=${FINERACT_PROCESS_COMMAND_NOTE_UPDATE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandNoteUpdate.wait-duration=${FINERACT_PROCESS_COMMAND_NOTE_UPDATE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandNoteUpdate.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_NOTE_UPDATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandNoteUpdate.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_NOTE_UPDATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandNoteUpdate.retryExceptions=${FINERACT_PROCESS_COMMAND_NOTE_UPDATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandNoteUpdate.max-attempts=${FINERACT_COMMAND_NOTE_UPDATE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandNoteUpdate.wait-duration=${FINERACT_COMMAND_NOTE_UPDATE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandNoteUpdate.enable-exponential-backoff=${FINERACT_COMMAND_NOTE_UPDATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandNoteUpdate.exponential-backoff-multiplier=${FINERACT_COMMAND_NOTE_UPDATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandNoteUpdate.retryExceptions=${FINERACT_COMMAND_NOTE_UPDATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandNoteDelete.max-attempts=${FINERACT_PROCESS_COMMAND_NOTE_DELETE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandNoteDelete.wait-duration=${FINERACT_PROCESS_COMMAND_NOTE_DELETE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandNoteDelete.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_NOTE_DELETE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandNoteDelete.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_NOTE_DELETE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandNoteDelete.retryExceptions=${FINERACT_PROCESS_COMMAND_NOTE_DELETE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandNoteDelete.max-attempts=${FINERACT_COMMAND_NOTE_DELETE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandNoteDelete.wait-duration=${FINERACT_COMMAND_NOTE_DELETE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandNoteDelete.enable-exponential-backoff=${FINERACT_COMMAND_NOTE_DELETE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandNoteDelete.exponential-backoff-multiplier=${FINERACT_COMMAND_NOTE_DELETE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandNoteDelete.retryExceptions=${FINERACT_COMMAND_NOTE_DELETE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
# document
resilience4j.retry.instances.commandDocumentCreate.max-attempts=${FINERACT_COMMAND_DOCUMENT_CREATE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandDocumentCreate.wait-duration=${FINERACT_COMMAND_DOCUMENT_CREATE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandDocumentCreate.enable-exponential-backoff=${FINERACT_COMMAND_DOCUMENT_CREATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandDocumentCreate.exponential-backoff-multiplier=${FINERACT_COMMAND_DOCUMENT_CREATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandDocumentCreate.retryExceptions=${FINERACT_COMMAND_DOCUMENT_CREATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandDocumentUpdate.max-attempts=${FINERACT_COMMAND_DOCUMENT_UPDATE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandDocumentUpdate.wait-duration=${FINERACT_COMMAND_DOCUMENT_UPDATE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandDocumentUpdate.enable-exponential-backoff=${FINERACT_COMMAND_DOCUMENT_UPDATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandDocumentUpdate.exponential-backoff-multiplier=${FINERACT_COMMAND_DOCUMENT_UPDATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandDocumentUpdate.retryExceptions=${FINERACT_COMMAND_DOCUMENT_UPDATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandDocumentDelete.max-attempts=${FINERACT_COMMAND_DOCUMENT_DELETE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandDocumentDelete.wait-duration=${FINERACT_COMMAND_DOCUMENT_DELETE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandDocumentDelete.enable-exponential-backoff=${FINERACT_COMMAND_DOCUMENT_DELETE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandDocumentDelete.exponential-backoff-multiplier=${FINERACT_COMMAND_DOCUMENT_DELETE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandDocumentDelete.retryExceptions=${FINERACT_COMMAND_DOCUMENT_DELETE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
# image
resilience4j.retry.instances.commandImageCreate.max-attempts=${FINERACT_COMMAND_IMAGE_CREATE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandImageCreate.wait-duration=${FINERACT_COMMAND_IMAGE_CREATE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandImageCreate.enable-exponential-backoff=${FINERACT_COMMAND_IMAGE_CREATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandImageCreate.exponential-backoff-multiplier=${FINERACT_COMMAND_IMAGE_CREATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandImageCreate.retryExceptions=${FINERACT_COMMAND_IMAGE_CREATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandImageDelete.max-attempts=${FINERACT_COMMAND_IMAGE_DELETE_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandImageDelete.wait-duration=${FINERACT_COMMAND_IMAGE_DELETE_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandImageDelete.enable-exponential-backoff=${FINERACT_COMMAND_IMAGE_DELETE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandImageDelete.exponential-backoff-multiplier=${FINERACT_COMMAND_IMAGE_DELETE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandImageDelete.retryExceptions=${FINERACT_COMMAND_IMAGE_DELETE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
fineract.command.enabled=true
fineract.command.executor=${FINERACT_COMMAND_EXECUTOR:sync}
@@ -551,26 +589,26 @@ fineract.command.file-dead-letter-queue-path=${FINERACT_FILE_DEAD_LETTER_QUEUE_P
# command
resilience4j.retry.instances.commandAuditProcessing.max-attempts=${FINERACT_PROCESS_COMMAND_AUDIT_PROCESSING_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandAuditProcessing.wait-duration=${FINERACT_PROCESS_COMMAND_AUDIT_PROCESSING_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandAuditProcessing.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_AUDIT_PROCESSING_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandAuditProcessing.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_AUDIT_PROCESSING_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandAuditProcessing.retryExceptions=${FINERACT_PROCESS_COMMAND_AUDIT_PROCESSING_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandAuditProcessing.max-attempts=${FINERACT_COMMAND_AUDIT_PROCESSING_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandAuditProcessing.wait-duration=${FINERACT_COMMAND_AUDIT_PROCESSING_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandAuditProcessing.enable-exponential-backoff=${FINERACT_COMMAND_AUDIT_PROCESSING_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandAuditProcessing.exponential-backoff-multiplier=${FINERACT_COMMAND_AUDIT_PROCESSING_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandAuditProcessing.retryExceptions=${FINERACT_COMMAND_AUDIT_PROCESSING_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandAuditProcessed.max-attempts=${FINERACT_PROCESS_COMMAND_AUDIT_PROCESSED_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandAuditProcessed.wait-duration=${FINERACT_PROCESS_COMMAND_AUDIT_PROCESSED_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandAuditProcessed.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_AUDIT_PROCESSED_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandAuditProcessed.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_AUDIT_PROCESSED_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandAuditProcessed.retryExceptions=${FINERACT_PROCESS_COMMAND_AUDIT_PROCESSED_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandAuditProcessed.max-attempts=${FINERACT_COMMAND_AUDIT_PROCESSED_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandAuditProcessed.wait-duration=${FINERACT_COMMAND_AUDIT_PROCESSED_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandAuditProcessed.enable-exponential-backoff=${FINERACT_COMMAND_AUDIT_PROCESSED_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandAuditProcessed.exponential-backoff-multiplier=${FINERACT_COMMAND_AUDIT_PROCESSED_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandAuditProcessed.retryExceptions=${FINERACT_COMMAND_AUDIT_PROCESSED_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandAuditError.max-attempts=${FINERACT_PROCESS_COMMAND_AUDIT_ERROR_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandAuditError.wait-duration=${FINERACT_PROCESS_COMMAND_AUDIT_ERROR_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandAuditError.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_AUDIT_ERROR_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandAuditError.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_AUDIT_ERROR_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandAuditError.retryExceptions=${FINERACT_PROCESS_COMMAND_AUDIT_ERROR_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandAuditError.max-attempts=${FINERACT_COMMAND_AUDIT_ERROR_RETRY_MAX_ATTEMPTS:3}
resilience4j.retry.instances.commandAuditError.wait-duration=${FINERACT_COMMAND_AUDIT_ERROR_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandAuditError.enable-exponential-backoff=${FINERACT_COMMAND_AUDIT_ERROR_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandAuditError.exponential-backoff-multiplier=${FINERACT_COMMAND_AUDIT_ERROR_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandAuditError.retryExceptions=${FINERACT_COMMAND_AUDIT_ERROR_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandAuditFallback.max-attempts=${FINERACT_PROCESS_COMMAND_AUDIT_FALLBACK_RETRY_MAX_ATTEMPTS:1}
resilience4j.retry.instances.commandAuditFallback.wait-duration=${FINERACT_PROCESS_COMMAND_AUDIT_FALLBACK_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandAuditFallback.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_AUDIT_FALLBACK_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandAuditFallback.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_AUDIT_FALLBACK_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandAuditFallback.retryExceptions=${FINERACT_PROCESS_COMMAND_AUDIT_FALLBACK_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
resilience4j.retry.instances.commandAuditFallback.max-attempts=${FINERACT_COMMAND_AUDIT_FALLBACK_RETRY_MAX_ATTEMPTS:1}
resilience4j.retry.instances.commandAuditFallback.wait-duration=${FINERACT_COMMAND_AUDIT_FALLBACK_RETRY_WAIT_DURATION:1s}
resilience4j.retry.instances.commandAuditFallback.enable-exponential-backoff=${FINERACT_COMMAND_AUDIT_FALLBACK_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
resilience4j.retry.instances.commandAuditFallback.exponential-backoff-multiplier=${FINERACT_COMMAND_AUDIT_FALLBACK_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
resilience4j.retry.instances.commandAuditFallback.retryExceptions=${FINERACT_COMMAND_AUDIT_FALLBACK_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
@@ -34,7 +34,6 @@
<!-- Core Module Entities -->
<class>org.apache.fineract.useradministration.domain.Role</class>
<class>org.apache.fineract.portfolio.fund.domain.Fund</class>
<class>org.apache.fineract.infrastructure.documentmanagement.domain.Image</class>
<class>org.apache.fineract.organisation.workingdays.domain.WorkingDays</class>
<class>org.apache.fineract.useradministration.domain.Permission</class>
<class>org.apache.fineract.useradministration.domain.AppUserClientMapping</class>
@@ -228,9 +227,6 @@
<class>org.apache.fineract.portfolio.loanproduct.domain.SupportedInterestRefundTypesListConverter</class>
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanStatusConverter</class>
<!-- Document Module Entities -->
<class>org.apache.fineract.infrastructure.documentmanagement.domain.Document</class>
<!-- Tax Module Entities -->
<class>org.apache.fineract.portfolio.tax.domain.TaxComponent</class>
<class>org.apache.fineract.portfolio.tax.domain.TaxComponentHistory</class>