/*
* Copyright(c) 2005 Center for E-Commerce Infrastructure Development, The
* University of Hong Kong (HKU). All Rights Reserved.
*
* This software is licensed under the GNU GENERAL PUBLIC LICENSE Version 2.0 [1]
*
* [1] http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
*/
package hk.hku.cecid.edi.as2.pkg;
import hk.hku.cecid.piazza.commons.io.IOHandler;
import hk.hku.cecid.piazza.commons.security.SMimeMessage;
import hk.hku.cecid.piazza.commons.util.Generator;
import hk.hku.cecid.piazza.commons.util.StringUtilities;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.util.Date;
import java.util.Enumeration;
import javax.mail.MessagingException;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeBodyPart;
/**
* AS2Message represents an AS2 message.
*
* @author Hugo Y. K. Lam
*
*/
public class AS2Message {
private InternetHeaders headers;
private MimeBodyPart bodyPart;
/**
* Creates a new instance of AS2Message.
*/
public AS2Message() {
this.headers = new InternetHeaders();
this.bodyPart = new MimeBodyPart();
setHeader(AS2Header.SUBJECT, "AS2 Message");
setHeader(AS2Header.FROM, getDefaultEmailAddress());
setHeader(AS2Header.AS2_VERSION, "1.1");
setHeader(AS2Header.DATE, StringUtilities.toGMTString(new Date()));
setMessageID(generateID());
}
/**
* Creates a new instance of AS2Message.
*
* @param message the message as input stream.
* @throws AS2MessageException if unable to construct from the given input stream.
*/
public AS2Message(InputStream message) throws AS2MessageException {
try {
BufferedInputStream bis= new BufferedInputStream(message);
load(new InternetHeaders(bis), bis);
bis.close();
} catch (Exception e) {
throw new AS2MessageException(
"Unable to construct AS2 message from input stream", e);
}
}
/**
* Creates a new instance of AS2Message.
*
* @param headers the headers of this message.
* @param content the content stream.
* @throws AS2MessageException if unable to construct from the given content stream.
*/
public AS2Message(InternetHeaders headers, InputStream content)
throws AS2MessageException {
try {
load(headers, content);
} catch (Exception e) {
throw new AS2MessageException(
"Unable to construct AS2 message from content stream", e);
}
}
/**
* Loads the given headers and content to this message.
*
* @param headers the message headers.
* @param content the message content.
* @throws MessagingException if unable to construct the mime body part.
* @throws IOException unable to read the content stream.
*/
private void load(InternetHeaders headers, InputStream content)
throws MessagingException, IOException {
InternetHeaders bodyHeaders = new InternetHeaders();
copyHeaders(headers, bodyHeaders, "(?i)(?s)content-.*", true);
this.headers = headers;
this.bodyPart = new MimeBodyPart(bodyHeaders, IOHandler
.readBytes(content));
}
/**
* Gets the default email address.
*
* @return the default email address.
*/
private String getDefaultEmailAddress() {
return "as2@" + getLocalHost();
}
/**
* Gets the textual representation of the local host.
*
* @return the textual representation of the local host.
*/
private String getLocalHost() {
try {
return InetAddress.getLocalHost().getHostAddress();
}
catch (Exception e) {
return "127.0.0.1";
}
}
/**
* Gets the message ID.
*
* @return the message ID.
*/
public String getMessageID() {
return StringUtilities.trim(getHeader(AS2Header.MESSAGE_ID), "<", ">");
}
public void setMessageID(String id) {
if (id != null) {
setHeader(AS2Header.MESSAGE_ID, StringUtilities.wraps(id, "<", ">"));
}
}
public void setFromPartyID(String id) {
if (id != null) {
if (id.indexOf(' ') != -1) {
setHeader(AS2Header.AS2_FROM, StringUtilities.wraps(id, "\"", "\""));
}
else {
setHeader(AS2Header.AS2_FROM, id);
}
}
}
public void setToPartyID(String id) {
if (id != null) {
if (id.indexOf(' ') != -1) {
setHeader(AS2Header.AS2_TO, StringUtilities.wraps(id, "\"", "\""));
}
else {
setHeader(AS2Header.AS2_TO, id);
}
}
}
/**
* Gets the "from" party ID.
*
* @return the "from" party ID.
*/
public String getFromPartyID() {
return getPartyID(AS2Header.AS2_FROM);
}
/**
* Gets the "to" party ID.
*
* @return the "to" party ID.
*/
public String getToPartyID() {
return getPartyID(AS2Header.AS2_TO);
}
/**
* Gets the "from" party ID.
*
* @param type the party type.
* @return the party ID of the specified type.
*/
private String getPartyID(String type) {
return StringUtilities.trim(getHeader(type), "\"", "\"");
}
public void requestReceipt(String returnUrl, String micAlg) {
setHeader(AS2Header.DISPOSITION_NOTIFICATION_TO, getDefaultEmailAddress());
if (returnUrl != null) {
setHeader(AS2Header.RECEIPT_DELIVERY_OPTION, returnUrl);
}
if (micAlg != null) {
DispositionNotificationOptions dnos = new DispositionNotificationOptions();
DispositionNotificationOption option = dnos.addOption(DispositionNotificationOptions.SIGNED_RECEIPT_PROTOCOL);
option.addValue(DispositionNotificationOption.SIGNED_RECEIPT_PROTOCOL_PKCS7);
option = dnos.addOption(DispositionNotificationOptions.SIGNED_RECEIPT_MICALG);
option.addValue(micAlg);
setHeader(AS2Header.DISPOSITION_NOTIFICATION_OPTIONS, dnos.toString());
}
}
/**
* Checks if receipt of message is requested.
*
* @return true if receipt of message is requested.
*/
public boolean isReceiptRequested() {
return getHeader(AS2Header.DISPOSITION_NOTIFICATION_TO) != null;
}
/**
* Checks if the receipt of message should be sent synchronously.
*
* @return true if the receipt of message should be sent synchronously.
*/
public boolean isReceiptSynchronous() {
return getHeader(AS2Header.RECEIPT_DELIVERY_OPTION) == null;
}
/**
* Gets the disposition notification options.
*
* @return the disposition notification options.
*/
public DispositionNotificationOptions getDispositionNotificationOptions() {
String option = getHeader(AS2Header.DISPOSITION_NOTIFICATION_OPTIONS);
if (option == null) {
return null;
}
else {
return new DispositionNotificationOptions(option);
}
}
/**
* Gets a message header of the specified name.
*
* @param name the header name.
* @return the header value.
*/
public String getHeader(String name) {
String[] hs = headers.getHeader(name);
if (hs == null || hs.length < 1) {
return null;
} else {
StringBuffer header = new StringBuffer();
for (int i=0; i<hs.length; i++) {
header.append(hs[i]);
if (i+1<hs.length) {
header.append(", ");
}
}
return header.toString();
}
}
/**
* Gets a message header of the specified name.
*
* @param name the header name.
* @param def the default value.
* @return the header value.
*/
public String getHeader(String name, String def) {
String header = getHeader(name);
return header == null? def : header;
}
/**
* Sets a message header of the specified name.
*
* @param name the header name.
* @param value the header value.
*/
public void setHeader(String name, String value) {
if (name != null && value != null) {
headers.setHeader(name, value);
}
}
/**
* Removes a message header of the specified name.
*
* @param name the header name.
*/
public void removeHeader(String name) {
if (name != null) {
headers.removeHeader(name);
}
}
/**
* Adds a message header of the specified name.
*
* @param name the header name.
* @param value the header value.
*/
public void addHeader(String name, String value) {
if (name != null && value != null) {
headers.addHeader(name, value);
}
}
/**
* Gets the MIME body part of this message.
*
* @return the MIME body part.
*/
public MimeBodyPart getBodyPart() {
return bodyPart;
}
/**
* Sets the MIME body part of this message.
*
*/
public void setBodyPart(MimeBodyPart bp) {
if (bp != null) {
bodyPart = bp;
}
}
/**
* Sets a content to this message.
*
* @param content the content part.
* @param contentType the content type.
* @throws AS2MessageException if unable to set the content.
*/
public void setContent(Object content, String contentType)
throws AS2MessageException {
try {
bodyPart.setContent(content, contentType);
bodyPart.setHeader("Content-Type", contentType);
bodyPart.setHeader("Content-Transfer-Encoding", "binary");
} catch (MessagingException e) {
throw new AS2MessageException("Unable to set AS2 content", e);
}
}
/**
* Gets the content of this message.
*
* @return the content part.
* @throws AS2MessageException if unable to get the content.
*/
public Object getContent() throws AS2MessageException {
try {
return bodyPart.getContent();
} catch (Exception e) {
throw new AS2MessageException("Unable to get AS2 content", e);
}
}
/**
* Gets the content type.
*
* @return the content type.
* @throws AS2MessageException if unable to get the content type.
*/
public String getContentType() throws AS2MessageException {
try {
return bodyPart.getContentType();
} catch (MessagingException e) {
throw new AS2MessageException("Unable to get content type", e);
}
}
/**
* Gets the headers of this message.
*
* @return a copy of the headers of this message.
*/
public InternetHeaders getHeaders() {
InternetHeaders h = new InternetHeaders();
copyHeaders(headers, h, null, false);
copyHeaders(bodyPart, h, null, false);
return h;
}
/**
* Copy the given headers to a specified internet headers object.
*
* @param fromHeaders the headers source.
* @param toHeaders the headers destination.
* @param filter the filter in regular expression.
*/
private void copyHeaders(Object fromHeaders, InternetHeaders toHeaders,
String filter, boolean isMovingHeaders) {
if (fromHeaders != null && toHeaders != null) {
Enumeration enumeration;
if (fromHeaders instanceof InternetHeaders) {
enumeration = ((InternetHeaders) fromHeaders).getAllHeaderLines();
} else if (fromHeaders instanceof MimeBodyPart) {
try {
enumeration = ((MimeBodyPart) fromHeaders).getAllHeaderLines();
} catch (MessagingException e) {
return;
}
} else {
return;
}
while (enumeration.hasMoreElements()) {
String headerline = enumeration.nextElement().toString();
if (filter == null || headerline.matches(filter)) {
toHeaders.addHeaderLine(headerline);
if (isMovingHeaders) {
String headerName = headerline.split(":")[0];
if (fromHeaders instanceof InternetHeaders) {
((InternetHeaders) fromHeaders).removeHeader(headerName);
} else if (fromHeaders instanceof MimeBodyPart) {
try {
((MimeBodyPart) fromHeaders).removeHeader(headerName);
} catch (MessagingException e) {}
}
}
}
}
}
}
/**
* Gets the content stream of this message.
*
* @return the content stream of this message.
* @throws AS2MessageException if unable to retrieve the stream.
*/
public InputStream getContentStream() throws AS2MessageException {
try {
return bodyPart.getRawInputStream();
} catch (MessagingException e) {
try {
// try getting the input stream if there is no raw stream
// available.
return bodyPart.getInputStream();
} catch (Exception ex) {
throw new AS2MessageException("Unable to get content stream", e);
}
}
}
/**
* Gets the input stream of this message's content.
* Any transfer encodings will be decoded before the input stream is provided.
*
* @return the input stream of this message's content.
* @throws AS2MessageException if unable to retrieve the stream.
*/
public InputStream getInputStream() throws AS2MessageException {
try {
return bodyPart.getInputStream();
} catch (Exception e) {
throw new AS2MessageException("Unable to get input stream of content", e);
}
}
/**
* Writes the message to the given output stream.
*
* @param outs the output stream to be written.
* @throws AS2MessageException if unable to write the message.
*/
public void writeTo(OutputStream outs) throws AS2MessageException {
try {
Enumeration enumeration = headers.getAllHeaderLines();
while (enumeration.hasMoreElements()) {
outs.write((enumeration.nextElement() + "\r\n").getBytes());
}
bodyPart.writeTo(outs);
outs.flush();
} catch (Exception e) {
throw new AS2MessageException("Unable to write message", e);
}
}
/**
* Checks if this message is an MDN.
*
* @return true if this message is an MDN.
*/
public boolean isDispositionNotification() {
try {
SMimeMessage smime = new SMimeMessage(bodyPart);
if (smime.isSigned()) {
smime = smime.unsign();
}
return smime.getBodyPart().getContentType().toLowerCase().startsWith(
AS2Header.CONTENT_TYPE_MULTIPART_REPORT.toLowerCase());
}
catch (Exception e) {
return false;
}
}
/**
* Gets the MDN of this message.
*
* @return the MDN.
* @throws AS2MessageException if unable to construct the MDN.
*/
public DispositionNotification getDispositionNotification() throws AS2MessageException {
return new DispositionNotification(this);
}
/**
* Replies this message.
*
* @return the reply message.
* @throws AS2MessageException if unable to construct the message.
*/
public AS2Message reply() throws AS2MessageException {
try {
AS2Message ackMessage = new AS2Message();
ackMessage.setFromPartyID(getHeader(AS2Header.AS2_TO, "unknown"));
ackMessage.setToPartyID(getHeader(AS2Header.AS2_FROM, "unknown"));
ackMessage.setHeader(AS2Header.RECIPIENT_ADDRESS,
getHeader(AS2Header.FROM, "unknown@unknown"));
ackMessage.setHeader(AS2Header.SUBJECT, "Message Disposition Notification");
return ackMessage;
}
catch (Exception e) {
throw new AS2MessageException(
"Error in contructing reply message", e);
}
}
/**
* Returns a byte array which represents this message.
*
* @return a byte array which represents this message.
* @throws AS2MessageException if unable to convert this message into bytes.
*/
public byte[] toByteArray() throws AS2MessageException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writeTo(baos);
return baos.toByteArray();
}
/**
* Returns a string representation of this message.
*
* @return a string representation of this message.
* @see java.lang.Object#toString()
*/
public String toString() {
String id = getMessageID();
return "AS2 " + (isDispositionNotification()? "MDN":"Message") + " ["+ (id==null? "Unknown ID":id) +", From: "+getFromPartyID()+", To: "+getToPartyID()+"]";
}
/**
* Generates a new AS2 message ID.
*
* @return a new AS2 message ID.
*/
public static String generateID() {
return Generator.generateMessageID();
}
}