diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CargosoftExportAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CargosoftExportAdapter.java index 2db1e2e..76e0526 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CargosoftExportAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CargosoftExportAdapter.java @@ -76,7 +76,7 @@ public class CargosoftExportAdapter implements SignalAdapter { @Override public ItemCollection execute(ItemCollection document, ItemCollection event) throws AdapterException { - logger.info("......starting export..."); + logger.info("├── 📤 Cargosoft export..."); try { // read the cargosoft export options @@ -84,14 +84,14 @@ public class CargosoftExportAdapter implements SignalAdapter { if (evalItemCollection == null) { throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), CONFIG_ERROR, - "missign cargosoft configuration in model event - please check model configuration"); + "missing cargosoft configuration in model event - please check model configuration"); } String reportID = evalItemCollection.getItemValueString("report"); ItemCollection report = reportService.findReport(reportID); if (report == null) { throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), CONFIG_ERROR, - "missign cargosoft report '" + reportID + "' - please check model configuration"); + "missing cargosoft report '" + reportID + "' - please check model configuration"); } List sourceData = new ArrayList(); @@ -106,7 +106,6 @@ public class CargosoftExportAdapter implements SignalAdapter { // transfer file via FTP... if (ftpServer.isPresent()) { - logger.info("ftp transfer..."); ftpConnector.put(exportFile); } @@ -115,7 +114,7 @@ public class CargosoftExportAdapter implements SignalAdapter { } catch (PluginException | IOException | jakarta.xml.bind.JAXBException | javax.xml.transform.TransformerException e) { - logger.severe("cargosoft export failed: " + e.getMessage()); + logger.severe("├── ⚠️ Cargosoft export failed: " + e.getMessage()); document.setItemValue("cargosoft.error", e.getMessage()); document.event(EVENT_FAILURE); } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/FTPConnector.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/FTPConnector.java index 7438dad..6e55c3c 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/FTPConnector.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/FTPConnector.java @@ -82,10 +82,12 @@ public class FTPConnector { Optional ftpPassword; /** - * This method transfers a snapshot to a ftp server. + * This method transfers a file to a FTP server using atomic upload. + * The file is first uploaded with a temporary name and then renamed + * to avoid race conditions with the receiver. * - * @param fileData object - * @throws PluginException + * @param fileData object containing the file to upload + * @throws PluginException if the upload fails */ public void put(FileData fileData) throws PluginException { @@ -95,6 +97,8 @@ public class FTPConnector { } String fileName = fileData.getName(); + // Create temporary filename to avoid race conditions + String tempFileName = fileName + ".part"; // Compute file path String ftpWorkingPath = ftpPath.get(); @@ -108,7 +112,8 @@ public class FTPConnector { FTPClient ftpClient = null; try { - logger.finest("......put " + fileName + " to FTP server: " + ftpServer + "..."); + logger.info("├── 🔜 uploading " + fileName + " to FTP server: " + ftpServer + " ..."); + logger.info("│ ├── working directory=" + ftpWorkingPath); ftpClient = new FTPSClient("TLS", false); ftpClient.setBufferSize(8192); ftpClient.connect(ftpServer.get(), ftpPort.get().intValue()); @@ -120,38 +125,47 @@ public class FTPConnector { ftpClient.setFileType(FTP.ASCII_FILE_TYPE); ftpClient.setControlEncoding("UTF-8"); - // verify directories + // Verify directories if (!ftpClient.changeWorkingDirectory(ftpWorkingPath)) { throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR, "FTP file transfer failed: missing working directory '" + ftpWorkingPath + "' : " + ftpClient.getReplyString()); } - // upload file to FTP server. + // Upload file to FTP server with temporary name writer = new ByteArrayInputStream(fileData.getContent()); - if (!ftpClient.storeFile(fileName, writer)) { + if (!ftpClient.storeFile(tempFileName, writer)) { throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR, - "FTP file transfer failed: unable to write '" + ftpWorkingPath + fileName + "' : " + "FTP file transfer failed: unable to write '" + ftpWorkingPath + tempFileName + "' : " + ftpClient.getReplyString()); } - logger.finest("...." + ftpWorkingPath + fileName + " transfered successfull to " + ftpServer); + // Rename to final name - this is an atomic operation + logger.info("│ ├── rename '" + tempFileName + "' to '" + fileName + " ..."); + if (!ftpClient.rename(tempFileName, fileName)) { + throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR, + "FTP file transfer failed: unable to rename '" + tempFileName + "' to '" + fileName + "' : " + + ftpClient.getReplyString()); + } + logger.info("│ └── ✓ ftp transfer completed."); } catch (IOException e) { throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR, "FTP file transfer failed: " + e.getMessage(), e); } finally { - // do logout.... + // Cleanup try { if (writer != null) { writer.close(); } - ftpClient.logout(); - ftpClient.disconnect(); + if (ftpClient != null && ftpClient.isConnected()) { + ftpClient.logout(); + ftpClient.disconnect(); + } } catch (IOException e) { throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR, - "FTP file transfer failed: " + e.getMessage(), e); + "FTP file transfer failed during cleanup: " + e.getMessage(), e); } } }