From 49a718927cf2189f1cc780869e7cb9c75b310018 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Fri, 17 Apr 2026 12:11:34 +0200 Subject: [PATCH 1/4] [NAE-2409] Implement Plugin save/get file over gRPC for worker - Removed redundant task parameter from various file retrieval methods in `IDataService` and its implementations. - Updated `ActionDelegate` to match refactored method signatures in `IDataService`. - Added new file handling methods in `ActionApi` and their implementations in `ActionApiImpl`: - `saveFile` - `saveFiles` - `deleteFile` - `deleteFileByName` - `getFile` - `getFileByCaseAndName` - Introduced `ActionFileHolder` class for better encapsulation of file metadata and content. - Modified method signatures in `WorkflowController` to align with the updated service logic. These changes improve consistency, adhere to clean code principles, and extend file management capabilities. --- .../logic/action/ActionDelegate.groovy | 2 +- .../engine/actions/ActionApiImpl.java | 49 +++++++++++++++++++ .../engine/workflow/service/DataService.java | 19 ++++--- .../service/interfaces/IDataService.java | 9 ++-- .../workflow/web/WorkflowController.java | 2 +- .../adapter/spring/actions/ActionApi.java | 14 ++++++ .../spring/actions/ActionApiMethods.java | 8 ++- .../spring/actions/ActionFileHolder.java | 19 +++++++ 8 files changed, 109 insertions(+), 13 deletions(-) create mode 100644 nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionFileHolder.java diff --git a/application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy b/application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy index 1e686358e8..5ff63dcfae 100644 --- a/application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy +++ b/application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy @@ -1586,7 +1586,7 @@ class ActionDelegate { } FileFieldInputStream getFileFieldStream(Case useCase, Task task, FileField field, boolean forPreview = false) { - return this.dataService.getFile(useCase, task, field, forPreview) + return this.dataService.getFile(useCase, field, forPreview) } /** diff --git a/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java b/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java index e96009961a..ebcaf5b53a 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.netgrif.application.engine.adapter.spring.actions.ActionApi; +import com.netgrif.application.engine.adapter.spring.actions.ActionFileHolder; import com.netgrif.application.engine.auth.service.UserService; import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; import com.netgrif.application.engine.elastic.service.interfaces.IElasticTaskService; @@ -27,16 +28,21 @@ import com.netgrif.application.engine.workflow.params.CreateCaseParams; import com.netgrif.application.engine.workflow.params.DeleteCaseParams; import com.netgrif.application.engine.workflow.params.TaskParams; +import com.netgrif.application.engine.workflow.service.FileFieldInputStream; import com.netgrif.application.engine.workflow.service.interfaces.IDataService; import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import com.querydsl.core.types.Predicate; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; +import java.io.*; import java.util.*; @Slf4j @@ -208,6 +214,49 @@ public AbstractUser getSystemUser() { return userService.getSystem(); } + @Override + public SetDataEventOutcome saveFile(String taskId, String fieldId, ActionFileHolder file, Map params) { + MultipartFile multipartFile = new MockMultipartFile(file.getFileName(), file.getFileContent()); + return dataService.saveFile(taskId, fieldId, multipartFile, params); + } + + @Override + public SetDataEventOutcome saveFiles(String taskId, String fieldId, ActionFileHolder[] files, Map params) { + MultipartFile[] multipartFiles = new MultipartFile[files.length]; + for (int i = 0; i < files.length; i++) { + multipartFiles[i] = new MockMultipartFile(files[i].getFileName(), files[i].getFileContent()); + } + return dataService.saveFiles(taskId, fieldId, multipartFiles, params); + } + + @Override + public SetDataEventOutcome deleteFile(String taskId, String fieldId, Map params) { + return dataService.deleteFile(taskId, fieldId, params); + } + + @Override + public SetDataEventOutcome deleteFileByName(String taskId, String fieldId, String name, Map params) { + return dataService.deleteFileByName(taskId, fieldId, name, params); + } + + @Override + public ActionFileHolder getFile(String caseId, String fieldId, boolean forPreview, Map params) throws IOException { + FileFieldInputStream fileFieldInputStream = dataService.getFile(caseId, fieldId, forPreview, params); + return ActionFileHolder.builder() + .fileName(fileFieldInputStream.getFileName()) + .fileContent(IOUtils.toByteArray(fileFieldInputStream.getInputStream())) + .build(); + } + + @Override + public ActionFileHolder getFileByCaseAndName(String caseId, String fieldId, String name, Map params) throws IOException { + FileFieldInputStream fileFieldInputStream = dataService.getFileByCaseAndName(caseId, fieldId, name, params); + return ActionFileHolder.builder() + .fileName(fileFieldInputStream.getFileName()) + .fileContent(IOUtils.toByteArray(fileFieldInputStream.getInputStream())) + .build(); + } + private AbstractUser resolveAbstractUser(AuthPrincipalDto authPrincipalDto) { if (authPrincipalDto == null) { throw new IllegalArgumentException("AuthPrincipalDto cannot be null."); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/DataService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/DataService.java index f7f629f5c2..cc2fd19944 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/DataService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/DataService.java @@ -523,7 +523,7 @@ private void changeTaskRefBehavior(LocalisedField field, FieldBehavior behavior) public FileFieldInputStream getFileByTask(String taskId, String fieldId, boolean forPreview) throws FileNotFoundException { Task task = taskService.findOne(taskId); - FileFieldInputStream fileFieldInputStream = getFileByCase(task.getCaseId(), task, fieldId, forPreview); + FileFieldInputStream fileFieldInputStream = getFileByCase(task.getCaseId(), fieldId, forPreview); if (fileFieldInputStream == null || fileFieldInputStream.getInputStream() == null) throw new FileNotFoundException("File in field %s within task %s was not found!".formatted(fieldId, taskId)); @@ -543,10 +543,10 @@ public FileFieldInputStream getFileByTaskAndName(String taskId, String fieldId, } @Override - public FileFieldInputStream getFileByCase(String caseId, Task task, String fieldId, boolean forPreview) throws FileNotFoundException { + public FileFieldInputStream getFileByCase(String caseId, String fieldId, boolean forPreview) throws FileNotFoundException { Case useCase = workflowService.findOne(caseId); FileField field = (FileField) useCase.getPetriNet().getDataSet().get(fieldId); - return getFile(useCase, task, field, forPreview); + return getFile(useCase, field, forPreview); } @Override @@ -584,12 +584,19 @@ public FileFieldInputStream getFileByName(Case useCase, FileListField field, Str } @Override - public FileFieldInputStream getFile(Case useCase, Task task, FileField field, boolean forPreview) throws FileNotFoundException { - return getFile(useCase, task, field, forPreview, new HashMap<>()); + public FileFieldInputStream getFile(Case useCase, FileField field, boolean forPreview) throws FileNotFoundException { + return getFile(useCase, field, forPreview, new HashMap<>()); } @Override - public FileFieldInputStream getFile(Case useCase, Task task, FileField field, boolean forPreview, Map params) throws FileNotFoundException { + public FileFieldInputStream getFile(String caseId, String fieldId, boolean forPreview, Map params) throws FileNotFoundException { + Case useCase = workflowService.findOne(caseId); + FileField field = (FileField) useCase.getPetriNet().getDataSet().get(fieldId); + return getFile(useCase, field, forPreview, params); + } + + @Override + public FileFieldInputStream getFile(Case useCase, FileField field, boolean forPreview, Map params) throws FileNotFoundException { runGetActionsFromFileField(field.getEvents(), useCase, params); if (useCase.getFieldValue(field.getStringId()) == null) { throw new FileNotFoundException("Field %s not found on case %s".formatted(field.getStringId(), useCase.getStringId())); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IDataService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IDataService.java index 889e1c0ce4..df876e73ef 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IDataService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IDataService.java @@ -4,7 +4,6 @@ import com.netgrif.application.engine.objects.petrinet.domain.dataset.Field; import com.netgrif.application.engine.objects.petrinet.domain.dataset.FileField; import com.netgrif.application.engine.objects.petrinet.domain.dataset.FileListField; -import com.netgrif.application.engine.objects.petrinet.domain.dataset.UserFieldValue; import com.netgrif.application.engine.files.throwable.StorageException; import com.netgrif.application.engine.objects.petrinet.domain.dataset.*; import com.netgrif.application.engine.objects.workflow.domain.Case; @@ -51,9 +50,11 @@ public interface IDataService { SetDataEventOutcome setData(Task task, ObjectNode values, Map params, boolean runStrict); - FileFieldInputStream getFile(Case useCase, Task task, FileField field, boolean forPreview) throws FileNotFoundException; + FileFieldInputStream getFile(Case useCase, FileField field, boolean forPreview) throws FileNotFoundException; - FileFieldInputStream getFile(Case useCase, Task task, FileField field, boolean forPreview, Map params) throws FileNotFoundException; + FileFieldInputStream getFile(String caseId, String fieldId, boolean forPreview, Map params) throws FileNotFoundException; + + FileFieldInputStream getFile(Case useCase, FileField field, boolean forPreview, Map params) throws FileNotFoundException; FileFieldInputStream getFileByName(Case useCase, FileListField field, String name) throws FileNotFoundException; @@ -71,7 +72,7 @@ public interface IDataService { FileFieldInputStream getFileByTaskAndName(String taskId, String fieldId, String name, Map params) throws FileNotFoundException; - FileFieldInputStream getFileByCase(String caseId, Task task, String fieldId, boolean forPreview) throws FileNotFoundException; + FileFieldInputStream getFileByCase(String caseId, String fieldId, boolean forPreview) throws FileNotFoundException; FileFieldInputStream getFileByCaseAndName(String caseId, String fieldId, String name) throws FileNotFoundException; diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java index 8b61331f2b..9d4615b06e 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java @@ -249,7 +249,7 @@ public EntityModel deleteCase(Authentication auth, @Pat @Operation(summary = "Download case file field value", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/case/{id}/file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) public ResponseEntity getFile(@PathVariable("id") String caseId, @RequestParam("fieldId") String fieldId) throws FileNotFoundException { - FileFieldInputStream fileFieldInputStream = dataService.getFileByCase(caseId, null, fieldId, false); + FileFieldInputStream fileFieldInputStream = dataService.getFileByCase(caseId, fieldId, false); if (fileFieldInputStream.getInputStream() == null) throw new FileNotFoundException("File in field " + fieldId + " within case " + caseId + " was not found!"); diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApi.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApi.java index b2e112a6b1..a190562318 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApi.java +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApi.java @@ -18,6 +18,8 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import java.io.FileNotFoundException; +import java.io.IOException; import java.util.Map; import java.util.List; @@ -195,4 +197,16 @@ public interface ActionApi { * @return the system user */ AbstractUser getSystemUser(); + + SetDataEventOutcome saveFile(String taskId, String fieldId, ActionFileHolder file, Map params); + + SetDataEventOutcome saveFiles(String taskId, String fieldId, ActionFileHolder[] files, Map params); + + SetDataEventOutcome deleteFile(String taskId, String fieldId, Map params); + + SetDataEventOutcome deleteFileByName(String taskId, String fieldId, String name, Map params); + + ActionFileHolder getFile(String caseId, String fieldId, boolean forPreview, Map params) throws IOException; + + ActionFileHolder getFileByCaseAndName(String caseId, String fieldId, String name, Map params) throws IOException; } diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApiMethods.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApiMethods.java index 79b9d673d8..5c33b33b0e 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApiMethods.java +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApiMethods.java @@ -19,7 +19,13 @@ public enum ActionApiMethods { CANCEL_TASK("cancelTask"), FINISH_TASK("finishTask"), SEARCH_USERS("searchUsers"), - GET_SYSTEM_USER("getSystemUser"); + GET_SYSTEM_USER("getSystemUser"), + SAVE_FILE("saveFile"), + SAVE_FILES("saveFiles"), + DELETE_FILE("deleteFile"), + DELETE_FILE_BY_NAME("deleteFileByName"), + GET_FILE("getFile"), + GET_FILE_BY_CASE_NAME("getFileByCaseName"); private String methodName; diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionFileHolder.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionFileHolder.java new file mode 100644 index 0000000000..41048eb858 --- /dev/null +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionFileHolder.java @@ -0,0 +1,19 @@ +package com.netgrif.application.engine.adapter.spring.actions; + +import lombok.Builder; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +@Data +@Builder +public class ActionFileHolder implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String fileName; + + private byte[] fileContent; +} From 07e1b60d43caec3a49d7c6532f74e7c0a595cca7 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Fri, 17 Apr 2026 12:27:28 +0200 Subject: [PATCH 2/4] [NAE-2409] Implement Plugin save/get file over gRPC for worker Integrated new debug and trace logging to monitor core API methods such as data retrieval, task and case management, and file operations. This enhances traceability and simplifies troubleshooting by capturing detailed runtime information for key API calls. --- .../engine/actions/ActionApiImpl.java | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java b/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java index ebcaf5b53a..62398352b4 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java @@ -92,42 +92,51 @@ public void setElasticTaskService(IElasticTaskService elasticTaskService) { @Override public GetDataEventOutcome getData(String taskId, Map params) { + log.debug("Getting data for task [{}] with params: [{}]", taskId, params == null ? "null" : params.toString()); return dataService.getData(taskId, params); } @Override public SetDataEventOutcome setData(String taskId, Map> dataSet, Map params) throws JsonProcessingException { + log.debug("Setting data for task [{}] with params: [{}]", taskId, params == null ? "null" : params.toString()); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(dataSet); ObjectNode values = (ObjectNode) mapper.readTree(json); + log.trace("Setting data for task [{}] with params: [{}], values: [{}]", taskId, params == null ? "null" : params.toString(), values.toString()); return dataService.setData(taskId, values, params); } @Override public Page searchCases(String processIdentifier, Predicate predicate, Pageable pageable) { + log.debug("Searching cases for process identifier [{}] with predicate [{}], pageable [{}]", processIdentifier, predicate, pageable); return workflowService.search(predicate, pageable); } @Override public Page searchCases(List elasticStringQueries, AuthPrincipalDto authPrincipalDto, Pageable pageable, Boolean isIntersection) { + log.debug("Searching cases for elastic queries [{}] with auth principal [{}], pageable [{}], intersect [{}]", elasticStringQueries, authPrincipalDto, pageable, isIntersection); boolean intersect = Boolean.TRUE.equals(isIntersection); List caseSearchRequests = elasticStringQueries.stream().map(query -> CaseSearchRequest.builder().query(query).build()).toList(); LoggedUser loggedUser = ActorTransformer.toLoggedUser(resolveAbstractUser(authPrincipalDto)); Locale locale = LocaleContextHolder.getLocale(); + log.trace("Searching cases for elastic queries [{}] with auth principal [{}], pageable [{}], intersect [{}], locale [{}]", elasticStringQueries, authPrincipalDto, pageable, isIntersection, locale); return elasticCaseService.search(caseSearchRequests, loggedUser, pageable, locale, intersect); } @Override public Long countCases(List elasticStringQueries, AuthPrincipalDto authPrincipalDto, Boolean isIntersection) { + log.debug("Counting cases for elastic queries [{}] with auth principal [{}], intersect [{}]", elasticStringQueries, authPrincipalDto, isIntersection); boolean intersect = Boolean.TRUE.equals(isIntersection); List caseSearchRequests = elasticStringQueries.stream().map(query -> CaseSearchRequest.builder().query(query).build()).toList(); LoggedUser loggedUser = ActorTransformer.toLoggedUser(resolveAbstractUser(authPrincipalDto)); Locale locale = LocaleContextHolder.getLocale(); + log.trace("Counting cases for elastic queries [{}] with auth principal [{}], intersect [{}], locale [{}]", elasticStringQueries, authPrincipalDto, isIntersection, locale); return elasticCaseService.count(caseSearchRequests, loggedUser, locale, intersect); } @Override public CreateCaseEventOutcome createCaseByIdentifier(String identifier, String title, String color, AuthPrincipalDto authPrincipalDto, Map params) { + log.debug("Creating case with identifier [{}] and title [{}] and color [{}] with auth principal [{}] and params [{}]", identifier, title, color, authPrincipalDto, params); Locale locale = LocaleContextHolder.getLocale(); return workflowService.createCase(CreateCaseParams.with() .processIdentifier(identifier) @@ -141,6 +150,7 @@ public CreateCaseEventOutcome createCaseByIdentifier(String identifier, String t @Override public DeleteCaseEventOutcome deleteCase(String caseId, Map params) { + log.debug("Deleting case with id [{}] and params [{}]", caseId, params); return workflowService.deleteCase(DeleteCaseParams.with() .useCaseId(caseId) .params(params) @@ -149,20 +159,24 @@ public DeleteCaseEventOutcome deleteCase(String caseId, Map para @Override public Page searchTasks(String processIdentifier, Predicate predicate, Pageable pageable) { + log.debug("Searching tasks for process identifier [{}] with predicate [{}], pageable [{}]", processIdentifier, predicate, pageable); return taskService.search(predicate, pageable); } @Override public Page searchTasks(List elasticStringQueries, AuthPrincipalDto authPrincipalDto, Pageable pageable, Boolean isIntersection) { + log.debug("Searching tasks for elastic queries [{}] with auth principal [{}], pageable [{}], intersect [{}]", elasticStringQueries, authPrincipalDto, pageable, isIntersection); boolean intersect = Boolean.TRUE.equals(isIntersection); List taskSearchRequests = elasticStringQueries.stream().map(query -> ElasticTaskSearchRequest.builder().query(query).build()).toList(); LoggedUser loggedUser = ActorTransformer.toLoggedUser(resolveAbstractUser(authPrincipalDto)); Locale locale = LocaleContextHolder.getLocale(); + log.trace("Searching tasks for elastic queries [{}] with auth principal [{}], pageable [{}], intersect [{}], locale [{}]", elasticStringQueries, authPrincipalDto, pageable, isIntersection, locale); return elasticTaskService.search(taskSearchRequests, loggedUser, pageable, locale, intersect); } @Override public AssignTaskEventOutcome assignTask(String taskId, AuthPrincipalDto authPrincipalDto, Map params) throws TransitionNotExecutableException { + log.debug("Assigning task [{}] with auth principal [{}] and params [{}]", taskId, authPrincipalDto, params); Task task = taskService.findOne(taskId); AbstractUser user = resolveAbstractUser(authPrincipalDto); return taskService.assignTask(TaskParams.with() @@ -174,6 +188,7 @@ public AssignTaskEventOutcome assignTask(String taskId, AuthPrincipalDto authPri @Override public CancelTaskEventOutcome cancelTask(String taskId, AuthPrincipalDto authPrincipalDto, Map params) { + log.debug("Canceling task [{}] with auth principal [{}] and params [{}]", taskId, authPrincipalDto, params); Task task = taskService.findOne(taskId); AbstractUser user = resolveAbstractUser(authPrincipalDto); return taskService.cancelTask(TaskParams.with() @@ -185,6 +200,7 @@ public CancelTaskEventOutcome cancelTask(String taskId, AuthPrincipalDto authPri @Override public FinishTaskEventOutcome finishTask(String taskId, AuthPrincipalDto authPrincipalDto, Map params) throws TransitionNotExecutableException { + log.debug("Finishing task [{}] with auth principal [{}] and params [{}]", taskId, authPrincipalDto, params); Task task = taskService.findOne(taskId); AbstractUser user = resolveAbstractUser(authPrincipalDto); return taskService.finishTask(TaskParams.with() @@ -196,16 +212,19 @@ public FinishTaskEventOutcome finishTask(String taskId, AuthPrincipalDto authPri @Override public Case findCase(String caseId) { + log.debug("Finding case with id [{}]", caseId); return workflowService.findOne(caseId); } @Override public Task findTask(String taskId) { + log.debug("Finding task with id [{}]", taskId); return taskService.findOne(taskId); } @Override public Page searchUsers(Predicate predicate, Pageable pageable, String realmId) { + log.debug("Searching users with predicate [{}] and pageable [{}] and realm ID [{}]", predicate, pageable, realmId); return userService.search(predicate, pageable, realmId); } @@ -216,31 +235,39 @@ public AbstractUser getSystemUser() { @Override public SetDataEventOutcome saveFile(String taskId, String fieldId, ActionFileHolder file, Map params) { + log.debug("Saving file [{}] for task [{}] and field [{}] with params [{}]", file.getFileName(), taskId, fieldId, params); MultipartFile multipartFile = new MockMultipartFile(file.getFileName(), file.getFileContent()); + log.trace("Saving file [{}] for task [{}] and field [{}] with params [{}]", multipartFile.getOriginalFilename(), taskId, fieldId, params); return dataService.saveFile(taskId, fieldId, multipartFile, params); } @Override public SetDataEventOutcome saveFiles(String taskId, String fieldId, ActionFileHolder[] files, Map params) { + log.debug("Saving files [{}] for task [{}] and field [{}] with params [{}]", files.length, taskId, fieldId, params); MultipartFile[] multipartFiles = new MultipartFile[files.length]; for (int i = 0; i < files.length; i++) { - multipartFiles[i] = new MockMultipartFile(files[i].getFileName(), files[i].getFileContent()); + multipartFiles[i] = new MockMultipartFile(files[i].getFileName(), files[i].getFileName(), null, files[i].getFileContent()); + log.trace("Saving file [{}] for task [{}] and field [{}] with params [{}]", multipartFiles[i].getOriginalFilename(), taskId, fieldId, params); } + log.trace("Saving files [{}] for task [{}] and field [{}] with params [{}]", multipartFiles.length, taskId, fieldId, params); return dataService.saveFiles(taskId, fieldId, multipartFiles, params); } @Override public SetDataEventOutcome deleteFile(String taskId, String fieldId, Map params) { + log.debug("Deleting file for task [{}] and field [{}] with params [{}]", taskId, fieldId, params); return dataService.deleteFile(taskId, fieldId, params); } @Override public SetDataEventOutcome deleteFileByName(String taskId, String fieldId, String name, Map params) { + log.debug("Deleting file [{}] for task [{}] and field [{}] with params [{}]", name, taskId, fieldId, params); return dataService.deleteFileByName(taskId, fieldId, name, params); } @Override public ActionFileHolder getFile(String caseId, String fieldId, boolean forPreview, Map params) throws IOException { + log.debug("Getting file for case [{}] and field [{}] with preview [{}] and params [{}]", caseId, fieldId, forPreview, params); FileFieldInputStream fileFieldInputStream = dataService.getFile(caseId, fieldId, forPreview, params); return ActionFileHolder.builder() .fileName(fileFieldInputStream.getFileName()) @@ -250,6 +277,7 @@ public ActionFileHolder getFile(String caseId, String fieldId, boolean forPrevie @Override public ActionFileHolder getFileByCaseAndName(String caseId, String fieldId, String name, Map params) throws IOException { + log.debug("Getting file [{}] for case [{}] and field [{}] with params [{}]", name, caseId, fieldId, params); FileFieldInputStream fileFieldInputStream = dataService.getFileByCaseAndName(caseId, fieldId, name, params); return ActionFileHolder.builder() .fileName(fileFieldInputStream.getFileName()) From c58d022813f635b370609442733cc9e9d0e7eed6 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Fri, 17 Apr 2026 12:39:58 +0200 Subject: [PATCH 3/4] [NAE-2409] Implement Plugin save/get file over gRPC for worker Integrated new debug and trace logging to monitor core API methods such as data retrieval, task and case management, and file operations. This enhances traceability and simplifies troubleshooting by capturing detailed runtime information for key API calls. --- .../com/netgrif/application/engine/actions/ActionApiImpl.java | 4 ++-- .../application/engine/adapter/spring/actions/ActionApi.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java b/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java index 62398352b4..0261786e35 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java @@ -236,7 +236,7 @@ public AbstractUser getSystemUser() { @Override public SetDataEventOutcome saveFile(String taskId, String fieldId, ActionFileHolder file, Map params) { log.debug("Saving file [{}] for task [{}] and field [{}] with params [{}]", file.getFileName(), taskId, fieldId, params); - MultipartFile multipartFile = new MockMultipartFile(file.getFileName(), file.getFileContent()); + MultipartFile multipartFile = new MockMultipartFile(file.getFileName(), file.getFileName(), null, file.getFileContent()); log.trace("Saving file [{}] for task [{}] and field [{}] with params [{}]", multipartFile.getOriginalFilename(), taskId, fieldId, params); return dataService.saveFile(taskId, fieldId, multipartFile, params); } @@ -266,7 +266,7 @@ public SetDataEventOutcome deleteFileByName(String taskId, String fieldId, Strin } @Override - public ActionFileHolder getFile(String caseId, String fieldId, boolean forPreview, Map params) throws IOException { + public ActionFileHolder getFile(String caseId, String fieldId, Boolean forPreview, Map params) throws IOException { log.debug("Getting file for case [{}] and field [{}] with preview [{}] and params [{}]", caseId, fieldId, forPreview, params); FileFieldInputStream fileFieldInputStream = dataService.getFile(caseId, fieldId, forPreview, params); return ActionFileHolder.builder() diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApi.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApi.java index a190562318..09db189894 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApi.java +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApi.java @@ -206,7 +206,7 @@ public interface ActionApi { SetDataEventOutcome deleteFileByName(String taskId, String fieldId, String name, Map params); - ActionFileHolder getFile(String caseId, String fieldId, boolean forPreview, Map params) throws IOException; + ActionFileHolder getFile(String caseId, String fieldId, Boolean forPreview, Map params) throws IOException; ActionFileHolder getFileByCaseAndName(String caseId, String fieldId, String name, Map params) throws IOException; } From 8544ed9b1680b4c913c773eb0c8fd7e36849b2d5 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Fri, 17 Apr 2026 12:42:43 +0200 Subject: [PATCH 4/4] [NAE-2409] Implement Plugin save/get file over gRPC for worker Replaced direct InputStream usage with try-with-resources to ensure proper resource management and avoid potential memory leaks. Updated method signature and implementation to reflect accurate naming for file retrieval by case and name. --- .../engine/actions/ActionApiImpl.java | 20 +++++++++++-------- .../spring/actions/ActionApiMethods.java | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java b/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java index 0261786e35..e53d436208 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java @@ -269,20 +269,24 @@ public SetDataEventOutcome deleteFileByName(String taskId, String fieldId, Strin public ActionFileHolder getFile(String caseId, String fieldId, Boolean forPreview, Map params) throws IOException { log.debug("Getting file for case [{}] and field [{}] with preview [{}] and params [{}]", caseId, fieldId, forPreview, params); FileFieldInputStream fileFieldInputStream = dataService.getFile(caseId, fieldId, forPreview, params); - return ActionFileHolder.builder() - .fileName(fileFieldInputStream.getFileName()) - .fileContent(IOUtils.toByteArray(fileFieldInputStream.getInputStream())) - .build(); + try (InputStream inputStream = fileFieldInputStream.getInputStream()) { + return ActionFileHolder.builder() + .fileName(fileFieldInputStream.getFileName()) + .fileContent(IOUtils.toByteArray(inputStream)) + .build(); + } } @Override public ActionFileHolder getFileByCaseAndName(String caseId, String fieldId, String name, Map params) throws IOException { log.debug("Getting file [{}] for case [{}] and field [{}] with params [{}]", name, caseId, fieldId, params); FileFieldInputStream fileFieldInputStream = dataService.getFileByCaseAndName(caseId, fieldId, name, params); - return ActionFileHolder.builder() - .fileName(fileFieldInputStream.getFileName()) - .fileContent(IOUtils.toByteArray(fileFieldInputStream.getInputStream())) - .build(); + try (InputStream inputStream = fileFieldInputStream.getInputStream()) { + return ActionFileHolder.builder() + .fileName(fileFieldInputStream.getFileName()) + .fileContent(IOUtils.toByteArray(inputStream)) + .build(); + } } private AbstractUser resolveAbstractUser(AuthPrincipalDto authPrincipalDto) { diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApiMethods.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApiMethods.java index 5c33b33b0e..50973f139a 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApiMethods.java +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApiMethods.java @@ -25,7 +25,7 @@ public enum ActionApiMethods { DELETE_FILE("deleteFile"), DELETE_FILE_BY_NAME("deleteFileByName"), GET_FILE("getFile"), - GET_FILE_BY_CASE_NAME("getFileByCaseName"); + GET_FILE_BY_CASE_AND_NAME("getFileByCaseAndName"); private String methodName;