diff --git a/Bundle/pom.xml b/Bundle/pom.xml index c03ed880..9a4ae4ea 100644 --- a/Bundle/pom.xml +++ b/Bundle/pom.xml @@ -6,7 +6,7 @@ net.sf.openas2 OpenAS2 - 2.6.4 + 2.7.0 openas2-osgi diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index e49948e8..fab620f2 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,19 +1,21 @@ # OpenAS2 Server -# Version 2.6.4 +# Version 2.7.0 # RELEASE NOTES ----- -The OpenAS2 project is pleased to announce the release of OpenAS2 2.6.4 +The OpenAS2 project is pleased to announce the release of OpenAS2 2.7.0 -The release download file is: OpenAS2Server-2.6.4.zip +The release download file is: OpenAS2Server-2.7.0.zip The zip file contains a PDF document (OpenAS2HowTo.pdf) providing information on installing and using the application. -Version 2.6.4 - 2019-02-01 -This is a minor bugfix release: - **IMPORTANT NOTE**: Please review upgrade notes below if you are upgrading +Version 2.7.0 - 2019-03-03 +This is a minor enhancement release: + **IMPORTANT NOTE**: Please review upgrade notes if you are upgrading - 1. Change the "Reporting-UA" MDN attribute to use the "http.user.agent" property value instead of the OpenAS2 application version if set. - 2. Enhance documentation to show how to configure with multiple partners. + 1. Add an exclude filter option for the file polling module to support large files using a temporary extension until file is copied to directory + 2. Add support for including extra parts of a parsed file name into the final parameter in the "format" attribute for file name parsing. + 3. Support HTTP authentication on a per partnership basis. + 4. Document the use of HTTP authentication, file filters and file name parsing functionality. ##Upgrade Notes See the openAS2HowTo appendix for the general process on upgrading OpenAS2. diff --git a/Remote/pom.xml b/Remote/pom.xml index c3c014d9..66559690 100644 --- a/Remote/pom.xml +++ b/Remote/pom.xml @@ -4,7 +4,7 @@ net.sf.openas2 OpenAS2 - 2.6.4 + 2.7.0 4.0.0 diff --git a/Server/pom.xml b/Server/pom.xml index 144e8b0d..37f60958 100644 --- a/Server/pom.xml +++ b/Server/pom.xml @@ -6,7 +6,7 @@ net.sf.openas2 OpenAS2 - 2.6.4 + 2.7.0 openas2-server diff --git a/Server/src/config/partnerships.xml b/Server/src/config/partnerships.xml index 29ec49f1..71fe3503 100644 --- a/Server/src/config/partnerships.xml +++ b/Server/src/config/partnerships.xml @@ -17,7 +17,7 @@ - + @@ -37,7 +37,7 @@ - + diff --git a/Server/src/main/java/org/openas2/params/ParameterParser.java b/Server/src/main/java/org/openas2/params/ParameterParser.java index bf440935..965b7fe3 100644 --- a/Server/src/main/java/org/openas2/params/ParameterParser.java +++ b/Server/src/main/java/org/openas2/params/ParameterParser.java @@ -42,15 +42,19 @@ public void setParameters(String encodedParams) throws InvalidParameterException * msg.sender.as2_id,msg.receiver.as2_id,msg.header.content-type * @param delimiters delimiters in string to parse, like "-." * @param value string to parse, like "NORINCO-WALMART.application/X12" + * @param mergeExtraTokens if "value" string contains more tokens than the "foprmat" string merge the extra tokens into final token from "format" string * @throws OpenAS2Exception - error in the parameter format string */ - public void setParameters(String format, String delimiters, String value) + public void setParameters(String format, String delimiters, String value, boolean mergeExtraTokens) throws OpenAS2Exception { List keys = parseKeys(format); - StringTokenizer valueTokens = new StringTokenizer(value, delimiters, false); + StringTokenizer valueTokens = new StringTokenizer(value, delimiters, true); Iterator keyIt = keys.iterator(); String key; + String tail = ""; + String finalKey = ""; + String tailDelim = ""; while (keyIt.hasNext()) { if (!valueTokens.hasMoreTokens()) { @@ -58,11 +62,24 @@ public void setParameters(String format, String delimiters, String value) } key = ((String) keyIt.next()).trim(); + finalKey = key; if (!key.equals("")) { - setParameter(key, valueTokens.nextToken()); + String val = valueTokens.nextToken(); + setParameter(key, val); + // Move to next non-delimiter token if more + if (valueTokens.hasMoreTokens()) tailDelim = valueTokens.nextToken(); + finalKey = key; + tail = val; } } + if (mergeExtraTokens && valueTokens.hasMoreTokens()) { + while (valueTokens.hasMoreTokens()) { + tail = tail + tailDelim + valueTokens.nextToken(); + if (valueTokens.hasMoreTokens()) tailDelim = valueTokens.nextToken(); + } + setParameter(finalKey, tail); + } } /** @@ -121,6 +138,7 @@ public String format(String format) throws InvalidParameterException { protected List parseKeys(String format) { StringTokenizer tokens = new StringTokenizer(format, ",", false); + List keys = new ArrayList(); while (tokens.hasMoreTokens()) { diff --git a/Server/src/main/java/org/openas2/processor/receiver/DirectoryPollingModule.java b/Server/src/main/java/org/openas2/processor/receiver/DirectoryPollingModule.java index 16e54a21..5f0537b4 100644 --- a/Server/src/main/java/org/openas2/processor/receiver/DirectoryPollingModule.java +++ b/Server/src/main/java/org/openas2/processor/receiver/DirectoryPollingModule.java @@ -4,6 +4,8 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,225 +18,197 @@ import org.openas2.params.InvalidParameterException; import org.openas2.util.IOUtil; -public abstract class DirectoryPollingModule extends PollingModule -{ - public static final String PARAM_OUTBOX_DIRECTORY = "outboxdir"; - public static final String PARAM_FILE_EXTENSION_FILTER = "fileextensionfilter"; - private Map trackedFiles; - private String outboxDir; - private String errorDir; - private String sentDir = null; - - private Log logger = LogFactory.getLog(DirectoryPollingModule.class.getSimpleName()); - - public void init(Session session, Map options) throws OpenAS2Exception - { - super.init(session, options); - // Check all the directories are configured and actually exist on the file system - try - { - outboxDir = getParameter(PARAM_OUTBOX_DIRECTORY, true); - IOUtil.getDirectoryFile(outboxDir); - errorDir = getParameter(PARAM_ERROR_DIRECTORY, true); - IOUtil.getDirectoryFile(errorDir); - sentDir = getParameter(PARAM_SENT_DIRECTORY, false); - if (sentDir != null) - IOUtil.getDirectoryFile(sentDir); - String pendingInfoFolder = getSession().getProcessor().getParameters().get("pendingmdninfo"); - IOUtil.getDirectoryFile(pendingInfoFolder); - String pendingFolder = getSession().getProcessor().getParameters().get("pendingmdn"); - IOUtil.getDirectoryFile(pendingFolder); - - } catch (IOException e) - { - throw new OpenAS2Exception("Failed to initialise directory poller.", e); - } +public abstract class DirectoryPollingModule extends PollingModule { + public static final String PARAM_OUTBOX_DIRECTORY = "outboxdir"; + public static final String PARAM_FILE_EXTENSION_FILTER = "fileextensionfilter"; + public static final String PARAM_FILE_EXTENSION_EXCLUDE_FILTER = "fileextensionexcludefilter"; + private Map trackedFiles; + private String outboxDir; + private String errorDir; + private String sentDir = null; + private List allowExtensions; + private List excludeExtensions; + + private Log logger = LogFactory.getLog(DirectoryPollingModule.class.getSimpleName()); + + public void init(Session session, Map options) throws OpenAS2Exception { + super.init(session, options); + // Check all the directories are configured and actually exist on the file + // system + try { + outboxDir = getParameter(PARAM_OUTBOX_DIRECTORY, true); + IOUtil.getDirectoryFile(outboxDir); + errorDir = getParameter(PARAM_ERROR_DIRECTORY, true); + IOUtil.getDirectoryFile(errorDir); + sentDir = getParameter(PARAM_SENT_DIRECTORY, false); + if (sentDir != null) + IOUtil.getDirectoryFile(sentDir); + String pendingInfoFolder = getSession().getProcessor().getParameters().get("pendingmdninfo"); + IOUtil.getDirectoryFile(pendingInfoFolder); + String pendingFolder = getSession().getProcessor().getParameters().get("pendingmdn"); + IOUtil.getDirectoryFile(pendingFolder); + String allowExtensionFilter = getParameter(PARAM_FILE_EXTENSION_FILTER, ""); + String excludeExtensionFilter = getParameter(PARAM_FILE_EXTENSION_EXCLUDE_FILTER, ""); + + if (allowExtensionFilter == null || allowExtensionFilter.length() < 1) + this.allowExtensions = new ArrayList(); + else + this.allowExtensions = Arrays.asList(allowExtensionFilter.split("\\s*,\\s*")); + if (excludeExtensionFilter == null || excludeExtensionFilter.length() < 1) + this.excludeExtensions = new ArrayList(); + else + this.excludeExtensions = Arrays.asList(excludeExtensionFilter.split("\\s*,\\s*")); + + } catch (IOException e) { + throw new OpenAS2Exception("Failed to initialise directory poller.", e); } + } @Override - public boolean healthcheck(List failures) - { - try - { - IOUtil.getDirectoryFile(outboxDir); - } catch (IOException e) - { - failures.add(this.getClass().getSimpleName() + " - Polling directory is not accessible: " + outboxDir); - return false; - } - return true; + public boolean healthcheck(List failures) { + try { + IOUtil.getDirectoryFile(outboxDir); + } catch (IOException e) { + failures.add(this.getClass().getSimpleName() + " - Polling directory is not accessible: " + outboxDir); + return false; } - - public void poll() - { - try - { - // update tracking info. if a file is ready, process it - updateTracking(); - - // scan the directory for new files - scanDirectory(outboxDir); - } catch (OpenAS2Exception oae) - { - oae.terminate(); - } catch (Exception e) - { - logger.error("Unexpected error occurred polling directory for files to send: " + outboxDir, e); - } + return true; + } + + public void poll() { + try { + // update tracking info. if a file is ready, process it + updateTracking(); + + // scan the directory for new files + scanDirectory(outboxDir); + } catch (OpenAS2Exception oae) { + oae.terminate(); + } catch (Exception e) { + logger.error("Unexpected error occurred polling directory for files to send: " + outboxDir, e); + } + } + + protected void scanDirectory(String directory) throws IOException, InvalidParameterException { + File dir = IOUtil.getDirectoryFile(directory); + // get a list of entries in the directory + File[] files = IOUtil.getFiles(dir, allowExtensions, excludeExtensions); + if (files == null) { + throw new InvalidParameterException("Error getting list of files in directory", this, + PARAM_OUTBOX_DIRECTORY, dir.getAbsolutePath()); } - protected void scanDirectory(String directory) throws IOException, InvalidParameterException - { - File dir = IOUtil.getDirectoryFile(directory); - String extensionFilter = getParameter(PARAM_FILE_EXTENSION_FILTER, ""); - - // get a list of entries in the directory - File[] files = extensionFilter.length() > 0 ? IOUtil.getFiles(dir, extensionFilter) : dir.listFiles(); - if (files == null) - { - throw new InvalidParameterException("Error getting list of files in directory", this, - PARAM_OUTBOX_DIRECTORY, dir.getAbsolutePath()); - } + // iterator through each entry, and start tracking new files + if (files.length > 0) { + for (int i = 0; i < files.length; i++) { + File currentFile = files[i]; - // iterator through each entry, and start tracking new files - if (files.length > 0) - { - for (int i = 0; i < files.length; i++) - { - File currentFile = files[i]; - - if (checkFile(currentFile)) - { - // start watching the file's size if it's not already being - // watched - trackFile(currentFile); - } - } + if (checkFile(currentFile)) { + // start watching the file's size if it's not already being + // watched + trackFile(currentFile); } + } } - - protected boolean checkFile(File file) - { - if (file.exists() && file.isFile()) - { - try - { - // check for a write-lock on file, will skip file if it's write - // locked - FileOutputStream fOut = new FileOutputStream(file, true); - fOut.close(); - return true; - } catch (IOException ioe) - { - // a sharing violation occurred, ignore the file for now - if (logger.isDebugEnabled()) - { - try - { - logger.debug("Directory poller detected a non-writable file and will be ignored: " + file.getCanonicalPath()); - } catch (IOException e) - { - e.printStackTrace(); - } - } - } + } + + protected boolean checkFile(File file) { + if (file.exists() && file.isFile()) { + try { + // check for a write-lock on file, will skip file if it's write + // locked + FileOutputStream fOut = new FileOutputStream(file, true); + fOut.close(); + return true; + } catch (IOException ioe) { + // a sharing violation occurred, ignore the file for now + if (logger.isDebugEnabled()) { + try { + logger.debug("Directory poller detected a non-writable file and will be ignored: " + + file.getCanonicalPath()); + } catch (IOException e) { + e.printStackTrace(); + } } - return false; + } } - - private void trackFile(File file) - { - Map trackedFiles = getTrackedFiles(); - String filePath = file.getAbsolutePath(); - if (trackedFiles.get(filePath) == null) - { - trackedFiles.put(filePath, file.length()); - } + return false; + } + + private void trackFile(File file) { + Map trackedFiles = getTrackedFiles(); + String filePath = file.getAbsolutePath(); + if (trackedFiles.get(filePath) == null) { + trackedFiles.put(filePath, file.length()); } - - private void updateTracking() - { - // clone the trackedFiles map, iterator through the clone and modify the - // original to avoid iterator exceptions - // is there a better way to do this? - Map trackedFiles = getTrackedFiles(); - Map trackedFilesClone = new HashMap(trackedFiles); - - for (Map.Entry fileEntry : trackedFilesClone.entrySet()) - { - // get the file and it's stored length - File file = new File(fileEntry.getKey()); - long fileLength = fileEntry.getValue().longValue(); - - // if the file no longer exists, remove it from the tracker - if (!checkFile(file)) - { - trackedFiles.remove(fileEntry.getKey()); - } else - { - // if the file length has changed, update the tracker + } + + private void updateTracking() { + // clone the trackedFiles map, iterator through the clone and modify the + // original to avoid iterator exceptions + // is there a better way to do this? + Map trackedFiles = getTrackedFiles(); + Map trackedFilesClone = new HashMap(trackedFiles); + + for (Map.Entry fileEntry : trackedFilesClone.entrySet()) { + // get the file and it's stored length + File file = new File(fileEntry.getKey()); + long fileLength = fileEntry.getValue().longValue(); + + // if the file no longer exists, remove it from the tracker + if (!checkFile(file)) { + trackedFiles.remove(fileEntry.getKey()); + } else { + // if the file length has changed, update the tracker long newLength = file.length(); - if (newLength != fileLength) - { - trackedFiles.put(fileEntry.getKey(), Long.valueOf(newLength)); - } else - { - // if the file length has stayed the same, process the file - // and stop tracking it - try - { - processFile(file); - } catch (OpenAS2Exception e) - { - e.terminate(); - try - { - IOUtil.handleError(file, errorDir); - } catch (OpenAS2Exception e1) - { - logger.error("Error handling file error for file: " + file.getAbsolutePath(), e1); - forceStop(e1); - return; - } - } finally - { - trackedFiles.remove(fileEntry.getKey()); - } - } + if (newLength != fileLength) { + trackedFiles.put(fileEntry.getKey(), Long.valueOf(newLength)); + } else { + // if the file length has stayed the same, process the file + // and stop tracking it + try { + processFile(file); + } catch (OpenAS2Exception e) { + e.terminate(); + try { + IOUtil.handleError(file, errorDir); + } catch (OpenAS2Exception e1) { + logger.error("Error handling file error for file: " + file.getAbsolutePath(), e1); + forceStop(e1); + return; } + } finally { + trackedFiles.remove(fileEntry.getKey()); + } } + } } + } - protected void processFile(File file) throws OpenAS2Exception - { - - if (logger.isInfoEnabled()) - logger.info("processing " + file.getAbsolutePath()); - - try (FileInputStream in = new FileInputStream(file)) - { - processDocument(in, file.getName()); - try - { - IOUtil.deleteFile(file); - } catch (IOException e) - { - throw new OpenAS2Exception("Failed to delete file handed off for processing:" + file.getAbsolutePath(), e); - } - } catch (IOException e) - { - throw new OpenAS2Exception("Failed to process file:" + file.getAbsolutePath(), e); - } - } + protected abstract Message createMessage(); - protected abstract Message createMessage(); + protected void processFile(File file) throws OpenAS2Exception { - private Map getTrackedFiles() - { - if (trackedFiles == null) - { - trackedFiles = new HashMap(); - } - return trackedFiles; + if (logger.isInfoEnabled()) + logger.info("processing " + file.getAbsolutePath()); + + try (FileInputStream in = new FileInputStream(file)) { + processDocument(in, file.getName()); + try { + IOUtil.deleteFile(file); + } catch (IOException e) { + throw new OpenAS2Exception("Failed to delete file handed off for processing:" + file.getAbsolutePath(), + e); + } + } catch (IOException e) { + throw new OpenAS2Exception("Failed to process file:" + file.getAbsolutePath(), e); + } + } + + private Map getTrackedFiles() { + if (trackedFiles == null) { + trackedFiles = new HashMap(); } + return trackedFiles; + } } \ No newline at end of file diff --git a/Server/src/main/java/org/openas2/processor/receiver/MessageBuilderModule.java b/Server/src/main/java/org/openas2/processor/receiver/MessageBuilderModule.java index b66679ca..ac58bb17 100644 --- a/Server/src/main/java/org/openas2/processor/receiver/MessageBuilderModule.java +++ b/Server/src/main/java/org/openas2/processor/receiver/MessageBuilderModule.java @@ -32,287 +32,267 @@ import org.openas2.processor.sender.SenderModule; import org.openas2.util.AS2Util; - public abstract class MessageBuilderModule extends BaseReceiverModule { - public static final String PARAM_ERROR_DIRECTORY = "errordir"; - public static final String PARAM_SENT_DIRECTORY = "sentdir"; + public static final String PARAM_ERROR_DIRECTORY = "errordir"; + public static final String PARAM_SENT_DIRECTORY = "sentdir"; - public static final String PARAM_FORMAT = "format"; - public static final String PARAM_DELIMITERS = "delimiters"; - public static final String PARAM_DEFAULTS = "defaults"; - public static final String PARAM_MIMETYPE = "mimetype"; - public static final String PARAM_RESEND_MAX_RETRIES = "resend_max_retries"; + public static final String PARAM_FORMAT = "format"; + public static final String PARAM_DELIMITERS = "delimiters"; + public static final String PARAM_MERGE_EXTRA = "mergeextratokens"; + public static final String PARAM_DEFAULTS = "defaults"; + public static final String PARAM_MIMETYPE = "mimetype"; + public static final String PARAM_RESEND_MAX_RETRIES = "resend_max_retries"; - private Log logger = LogFactory.getLog(MessageBuilderModule.class.getSimpleName()); + private Log logger = LogFactory.getLog(MessageBuilderModule.class.getSimpleName()); - public void init(Session session, Map options) throws OpenAS2Exception { - super.init(session, options); - } + public void init(Session session, Map options) throws OpenAS2Exception { + super.init(session, options); + } - protected Message processDocument(InputStream ip, String filename) throws OpenAS2Exception, FileNotFoundException - { - Message msg = buildMessageMetadata(filename); + protected Message processDocument(InputStream ip, String filename) throws OpenAS2Exception, FileNotFoundException { + Message msg = buildMessageMetadata(filename); - String pendingFile = msg.getAttribute(FileAttribute.MA_PENDINGFILE); - // Persist the file that has been passed in - File doc = new File(pendingFile); - FileOutputStream fo = null; - try - { - fo = new FileOutputStream(doc); - } catch (FileNotFoundException e1) - { - throw new OpenAS2Exception("Could not create file in pending folder: " + pendingFile, e1); - } - try - { - IOUtils.copy(ip, fo); - } catch (IOException e1) - { - fo = null; - throw new OpenAS2Exception("Could not write file to pending folder: " + pendingFile, e1); - } - try - { - ip.close(); - } catch (IOException e1) - { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - ip = null; - try - { - fo.close(); - } catch (IOException e1) - { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - fo = null; + String pendingFile = msg.getAttribute(FileAttribute.MA_PENDINGFILE); + // Persist the file that has been passed in + File doc = new File(pendingFile); + FileOutputStream fo = null; + try { + fo = new FileOutputStream(doc); + } catch (FileNotFoundException e1) { + throw new OpenAS2Exception("Could not create file in pending folder: " + pendingFile, e1); + } + try { + IOUtils.copy(ip, fo); + } catch (IOException e1) { + fo = null; + throw new OpenAS2Exception("Could not write file to pending folder: " + pendingFile, e1); + } + try { + ip.close(); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + ip = null; + try { + fo.close(); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + fo = null; - FileInputStream fis = new FileInputStream(doc); - try - { - buildMessageData(msg, fis, filename); - } - finally - { - try - { - fis.close(); - } catch (IOException e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - fis = null; - doc = null; - } - String customHeaderList = msg.getPartnership().getAttribute(Partnership.PA_CUSTOM_MIME_HEADER_NAMES_FROM_FILENAME); - if (customHeaderList != null && customHeaderList.length() > 0) - { - String[] headerNames = customHeaderList.split("\\s*,\\s*"); - String delimiters = msg.getPartnership().getAttribute(Partnership.PA_CUSTOM_MIME_HEADER_NAME_DELIMITERS_IN_FILENAME); - if (logger.isTraceEnabled()) logger.trace("Adding custom headers based on message file name to custom headers map. Delimeters: " + delimiters + msg.getLogMsgID()); - if (delimiters != null) - { - // Extract the values based on delimiters which means the mime header names are prefixed with a target - StringTokenizer valueTokens = new StringTokenizer(filename, delimiters, false); - if (valueTokens != null && valueTokens.countTokens()!= headerNames.length) - { - msg.setLogMsg("Filename does not match headers list: Headers=" + customHeaderList + " ::: Filename=" + filename + " ::: String delimiters=" + delimiters); - logger.error(msg); - throw new OpenAS2Exception("Invalid filename for extracting custom headers: " + filename); - } - for (int i = 0; i < headerNames.length; i++) - { - String[] header = headerNames[i].split("\\."); - if (logger.isTraceEnabled()) logger.trace("Adding custom header: " + headerNames[i] - + " :::Split count:" + header.length + msg.getLogMsgID()); - if (header.length != 2) throw new OpenAS2Exception("Invalid custom header: " + headerNames[i] + " :: The header name must be prefixed by \"header.\" or \"junk.\" etc"); - if (!"header".equalsIgnoreCase(header[0])) continue; // Ignore anything not prefixed by "header" - msg.addCustomOuterMimeHeader(header[1], valueTokens.nextToken()); - } - } - else - { - String regex = msg.getPartnership().getAttribute(Partnership.PA_CUSTOM_MIME_HEADER_NAMES_REGEX_ON_FILENAME); - if (regex != null) - { - Pattern p = Pattern.compile(regex); - Matcher m = p.matcher(filename); - if (!m.find() || m.groupCount() != headerNames.length) - { - msg.setLogMsg("Could not match filename to headers required using the regex provided: " - + (m.find()?("Mismatch in header count to extracted group count: " - + headerNames.length + "::" + m.groupCount()):"No match found in filename")); - logger.error(msg); - throw new OpenAS2Exception("Invalid filename for extracting custom headers: " + filename); - } - for (int i = 0; i < headerNames.length; i++) - { - msg.addCustomOuterMimeHeader(headerNames[i], m.group(i+1)); - } - } - } + FileInputStream fis = new FileInputStream(doc); + try { + buildMessageData(msg, fis, filename); + } finally { + try { + fis.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + fis = null; + doc = null; + } + String customHeaderList = msg.getPartnership() + .getAttribute(Partnership.PA_CUSTOM_MIME_HEADER_NAMES_FROM_FILENAME); + if (customHeaderList != null && customHeaderList.length() > 0) { + String[] headerNames = customHeaderList.split("\\s*,\\s*"); + String delimiters = msg.getPartnership() + .getAttribute(Partnership.PA_CUSTOM_MIME_HEADER_NAME_DELIMITERS_IN_FILENAME); + if (logger.isTraceEnabled()) + logger.trace("Adding custom headers based on message file name to custom headers map. Delimeters: " + + delimiters + msg.getLogMsgID()); + if (delimiters != null) { + // Extract the values based on delimiters which means the mime header names are + // prefixed with a target + StringTokenizer valueTokens = new StringTokenizer(filename, delimiters, false); + if (valueTokens != null && valueTokens.countTokens() != headerNames.length) { + msg.setLogMsg("Filename does not match headers list: Headers=" + customHeaderList + " ::: Filename=" + + filename + " ::: String delimiters=" + delimiters); + logger.error(msg); + throw new OpenAS2Exception("Invalid filename for extracting custom headers: " + filename); } - if (logger.isInfoEnabled()) - logger.info("File assigned to message: " + filename + msg.getLogMsgID()); - - if (msg.getData() == null) - { - throw new InvalidMessageException("Failed to retrieve data for outbound AS2 message for file: " + filename); + for (int i = 0; i < headerNames.length; i++) { + String[] header = headerNames[i].split("\\."); + if (logger.isTraceEnabled()) + logger.trace("Adding custom header: " + headerNames[i] + " :::Split count:" + header.length + + msg.getLogMsgID()); + if (header.length != 2) + throw new OpenAS2Exception("Invalid custom header: " + headerNames[i] + + " :: The header name must be prefixed by \"header.\" or \"junk.\" etc"); + if (!"header".equalsIgnoreCase(header[0])) + continue; // Ignore anything not prefixed by "header" + msg.addCustomOuterMimeHeader(header[1], valueTokens.nextToken()); } - if (logger.isTraceEnabled()) - logger.trace("PARTNERSHIP parms: " + msg.getPartnership().getAttributes() + msg.getLogMsgID()); - // Retry count - first try on partnership then directory polling module - String maxRetryCnt = msg.getPartnership().getAttribute(Partnership.PA_RESEND_MAX_RETRIES); - if (maxRetryCnt == null || maxRetryCnt.length() < 1) - { - maxRetryCnt = getSession().getProcessor().getParameters().get(PARAM_RESEND_MAX_RETRIES); + } else { + String regex = msg.getPartnership() + .getAttribute(Partnership.PA_CUSTOM_MIME_HEADER_NAMES_REGEX_ON_FILENAME); + if (regex != null) { + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(filename); + if (!m.find() || m.groupCount() != headerNames.length) { + msg.setLogMsg( + "Could not match filename to headers required using the regex provided: " + (m.find() + ? ("Mismatch in header count to extracted group count: " + headerNames.length + + "::" + m.groupCount()) + : "No match found in filename")); + logger.error(msg); + throw new OpenAS2Exception("Invalid filename for extracting custom headers: " + filename); + } + for (int i = 0; i < headerNames.length; i++) { + msg.addCustomOuterMimeHeader(headerNames[i], m.group(i + 1)); + } } - if (logger.isTraceEnabled()) - logger.trace("RESEND COUNT extracted from config: " + maxRetryCnt + msg.getLogMsgID()); - Map options = msg.getOptions(); - options.put(ResenderModule.OPTION_RETRIES, maxRetryCnt); + } + } + if (logger.isInfoEnabled()) + logger.info("File assigned to message: " + filename + msg.getLogMsgID()); - if (logger.isTraceEnabled()) - try - { - - logger.trace("Message object in directory polling module. Content-Disposition: " + msg.getContentDisposition() - + "\n Content-Type : " + msg.getContentType() - + "\n HEADERS : " + AS2Util.printHeaders(msg.getData().getAllHeaders()) - + "\n Content-Disposition in MSG getData() MIMEPART: " - + msg.getData().getContentType() - +msg.getLogMsgID() ); - } catch (Exception e){} - try - { - msg.setStatus(Message.MSG_STATUS_MSG_SEND); - // Transmit the message - getSession().getProcessor().handle(SenderModule.DO_SEND, msg, options); - } catch (Exception e) - { - msg.setLogMsg("Fatal error sending message: " + org.openas2.logging.Log.getExceptionMsg(e)); - logger.error(msg, e); - AS2Util.cleanupFiles(msg, true); - } - return msg; - + if (msg.getData() == null) { + throw new InvalidMessageException("Failed to retrieve data for outbound AS2 message for file: " + filename); } + if (logger.isTraceEnabled()) + logger.trace("PARTNERSHIP parms: " + msg.getPartnership().getAttributes() + msg.getLogMsgID()); + // Retry count - first try on partnership then directory polling module + String maxRetryCnt = msg.getPartnership().getAttribute(Partnership.PA_RESEND_MAX_RETRIES); + if (maxRetryCnt == null || maxRetryCnt.length() < 1) { + maxRetryCnt = getSession().getProcessor().getParameters().get(PARAM_RESEND_MAX_RETRIES); + } + if (logger.isTraceEnabled()) + logger.trace("RESEND COUNT extracted from config: " + maxRetryCnt + msg.getLogMsgID()); + Map options = msg.getOptions(); + options.put(ResenderModule.OPTION_RETRIES, maxRetryCnt); - protected abstract Message createMessage(); + if (logger.isTraceEnabled()) + try { - public Message buildMessageMetadata(String filename) throws OpenAS2Exception - { - Message msg = createMessage(); - msg.setAttribute(FileAttribute.MA_FILENAME, filename); - msg.setPayloadFilename(filename); - MessageParameters params = new MessageParameters(msg); + logger.trace("Message object in directory polling module. Content-Disposition: " + + msg.getContentDisposition() + "\n Content-Type : " + msg.getContentType() + + "\n HEADERS : " + AS2Util.printHeaders(msg.getData().getAllHeaders()) + + "\n Content-Disposition in MSG getData() MIMEPART: " + msg.getData().getContentType() + + msg.getLogMsgID()); + } catch (Exception e) { + } + try { + msg.setStatus(Message.MSG_STATUS_MSG_SEND); + // Transmit the message + getSession().getProcessor().handle(SenderModule.DO_SEND, msg, options); + } catch (Exception e) { + msg.setLogMsg("Fatal error sending message: " + org.openas2.logging.Log.getExceptionMsg(e)); + logger.error(msg, e); + AS2Util.cleanupFiles(msg, true); + } + return msg; - // Get the parameter that should provide the link between the polled directory and an AS2 sender and recipient - String defaults = getParameter(PARAM_DEFAULTS, false); - // Link the file to an AS2 sender and recipient via the Message object associated with the file - if (defaults != null) - { - params.setParameters(defaults); - } + } - String format = getParameter(PARAM_FORMAT, false); + protected abstract Message createMessage(); - if (format != null) - { - String delimiters = getParameter(PARAM_DELIMITERS, ".-"); - params.setParameters(format, delimiters, filename); - } + public Message buildMessageMetadata(String filename) throws OpenAS2Exception { + Message msg = createMessage(); + msg.setAttribute(FileAttribute.MA_FILENAME, filename); + msg.setPayloadFilename(filename); + MessageParameters params = new MessageParameters(msg); - // Should have sender/receiver now so update the message's partnership with any stored information based on the identified partner IDs - getSession().getPartnershipFactory().updatePartnership(msg, true); - msg.updateMessageID(); - // Set the sender and receiver in the Message object headers - msg.setHeader("AS2-To", msg.getPartnership().getReceiverID(Partnership.PID_AS2)); - msg.setHeader("AS2-From", msg.getPartnership().getSenderID(Partnership.PID_AS2)); - // Now build the filename since it is by default dependent on having sender and receiver ID - String pendingFile = AS2Util.buildPendingFileName(msg, getSession().getProcessor(), "pendingmdn"); - msg.setAttribute(FileAttribute.MA_PENDINGFILE, pendingFile); - msg.setAttribute(FileAttribute.MA_ERROR_DIR, getParameter(PARAM_ERROR_DIRECTORY, true)); - if (getParameter(PARAM_SENT_DIRECTORY, false) != null) - msg.setAttribute(FileAttribute.MA_SENT_DIR, getParameter(PARAM_SENT_DIRECTORY, false)); + // Get the parameter that should provide the link between the polled directory + // and an AS2 sender and recipient + String defaults = getParameter(PARAM_DEFAULTS, false); + // Link the file to an AS2 sender and recipient via the Message object + // associated with the file + if (defaults != null) { + params.setParameters(defaults); + } - return msg; + String format = getParameter(PARAM_FORMAT, false); + if (format != null) { + String delimiters = getParameter(PARAM_DELIMITERS, ".-"); + String mergeExtra = getParameter(PARAM_MERGE_EXTRA, "false"); + boolean mergeExtraTokens = "true".equalsIgnoreCase(mergeExtra); + params.setParameters(format, delimiters, filename, mergeExtraTokens); } - public void buildMessageData(Message msg, InputStream ip, String filename) throws OpenAS2Exception - { - MessageParameters params = new MessageParameters(msg); + // Should have sender/receiver now so update the message's partnership with any + // stored information based on the identified partner IDs + getSession().getPartnershipFactory().updatePartnership(msg, true); + msg.updateMessageID(); + // Set the sender and receiver in the Message object headers + msg.setHeader("AS2-To", msg.getPartnership().getReceiverID(Partnership.PID_AS2)); + msg.setHeader("AS2-From", msg.getPartnership().getSenderID(Partnership.PID_AS2)); + // Now build the filename since it is by default dependent on having sender and + // receiver ID + String pendingFile = AS2Util.buildPendingFileName(msg, getSession().getProcessor(), "pendingmdn"); + msg.setAttribute(FileAttribute.MA_PENDINGFILE, pendingFile); + msg.setAttribute(FileAttribute.MA_ERROR_DIR, getParameter(PARAM_ERROR_DIRECTORY, true)); + if (getParameter(PARAM_SENT_DIRECTORY, false) != null) + msg.setAttribute(FileAttribute.MA_SENT_DIR, getParameter(PARAM_SENT_DIRECTORY, false)); - try - { - //byte[] data = IOUtilOld.getFileBytes(file); - // 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); - } - } - javax.mail.util.ByteArrayDataSource byteSource = new javax.mail.util.ByteArrayDataSource(ip, contentType); - MimeBodyPart body = new MimeBodyPart(); - body.setDataHandler(new DataHandler(byteSource)); + return msg; + } - // below statement is not filename related, just want to make it - // consist with the parameter "mimetype="application/EDI-X12"" - // defined in config.xml 2007-06-01 + public void buildMessageData(Message msg, InputStream ip, String filename) throws OpenAS2Exception { + MessageParameters params = new MessageParameters(msg); - body.setHeader("Content-Type", contentType); + try { + // byte[] data = IOUtilOld.getFileBytes(file); + // 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); + } + } + javax.mail.util.ByteArrayDataSource byteSource = new javax.mail.util.ByteArrayDataSource(ip, contentType); + MimeBodyPart body = new MimeBodyPart(); + body.setDataHandler(new DataHandler(byteSource)); - // add below statement will tell the receiver to save the filename - // as the one sent by sender. 2007-06-01 - String sendFileName = getParameter("sendfilename", false); - if (sendFileName != null && sendFileName.equals("true")) - { - String contentDisposition = "Attachment; filename=\"" + msg.getAttribute(FileAttribute.MA_FILENAME) + "\""; - body.setHeader("Content-Disposition", contentDisposition); - msg.setContentDisposition(contentDisposition); - } + // below statement is not filename related, just want to make it + // consist with the parameter "mimetype="application/EDI-X12"" + // defined in config.xml 2007-06-01 - msg.setData(body); - } catch (MessagingException me) - { - throw new WrappedException(me); - } catch (IOException ioe) - { - throw new WrappedException(ioe); - } + body.setHeader("Content-Type", contentType); - /* Not sure it should be set at this level as there is no encoding of the content at this point so make it configurable */ - if (msg.getPartnership().isSetTransferEncodingOnInitialBodyPart()) - { - String contentTxfrEncoding = msg.getPartnership().getAttribute(Partnership.PA_CONTENT_TRANSFER_ENCODING); - if (contentTxfrEncoding == null) - contentTxfrEncoding = Session.DEFAULT_CONTENT_TRANSFER_ENCODING; - try - { - msg.getData().setHeader("Content-Transfer-Encoding", contentTxfrEncoding); - } catch (MessagingException e) - { - throw new OpenAS2Exception("Failed to set content transfer encoding in created MimeBodyPart: " - + org.openas2.logging.Log.getExceptionMsg(e), e); - } - } + // add below statement will tell the receiver to save the filename + // as the one sent by sender. 2007-06-01 + String sendFileName = getParameter("sendfilename", false); + if (sendFileName != null && sendFileName.equals("true")) { + String contentDisposition = "Attachment; filename=\"" + msg.getAttribute(FileAttribute.MA_FILENAME) + + "\""; + body.setHeader("Content-Disposition", contentDisposition); + msg.setContentDisposition(contentDisposition); + } + + msg.setData(body); + } catch (MessagingException me) { + throw new WrappedException(me); + } catch (IOException ioe) { + throw new WrappedException(ioe); + } + + /* + * Not sure it should be set at this level as there is no encoding of the + * content at this point so make it configurable + */ + if (msg.getPartnership().isSetTransferEncodingOnInitialBodyPart()) { + String contentTxfrEncoding = msg.getPartnership().getAttribute(Partnership.PA_CONTENT_TRANSFER_ENCODING); + if (contentTxfrEncoding == null) + contentTxfrEncoding = Session.DEFAULT_CONTENT_TRANSFER_ENCODING; + try { + msg.getData().setHeader("Content-Transfer-Encoding", contentTxfrEncoding); + } catch (MessagingException e) { + throw new OpenAS2Exception("Failed to set content transfer encoding in created MimeBodyPart: " + + org.openas2.logging.Log.getExceptionMsg(e), e); + } } + } } diff --git a/Server/src/main/java/org/openas2/processor/sender/AS2SenderModule.java b/Server/src/main/java/org/openas2/processor/sender/AS2SenderModule.java index 7a86a2a6..a81b79f2 100644 --- a/Server/src/main/java/org/openas2/processor/sender/AS2SenderModule.java +++ b/Server/src/main/java/org/openas2/processor/sender/AS2SenderModule.java @@ -192,9 +192,12 @@ private void sendMessage(String url, Message msg, MimeBodyPart securedData, Stri logger.info("Connecting to: " + url + msg.getLogMsgID()); } + Map httpOptions = getHttpOptions(); + httpOptions.put(HTTPUtil.PARAM_HTTP_USER, msg.getPartnership().getAttribute(HTTPUtil.PARAM_HTTP_USER)); + httpOptions.put(HTTPUtil.PARAM_HTTP_PWD, msg.getPartnership().getAttribute(HTTPUtil.PARAM_HTTP_PWD)); long maxSize = msg.getPartnership().getNoChunkedMaxSize(); ResponseWrapper resp = HTTPUtil.execRequest(HTTPUtil.Method.POST, url, ih.getAllHeaders() - , null, securedData.getInputStream(), getHttpOptions(), maxSize); + , null, securedData.getInputStream(), httpOptions, maxSize); if (logger.isInfoEnabled()) { logger.info("Message sent and response received in " + resp.getTransferTimeMs() + "ms" + msg.getLogMsgID()); diff --git a/Server/src/main/java/org/openas2/processor/sender/MDNSenderModule.java b/Server/src/main/java/org/openas2/processor/sender/MDNSenderModule.java index 04512f42..f0d256ea 100644 --- a/Server/src/main/java/org/openas2/processor/sender/MDNSenderModule.java +++ b/Server/src/main/java/org/openas2/processor/sender/MDNSenderModule.java @@ -113,8 +113,11 @@ private boolean sendAsyncMDN(MessageMDN mdn, String url, DispositionType disposi if (logger.isDebugEnabled()) logger.debug("ASYNC MDN attempting connection to: " + url + mdn.getMessage().getLogMsgID()); long maxSize = msg.getPartnership().getNoChunkedMaxSize(); + Map httpOptions = getHttpOptions(); + httpOptions.put(HTTPUtil.PARAM_HTTP_USER, msg.getPartnership().getAttribute(HTTPUtil.PARAM_HTTP_USER)); + httpOptions.put(HTTPUtil.PARAM_HTTP_PWD, msg.getPartnership().getAttribute(HTTPUtil.PARAM_HTTP_PWD)); ResponseWrapper resp = HTTPUtil.execRequest(HTTPUtil.Method.POST, url, mdn.getHeaders().getAllHeaders() - , null, mdn.getData().getInputStream(), getHttpOptions(), maxSize); + , null, mdn.getData().getInputStream(), httpOptions, maxSize); int respCode = resp.getStatusCode(); // Check the HTTP Response code diff --git a/Server/src/main/java/org/openas2/util/HTTPUtil.java b/Server/src/main/java/org/openas2/util/HTTPUtil.java index 5b025e2c..29911871 100644 --- a/Server/src/main/java/org/openas2/util/HTTPUtil.java +++ b/Server/src/main/java/org/openas2/util/HTTPUtil.java @@ -76,6 +76,8 @@ public class HTTPUtil { public static final String PARAM_READ_TIMEOUT = "readtimeout"; public static final String PARAM_CONNECT_TIMEOUT = "connecttimeout"; public static final String PARAM_SOCKET_TIMEOUT = "sockettimeout"; + public static final String PARAM_HTTP_USER = "http_user"; + public static final String PARAM_HTTP_PWD = "http_password"; public static final String HEADER_CONTENT_TYPE = "Content-Type"; public static final String HEADER_USER_AGENT = "User-Agent"; @@ -374,6 +376,15 @@ public static ResponseWrapper execRequest(String method, String url } } final HttpUriRequest request = rb.build(); + + String httpUser = options.get(HTTPUtil.PARAM_HTTP_USER); + String httpPwd = options.get(HTTPUtil.PARAM_HTTP_PWD); + if (httpUser != null) { + CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, + new UsernamePasswordCredentials(httpUser, httpPwd)); + httpBuilder.setDefaultCredentialsProvider(credentialsProvider); + } try (CloseableHttpClient httpClient = httpBuilder.build()) { ProfilerStub transferStub = Profiler.startProfile(); try (CloseableHttpResponse response = httpClient.execute(request)) { diff --git a/Server/src/main/java/org/openas2/util/IOUtil.java b/Server/src/main/java/org/openas2/util/IOUtil.java index 1fd0cc73..1eefbac0 100644 --- a/Server/src/main/java/org/openas2/util/IOUtil.java +++ b/Server/src/main/java/org/openas2/util/IOUtil.java @@ -10,6 +10,7 @@ import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.StandardCopyOption; +import java.util.List; import java.util.UUID; import org.apache.commons.io.FileUtils; @@ -249,12 +250,23 @@ public static void deleteFile(File f) throws IOException } } - public static File[] getFiles(File dir, final String extensionFilter) + public static File[] getFiles(File dir, List allowedExtensions, List excludedExtensions) { + final List allowExtensions = allowedExtensions; + final List excludeExtensions = excludedExtensions; + return dir.listFiles(new FilenameFilter() { - public boolean accept(File dir, String name) - { - return name.toLowerCase().endsWith("." + extensionFilter); + public boolean accept(File dir, String name) { + String extension = name.substring(name.lastIndexOf(".") + 1, name.length()); + boolean isAllowed = true; + if (!allowExtensions.isEmpty()) { + isAllowed = allowExtensions.contains(extension); + if (!isAllowed) return false; + } + // Check for the excluded filters + if (!excludeExtensions.isEmpty()) + isAllowed = !excludeExtensions.contains(extension); + return isAllowed; } }); } diff --git a/Server/src/test/java/org/openas2/util/IOUtilTest.java b/Server/src/test/java/org/openas2/util/IOUtilTest.java new file mode 100644 index 00000000..fb82b727 --- /dev/null +++ b/Server/src/test/java/org/openas2/util/IOUtilTest.java @@ -0,0 +1,133 @@ +package org.openas2.util; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import org.openas2.message.AS2Message; +import org.openas2.params.MessageParameters; + +@RunWith(MockitoJUnitRunner.class) +public class IOUtilTest { + private AS2Message message = new AS2Message(); + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + private class Record { + String filename; + String format; + String delimiters; + boolean mergeExtraTokens; + + String expectFrom; + String expectTo; + String expectFileName; + + public Record(String filename, String format, String delimiters, boolean mergeExtraTokens, + String expectFileName, String expectFrom, String expectTo) { + this.filename = filename; + this.format = format; + this.delimiters = delimiters; + this.mergeExtraTokens = mergeExtraTokens; + this.expectFileName = expectFileName; + this.expectFrom = expectFrom; + this.expectTo = expectTo; + } + } + + @SuppressWarnings("serial") + private List testParameterParsingData = new ArrayList(){ + { + add(new Record("MyCo-PartnerCo-MyFileNameWithExtension.edi","sender.as2_id, receiver.as2_id," + + " attributes.filename", "-.",false,"MyFileNameWithExtension","MyCo","PartnerCo")); + add(new Record("MyCo-PartnerCo-MyFileNameWithExtension.edi","sender.as2_id, receiver.as2_id," + + " attributes.filename","-.",true,"MyFileNameWithExtension.edi","MyCo","PartnerCo")); + }}; + + /* Test records for polling filters + * Format is , , "excluded extensions list>, allow = new ArrayList(); + if (strings[1] != null && strings[1].length() > 0) + allow = Arrays.asList(strings[1].split("\\s*,\\s*")); + List exclude = new ArrayList(); + if (strings[2] != null && strings[2].length() > 0) + exclude = Arrays.asList(strings[2].split("\\s*,\\s*")); + File[] files = IOUtil.getFiles(tmp.getRoot(), allow, exclude); + assertThat( "Check that a file is detected for " + name.getName() + ":" + allow + ":" + exclude, + files.length == Integer.parseInt(strings[3]), + is(true)); + // Cleanup for next loop + name.delete(); + } + + } +} diff --git a/Server/src/test/resources/SingleServerTest/MyCompany/config/partnerships.xml b/Server/src/test/resources/SingleServerTest/MyCompany/config/partnerships.xml index 14f795d6..ca055522 100644 --- a/Server/src/test/resources/SingleServerTest/MyCompany/config/partnerships.xml +++ b/Server/src/test/resources/SingleServerTest/MyCompany/config/partnerships.xml @@ -7,26 +7,28 @@ + email="as2msgs@partnera.com"/> + + - + - - - + + - + - - @@ -34,23 +36,59 @@ - + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + diff --git a/changes.txt b/changes.txt index 79108885..133eb25c 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,12 @@ +Version 2.7.0 - 2019-03-03 +This is a minor enhancement release: + **IMPORTANT NOTE**: Please review upgrade notes in the RELEASE-NOTES.md if you are upgrading + + 1. Add an exclude filter option for the file polling module to support large files using a temporary extension until file is copied to directory + 2. Add support for including extra parts of a parsed file name into the final parameter in the "format" attribute for file name parsing. + 3. Support HTTP authentication on a per partnership basis. + 4. Document the use of HTTP authentication, file filters and file name parsing functionality. + Version 2.6.4 - 2019-02-01 This is a minor bugfix release: **IMPORTANT NOTE**: Please review upgrade notes in the RELEASE-NOTES.md if you are upgrading diff --git a/docs/OpenAS2HowTo.odt b/docs/OpenAS2HowTo.odt index a1049c77..d0932b85 100644 Binary files a/docs/OpenAS2HowTo.odt and b/docs/OpenAS2HowTo.odt differ diff --git a/docs/OpenAS2HowTo.pdf b/docs/OpenAS2HowTo.pdf index 43606694..83b6d6a6 100644 Binary files a/docs/OpenAS2HowTo.pdf and b/docs/OpenAS2HowTo.pdf differ diff --git a/pom.xml b/pom.xml index be1d9348..8571b13a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 net.sf.openas2 OpenAS2 - 2.6.4 + 2.7.0 OpenAS2 pom