Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic content type mapping based on filename extension. #347

Merged
merged 26 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a79f83d
Add configDir to make testing easier
uhurusurfa Nov 6, 2023
aad7084
Helper class to aid testing
uhurusurfa Nov 6, 2023
a81fe70
Remove config poller for partnership
uhurusurfa Nov 6, 2023
1fee11e
Check and load Content-Type mappings at system level.
uhurusurfa Nov 6, 2023
2e67627
Check and load Content-Type mappings at partnership level.
uhurusurfa Nov 6, 2023
5e2455a
Sample file for Content-Type dynamic mappings.
uhurusurfa Nov 6, 2023
dcd27ff
Enable Content-Type mapping in server to server test
uhurusurfa Nov 6, 2023
5be8b46
Additional utility methods for file operations
uhurusurfa Nov 6, 2023
d509974
Fix outbox to support multiple sending <-> receiving partner
uhurusurfa Nov 7, 2023
f189c01
Use a more appropriately named variable for readability
uhurusurfa Nov 7, 2023
a853f9b
Extract common server test stuff to base class
uhurusurfa Nov 7, 2023
0114b95
Tests for dynamic Content-Type mapping
uhurusurfa Nov 7, 2023
7182e9e
Helper methods for file related stuff
uhurusurfa Nov 7, 2023
66c4b31
Fix spelling error
uhurusurfa Nov 7, 2023
8480dc6
Enhance to support dynamic Content-Type setting based on filename
uhurusurfa Nov 7, 2023
93ca055
Change visibility scope of static to support testing
uhurusurfa Nov 7, 2023
f194cfa
Use partnership based polller instead of config
uhurusurfa Nov 7, 2023
f2acb42
Support system level Content-Type mapping
uhurusurfa Nov 7, 2023
ec9d2d4
Minor cleanup
uhurusurfa Nov 7, 2023
d94f5aa
Fix formatting to use spaces instead of tabs
uhurusurfa Nov 7, 2023
567258e
Release notes and associated documentation.
uhurusurfa Nov 7, 2023
2494662
Version updates.
uhurusurfa Nov 7, 2023
5ebc9c1
Add debug for Microsoft crap as usual
uhurusurfa Nov 7, 2023
44971e0
Escape backslash in file path so it works on Windows
uhurusurfa Nov 7, 2023
b51a9c5
Remove the custom properties file and associated system property to
uhurusurfa Nov 8, 2023
a13dbb1
Escape the backslash in Windows paths when setting the custom Openas2
uhurusurfa Nov 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
# OpenAS2 Server
# Version 3.7.0
# Version 3.8.0
# RELEASE NOTES
-----
The OpenAS2 project is pleased to announce the release of OpenAS2 3.7.0
The OpenAS2 project is pleased to announce the release of OpenAS2 3.8.0

The release download file is: OpenAS2Server-3.7.0.zip
The release download file is: OpenAS2Server-3.8.0.zip

The zip file contains a PDF document (OpenAS2HowTo.pdf) providing information on installing and using the application.
## NOTE: Testing covers Java 8 to 17. The application should work for older versions down to Java 7 but they are not tested as part of the CI/CD pipeline.

Version 3.7.0 - 2023-09-12
This is an enhancement and bugfix release:
Version 3.8.0 - 2023-11-07
This is an enhancement release:
**IMPORTANT NOTE**: Please review upgrade notes below if you are upgrading

1. Support parallel mode processing for the directory polling configuration to achieve high volume throughput.
2. Enhance error handling when chacking for files that never received an DMN response.
3. Added logging to indicate reading a fixed byte count message from HTTP stream to aid debugging.
1. Support for configurable dynamic Content-Type based on the file extension. See documentation section 7.5 "Setting Content Type"

##Upgrade Notes
See the openAS2HowTo appendix for the general process on upgrading OpenAS2.
Expand Down
2 changes: 1 addition & 1 deletion Remote/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>net.sf.openas2</groupId>
<artifactId>OpenAS2</artifactId>
<version>3.7.0</version>
<version>3.8.0</version>
</parent>

<modelVersion>4.0.0</modelVersion>
Expand Down
2 changes: 1 addition & 1 deletion Server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<!-- DO NOT CHANGE THIS "groupId" WITHOUT CHANGING XMLSession.getManifestAttributes.MANIFEST_VENDOR_ID_ATTRIB -->
<groupId>net.sf.openas2</groupId>
<artifactId>OpenAS2</artifactId>
<version>3.7.0</version>
<version>3.8.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
3 changes: 3 additions & 0 deletions Server/src/config/content_type_mappings.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
xml=application/xml
edi=application/edifact
txt=text/plain
12 changes: 10 additions & 2 deletions Server/src/main/java/org/openas2/XMLSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
import org.openas2.params.InvalidParameterException;
import org.openas2.params.ParameterParser;
import org.openas2.message.MessageFactory;
import org.openas2.partner.Partnership;
import org.openas2.partner.PartnershipFactory;
import org.openas2.processor.Processor;
import org.openas2.processor.ProcessorModule;
import org.openas2.processor.receiver.PollingModule;
import org.openas2.schedule.SchedulerComponent;
import org.openas2.util.FileUtil;
import org.openas2.util.Properties;
import org.openas2.util.XMLUtil;
import org.w3c.dom.Document;
Expand Down Expand Up @@ -141,8 +143,9 @@ protected void load(InputStream in) throws Exception {
*
* @param propNode - the "properties" element of the configuration file containing property values
* @throws InvalidParameterException
* @throws IOException
*/
private void loadProperties(Node propNode) throws InvalidParameterException {
private void loadProperties(Node propNode) throws InvalidParameterException, IOException {
LOGGER.info("Loading properties...");

Map<String, String> properties = XMLUtil.mapAttributes(propNode, false);
Expand All @@ -151,7 +154,7 @@ private void loadProperties(Node propNode) throws InvalidParameterException {
properties.put(Properties.APP_TITLE_PROP, getAppTitle());
properties.put(Properties.APP_VERSION_PROP, getAppVersion());
Properties.setProperties(properties);
String appPropsFile = System.getProperty("openas2.properties.file");
String appPropsFile = System.getProperty(Properties.OPENAS2_PROPERTIES_FILE_PROP);
if (appPropsFile != null && appPropsFile.length() > 1) {
java.util.Properties appProps = new java.util.Properties();
FileInputStream fis = null;
Expand Down Expand Up @@ -210,6 +213,11 @@ private void loadProperties(Node propNode) throws InvalidParameterException {
Properties.setProperty(key, entry.getValue());
}
}
// Now check if we need to load Content-Type mappings
String contentTypeMapFilename = Properties.getProperty(Partnership.PA_CONTENT_TYPE_MAPPING_FILE, null);
if (contentTypeMapFilename != null) {
Properties.setContentTypeMap(FileUtil.loadProperties(contentTypeMapFilename));
}
}

private void loadCertificates(Node rootNode) throws OpenAS2Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import java.util.regex.Pattern;

/**
* adds a new partnership entry in partneship store
* adds a new partnership entry in partnership store
*
* @author joseph mcverry
*/
Expand Down Expand Up @@ -56,7 +56,7 @@ public CommandResult execute(PartnershipFactory partFx, Object[] params) throws

for (int i = 0; i < params.length; i++) {
String param = (String) params[i];
int pos = param.indexOf('=');
int equalsPos = param.indexOf('=');
if (i == 0) {
partnershipRoot.setAttribute("name", param);
} else if (i == 1) {
Expand All @@ -67,9 +67,9 @@ public CommandResult execute(PartnershipFactory partFx, Object[] params) throws
Element elem = doc.createElement(Partnership.PCFG_RECEIVER);
elem.setAttribute("name", param);
partnershipRoot.appendChild(elem);
} else if (pos == 0) {
} else if (equalsPos == 0) {
return new CommandResult(CommandResult.TYPE_ERROR, "incoming parameter missing name");
} else if (pos > 0) {
} else if (equalsPos > 0) {
if (param.startsWith("pollerConfig.")) {
// Add a pollerConfig element
String regex = "^pollerConfig.([^=]*)=((?:[^\"']+)|'(?:[^']*)'|\"(?:[^\"]*)\")";
Expand All @@ -86,8 +86,8 @@ public CommandResult execute(PartnershipFactory partFx, Object[] params) throws
pollerConfigElem.setAttribute(name, val);
} else {
Element elem = doc.createElement("attribute");
elem.setAttribute("name", param.substring(0, pos));
elem.setAttribute("value", param.substring(pos + 1));
elem.setAttribute("name", param.substring(0, equalsPos));
elem.setAttribute("value", param.substring(equalsPos + 1));
partnershipRoot.appendChild(elem);
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ public interface FileAttribute {
String MA_ERROR_FILENAME = "errorfilename";
String MA_SENT_DIR = "sentdir";
String MA_SENT_FILENAME = "sentfilename";
String MA_FILENAME_EXTENSION = "filename_extension";
}
63 changes: 61 additions & 2 deletions Server/src/main/java/org/openas2/partner/Partnership.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.openas2.OpenAS2Exception;
import org.openas2.cert.CertificateNotFoundException;
import org.openas2.util.FileUtil;
import org.openas2.util.Properties;

import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
Expand Down Expand Up @@ -37,11 +39,13 @@ public class Partnership implements Serializable {
/* partnership definition attributes */
public static final String PA_SUBJECT = "subject"; // Subject sent in messages
public static final String PA_CONTENT_TYPE = "content_type"; // optional content type for mime parts
public static final String PA_USE_DYNAMIC_CONTENT_TYPE_MAPPING = "use_dynamic_content_type_mapping"; // use file extension to Content-Type mapping
public static final String PA_CONTENT_TYPE_MAPPING_FILE = "content_type_mapping_file"; // file containing file extension to Content-Type mapping
public static final String PA_CONTENT_TRANSFER_ENCODING = "content_transfer_encoding"; // optional content transfer enc value
public static final String PA_SET_CONTENT_TRANSFER_ENCODING_HTTP = "set_content_transfer_encoding_http_header"; // See as an HTTP header
public static final String PA_REMOVE_PROTECTION_ATTRIB = "remove_cms_algorithm_protection_attrib"; // Some AS2 systems do not support the attribute
public static final String PA_SET_CONTENT_TRANSFER_ENCODING_OMBP = "set_content_transfer_encoding_on_outer_mime_bodypart"; // optional content transfer enc value
public static final String PA_RESEND_REQUIRES_NEW_MESSAGE_ID = "resend_requires_new_message_id"; // list of nme/value pairs for setting custom mime headers
public static final String PA_RESEND_REQUIRES_NEW_MESSAGE_ID = "resend_requires_new_message_id"; // list of name/value pairs for setting custom mime headers
public static final String PA_COMPRESSION_TYPE = "compression";
public static final String PA_SIGNATURE_ALGORITHM = "sign";
public static final String PA_ENCRYPTION_ALGORITHM = "encrypt";
Expand Down Expand Up @@ -80,6 +84,9 @@ public class Partnership implements Serializable {
private Map<String, Object> receiverIDs;
private Map<String, Object> senderIDs;
private String name;
private java.util.Properties overrideContentTypeFromFileExtensionMap = null;
private java.util.Properties contentTypeFromFileExtensionMap = null;
private boolean useDynamicContentTypeLookup = false;

public String getName() {
return name;
Expand Down Expand Up @@ -173,7 +180,59 @@ public boolean matches(Partnership partnership) {

}

public String getAlias(String partnershipType) throws OpenAS2Exception {
public boolean isUseDynamicContentTypeLookup() {
return useDynamicContentTypeLookup;
}

/** This method is called if the partnership is configured to use dynamic mappings.
* It will check that there are either system or partnership specific mappings available
* load them into a partnership mapping cache.
* @param useDynamicContentTypeLookup - if true then enable dynamic mapping
* @throws OpenAS2Exception
* @throws IOException
*/
public void setUseDynamicContentTypeLookup(boolean useDynamicContentTypeLookup) throws OpenAS2Exception, IOException {
if (useDynamicContentTypeLookup) {
// Make sure there is a lookup available
// If there is a partnership specific override then make the partnership use
// that otherwise point it at the system mapping if available
String contentTypeMapFilename = getAttribute(Partnership.PA_CONTENT_TYPE_MAPPING_FILE);
if (contentTypeMapFilename != null) {
if (Properties.getContentTypeMap() != null) {
// Copy the system level mapping in first then override/add the custom mappings
overrideContentTypeFromFileExtensionMap = new java.util.Properties();
overrideContentTypeFromFileExtensionMap.putAll(Properties.getContentTypeMap());
overrideContentTypeFromFileExtensionMap.putAll(FileUtil.loadProperties(contentTypeMapFilename));
} else {
// Get the override map
setOverrideContentTypeFromFileExtension(FileUtil.loadProperties(contentTypeMapFilename));
}
// Configure this partnership to use the override lookup
contentTypeFromFileExtensionMap = overrideContentTypeFromFileExtensionMap;
} else {
// Set the partnership to use the global map
contentTypeFromFileExtensionMap = Properties.getContentTypeMap();
}
// If there is no map to do the lookup throw an excpetion
if (this.contentTypeFromFileExtensionMap == null) {
throw new OpenAS2Exception("Trying to use Content-Type mapping functionality but no mappings loaded.");
}
}
this.useDynamicContentTypeLookup = useDynamicContentTypeLookup;
}

public String getContentTypeFromFileExtension(String key) {
if (contentTypeFromFileExtensionMap == null) {
return null;
}
return (String) contentTypeFromFileExtensionMap.get(key);
}

public void setOverrideContentTypeFromFileExtension(java.util.Properties contentTypeFromFileExtension) {
this.overrideContentTypeFromFileExtensionMap = contentTypeFromFileExtension;
}

public String getAlias(String partnershipType) throws OpenAS2Exception {
String alias = null;

if (partnershipType == PTYPE_RECEIVER) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,15 @@ public void loadPartnership(Map<String, Object> partners, List<Partnership> part

// read in the partnership attributes
loadAttributes(node, partnership);

// Now check if we need to enable Content-Type mappings for this partnership
if ("true".equalsIgnoreCase(partnership.getAttributeOrProperty(Partnership.PA_USE_DYNAMIC_CONTENT_TYPE_MAPPING, "false"))) {
try {
partnership.setUseDynamicContentTypeLookup(true);
} catch (IOException e) {
logger.error("Error setting up dynamic Content-Type lookup: " + e.getMessage(), e);
throw new OpenAS2Exception("Partnership failed to be set up correctly for dynamic Content-Type lookup: " + getName());
}
}
// add the partnership to the list of available partnerships
partnerships.add(partnership);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.openas2.processor.resender.ResenderModule;
import org.openas2.processor.sender.SenderModule;
import org.openas2.util.AS2Util;
import org.openas2.util.FileUtil;
import org.openas2.util.IOUtil;
import org.openas2.util.Properties;

Expand Down Expand Up @@ -280,6 +281,8 @@ public Message buildBaseMessage(String filename) throws OpenAS2Exception {
public void addMessageMetadata(Message msg, String filename) throws OpenAS2Exception {
msg.setAttribute(FileAttribute.MA_FILENAME, filename);
msg.setPayloadFilename(filename);
// Set the filename extension if it has one
msg.setAttribute(FileAttribute.MA_FILENAME_EXTENSION, FileUtil.getFilenameExtension(filename));
// Set a new message ID
msg.updateMessageID();
// Set the sender and receiver in the Message object headers
Expand Down Expand Up @@ -352,24 +355,35 @@ public void buildMessageData(Message msg, DataSource dataSource, String contentT
msg.setData(body);
}

private String getMessageContentType(Message msg) throws OpenAS2Exception {
public String getMessageContentType(Message msg) throws OpenAS2Exception {
MessageParameters params = new MessageParameters(msg);

// Allow Content-Type to be overridden at partnership level or as property
String contentType = msg.getPartnership().getAttributeOrProperty(Partnership.PA_CONTENT_TYPE, null);
if (contentType == null) {
contentType = getParameter(PARAM_MIMETYPE, false);
}
if (contentType == null) {
contentType = "application/octet-stream";
} else {
try {
contentType = ParameterParser.parse(contentType, params);
} catch (InvalidParameterException e) {
throw new OpenAS2Exception("Bad content-type" + contentType, e);
// Allow Content-Type to be overridden at partnership level or as property
String contentType = msg.getPartnership().getAttributeOrProperty(Partnership.PA_CONTENT_TYPE, null);
// The content type could be determined dynamically based on filename extension
if (msg.getPartnership().isUseDynamicContentTypeLookup()) {
String fileExtension = msg.getAttribute(FileAttribute.MA_FILENAME_EXTENSION);
if (fileExtension != null) {
String dynamicContentType = msg.getPartnership().getContentTypeFromFileExtension(fileExtension);
if (dynamicContentType != null) {
// Dynamic override found so use it
contentType = dynamicContentType;
}
}
return contentType;
}
if (contentType == null) {
contentType = getParameter(PARAM_MIMETYPE, false);
}
if (contentType == null) {
contentType = "application/octet-stream";
} else {
try {
contentType = ParameterParser.parse(contentType, params);
} catch (InvalidParameterException e) {
throw new OpenAS2Exception("Bad content-type" + contentType, e);
}
}
return contentType;
}

private void setAdditionalMetaData(Message msg, MimeBodyPart mimeBodyPart) throws OpenAS2Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@


public abstract class PollingModule extends MessageBuilderModule {
private static final String PARAM_POLLING_INTERVAL = "interval";
protected final String PARAM_POLLING_INTERVAL = "interval";
private Timer timer;
private boolean busy;
private String outboxDir;
Expand Down
30 changes: 30 additions & 0 deletions Server/src/main/java/org/openas2/util/FileUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,46 @@
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;


public class FileUtil {

//private static final Log logger = LogFactory.getLog(FileUtil.class.getSimpleName());

public static Properties loadProperties(String filename) throws IOException {
Properties fileProps = new java.util.Properties();
FileInputStream fis = null;
fis = new FileInputStream(filename);
try {
fileProps.load(fis);
} finally {
if (fis != null) {
fis.close();
}
}
return fileProps;
}

/** Attempts to extract the filename extension by searching for the last occurrence
* of a period and returning all characters following that period.
* If no period is found then it returns null.
* @param filename - the full name of the file including extension
* @return the extension of the filename excluding the period
*/
public static String getFilenameExtension(String filename) {
int period_index = filename.lastIndexOf(".");
if (period_index == -1) {
return null;
}
return filename.substring(filename.lastIndexOf(".") + 1);
}

public static void splitLineBasedFile(File sourceFile, String outputDir, long maxFileSize, boolean containsHeaderRow, String newFileBaseName, String filenamePrefix) throws OpenAS2Exception {
FileReader fileReader;
try {
Expand Down
Loading
Loading