Skip to content

Commit 641eb93

Browse files
authored
File splitting enhancement (OpenAS2#311)
* Minor changes to messaging for MDN disposition errors. * Use find_java to look for JAVA_HOME * Remove double square brackets for Ubuntu dash compat * Handle cleanup error better. * Cater for unusable or questionable file names * Fix formatting failures * Handle EOF exception gracefully for unreadable pending info files * Refresh the partnership variables just before processing document. * Use find_java for identifying Java * Use the java executable instead of javac as it is not always installed. Use bash shell explicitly to cater for Ubuntu mapping sh to dash * Simplify reconstituting the mime body part. Provide backwards compat for now. * Extract more values to properties in preparation for automated upgrades. * Sample properties file for property driven custom configuration. * Upgrade notes * Updated documentation for 3.4.1 * Add the reject_unsigned_meesages attribute as an example. * New version and updated libraries to latest. * Extract the file splitting functionality to a separate class. Support running it as a thread or as a standalone app from command line * Minor changes to logging to make more sense. * Fix comment. * Version change and update libraries to latest releases. * Merge with upstream * Update mvnw * Fixes to accommodate RestAPI shortcomings with Java 8
1 parent 7e2a5cf commit 641eb93

File tree

10 files changed

+312
-101
lines changed

10 files changed

+312
-101
lines changed

.github/workflows/actions.yml

+10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ jobs:
1616
with:
1717
java-version: ${{ matrix.java_version }}
1818
distribution: 'adopt'
19+
- name: Remove Rest API for Java 8 because of library incompatibility
20+
if: contains(matrix.java_version, '8') == true && contains(matrix.os, 'win') == true
21+
run: |
22+
rmdir -Recurse -Force Server/src/main/java/org/openas2/cmd/processor/restapi
23+
del -Force Server/src/main/java/org/openas2/cmd/processor/RestCommandProcessor.java
24+
- name: Remove Rest API for Java 8 because of library incompatibility
25+
if: contains(matrix.java_version, '8') == true && contains(matrix.os, 'win') == false
26+
run: |
27+
rm -rf Server/src/main/java/org/openas2/cmd/processor/restapi
28+
rm -rf Server/src/main/java/org/openas2/cmd/processor/RestCommandProcessor.java
1929
- name: Make Maven Wrapper and Java finder executable. Copy cacerts
2030
if: contains(matrix.os, 'win') == false
2131
run: |

Remote/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>net.sf.openas2</groupId>
66
<artifactId>OpenAS2</artifactId>
7-
<version>3.4.1</version>
7+
<version>3.5.0</version>
88
</parent>
99

1010
<modelVersion>4.0.0</modelVersion>

Server/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<!-- DO NOT CHANGE THIS "groupId" WITHOUT CHANGING XMLSession.getManifestAttributes.MANIFEST_VENDOR_ID_ATTRIB -->
88
<groupId>net.sf.openas2</groupId>
99
<artifactId>OpenAS2</artifactId>
10-
<version>3.4.1</version>
10+
<version>3.5.0</version>
1111
<relativePath>../pom.xml</relativePath>
1212
</parent>
1313

Server/src/main/java/CheckCertificate.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ private CommandLine parseCommandLine(String[] args) {
8585
try {
8686
line = parser.parse(options, args);
8787
} catch (ParseException e) {
88-
System.out.println("Unexpected exception:" + e.getMessage());
88+
System.out.println("Command line parsing error: " + e.getMessage());
8989
usage(options);
9090
}
9191
return line;
@@ -323,7 +323,7 @@ public static void main(String[] args) {
323323
CheckCertificate mgr = new CheckCertificate();
324324
mgr.process(args);
325325
} catch (Exception e) {
326-
System.out.println("Unexpected exception:" + e.getMessage());
326+
System.out.println("Processing error occurred: " + e.getMessage());
327327
}
328328
}
329329
}
+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import org.apache.commons.cli.CommandLine;
2+
import org.apache.commons.cli.CommandLineParser;
3+
import org.apache.commons.cli.DefaultParser;
4+
import org.apache.commons.cli.HelpFormatter;
5+
import org.apache.commons.cli.Option;
6+
import org.apache.commons.cli.Options;
7+
import org.apache.commons.cli.ParseException;
8+
import org.openas2.util.FileUtil;
9+
import java.io.File;
10+
11+
/**
12+
* Class used to add the server's certificate to the KeyStore with your trusted
13+
* certificates.
14+
*/
15+
public class SplitCsvFile {
16+
17+
public static final String SOURCE_FILE = "s";
18+
public static final String OUT_FILENAME_PREFIX = "p";
19+
public static final String OUTPUT_DIR = "o";
20+
public static final String MAX_FILE_SIZE = "m";
21+
public static final String HAS_HEADER_ROW = "h";
22+
public static final String DEBUG = "d";
23+
public static final String HELP_OPT = "h";
24+
25+
/*
26+
* Options in this format: short-opt, long-opt, has-argument, required,
27+
* description
28+
*/
29+
public String[][] opts = {
30+
{SOURCE_FILE, "source", "true", "true", "the source file name including path"},
31+
{MAX_FILE_SIZE, "max_size", "true", "true", "the maximum size of the files"},
32+
{HAS_HEADER_ROW, "has_header", "false", "false", "if set, the file has a header row that wil be replicated into every file"},
33+
{OUT_FILENAME_PREFIX, "out_file_prefix", "true", "false", "the prefix for the split file names"},
34+
{OUTPUT_DIR, "out_dir", "true", "false", "output directory for the split files - defaults to current dir"},
35+
{DEBUG, "debug", "true", "false", "Enabling debug logging"},
36+
{HELP_OPT, "help", "false", "false", "print this help"}
37+
};
38+
39+
private void usage(Options options) {
40+
String header = "Splits CSV file." + "\nReads the file as a line based file creating new files that will not exceed the specified maximum size.";
41+
String footer = "NOTE: The file is expected to contain lines separated by newline characters.";
42+
43+
HelpFormatter formatter = new HelpFormatter();
44+
formatter.printHelp(this.getClass().getName(), header, options, footer, true);
45+
}
46+
47+
private CommandLine parseCommandLine(String[] args) {
48+
// create the command line parser
49+
CommandLineParser parser = new DefaultParser();
50+
51+
// create the Options
52+
Options options = new Options();
53+
for (String[] opt : opts) {
54+
Option option = Option.builder(opt[0]).longOpt(opt[1]).hasArg("true".equalsIgnoreCase(opt[2])).desc(opt[4]).build();
55+
option.setRequired("true".equalsIgnoreCase(opt[3]));
56+
options.addOption(option);
57+
}
58+
59+
// parse the command line arguments
60+
CommandLine line = null;
61+
try {
62+
line = parser.parse(options, args);
63+
} catch (ParseException e) {
64+
System.out.println("Command line parsing error: " + e.getMessage());
65+
usage(options);
66+
System.exit(-1);
67+
}
68+
return line;
69+
}
70+
71+
private void process(String[] args) throws Exception {
72+
CommandLine options = parseCommandLine(args);
73+
if (options == null) {
74+
return;
75+
}
76+
String sourceFileName = options.getOptionValue(SOURCE_FILE);
77+
File sourceFile = new File(sourceFileName);
78+
if (!sourceFile.exists()) {
79+
throw new Exception("File does not exist: " + sourceFileName);
80+
}
81+
long maxFileSize = Long.parseLong(options.getOptionValue(MAX_FILE_SIZE));
82+
String outputDirName = null;
83+
if (options.hasOption(OUTPUT_DIR)) {
84+
outputDirName = options.getOptionValue(OUTPUT_DIR);
85+
} else {
86+
outputDirName = System.getProperty("user.dir");
87+
}
88+
String prefix = (options.hasOption(OUT_FILENAME_PREFIX)) ? options.getOptionValue(OUT_FILENAME_PREFIX) : "";
89+
boolean hasHeaderRow = options.hasOption(HAS_HEADER_ROW);
90+
91+
if (options.hasOption(DEBUG) && "true".equalsIgnoreCase(options.getOptionValue(DEBUG))) {
92+
System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog");
93+
System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true");
94+
System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http", "DEBUG");
95+
System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.wire", "ERROR");
96+
}
97+
try {
98+
FileUtil.splitLineBasedFile(sourceFile, outputDirName, maxFileSize, hasHeaderRow, sourceFile.getName(), prefix);
99+
} catch (Exception e) {
100+
e.printStackTrace();
101+
System.out.println("Processing error occurred: " + e.getMessage());
102+
System.exit(-1);
103+
}
104+
}
105+
106+
public static void main(String[] args) {
107+
try {
108+
SplitCsvFile mgr = new SplitCsvFile();
109+
mgr.process(args);
110+
System.exit(0);
111+
} catch (Exception e) {
112+
System.out.println("Processing error occurred: " + e.getMessage());
113+
System.exit(-1);
114+
}
115+
}
116+
}

Server/src/main/java/org/openas2/XMLSession.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ private void loadProcessorModule(Processor proc, Node moduleNode) throws OpenAS2
335335
String partnershipName = null;
336336
Node defaultsNode = moduleNode.getAttributes().getNamedItem("defaults");
337337
if (defaultsNode == null) {
338-
// If there is a format nodethen this is a generic poller module
338+
// If there is a format node then this is a generic poller module
339339
Node formatNode = moduleNode.getAttributes().getNamedItem("format");
340340
if (formatNode == null) {
341341
throw new OpenAS2Exception("Invalid poller module coniguration: " + moduleNode.toString());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.openas2.processor.receiver;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
6+
import org.apache.commons.logging.Log;
7+
import org.apache.commons.logging.LogFactory;
8+
import org.openas2.OpenAS2Exception;
9+
import org.openas2.util.FileUtil;
10+
import org.openas2.util.IOUtil;
11+
12+
public class FileSplitter implements Runnable {
13+
private File sourceFile;
14+
private String outputDir;
15+
private long maxFileSize;
16+
private boolean containsHeaderRow;
17+
private String newFileBaseName;
18+
private String filenamePrefix;
19+
20+
private static final Log logger = LogFactory.getLog(FileUtil.class.getSimpleName());
21+
22+
public FileSplitter(File sourceFile, String outputDir, long maxFileSize, boolean containsHeaderRow, String newFileBaseName, String filenamePrefix) {
23+
this.sourceFile = sourceFile;
24+
this.outputDir = outputDir;
25+
this.maxFileSize = maxFileSize;
26+
this.containsHeaderRow = containsHeaderRow;
27+
this.newFileBaseName = newFileBaseName;
28+
this.filenamePrefix = filenamePrefix;
29+
}
30+
31+
public void run(){
32+
if (logger.isDebugEnabled()) {
33+
logger.debug("File splitter thread invoked for file: " + this.sourceFile.getAbsolutePath());
34+
}
35+
try {
36+
FileUtil.splitLineBasedFile(this.sourceFile, this.outputDir, this.maxFileSize, this.containsHeaderRow, this.newFileBaseName, this.filenamePrefix);
37+
if (logger.isDebugEnabled()) {
38+
logger.debug("Successfully split the file: " + this.sourceFile.getAbsolutePath());
39+
}
40+
// Must have been successful so remove the original file
41+
try {
42+
IOUtil.deleteFile(sourceFile);
43+
} catch (IOException e) {
44+
throw new OpenAS2Exception("Failed to delete file after split processing: " + sourceFile.getAbsolutePath(), e);
45+
}
46+
} catch (OpenAS2Exception e) {
47+
logger.error("Failed to successfully split the file: " + this.sourceFile.getAbsolutePath() + " - " + e.getMessage(), e);
48+
}
49+
}
50+
}

Server/src/main/java/org/openas2/processor/receiver/MessageBuilderModule.java

+11-92
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,17 @@
2121
import org.openas2.processor.sender.SenderModule;
2222
import org.openas2.util.AS2Util;
2323
import org.openas2.util.IOUtil;
24-
import org.openas2.util.StringUtil;
24+
import org.openas2.util.Properties;
2525

2626
import javax.activation.DataHandler;
2727
import javax.activation.DataSource;
2828
import javax.activation.FileDataSource;
2929
import javax.mail.MessagingException;
3030
import javax.mail.internet.MimeBodyPart;
3131

32-
import java.io.BufferedOutputStream;
33-
import java.io.BufferedReader;
3432
import java.io.File;
3533
import java.io.FileNotFoundException;
3634
import java.io.FileOutputStream;
37-
import java.io.FileReader;
3835
import java.io.IOException;
3936
import java.io.InputStream;
4037
import java.util.HashMap;
@@ -90,96 +87,18 @@ protected Message processDocument(File fileToSend, String filename) throws OpenA
9087
newFileNamePrefix = "";
9188
}
9289
boolean containsHeaderRow = "true".equals(msg.getPartnership().getAttribute(Partnership.PA_SPLIT_FILE_CONTAINS_HEADER_ROW));
93-
FileReader fileReader = new FileReader(fileToSend);
94-
BufferedReader bufferedReader = new BufferedReader(fileReader);
90+
String preprocessDir = Properties.getProperty("storageBaseDir", fileToSend.getParent()) + File.separator + "preprocess";
91+
// Move the file to a holding folder so it is not processed by the directory poller anymore
92+
String movedFilePath = preprocessDir + File.separator + filename;
93+
File movedFile = new File(movedFilePath);
9594
try {
96-
byte[] headerRow = new byte[0];
97-
if (containsHeaderRow) {
98-
try {
99-
String headerLine = bufferedReader.readLine();
100-
if (headerLine != null) {
101-
headerRow = (headerLine + "\n").getBytes();
102-
}
103-
} catch (IOException e1) {
104-
throw new OpenAS2Exception("Failed to read header row from input file.", e1);
105-
}
106-
}
107-
long headerRowByteCount = headerRow.length;
108-
if (fileSizeThreshold < headerRowByteCount) {
109-
// Would just write header repeatedly so throw error
110-
throw new OpenAS2Exception("Split file size is less than the header row size.");
111-
}
112-
long expectedFileCnt = Math.floorDiv(fileToSend.length(), fileSizeThreshold);
113-
// Figure out how many digits to pad the filename with - add 1 to cater for header row
114-
int fileCntDigits = Long.toString(expectedFileCnt).length() +1;
115-
int fileCount = 0;
116-
boolean notEof = true;
117-
while (notEof) {
118-
fileCount += 1;
119-
long fileSize = 0;
120-
String newFilename = newFileNamePrefix + StringUtil.padLeftZeros(Integer.toString(fileCount), fileCntDigits) + "-" + filename;
121-
addMessageMetadata(msg, newFilename);
122-
File pendingFile = new File(msg.getAttribute(FileAttribute.MA_PENDINGFILE));
123-
BufferedOutputStream fos = null;
124-
try {
125-
try {
126-
fos = new BufferedOutputStream(new FileOutputStream(pendingFile));
127-
} catch (IOException e) {
128-
throw new OpenAS2Exception("Failed to initialise output file for file splitting on file " + fileCount, e);
129-
}
130-
if (containsHeaderRow) {
131-
try {
132-
fos.write(headerRow);
133-
} catch (IOException e) {
134-
throw new OpenAS2Exception("Failed to write header row to output file for file splitting on file " + fileCount, e);
135-
}
136-
fileSize += headerRowByteCount;
137-
}
138-
while (fileSize < fileSizeThreshold) {
139-
String line = null;
140-
try {
141-
line = bufferedReader.readLine();
142-
} catch (IOException e) {
143-
throw new OpenAS2Exception("Failed to write output file for file splitting on file " + fileCount, e);
144-
}
145-
if (line == null) {
146-
notEof = false;
147-
break;
148-
}
149-
byte[] lineBytes = (line + "\n").getBytes();
150-
fos.write(lineBytes);
151-
fileSize += lineBytes.length;
152-
}
153-
fos.flush();
154-
fos.close();
155-
// Update the message's partnership with any additional attributes since initial call in case dynamic variables were not set initially
156-
getSession().getPartnershipFactory().updatePartnership(msg, true);
157-
processDocument(pendingFile, msg);
158-
} catch (IOException e) {
159-
throw new OpenAS2Exception("Failed to write output file for file splitting on file " + fileCount, e);
160-
} finally {
161-
try {
162-
if (fos != null) {
163-
fos.close();
164-
}
165-
} catch (IOException e) {
166-
}
167-
}
168-
}
169-
} finally {
170-
try {
171-
bufferedReader.close();
172-
} catch (IOException e) {
173-
throw new OpenAS2Exception("Failed to close reader for input file.", e);
174-
}
175-
}
176-
// Must have been successful so remove the original file
177-
try {
178-
IOUtil.deleteFile(fileToSend);
179-
} catch (IOException e) {
180-
throw new OpenAS2Exception("Failed to delete file after split processing: " + fileToSend.getAbsolutePath(), e);
95+
IOUtil.moveFile(fileToSend, movedFile, false);
96+
} catch (IOException e1) {
97+
throw new OpenAS2Exception("Failed to move file for split processing: " + fileToSend.getAbsolutePath(), e1);
18198
}
182-
return msg;
99+
FileSplitter fileSplitter = new FileSplitter(movedFile, fileToSend.getParent(), fileSizeThreshold, containsHeaderRow, filename, newFileNamePrefix);
100+
new Thread(fileSplitter).start();
101+
return null;
183102
} else {
184103
addMessageMetadata(msg, filename);
185104
File pendingFile = new File(msg.getAttribute(FileAttribute.MA_PENDINGFILE));

0 commit comments

Comments
 (0)