From d20e6bef1ecfe45e7c3ce8d17699220ff5eaa057 Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Tue, 24 Mar 2026 16:55:16 +0100 Subject: [PATCH 1/4] fix: code formatting not every save, but on add --- .../frontend/app/routes/editor/editor.tsx | 4 +--- .../app/routes/studio/canvas/flow.tsx | 2 +- .../app/services/configuration-service.ts | 6 ++--- .../flow/adapter/AdapterService.java | 1 - .../ConfigurationController.java | 14 ++++------- .../configuration/ConfigurationService.java | 24 +++++++++---------- .../flow/filetree/FileTreeController.java | 5 +++- .../flow/filetree/FileTreeService.java | 7 +++++- .../ConfigurationControllerTest.java | 2 +- .../ConfigurationServiceTest.java | 4 ++-- .../flow/filetree/FileTreeServiceTest.java | 7 ++++-- 11 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/main/frontend/app/routes/editor/editor.tsx b/src/main/frontend/app/routes/editor/editor.tsx index e106a567..4ac4fb7f 100644 --- a/src/main/frontend/app/routes/editor/editor.tsx +++ b/src/main/frontend/app/routes/editor/editor.tsx @@ -7,7 +7,6 @@ import { useShallow } from 'zustand/react/shallow' import SidebarLayout from '~/components/sidebars-layout/sidebar-layout' import SidebarContentClose from '~/components/sidebars-layout/sidebar-content-close' import { SidebarSide } from '~/components/sidebars-layout/sidebar-layout-store' -import SidebarClose from '~/components/sidebars-layout/sidebar-close' import { useTheme } from '~/hooks/use-theme' import { useCallback, useEffect, useRef, useState } from 'react' import { useProjectStore } from '~/stores/project-store' @@ -181,8 +180,7 @@ export default function CodeEditor() { setSaveStatus('saving') try { - const xmlResponse = await saveConfiguration(project.name, configPath, updatedContent) - setXmlContent(xmlResponse.xmlContent) + await saveConfiguration(project.name, configPath, updatedContent) contentCacheRef.current.set(activeTabFilePath, updatedContent) setSaveStatus('saved') if (savedTimerRef.current) clearTimeout(savedTimerRef.current) diff --git a/src/main/frontend/app/routes/studio/canvas/flow.tsx b/src/main/frontend/app/routes/studio/canvas/flow.tsx index 61fbc836..2663ca01 100644 --- a/src/main/frontend/app/routes/studio/canvas/flow.tsx +++ b/src/main/frontend/app/routes/studio/canvas/flow.tsx @@ -163,7 +163,7 @@ function FlowCanvas() { await saveConfiguration(currentProject.name, configurationPath, updatedConfigXml) clearConfigurationCache(currentProject.name, configurationPath) useEditorTabStore.getState().refreshAllTabs() - if (currentProject.isGitRepository) refreshOpenDiffs(currentProject.name) + if (currentProject.isGitRepository) await refreshOpenDiffs(currentProject.name) setSaveStatus('saved') if (savedTimerRef.current) clearTimeout(savedTimerRef.current) diff --git a/src/main/frontend/app/services/configuration-service.ts b/src/main/frontend/app/services/configuration-service.ts index ab7499c6..e5ecbd99 100644 --- a/src/main/frontend/app/services/configuration-service.ts +++ b/src/main/frontend/app/services/configuration-service.ts @@ -1,5 +1,5 @@ import { apiFetch } from '~/utils/api' -import type { Project, XmlResponse } from '~/types/project.types' +import type { Project } from '~/types/project.types' const configCache = new Map() @@ -32,8 +32,8 @@ export async function fetchConfiguration(projectName: string, filepath: string, return data.content } -export async function saveConfiguration(projectName: string, filepath: string, content: string): Promise { - return apiFetch(`/projects/${encodeURIComponent(projectName)}/configuration`, { +export async function saveConfiguration(projectName: string, filepath: string, content: string): Promise { + return apiFetch(`/projects/${encodeURIComponent(projectName)}/configuration`, { method: 'PUT', body: JSON.stringify({ filepath, content }), }) diff --git a/src/main/java/org/frankframework/flow/adapter/AdapterService.java b/src/main/java/org/frankframework/flow/adapter/AdapterService.java index a91f2536..d9742461 100644 --- a/src/main/java/org/frankframework/flow/adapter/AdapterService.java +++ b/src/main/java/org/frankframework/flow/adapter/AdapterService.java @@ -84,7 +84,6 @@ public boolean updateAdapter(Path configurationFile, String adapterName, String String updatedXml = XmlConfigurationUtils.convertNodeToString(configDoc); Files.writeString(absConfigFile, updatedXml, StandardCharsets.UTF_8, StandardOpenOption.TRUNCATE_EXISTING); return true; - } catch (AdapterNotFoundException e) { throw e; } catch (Exception e) { diff --git a/src/main/java/org/frankframework/flow/configuration/ConfigurationController.java b/src/main/java/org/frankframework/flow/configuration/ConfigurationController.java index 90280d73..a27362a2 100644 --- a/src/main/java/org/frankframework/flow/configuration/ConfigurationController.java +++ b/src/main/java/org/frankframework/flow/configuration/ConfigurationController.java @@ -8,7 +8,6 @@ import org.frankframework.flow.project.ProjectDTO; import org.frankframework.flow.project.ProjectNotFoundException; import org.frankframework.flow.project.ProjectService; -import org.frankframework.flow.xml.XmlDTO; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -39,20 +38,17 @@ public ResponseEntity getConfigurationByPath(@RequestBody Conf } @PutMapping("/{projectName}/configuration") - public ResponseEntity updateConfiguration( + public ResponseEntity updateConfiguration( @RequestBody ConfigurationDTO configurationDTO) - throws ConfigurationNotFoundException, IOException, ParserConfigurationException, - SAXException, TransformerException { - String updatedContent = configurationService.updateConfiguration( - configurationDTO.filepath(), configurationDTO.content()); - XmlDTO xmlDTO = new XmlDTO(updatedContent); - return ResponseEntity.ok(xmlDTO); + throws ConfigurationNotFoundException, IOException { + boolean updated = configurationService.updateConfiguration(configurationDTO.filepath(), configurationDTO.content()); + return updated ? ResponseEntity.ok().build() : ResponseEntity.notFound().build(); } @PostMapping("/{projectName}/configurations/{configName}") public ResponseEntity addConfiguration( @PathVariable String projectName, @PathVariable String configName) - throws ProjectNotFoundException, IOException { + throws ProjectNotFoundException, IOException, ParserConfigurationException, TransformerException, SAXException { Project project = configurationService.addConfiguration(projectName, configName); return ResponseEntity.ok(projectService.toDto(project)); } diff --git a/src/main/java/org/frankframework/flow/configuration/ConfigurationService.java b/src/main/java/org/frankframework/flow/configuration/ConfigurationService.java index 8bb6a01a..a882b4f7 100644 --- a/src/main/java/org/frankframework/flow/configuration/ConfigurationService.java +++ b/src/main/java/org/frankframework/flow/configuration/ConfigurationService.java @@ -44,9 +44,8 @@ public String getConfigurationContent(String filepath) throws IOException, Confi return fileSystemStorage.readFile(filePath.toString()); } - public String updateConfiguration(String filepath, String content) - throws IOException, ConfigurationNotFoundException, ParserConfigurationException, SAXException, - TransformerException { + public boolean updateConfiguration(String filepath, String content) + throws IOException, ConfigurationNotFoundException { Path absolutePath = fileSystemStorage.toAbsolutePath(filepath); if (!Files.exists(absolutePath)) { @@ -56,16 +55,13 @@ public String updateConfiguration(String filepath, String content) if (Files.isDirectory(absolutePath)) { throw new ConfigurationNotFoundException("Invalid file path: " + filepath); } - Document updatedDocument = XmlConfigurationUtils.insertFlowNamespace(content); - String updatedContent = XmlConfigurationUtils.convertNodeToString(updatedDocument); - // Just write to the disk. ProjectService reads directly from disk now! - fileSystemStorage.writeFile(absolutePath.toString(), updatedContent); - return updatedContent; + fileSystemStorage.writeFile(absolutePath.toString(), content); + return true; } public Project addConfiguration(String projectName, String configurationName) - throws ProjectNotFoundException, IOException { + throws ProjectNotFoundException, IOException, TransformerException, ParserConfigurationException, SAXException { Project project = projectService.getProject(projectName); Path absProjectPath = fileSystemStorage.toAbsolutePath(project.getRootPath()); @@ -81,14 +77,16 @@ public Project addConfiguration(String projectName, String configurationName) } String defaultXml = loadDefaultConfigurationXml(); - fileSystemStorage.writeFile(filePath.toString(), defaultXml); + Document updatedDocument = XmlConfigurationUtils.insertFlowNamespace(defaultXml); + String updatedContent = XmlConfigurationUtils.convertNodeToString(updatedDocument); + fileSystemStorage.writeFile(filePath.toString(), updatedContent); // Returning the project handles everything, as 'toDto' will pick up the new file return project; } public Project addConfigurationToFolder(String projectName, String configurationName, String folderPath) - throws IOException, ApiException { + throws IOException, ApiException, ParserConfigurationException, SAXException, TransformerException { Project project = projectService.getProject(projectName); Path absProjectPath = fileSystemStorage.toAbsolutePath(project.getRootPath()); @@ -112,7 +110,9 @@ public Project addConfigurationToFolder(String projectName, String configuration } String defaultXml = loadDefaultConfigurationXml(); - fileSystemStorage.writeFile(filePath.toString(), defaultXml); + Document updatedDocument = XmlConfigurationUtils.insertFlowNamespace(defaultXml); + String updatedContent = XmlConfigurationUtils.convertNodeToString(updatedDocument); + fileSystemStorage.writeFile(filePath.toString(), updatedContent); return project; } diff --git a/src/main/java/org/frankframework/flow/filetree/FileTreeController.java b/src/main/java/org/frankframework/flow/filetree/FileTreeController.java index 24927db5..4b2733e9 100644 --- a/src/main/java/org/frankframework/flow/filetree/FileTreeController.java +++ b/src/main/java/org/frankframework/flow/filetree/FileTreeController.java @@ -1,6 +1,8 @@ package org.frankframework.flow.filetree; import java.io.IOException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; import org.frankframework.flow.exception.ApiException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -13,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.xml.sax.SAXException; @RestController @RequestMapping("/projects") @@ -48,7 +51,7 @@ public FileTreeNode getDirectoryContent(@PathVariable String projectName, @Reque @PostMapping("/{projectName}/files") public ResponseEntity createFile(@PathVariable String projectName, @RequestBody FileCreateDTO dto) - throws IOException, ApiException { + throws IOException, ApiException, ParserConfigurationException, TransformerException, SAXException { FileTreeNode node = fileTreeService.createFile(projectName, dto.path(), dto.name()); return ResponseEntity.status(HttpStatus.CREATED.value()).body(node); } diff --git a/src/main/java/org/frankframework/flow/filetree/FileTreeService.java b/src/main/java/org/frankframework/flow/filetree/FileTreeService.java index a2e5daea..ba5fa061 100644 --- a/src/main/java/org/frankframework/flow/filetree/FileTreeService.java +++ b/src/main/java/org/frankframework/flow/filetree/FileTreeService.java @@ -12,6 +12,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + import org.frankframework.flow.configuration.ConfigurationService; import org.frankframework.flow.exception.ApiException; import org.frankframework.flow.filesystem.FileSystemStorage; @@ -23,6 +26,7 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; @Service @@ -127,10 +131,11 @@ public FileTreeNode getConfigurationsDirectoryTree(String projectName) throws IO } public FileTreeNode createFile(String projectName, String parentPath, String fileName) - throws IOException, ApiException { + throws IOException, ApiException, ParserConfigurationException, TransformerException, SAXException, ParserConfigurationException, TransformerException, SAXException { if (parentPath == null || parentPath.isBlank()) { throw new IllegalArgumentException("Parent path must not be empty"); } + validateFileName(fileName); String fullPath = parentPath.endsWith("/") ? parentPath + fileName : parentPath + "/" + fileName; validateWithinProject(projectName, fullPath); diff --git a/src/test/java/org/frankframework/flow/configuration/ConfigurationControllerTest.java b/src/test/java/org/frankframework/flow/configuration/ConfigurationControllerTest.java index de155c53..cd0c48fb 100644 --- a/src/test/java/org/frankframework/flow/configuration/ConfigurationControllerTest.java +++ b/src/test/java/org/frankframework/flow/configuration/ConfigurationControllerTest.java @@ -104,7 +104,7 @@ void updateConfigurationSuccessReturns200() throws Exception { String xmlContent = "updated"; when(configurationService.updateConfiguration(filepath, xmlContent)) - .thenReturn(xmlContent); + .thenReturn(true); mockMvc.perform( put("/api/projects/" + TEST_PROJECT_NAME + "/configuration") diff --git a/src/test/java/org/frankframework/flow/configuration/ConfigurationServiceTest.java b/src/test/java/org/frankframework/flow/configuration/ConfigurationServiceTest.java index 792ad8a5..4d856ae1 100644 --- a/src/test/java/org/frankframework/flow/configuration/ConfigurationServiceTest.java +++ b/src/test/java/org/frankframework/flow/configuration/ConfigurationServiceTest.java @@ -109,8 +109,8 @@ void updateConfiguration_Success() throws Exception { configurationService.updateConfiguration(file.toString(), ""); - assertEquals("\n", Files.readString(file, StandardCharsets.UTF_8)); - verify(fileSystemStorage).writeFile(file.toString(), "\n"); + assertEquals("", Files.readString(file, StandardCharsets.UTF_8)); + verify(fileSystemStorage).writeFile(file.toString(), ""); } @Test diff --git a/src/test/java/org/frankframework/flow/filetree/FileTreeServiceTest.java b/src/test/java/org/frankframework/flow/filetree/FileTreeServiceTest.java index cfdb12fb..ae25586a 100644 --- a/src/test/java/org/frankframework/flow/filetree/FileTreeServiceTest.java +++ b/src/test/java/org/frankframework/flow/filetree/FileTreeServiceTest.java @@ -13,6 +13,8 @@ import java.nio.file.Paths; import java.util.Comparator; import java.util.List; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; import org.frankframework.flow.configuration.ConfigurationService; import org.frankframework.flow.exception.ApiException; import org.frankframework.flow.filesystem.FileOperations; @@ -27,6 +29,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.xml.sax.SAXException; @ExtendWith(MockitoExtension.class) public class FileTreeServiceTest { @@ -337,7 +340,7 @@ void createFile_NameWithDoubleDots_ThrowsIllegalArgument() { @Test @DisplayName("Should create a file and return a FileTreeNode with FILE type") - void createFile_Success() throws IOException, ProjectNotFoundException, ApiException { + void createFile_Success() throws IOException, ApiException, ParserConfigurationException, TransformerException, SAXException { stubToAbsolutePath(); stubCreateFile(); @@ -358,7 +361,7 @@ void createFile_Success() throws IOException, ProjectNotFoundException, ApiExcep @Test @DisplayName("Should create a file correctly when parent path already ends with a slash") void createFile_ParentPathWithTrailingSlash_DoesNotDoubleSlash() - throws IOException, ProjectNotFoundException, ApiException { + throws IOException, ApiException, ParserConfigurationException, TransformerException, SAXException { stubToAbsolutePath(); stubCreateFile(); From c2e92cfe50a2104f1323ee60ad87ba94fa5475df Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Tue, 24 Mar 2026 17:58:30 +0100 Subject: [PATCH 2/4] fix: applied linting --- .../java/org/frankframework/flow/filetree/FileTreeService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/frankframework/flow/filetree/FileTreeService.java b/src/main/java/org/frankframework/flow/filetree/FileTreeService.java index ba5fa061..f5470968 100644 --- a/src/main/java/org/frankframework/flow/filetree/FileTreeService.java +++ b/src/main/java/org/frankframework/flow/filetree/FileTreeService.java @@ -14,7 +14,6 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; - import org.frankframework.flow.configuration.ConfigurationService; import org.frankframework.flow.exception.ApiException; import org.frankframework.flow.filesystem.FileSystemStorage; From ba4f81ebdbd97569257c501253cc5d8582c69a37 Mon Sep 17 00:00:00 2001 From: Stijn Potters Date: Tue, 24 Mar 2026 18:39:23 +0100 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Stijn Potters --- .../flow/configuration/ConfigurationController.java | 4 ++-- .../flow/filetree/FileTreeController.java | 10 +++++++--- .../frankframework/flow/filetree/FileTreeService.java | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/frankframework/flow/configuration/ConfigurationController.java b/src/main/java/org/frankframework/flow/configuration/ConfigurationController.java index a27362a2..037b236e 100644 --- a/src/main/java/org/frankframework/flow/configuration/ConfigurationController.java +++ b/src/main/java/org/frankframework/flow/configuration/ConfigurationController.java @@ -41,8 +41,8 @@ public ResponseEntity getConfigurationByPath(@RequestBody Conf public ResponseEntity updateConfiguration( @RequestBody ConfigurationDTO configurationDTO) throws ConfigurationNotFoundException, IOException { - boolean updated = configurationService.updateConfiguration(configurationDTO.filepath(), configurationDTO.content()); - return updated ? ResponseEntity.ok().build() : ResponseEntity.notFound().build(); + configurationService.updateConfiguration(configurationDTO.filepath(), configurationDTO.content()); + return ResponseEntity.ok().build(); } @PostMapping("/{projectName}/configurations/{configName}") diff --git a/src/main/java/org/frankframework/flow/filetree/FileTreeController.java b/src/main/java/org/frankframework/flow/filetree/FileTreeController.java index 4b2733e9..43f9ed5c 100644 --- a/src/main/java/org/frankframework/flow/filetree/FileTreeController.java +++ b/src/main/java/org/frankframework/flow/filetree/FileTreeController.java @@ -51,9 +51,13 @@ public FileTreeNode getDirectoryContent(@PathVariable String projectName, @Reque @PostMapping("/{projectName}/files") public ResponseEntity createFile(@PathVariable String projectName, @RequestBody FileCreateDTO dto) - throws IOException, ApiException, ParserConfigurationException, TransformerException, SAXException { - FileTreeNode node = fileTreeService.createFile(projectName, dto.path(), dto.name()); - return ResponseEntity.status(HttpStatus.CREATED.value()).body(node); + throws IOException, ApiException, ParserConfigurationException, SAXException { + try { + FileTreeNode node = fileTreeService.createFile(projectName, dto.path(), dto.name()); + return ResponseEntity.status(HttpStatus.CREATED.value()).body(node); + } catch (TransformerException e) { + throw new ApiException("Failed to create file for project '" + projectName + "' at path '" + dto.path() + "'", e); + } } @PostMapping("/{projectName}/folders") diff --git a/src/main/java/org/frankframework/flow/filetree/FileTreeService.java b/src/main/java/org/frankframework/flow/filetree/FileTreeService.java index f5470968..95204f2a 100644 --- a/src/main/java/org/frankframework/flow/filetree/FileTreeService.java +++ b/src/main/java/org/frankframework/flow/filetree/FileTreeService.java @@ -130,7 +130,7 @@ public FileTreeNode getConfigurationsDirectoryTree(String projectName) throws IO } public FileTreeNode createFile(String projectName, String parentPath, String fileName) - throws IOException, ApiException, ParserConfigurationException, TransformerException, SAXException, ParserConfigurationException, TransformerException, SAXException { + throws IOException, ApiException, ParserConfigurationException, TransformerException, SAXException { if (parentPath == null || parentPath.isBlank()) { throw new IllegalArgumentException("Parent path must not be empty"); } From 272484e60461b6f481f14517765d30cf44c81096 Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Tue, 24 Mar 2026 18:43:28 +0100 Subject: [PATCH 4/4] fix: exception error in controller --- .../org/frankframework/flow/filetree/FileTreeController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/frankframework/flow/filetree/FileTreeController.java b/src/main/java/org/frankframework/flow/filetree/FileTreeController.java index 43f9ed5c..2eb41dd4 100644 --- a/src/main/java/org/frankframework/flow/filetree/FileTreeController.java +++ b/src/main/java/org/frankframework/flow/filetree/FileTreeController.java @@ -56,7 +56,7 @@ public ResponseEntity createFile(@PathVariable String projectName, FileTreeNode node = fileTreeService.createFile(projectName, dto.path(), dto.name()); return ResponseEntity.status(HttpStatus.CREATED.value()).body(node); } catch (TransformerException e) { - throw new ApiException("Failed to create file for project '" + projectName + "' at path '" + dto.path() + "'", e); + throw new ApiException("Failed to create file for project '" + projectName + "' at path '" + dto.path() + "'", HttpStatus.INTERNAL_SERVER_ERROR, e); } }