//$Header: /cvsroot-fuse/mec-as2/39/mendelson/comm/as2/message/store/MessageStoreHandler.java,v 1.1 2012/04/18 14:10:31 heller Exp $
package de.mendelson.comm.as2.message.store;
import de.mendelson.comm.as2.AS2ServerVersion;
import de.mendelson.comm.as2.message.AS2Info;
import de.mendelson.comm.as2.message.AS2Message;
import de.mendelson.comm.as2.message.AS2MessageInfo;
import de.mendelson.comm.as2.message.AS2Payload;
import de.mendelson.comm.as2.message.MessageAccessDB;
import de.mendelson.comm.as2.partner.Partner;
import de.mendelson.comm.as2.partner.PartnerAccessDB;
import de.mendelson.comm.as2.preferences.PreferencesAS2;
import de.mendelson.comm.as2.server.AS2Server;
import de.mendelson.util.MecResourceBundle;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
/*
* Copyright (C) mendelson-e-commerce GmbH Berlin Germany
*
* This software is subject to the license agreement set forth in the license.
* Please read and agree to all terms before using this software.
* Other product and brand names are trademarks of their respective owners.
*/
/**
* Stores messages in specified directories
* @author S.Heller
* @version $Revision: 1.1 $
*/
public class MessageStoreHandler {
/**products preferences*/
private PreferencesAS2 preferences = new PreferencesAS2();
private Logger logger = Logger.getLogger(AS2Server.SERVER_LOGGER_NAME);
/**localize the output*/
private MecResourceBundle rb = null;
private final String CRLF = new String(new byte[]{0x0d, 0x0a});
private Connection configConnection;
private Connection runtimeConnection;
public MessageStoreHandler(Connection configConnection, Connection runtimeConnection) {
this.configConnection = configConnection;
this.runtimeConnection = runtimeConnection;
//Load resourcebundle
try {
this.rb = (MecResourceBundle) ResourceBundle.getBundle(
ResourceBundleMessageStoreHandler.class.getName());
} //load up resourcebundle
catch (MissingResourceException e) {
throw new RuntimeException("Oops..resource bundle " + e.getClassName() + " not found.");
}
}
/**Stores incoming data for the server without analyzing it, raw
*Returns the raw filename and the header filename
*/
public String[] storeRawIncomingData(byte[] data, Properties header, String remoteHost) throws IOException {
String[] filenames = new String[2];
File inRawDir = new File(new File(this.preferences.get(PreferencesAS2.DIR_MSG)).getAbsolutePath() + File.separator + "_rawincoming");
//ensure the directory exists
if (!inRawDir.exists()) {
boolean created = inRawDir.mkdirs();
if (!created) {
this.logger.warning(this.rb.getResourceString("dir.createerror",
inRawDir.getAbsolutePath()));
}
}
DateFormat format = new SimpleDateFormat("yyyyMMddHHmmssSSS");
StringBuilder rawFileName = new StringBuilder();
rawFileName.append(inRawDir.getAbsolutePath()).append(File.separator).append(format.format(new Date())).append("_");
if (remoteHost != null) {
rawFileName.append(remoteHost);
} else {
rawFileName.append("unknownhost");
}
rawFileName.append(".as2");
File rawDataFile = new File(rawFileName.toString());
//write raw data
FileOutputStream outStream = new FileOutputStream(rawDataFile);
ByteArrayInputStream inStream = new ByteArrayInputStream(data);
this.copyStreams(inStream, outStream);
outStream.flush();
outStream.close();
inStream.close();
//write header
File headerFile = new File(rawDataFile.getAbsolutePath() + ".header");
FileOutputStream outStreamHeader = new FileOutputStream(headerFile);
Enumeration enumeration = header.keys();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
outStreamHeader.write((key + " = " + header.getProperty(key) + CRLF).getBytes());
}
outStreamHeader.flush();
outStreamHeader.close();
filenames[0] = rawDataFile.getAbsolutePath();
filenames[1] = headerFile.getAbsolutePath();
return (filenames);
}
/**Copies all data from one stream to another*/
private void copyStreams(InputStream in, OutputStream out) throws IOException {
BufferedInputStream inStream = new BufferedInputStream(in);
BufferedOutputStream outStream = new BufferedOutputStream(out);
//copy the contents to an output stream
byte[] buffer = new byte[2048];
int read = 0;
//a read of 0 must be allowed, sometimes it takes time to
//extract data from the input
while (read != -1) {
read = inStream.read(buffer);
if (read > 0) {
outStream.write(buffer, 0, read);
}
}
outStream.flush();
}
/**If a message state is OK the payload has to be moved to the right directory
* @param messageType could be a normal EDI message or a CEM
*/
public void movePayloadToInbox(int messageType, String messageId, Partner localstation, Partner senderstation) throws Exception {
StringBuilder inBoxDirPath = new StringBuilder();
inBoxDirPath.append(localstation.getMessagePath(this.preferences.get(PreferencesAS2.DIR_MSG)));
inBoxDirPath.append(File.separator);
if (messageType == AS2Message.MESSAGETYPE_AS2) {
inBoxDirPath.append("inbox");
} else if (messageType == AS2Message.MESSAGETYPE_CEM) {
inBoxDirPath.append("certificates");
}
if (this.preferences.getBoolean(PreferencesAS2.RECEIPT_PARTNER_SUBDIR)) {
inBoxDirPath.append(File.separator);
inBoxDirPath.append(convertToValidFilename(senderstation.getName()));
}
//store incoming message
File inboxDir = new File(inBoxDirPath.toString());
//ensure the directory exists
if (!inboxDir.exists()) {
boolean created = inboxDir.mkdirs();
if (!created) {
this.logger.warning(this.rb.getResourceString("dir.createerror",
inboxDir.getAbsolutePath()));
}
}
//load message overview from database
MessageAccessDB messageAccess = new MessageAccessDB(this.configConnection, this.runtimeConnection);
List<AS2Payload> payloadList = messageAccess.getPayload(messageId);
AS2MessageInfo messageInfo = messageAccess.getLastMessageEntry(messageId);
if (payloadList != null) {
for (int i = 0; i < payloadList.size(); i++) {
String payloadFilename = payloadList.get(i).getPayloadFilename();
if (payloadFilename == null) {
continue;
}
//source where to copy from
File inFile = new File(payloadFilename);
//is it defined to keep the original filename for messages from this sender?
if (senderstation.getKeepOriginalFilenameOnReceipt() && payloadList.get(i).getOriginalFilename() != null && payloadList.get(i).getOriginalFilename().length() > 0) {
payloadFilename = payloadList.get(i).getOriginalFilename();
}
//is it a CEM? Take the content id as filename and add an extension
if (messageInfo.getMessageType() == AS2Message.MESSAGETYPE_CEM && payloadList.get(i).getContentId() != null) {
payloadFilename = payloadFilename + "_" + convertToValidFilename(payloadList.get(i).getContentId());
if (payloadList.get(i).getContentType() != null) {
if (payloadList.get(i).getContentType().toLowerCase().contains("ediint-cert-exchange+xml")) {
payloadFilename = payloadFilename + ".xml";
} else {
payloadFilename = payloadFilename + ".p7c";
}
}
}
StringBuilder outFilename = new StringBuilder();
outFilename.append(inboxDir.getAbsolutePath());
outFilename.append(File.separator);
outFilename.append(new File(payloadFilename).getName());
File outFile = new File(outFilename.toString());
inFile.renameTo(outFile);
payloadList.get(i).setPayloadFilename(outFilename.toString());
this.logger.log(Level.FINE, this.rb.getResourceString("comm.success",
new Object[]{
messageInfo.getMessageId(),
String.valueOf(i + 1),
outFilename.toString()
}), messageInfo);
}
messageAccess.insertPayload(messageId, payloadList);
}
}
/**Stores an incoming message payload to the right partners mailbox, the decrypted message to the raw directory
*The filenames of the files where the data has been stored in is written to the message object
*/
public void storeParsedIncomingMessage(AS2Message message, Partner localstation) throws Exception {
//do not store signals payload in pending dir
if (!message.getAS2Info().isMDN()) {
StringBuilder inBoxDirPath = new StringBuilder();
inBoxDirPath.append(localstation.getMessagePath(this.preferences.get(PreferencesAS2.DIR_MSG)));
inBoxDirPath.append(File.separator);
inBoxDirPath.append("inbox");
//store incoming message
File inboxDir = new File(inBoxDirPath.toString());
//ensure the directory exists
if (!inboxDir.exists()) {
boolean created = inboxDir.mkdirs();
if (!created) {
this.logger.warning(this.rb.getResourceString("dir.createerror",
inboxDir.getAbsolutePath()));
}
}
//store the payload to the pending directory. It resists there as long as no positive MDN comes in
File pendingDir = new File(inboxDir.getAbsolutePath() + File.separator + "pending");
if (!pendingDir.exists()) {
boolean created = pendingDir.mkdirs();
if (!created) {
this.logger.warning(this.rb.getResourceString("dir.createerror",
pendingDir.getAbsolutePath()));
}
}
for (int i = 0; i < message.getPayloadCount(); i++) {
AS2Payload payload = message.getPayload(i);
StringBuilder pendingFilename = new StringBuilder();
pendingFilename.append(pendingDir.getAbsolutePath());
pendingFilename.append(File.separator);
pendingFilename.append(MessageStoreHandler.convertToValidFilename(message.getAS2Info().getMessageId()));
if (message.getPayloadCount() > 1) {
pendingFilename.append("_").append(String.valueOf(i));
}
File pendingFile = new File(pendingFilename.toString());
payload.writeTo(pendingFile);
payload.setPayloadFilename(pendingFile.getAbsolutePath());
}
MessageAccessDB messageAccess = new MessageAccessDB(this.configConnection, this.runtimeConnection);
messageAccess.insertPayload(message.getAS2Info().getMessageId(), message.getPayloads());
File decryptedRawFile = new File(message.getAS2Info().getRawFilename() + ".decrypted");
FileOutputStream outStream = new FileOutputStream(decryptedRawFile);
ByteArrayInputStream inStream = new ByteArrayInputStream(message.getDecryptedRawData());
this.copyStreams(inStream, outStream);
outStream.flush();
outStream.close();
((AS2MessageInfo) message.getAS2Info()).setRawFilenameDecrypted(decryptedRawFile.getAbsolutePath());
}
}
/**Stores the message if an error occured during creation
*or sending the message
*/
public void storeSentErrorMessage(AS2Message message, Partner localstation, Partner receiver) throws Exception {
DateFormat format = new SimpleDateFormat("yyyyMMdd");
StringBuilder errorDirName = new StringBuilder();
errorDirName.append(new File(this.preferences.get(PreferencesAS2.DIR_MSG)).getAbsolutePath());
errorDirName.append(File.separator);
errorDirName.append(convertToValidFilename(receiver.getName())).append(File.separator).append("error");
errorDirName.append(File.separator).append(convertToValidFilename(localstation.getName()));
errorDirName.append(File.separator).append(format.format(new Date()));
//store sent message
File errorDir = new File(errorDirName.toString());
//ensure the directory exists
if (!errorDir.exists()) {
boolean created = errorDir.mkdirs();
if (!created) {
this.logger.warning(this.rb.getResourceString("dir.createerror",
errorDir.getAbsolutePath()));
}
}
//write out the payload(s)
for (int i = 0; i < message.getPayloadCount(); i++) {
File payloadFile = File.createTempFile("AS2Message", ".as2", errorDir);
message.getPayload(i).writeTo(payloadFile);
message.getPayload(i).setPayloadFilename(payloadFile.getAbsolutePath());
this.logger.log(Level.SEVERE, this.rb.getResourceString("message.error.stored",
new Object[]{
message.getAS2Info().getMessageId(),
payloadFile.getAbsolutePath()
}), message.getAS2Info());
}
//write raw file to error/raw
File errorRawDir = new File(errorDir.getAbsolutePath() + File.separator + "raw");
//ensure the directory exists
if (!errorRawDir.exists()) {
boolean created = errorRawDir.mkdirs();
if (!created) {
this.logger.warning(this.rb.getResourceString("dir.createerror",
errorRawDir.getAbsolutePath()));
}
}
File errorRawFile = File.createTempFile("error", ".raw", errorRawDir);
message.writeRawDecryptedTo(errorRawFile);
this.logger.log(Level.SEVERE, this.rb.getResourceString("message.error.raw.stored",
new Object[]{
message.getAS2Info().getMessageId(), errorRawFile.getAbsolutePath()
}), message.getAS2Info());
MessageAccessDB messageAccess = new MessageAccessDB(this.configConnection, this.runtimeConnection);
if (!message.getAS2Info().isMDN()) {
AS2MessageInfo messageInfo = (AS2MessageInfo) message.getAS2Info();
messageInfo.setRawFilenameDecrypted(errorRawFile.getAbsolutePath());
//update the filenames in the db
messageAccess.updateFilenames(messageInfo);
}
messageAccess.insertPayload(message.getAS2Info().getMessageId(), message.getPayloads());
}
/**Stores an outgoing message in a sent directory
*/
public void storeSentMessage(AS2Message message, Partner localstation, Partner receiver, Properties header) throws Exception {
DateFormat format = new SimpleDateFormat("yyyyMMdd");
String receiverName = "unidentified";
if (receiver != null) {
receiverName = convertToValidFilename(receiver.getName());
}
String localStationName = "unknown";
if (localstation != null) {
localStationName = convertToValidFilename(localstation.getName());
}
//store sent message
File sentDir = new File(new File(this.preferences.get(PreferencesAS2.DIR_MSG)).getAbsolutePath() + File.separator + receiverName + File.separator + "sent" + File.separator + localStationName + File.separator + format.format(new Date()));
//ensure the directory exists
if (!sentDir.exists()) {
boolean created = sentDir.mkdirs();
if (!created) {
this.logger.warning(this.rb.getResourceString("dir.createerror",
sentDir.getAbsolutePath()));
}
}
AS2Info as2Info = message.getAS2Info();
String requestType = "";
if (as2Info.isMDN()) {
requestType = "_MDN";
}
StringBuilder rawFilename = new StringBuilder();
rawFilename.append(sentDir.getAbsolutePath());
rawFilename.append(File.separator);
rawFilename.append(MessageStoreHandler.convertToValidFilename(as2Info.getMessageId()));
rawFilename.append(requestType);
rawFilename.append(".as2");
File headerFile = new File(rawFilename.toString() + ".header");
FileOutputStream outStream = new FileOutputStream(headerFile);
Enumeration enumeration = header.keys();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
outStream.write((key + " = " + header.getProperty(key) + CRLF).getBytes());
}
outStream.close();
outStream.flush();
as2Info.setHeaderFilename(headerFile.getAbsolutePath());
File rawFile = new File(rawFilename.toString());
outStream = new FileOutputStream(rawFile);
ByteArrayInputStream inStream = new ByteArrayInputStream(message.getDecryptedRawData());
this.copyStreams(inStream, outStream);
inStream.close();
outStream.flush();
outStream.close();
byte[] contentSource = null;
if (as2Info.isMDN()) {
contentSource = message.getRawData();
} else {
contentSource = message.getDecryptedRawData();
}
File rawFileDecrypted = new File(rawFilename.toString() + ".decrypted");
outStream = new FileOutputStream(rawFileDecrypted);
inStream = new ByteArrayInputStream(contentSource);
this.copyStreams(inStream, outStream);
inStream.close();
outStream.flush();
outStream.close();
for (int i = 0; i < message.getPayloadCount(); i++) {
StringBuilder payloadFilename = new StringBuilder();
payloadFilename.append(sentDir.getAbsolutePath()).append(File.separator);
String originalFilename = message.getPayload(i).getOriginalFilename();
if (originalFilename == null) {
originalFilename = "unknown";
}
payloadFilename.append(MessageStoreHandler.convertToValidFilename(as2Info.getMessageId()));
payloadFilename.append(".payload");
File payloadFile = new File(payloadFilename.toString());
message.getPayload(i).writeTo(payloadFile);
message.getPayload(i).setPayloadFilename(payloadFile.getAbsolutePath());
}
//set all filenames to the message object
as2Info.setRawFilename(rawFile.getAbsolutePath());
as2Info.setHeaderFilename(headerFile.getAbsolutePath());
//update the filenames in the db
MessageAccessDB messageAccess = new MessageAccessDB(this.configConnection, this.runtimeConnection);
if (!as2Info.isMDN()) {
AS2MessageInfo messageInfo = (AS2MessageInfo) as2Info;
messageInfo.setRawFilenameDecrypted(rawFileDecrypted.getAbsolutePath());
messageAccess.updateFilenames(messageInfo);
}
messageAccess.insertPayload(message.getAS2Info().getMessageId(), message.getPayloads());
}
/**Converts a suggested filename to a valid filename. This may be necessary if as2 ids contain chars that are not allowed in
*the current file system
*/
public static String convertToValidFilename(String filename) {
File file = new File(filename);
//no trouble, file already exists on the file system
if (file.exists()) {
return (filename);
}
//seems not to be a valid filename, replace some chars
StringBuilder buffer = new StringBuilder();
for (int i = 0, length = filename.length(); i < length; i++) {
char c = filename.charAt(i);
int type = Character.getType(c);
if (c == '@' || type == Character.DECIMAL_DIGIT_NUMBER || type == Character.LETTER_NUMBER || type == Character.LOWERCASE_LETTER || type == Character.OTHER_LETTER || type == Character.OTHER_NUMBER || type == Character.TITLECASE_LETTER || type == Character.UPPERCASE_LETTER) {
buffer.append(c);
} else {
buffer.append('_');
}
}
return (buffer.toString());
}
/**Stores the status information for outbound transactions in a file*/
public void writeOutboundStatusFile(AS2MessageInfo messageInfo) throws Exception {
//ignore the write process if this is not requested in the preferences
if (!this.preferences.getBoolean(PreferencesAS2.WRITE_OUTBOUND_STATUS_FILE)) {
return;
}
PartnerAccessDB partnerAccessDB = new PartnerAccessDB(this.configConnection, this.configConnection);
Partner sender = partnerAccessDB.getPartner(messageInfo.getSenderId());
Partner receiver = partnerAccessDB.getPartner(messageInfo.getReceiverId());
MessageAccessDB access = new MessageAccessDB(this.configConnection, this.runtimeConnection);
List<AS2Payload> payload = access.getPayload(messageInfo.getMessageId());
//deal with the status directory
File statusDir = new File("outboundstatus");
//ensure the directory exists
if (!statusDir.exists()) {
boolean created = statusDir.mkdirs();
if (!created) {
this.logger.warning(this.rb.getResourceString("dir.createerror",
statusDir.getAbsolutePath()));
}
}
StringBuilder rawFilename = new StringBuilder();
rawFilename.append(statusDir.getAbsolutePath());
rawFilename.append(File.separator);
for (int i = 0; i < payload.size(); i++) {
rawFilename.append(payload.get(i).getOriginalFilename());
rawFilename.append("_");
}
rawFilename.append(messageInfo.getMessageId());
rawFilename.append(".sent.state");
File statusFile = new File(rawFilename.toString());
FileOutputStream outStream = new FileOutputStream(statusFile);
outStream.write("product=".getBytes());
outStream.write(AS2ServerVersion.getProductName().getBytes());
outStream.write(" ".getBytes());
outStream.write(AS2ServerVersion.getVersion().getBytes());
outStream.write(" ".getBytes());
outStream.write(AS2ServerVersion.getBuild().getBytes());
outStream.write("\n".getBytes());
for (int i = 0; i < payload.size(); i++) {
String originalFileKey = "originalfile." + i + "=";
outStream.write(originalFileKey.getBytes());
outStream.write(payload.get(i).getOriginalFilename().getBytes());
outStream.write("\n".getBytes());
}
outStream.write("messageid=".getBytes());
outStream.write(messageInfo.getMessageId().getBytes());
outStream.write("\n".getBytes());
outStream.write("sender=".getBytes());
outStream.write(sender.getName().getBytes());
outStream.write("\n".getBytes());
outStream.write("senderAS2Id=".getBytes());
outStream.write(sender.getAS2Identification().getBytes());
outStream.write("\n".getBytes());
outStream.write("receiver=".getBytes());
outStream.write(receiver.getName().getBytes());
outStream.write("\n".getBytes());
outStream.write("receiverAS2Id=".getBytes());
outStream.write(receiver.getAS2Identification().getBytes());
outStream.write("\n".getBytes());
outStream.write("state=".getBytes());
if (messageInfo.getState() == AS2Message.STATE_FINISHED) {
outStream.write("OK".getBytes());
} else {
outStream.write("ERROR".getBytes());
}
outStream.flush();
outStream.close();
this.logger.log(Level.FINE, this.rb.getResourceString("outboundstatus.written",
new Object[]{
messageInfo.getMessageId(),
statusFile.getAbsolutePath()
}), messageInfo);
}
}