office-alexander-logistics/src/main/java/com/alexanderlogistics/FTPConnector.java

247 lines
10 KiB
Java

/*******************************************************************************
* Imixs Workflow Technology
* Copyright (C) 2001, 2008 Imixs Software Solutions GmbH,
* http://www.imixs.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You can receive a copy of the GNU General Public
* License at http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* Imixs Software Solutions GmbH - initial API and implementation
* Ralph Soika
*******************************************************************************/
package com.alexanderlogistics;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import java.util.logging.Logger;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPSClient;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.imixs.workflow.FileData;
import org.imixs.workflow.exceptions.PluginException;
import jakarta.ejb.Stateless;
import jakarta.inject.Inject;
/**
* The FTPConnector service provides methods to push invoices to cargosoft
*
* @version 1.0
* @author rsoika
*/
@Stateless
public class FTPConnector {
public static final String FTP_ERROR = "FTP_ERROR";
public static final String ENV_EXPORT_FTP_HOST = "cargosoft.export.ftp.host";
public static final String ENV_EXPORT_FTP_PATH = "cargosoft.export.ftp.path";
public static final String ENV_EXPORT_FTP_PORT = "cargosoft.export.ftp.port";
public static final String ENV_EXPORT_FTP_USER = "cargosoft.export.ftp.user";
public static final String ENV_EXPORT_FTP_PASSWORD = "cargosoft.export.ftp.password";
public static final String ENV_EXPORT_FTP_MAX_ATTACHMENT_SIZED = "cargosoft.export.ftp.maxattachmentsize";
private static Logger logger = Logger.getLogger(FTPConnector.class.getName());
@Inject
@ConfigProperty(name = ENV_EXPORT_FTP_HOST)
Optional<String> ftpServer;
@Inject
@ConfigProperty(name = ENV_EXPORT_FTP_PATH)
Optional<String> ftpPath;
@Inject
@ConfigProperty(name = ENV_EXPORT_FTP_PORT, defaultValue = "21")
Optional<Integer> ftpPort;
@Inject
@ConfigProperty(name = ENV_EXPORT_FTP_USER)
Optional<String> ftpUser;
@Inject
@ConfigProperty(name = ENV_EXPORT_FTP_PASSWORD)
Optional<String> ftpPassword;
/**
* 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 containing the file to upload
* @throws PluginException if the upload fails
*/
public void put(FileData fileData) throws PluginException {
if (!ftpServer.isPresent()) {
throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR,
"FTP file transfer failed: no ftp host name provided (" + ENV_EXPORT_FTP_HOST + ")!");
}
String fileName = fileData.getName();
// Create temporary filename to avoid race conditions
String tempFileName = fileName + ".part";
// Compute file path
String ftpWorkingPath = ftpPath.get();
if (!ftpWorkingPath.startsWith("/")) {
ftpWorkingPath = "/" + ftpWorkingPath;
}
if (!ftpWorkingPath.endsWith("/")) {
ftpWorkingPath = ftpWorkingPath + "/";
}
InputStream writer = null;
FTPClient ftpClient = null;
try {
logger.info("├── 🔜 uploading " + fileName + " to FTP server: " + ftpServer + " ...");
logger.info("│ ├── port=" + ftpPort.get().intValue());
logger.info("│ ├── working directory=" + ftpWorkingPath);
logger.info("│ ├── user=" + ftpUser.get());
// logger.info("│ ├── password=" + ftpPassword.get());
ftpClient = new FTPSClient("TLS", false);
ftpClient.setBufferSize(8192);
ftpClient.connect(ftpServer.get(), ftpPort.get().intValue());
if (ftpClient.login(ftpUser.get(), ftpPassword.get()) == false) {
throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR,
"FTP file transfer failed: login failed!");
}
ftpClient.enterLocalPassiveMode();
ftpClient.setFileType(FTP.ASCII_FILE_TYPE);
ftpClient.setControlEncoding("UTF-8");
// 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 with temporary name
writer = new ByteArrayInputStream(fileData.getContent());
if (!ftpClient.storeFile(tempFileName, writer)) {
throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR,
"FTP file transfer failed: unable to write '" + ftpWorkingPath + tempFileName + "' : "
+ ftpClient.getReplyString());
}
// 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 | PluginException e) {
// Log the real cause immediately, before the finally block runs -
// otherwise a cleanup failure could overwrite/hide this exception.
logger.warning("│ ├── ⚠️ FTP connection error: " + e.getMessage());
if (e instanceof PluginException) {
throw (PluginException) e;
}
throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR,
"FTP file transfer failed: " + e.getMessage(), e);
} finally {
// Cleanup - never let a cleanup failure mask the primary exception
try {
if (writer != null) {
writer.close();
}
if (ftpClient != null && ftpClient.isConnected()) {
ftpClient.logout();
ftpClient.disconnect();
}
} catch (IOException cleanupException) {
throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR,
"FTP file transfer failed during cleanup: " + cleanupException.getMessage(),
cleanupException);
}
}
}
/**
* This method reads data form the current working directory
*
* @param snapshot
* @throws ArchiveException
* @return data
*/
public byte[] get(FTPClient ftpClient, String fileName) throws PluginException {
if (ftpClient == null) {
throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR,
"FTP file transfer failed: no ftpClient provided!");
}
long l = System.currentTimeMillis();
ByteArrayOutputStream bos = null;
try {
bos = new ByteArrayOutputStream();
ftpClient.retrieveFile(fileName, bos);
byte[] result = bos.toByteArray();
logger.finest("......" + fileName + " transfered successfull from " + ftpServer + " in "
+ (System.currentTimeMillis() - l) + "ms");
return result;
} catch (IOException e) {
throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR,
"FTP file transfer failed: " + e.getMessage(), e);
} finally {
// do logout....
try {
if (bos != null) {
bos.close();
}
} catch (IOException e) {
throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR,
"FTP file transfer failed: " + e.getMessage(), e);
}
}
}
/**
* This method changes the current working sub-directy. If no corresponding
* directory exits the method creats one.
*
* @throws ArchiveException
*/
@SuppressWarnings("unused")
private void changeWorkingDirectory(FTPClient ftpClient, String subDirectory) throws PluginException {
// test if we have the subdreictory
try {
if (!ftpClient.changeWorkingDirectory(subDirectory)) {
// try to creat it....
if (!ftpClient.makeDirectory(subDirectory)) {
throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR,
"FTP Error: unable to create sub-directory '" + subDirectory + "' : "
+ ftpClient.getReplyString());
}
ftpClient.changeWorkingDirectory(subDirectory);
}
} catch (IOException e) {
throw new PluginException(CargosoftExportAdapter.class.getSimpleName(), FTP_ERROR,
"FTP file transfer failed: " + e.getMessage(), e);
}
}
}