de.mendelson.comm.as2.send.MessageHttpUploader.java Source code

Java tutorial

Introduction

Here is the source code for de.mendelson.comm.as2.send.MessageHttpUploader.java

Source

//$Header: /cvsroot-fuse/mec-as2/39/mendelson/comm/as2/send/MessageHttpUploader.java,v 1.1 2012/04/18 14:10:35 heller Exp $
package de.mendelson.comm.as2.send;

import de.mendelson.comm.as2.client.rmi.GenericClient;
import de.mendelson.comm.as2.clientserver.ErrorObject;
import de.mendelson.comm.as2.clientserver.message.RefreshClientMessageOverviewList;
import de.mendelson.comm.as2.clientserver.serialize.CommandObjectIncomingMessage;
import de.mendelson.comm.as2.message.AS2Info;
import de.mendelson.comm.as2.message.AS2MDNInfo;
import de.mendelson.comm.as2.message.AS2Message;
import de.mendelson.comm.as2.message.AS2MessageInfo;
import de.mendelson.comm.as2.message.MDNAccessDB;
import de.mendelson.comm.as2.message.MessageAccessDB;
import de.mendelson.comm.as2.message.store.MessageStoreHandler;
import de.mendelson.comm.as2.partner.HTTPAuthentication;
import de.mendelson.comm.as2.partner.Partner;
import de.mendelson.comm.as2.partner.PartnerHttpHeader;
import de.mendelson.comm.as2.preferences.PreferencesAS2;
import de.mendelson.comm.as2.server.AS2Server;
import de.mendelson.comm.as2.statistic.QuotaAccessDB;
import de.mendelson.util.AS2Tools;
import de.mendelson.util.MecResourceBundle;
import de.mendelson.util.clientserver.ClientServer;
import de.mendelson.util.security.BCCryptoHelper;
import de.mendelson.util.security.cert.KeystoreStorage;
import de.mendelson.util.security.cert.KeystoreStorageImplFile;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.sql.Connection;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.StatusLine;
import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.conn.params.ConnRouteParams;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.NoConnectionReuseStrategy;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;

/*
 * 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.
 */
/**
 * Class to allow HTTP multipart uploads
 * @author  S.Heller
 * @version $Revision: 1.1 $
 */
public class MessageHttpUploader {

    private Logger logger = null;
    private PreferencesAS2 preferences = new PreferencesAS2();
    /**localisze the GUI*/
    private MecResourceBundle rb = null;
    /**The header that has been built fro the request*/
    private Properties requestHeader = new Properties();
    /**remote answer*/
    private byte[] responseData = null;
    /**remote answer*/
    private Header[] responseHeader = null;
    /**remote answer*/
    private StatusLine responseStatusLine = null;
    private ClientServer clientserver = null;
    //DB connection
    private Connection configConnection = null;
    private Connection runtimeConnection = null;
    //keystore data
    private KeystoreStorage certStore = null;
    private KeystoreStorage trustStore = null;

    /** Creates new message uploader instance
     * @param hostname Name of the host to connect to
     * @param username Name of the user that will connect to the remote ftp server
     * @param password password to connect to the ftp server
     */
    public MessageHttpUploader() throws Exception {
        //Load default resourcebundle
        try {
            this.rb = (MecResourceBundle) ResourceBundle.getBundle(ResourceBundleHttpUploader.class.getName());
        } //load up resourcebundle
        catch (MissingResourceException e) {
            throw new RuntimeException("Oops..resource bundle " + e.getClassName() + " not found.");
        }
    }

    /**Sets keystore parameter for SSL sending. This is only necessary if HTTPS is the
     * protocol used for the message POST
     * @param truststore Truststore file
     * @param truststorePass Password for the truststore
     * @param certstore Keystore file
     * @param certstorePass Password for the keystore
     */
    public void setSSLParameter(KeystoreStorage certStore, KeystoreStorage trustStore) {
        this.certStore = certStore;
        this.trustStore = trustStore;
    }

    /**Passes a logger to this class for logging purpose*/
    public void setLogger(Logger logger) {
        this.logger = logger;
    }

    /**Passes a server instance to this class to refresh messages automatically for logging purpose*/
    public void setAbstractServer(ClientServer clientserver) {
        this.clientserver = clientserver;
    }

    /**Pass a DB connection to this class for loggin purpose*/
    public void setDBConnection(Connection configConnection, Connection runtimeConnection) {
        this.configConnection = configConnection;
        this.runtimeConnection = runtimeConnection;
    }

    /**Returns the created header for the sent data*/
    public Properties upload(HttpConnectionParameter connectionParameter, AS2Message message, Partner sender,
            Partner receiver) throws Exception {
        NumberFormat formatter = new DecimalFormat("0.00");
        AS2Info as2Info = message.getAS2Info();
        MessageAccessDB messageAccess = null;
        MDNAccessDB mdnAccess = null;
        if (this.runtimeConnection != null && messageAccess == null && !as2Info.isMDN()) {
            messageAccess = new MessageAccessDB(this.configConnection, this.runtimeConnection);
            messageAccess.initializeOrUpdateMessage((AS2MessageInfo) as2Info);
        } else if (this.runtimeConnection != null && as2Info.isMDN()) {
            mdnAccess = new MDNAccessDB(this.configConnection, this.runtimeConnection);
            mdnAccess.initializeOrUpdateMDN((AS2MDNInfo) as2Info);
        }
        if (this.clientserver != null) {
            this.clientserver.broadcastToClients(new RefreshClientMessageOverviewList());
        }
        long startTime = System.currentTimeMillis();
        //sets the global requestHeader
        int returnCode = this.performUpload(connectionParameter, message, sender, receiver);
        long size = message.getRawDataSize();
        long transferTime = System.currentTimeMillis() - startTime;
        float bytePerSec = (float) ((float) size * 1000f / (float) transferTime);
        float kbPerSec = (float) (bytePerSec / 1024f);
        if (returnCode == HttpServletResponse.SC_OK) {
            if (this.logger != null) {
                this.logger.log(Level.INFO,
                        this.rb.getResourceString("returncode.ok",
                                new Object[] { as2Info.getMessageId(), String.valueOf(returnCode),
                                        AS2Tools.getDataSizeDisplay(size), AS2Tools.getTimeDisplay(transferTime),
                                        formatter.format(kbPerSec), }),
                        as2Info);
            }
        } else if (returnCode == HttpServletResponse.SC_ACCEPTED || returnCode == HttpServletResponse.SC_CREATED
                || returnCode == HttpServletResponse.SC_NO_CONTENT
                || returnCode == HttpServletResponse.SC_RESET_CONTENT
                || returnCode == HttpServletResponse.SC_PARTIAL_CONTENT) {
            if (this.logger != null) {
                this.logger.log(Level.INFO,
                        this.rb.getResourceString("returncode.accepted",
                                new Object[] { as2Info.getMessageId(), String.valueOf(returnCode),
                                        AS2Tools.getDataSizeDisplay(size), AS2Tools.getTimeDisplay(transferTime),
                                        formatter.format(kbPerSec), }),
                        as2Info);
            }
        } else {
            //the system was unable to connect the partner
            if (returnCode < 0) {
                throw new NoConnectionException(
                        this.rb.getResourceString("error.noconnection", as2Info.getMessageId()));
            }
            if (this.runtimeConnection != null) {
                if (messageAccess == null) {
                    messageAccess = new MessageAccessDB(this.configConnection, this.runtimeConnection);
                }
                messageAccess.setMessageState(as2Info.getMessageId(), AS2Message.STATE_STOPPED);
            }
            throw new Exception(as2Info.getMessageId() + ": HTTP " + returnCode);
        }
        if (this.configConnection != null) {
            //inc the sent data size, this is for new connections (as2 messages, async mdn)
            AS2Server.incRawSentData(size);
            if (message.getAS2Info().isMDN()) {
                AS2MDNInfo mdnInfo = (AS2MDNInfo) message.getAS2Info();
                //ASYNC MDN sent: insert an entry into the statistic table
                QuotaAccessDB.incReceivedMessages(this.configConnection, this.runtimeConnection,
                        mdnInfo.getSenderId(), mdnInfo.getReceiverId(), mdnInfo.getState(),
                        mdnInfo.getRelatedMessageId());
            }
        }
        if (this.configConnection != null) {
            MessageStoreHandler messageStoreHandler = new MessageStoreHandler(this.configConnection,
                    this.runtimeConnection);
            messageStoreHandler.storeSentMessage(message, sender, receiver, this.requestHeader);
        }
        //inform the server of the result if a sync MDN has been requested
        if (!message.isMDN()) {
            AS2MessageInfo messageInfo = (AS2MessageInfo) message.getAS2Info();
            if (messageInfo.requestsSyncMDN()) {
                //perform a check if the answer really contains a MDN or is just an empty HTTP 200 with some header data
                //this check looks for the existance of some key header values
                boolean as2FromExists = false;
                boolean as2ToExists = false;
                for (int i = 0; i < this.getResponseHeader().length; i++) {
                    String key = this.getResponseHeader()[i].getName();
                    if (key.toLowerCase().equals("as2-to")) {
                        as2ToExists = true;
                    } else if (key.toLowerCase().equals("as2-from")) {
                        as2FromExists = true;
                    }
                }
                if (!as2ToExists) {
                    throw new Exception(this.rb.getResourceString("answer.no.sync.mdn",
                            new Object[] { as2Info.getMessageId(), "as2-to" }));
                }
                //send the data to the as2 server. It does not care if the MDN has been sync or async anymore
                GenericClient client = new GenericClient();
                CommandObjectIncomingMessage commandObject = new CommandObjectIncomingMessage();
                //create temporary file to store the data
                File tempFile = AS2Tools.createTempFile("SYNCMDN_received", ".bin");
                FileOutputStream outStream = new FileOutputStream(tempFile);
                ByteArrayInputStream memIn = new ByteArrayInputStream(this.responseData);
                this.copyStreams(memIn, outStream);
                memIn.close();
                outStream.flush();
                outStream.close();
                commandObject.setMessageDataFilename(tempFile.getAbsolutePath());
                for (int i = 0; i < this.getResponseHeader().length; i++) {
                    String key = this.getResponseHeader()[i].getName();
                    String value = this.getResponseHeader()[i].getValue();
                    commandObject.addHeader(key.toLowerCase(), value);
                    if (key.toLowerCase().equals("content-type")) {
                        commandObject.setContentType(value);
                    }
                }
                //compatibility issue: some AS2 systems do not send a as2-from in the sync case, even if
                //this if _NOT_ RFC conform
                //see RFC 4130, section 6.2: The AS2-To and AS2-From header fields MUST be
                //present in all AS2 messages and AS2 MDNs whether asynchronous or synchronous in nature,
                //except for asynchronous MDNs, which are sent using SMTP.
                if (!as2FromExists) {
                    commandObject.addHeader("as2-from",
                            AS2Message.escapeFromToHeader(receiver.getAS2Identification()));
                }
                ErrorObject errorObject = client.send(commandObject);
                if (errorObject.getErrors() > 0) {
                    messageAccess.setMessageState(as2Info.getMessageId(), AS2Message.STATE_STOPPED);
                }
                tempFile.delete();
            }
        }
        return (this.requestHeader);
    }

    /**Sets necessary HTTP authentication for this partner, depending on if it is an asny MDN that will be sent or an AS2 message.
     *If the partner is not configured to use HTTP authentication in any kind nothing will happen in here
     */
    private void setHTTPAuthentication(DefaultHttpClient client, Partner receiver, boolean isMDN) {
        HTTPAuthentication authentication = null;
        if (isMDN) {
            authentication = receiver.getAuthenticationAsyncMDN();
        } else {
            authentication = receiver.getAuthentication();
        }
        if (authentication.isEnabled()) {
            Credentials userPassCredentials = new UsernamePasswordCredentials(authentication.getUser(),
                    authentication.getPassword());
            client.getCredentialsProvider().setCredentials(AuthScope.ANY, userPassCredentials);
            BasicHttpContext localcontext = new BasicHttpContext();
            // Generate BASIC scheme object and stick it to the local
            // execution context
            BasicScheme basicAuth = new BasicScheme();
            localcontext.setAttribute("preemptive-auth", basicAuth);
            // Add as the first request interceptor
            client.addRequestInterceptor(new PreemptiveAuth(), 0);
        }
    }

    /**Builds a proxy object from the actual preferences, returns null if no proxy is requested*/
    public ProxyObject createProxyObjectFromPreferences() {
        if (!this.preferences.getBoolean(PreferencesAS2.PROXY_USE)) {
            //return empty proxy object, is not used
            return (null);
        }
        ProxyObject proxy = new ProxyObject();
        proxy.setHost(this.preferences.get(PreferencesAS2.PROXY_HOST));
        proxy.setPort(this.preferences.getInt(PreferencesAS2.PROXY_PORT));
        if (this.preferences.getBoolean(PreferencesAS2.AUTH_PROXY_USE)) {
            proxy.setUser(this.preferences.get(PreferencesAS2.AUTH_PROXY_USER));
            proxy.setPassword(this.preferences.get(PreferencesAS2.AUTH_PROXY_PASS).toCharArray());
        }
        return (proxy);
    }

    /**Sets the proxy authentification for the client*/
    private void setProxyToConnection(DefaultHttpClient client, AS2Message message, ProxyObject proxy) {
        //is a proxy requested?
        if (proxy.getHost() == null) {
            return;
        }
        HttpHost proxyHost = new HttpHost(proxy.getHost(), proxy.getPort(), "http");
        client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxyHost);
        if (proxy.getUser() != null) {
            CredentialsProvider credsProvider = new BasicCredentialsProvider();
            credsProvider.setCredentials(new AuthScope(proxy.getHost(), proxy.getPort()),
                    new UsernamePasswordCredentials(proxy.getUser(), String.valueOf(proxy.getPassword())));
            client.setCredentialsProvider(credsProvider);
        }
        if (this.logger != null) {
            this.logger.log(Level.INFO, this.rb.getResourceString("using.proxy", new Object[] {
                    message.getAS2Info().getMessageId(), proxy.getHost(), String.valueOf(proxy.getPort()), }),
                    message.getAS2Info());
        }
    }

    /**Uploads the data, returns the HTTP result code*/
    public int performUpload(HttpConnectionParameter connectionParameter, AS2Message message, Partner sender,
            Partner receiver) {
        return (this.performUpload(connectionParameter, message, sender, receiver, null));
    }

    /**Uploads the data, returns the HTTP result code*/
    public int performUpload(HttpConnectionParameter connectionParameter, AS2Message message, Partner sender,
            Partner receiver, URL receiptURL) {
        String ediintFeatures = "multiple-attachments, CEM";
        //set the http connection/routing/protocol parameter
        HttpParams httpParams = new BasicHttpParams();
        if (connectionParameter.getConnectionTimeoutMillis() != -1) {
            HttpConnectionParams.setConnectionTimeout(httpParams, connectionParameter.getConnectionTimeoutMillis());
        }
        if (connectionParameter.getSoTimeoutMillis() != -1) {
            HttpConnectionParams.setSoTimeout(httpParams, connectionParameter.getSoTimeoutMillis());
        }
        HttpConnectionParams.setStaleCheckingEnabled(httpParams, connectionParameter.isStaleConnectionCheck());
        if (connectionParameter.getHttpProtocolVersion() == null) {
            //default settings: HTTP 1.1
            HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
        } else if (connectionParameter.getHttpProtocolVersion().equals(HttpConnectionParameter.HTTP_1_0)) {
            HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_0);
        } else if (connectionParameter.getHttpProtocolVersion().equals(HttpConnectionParameter.HTTP_1_1)) {
            HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
        }
        HttpProtocolParams.setUseExpectContinue(httpParams, connectionParameter.isUseExpectContinue());
        HttpProtocolParams.setUserAgent(httpParams, connectionParameter.getUserAgent());
        if (connectionParameter.getLocalAddress() != null) {
            ConnRouteParams.setLocalAddress(httpParams, connectionParameter.getLocalAddress());
        }
        int status = -1;
        HttpPost filePost = null;
        DefaultHttpClient httpClient = null;
        try {
            ClientConnectionManager clientConnectionManager = this.createClientConnectionManager(httpParams);
            httpClient = new DefaultHttpClient(clientConnectionManager, httpParams);
            //some ssl implementations have problems with a session/connection reuse
            httpClient.setReuseStrategy(new NoConnectionReuseStrategy());
            //disable SSL hostname verification. Do not confuse this with SSL trust verification!
            SSLSocketFactory sslFactory = (SSLSocketFactory) httpClient.getConnectionManager().getSchemeRegistry()
                    .get("https").getSocketFactory();
            sslFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            //determine the receipt URL if it is not set
            if (receiptURL == null) {
                //async MDN requested?
                if (message.isMDN()) {
                    if (this.runtimeConnection == null) {
                        throw new IllegalArgumentException(
                                "MessageHTTPUploader.performUpload(): A MDN receipt URL is not set, unable to determine where to send the MDN");
                    }
                    MessageAccessDB messageAccess = new MessageAccessDB(this.configConnection,
                            this.runtimeConnection);
                    AS2MessageInfo relatedMessageInfo = messageAccess
                            .getLastMessageEntry(((AS2MDNInfo) message.getAS2Info()).getRelatedMessageId());
                    receiptURL = new URL(relatedMessageInfo.getAsyncMDNURL());
                } else {
                    receiptURL = new URL(receiver.getURL());
                }
            }
            filePost = new HttpPost(receiptURL.toExternalForm());
            filePost.addHeader("as2-version", "1.2");
            filePost.addHeader("ediint-features", ediintFeatures);
            filePost.addHeader("mime-version", "1.0");
            filePost.addHeader("recipient-address", receiptURL.toExternalForm());
            filePost.addHeader("message-id", "<" + message.getAS2Info().getMessageId() + ">");
            filePost.addHeader("as2-from", AS2Message.escapeFromToHeader(sender.getAS2Identification()));
            filePost.addHeader("as2-to", AS2Message.escapeFromToHeader(receiver.getAS2Identification()));
            String originalFilename = null;
            if (message.getPayloads() != null && message.getPayloads().size() > 0) {
                originalFilename = message.getPayloads().get(0).getOriginalFilename();
            }
            if (originalFilename != null) {
                String subject = this.replace(message.getAS2Info().getSubject(), "${filename}", originalFilename);
                filePost.addHeader("subject", subject);
                //update the message infos subject with the actual content
                if (!message.isMDN()) {
                    ((AS2MessageInfo) message.getAS2Info()).setSubject(subject);
                    //refresh this in the database if it is requested
                    if (this.runtimeConnection != null) {
                        MessageAccessDB access = new MessageAccessDB(this.configConnection, this.runtimeConnection);
                        access.updateSubject((AS2MessageInfo) message.getAS2Info());
                    }
                }
            } else {
                filePost.addHeader("subject", message.getAS2Info().getSubject());
            }
            filePost.addHeader("from", sender.getEmail());
            filePost.addHeader("connection", "close, TE");
            //the data header must be always in english locale else there would be special
            //french characters (e.g. 13 dc. 2011 16:28:56 CET) which is not allowed after 
            //RFC 4130           
            DateFormat format = new SimpleDateFormat("EE, dd MMM yyyy HH:mm:ss zz", Locale.US);
            filePost.addHeader("date", format.format(new Date()));
            String contentType = null;
            if (message.getAS2Info().getEncryptionType() != AS2Message.ENCRYPTION_NONE) {
                contentType = "application/pkcs7-mime; smime-type=enveloped-data; name=smime.p7m";
            } else {
                contentType = message.getContentType();
            }
            filePost.addHeader("content-type", contentType);
            //MDN header, this is always the way for async MDNs
            if (message.isMDN()) {
                if (this.logger != null) {
                    this.logger.log(Level.INFO,
                            this.rb.getResourceString("sending.mdn.async",
                                    new Object[] { message.getAS2Info().getMessageId(), receiptURL }),
                            message.getAS2Info());
                }
                filePost.addHeader("server", message.getAS2Info().getUserAgent());
            } else {
                AS2MessageInfo messageInfo = (AS2MessageInfo) message.getAS2Info();
                //outbound AS2/CEM message
                if (messageInfo.requestsSyncMDN()) {
                    if (this.logger != null) {
                        if (messageInfo.getMessageType() == AS2Message.MESSAGETYPE_CEM) {
                            this.logger.log(Level.INFO,
                                    this.rb.getResourceString("sending.cem.sync",
                                            new Object[] { messageInfo.getMessageId(), receiver.getURL() }),
                                    messageInfo);
                        } else if (messageInfo.getMessageType() == AS2Message.MESSAGETYPE_AS2) {
                            this.logger.log(Level.INFO,
                                    this.rb.getResourceString("sending.msg.sync",
                                            new Object[] { messageInfo.getMessageId(), receiver.getURL() }),
                                    messageInfo);
                        }
                    }
                } else {
                    //Message with ASYNC MDN request
                    if (this.logger != null) {
                        if (messageInfo.getMessageType() == AS2Message.MESSAGETYPE_CEM) {
                            this.logger.log(Level.INFO,
                                    this.rb.getResourceString("sending.cem.async", new Object[] {
                                            messageInfo.getMessageId(), receiver.getURL(), sender.getMdnURL() }),
                                    messageInfo);
                        } else if (messageInfo.getMessageType() == AS2Message.MESSAGETYPE_AS2) {
                            this.logger.log(Level.INFO,
                                    this.rb.getResourceString("sending.msg.async", new Object[] {
                                            messageInfo.getMessageId(), receiver.getURL(), sender.getMdnURL() }),
                                    messageInfo);
                        }
                    }
                    //The following header indicates that this requests an asnc MDN.
                    //When the header "receipt-delivery-option" is present,
                    //the header "disposition-notification-to" serves as a request
                    //for an asynchronous MDN.
                    //The header "receipt-delivery-option" must always be accompanied by
                    //the header "disposition-notification-to".
                    //When the header "receipt-delivery-option" is not present and the header
                    //"disposition-notification-to" is present, the header "disposition-notification-to"
                    //serves as a request for a synchronous MDN.
                    filePost.addHeader("receipt-delivery-option", sender.getMdnURL());
                }
                filePost.addHeader("disposition-notification-to", sender.getMdnURL());
                //request a signed MDN if this is set up in the partner configuration
                if (receiver.isSignedMDN()) {
                    filePost.addHeader("disposition-notification-options",
                            messageInfo.getDispositionNotificationOptions().getHeaderValue());
                }
                if (messageInfo.getSignType() != AS2Message.SIGNATURE_NONE) {
                    filePost.addHeader("content-disposition", "attachment; filename=\"smime.p7m\"");
                } else if (messageInfo.getSignType() == AS2Message.SIGNATURE_NONE
                        && message.getAS2Info().getSignType() == AS2Message.ENCRYPTION_NONE) {
                    filePost.addHeader("content-disposition",
                            "attachment; filename=\"" + message.getPayload(0).getOriginalFilename() + "\"");
                }
            }
            int port = receiptURL.getPort();
            if (port == -1) {
                port = receiptURL.getDefaultPort();
            }
            filePost.addHeader("host", receiptURL.getHost() + ":" + port);
            InputStream rawDataInputStream = message.getRawDataInputStream();
            InputStreamEntity postEntity = new InputStreamEntity(rawDataInputStream, message.getRawDataSize());
            postEntity.setContentType(contentType);
            filePost.setEntity(postEntity);
            if (connectionParameter.getProxy() != null) {
                this.setProxyToConnection(httpClient, message, connectionParameter.getProxy());
            }
            this.setHTTPAuthentication(httpClient, receiver, message.getAS2Info().isMDN());
            this.updateUploadHttpHeader(filePost, receiver);
            HttpHost targetHost = new HttpHost(receiptURL.getHost(), receiptURL.getPort(),
                    receiptURL.getProtocol());
            BasicHttpContext localcontext = new BasicHttpContext();
            // Generate BASIC scheme object and stick it to the local
            // execution context. Without this a HTTP authentication will not be sent
            BasicScheme basicAuth = new BasicScheme();
            localcontext.setAttribute("preemptive-auth", basicAuth);
            HttpResponse httpResponse = httpClient.execute(targetHost, filePost, localcontext);
            rawDataInputStream.close();
            this.responseData = this.readEntityData(httpResponse);
            if (httpResponse != null) {
                this.responseStatusLine = httpResponse.getStatusLine();
                status = this.responseStatusLine.getStatusCode();
                this.responseHeader = httpResponse.getAllHeaders();
            }
            for (Header singleHeader : filePost.getAllHeaders()) {
                if (singleHeader.getValue() != null) {
                    this.requestHeader.setProperty(singleHeader.getName(), singleHeader.getValue());
                }
            }
            //accept all 2xx answers
            //SC_ACCEPTED Status code (202) indicating that a request was accepted for processing, but was not completed.
            //SC_CREATED  Status code (201) indicating the request succeeded and created a new resource on the server.
            //SC_NO_CONTENT Status code (204) indicating that the request succeeded but that there was no new information to return.
            //SC_NON_AUTHORITATIVE_INFORMATION Status code (203) indicating that the meta information presented by the client did not originate from the server.
            //SC_OK Status code (200) indicating the request succeeded normally.
            //SC_RESET_CONTENT Status code (205) indicating that the agent SHOULD reset the document view which caused the request to be sent.
            //SC_PARTIAL_CONTENT Status code (206) indicating that the server has fulfilled the partial GET request for the resource.
            if (status != HttpServletResponse.SC_OK && status != HttpServletResponse.SC_ACCEPTED
                    && status != HttpServletResponse.SC_CREATED && status != HttpServletResponse.SC_NO_CONTENT
                    && status != HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION
                    && status != HttpServletResponse.SC_RESET_CONTENT
                    && status != HttpServletResponse.SC_PARTIAL_CONTENT) {
                if (this.logger != null) {
                    this.logger
                            .severe(this.rb.getResourceString("error.httpupload",
                                    new Object[] { message.getAS2Info().getMessageId(),
                                            URLDecoder.decode(
                                                    this.responseStatusLine == null ? ""
                                                            : this.responseStatusLine.getReasonPhrase(),
                                                    "UTF-8") }));
                }
            }
        } catch (Exception ex) {
            if (this.logger != null) {
                StringBuilder errorMessage = new StringBuilder(message.getAS2Info().getMessageId());
                errorMessage.append(": MessageHTTPUploader.performUpload: [");
                errorMessage.append(ex.getClass().getSimpleName());
                errorMessage.append("]");
                if (ex.getMessage() != null) {
                    errorMessage.append(": ").append(ex.getMessage());
                }
                this.logger.log(Level.SEVERE, errorMessage.toString(), message.getAS2Info());
            }
        } finally {
            if (httpClient != null && httpClient.getConnectionManager() != null) {
                //shutdown the HTTPClient to release the resources
                httpClient.getConnectionManager().shutdown();
            }
        }
        return (status);
    }

    private ClientConnectionManager createClientConnectionManager(HttpParams httpParams) throws Exception {
        //register protocols
        SchemeRegistry registry = new SchemeRegistry();
        Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
        registry.register(http);
        registry.register(this.createHTTPSScheme());
        ClientConnectionManager manager = new ThreadSafeClientConnManager(httpParams, registry);
        return (manager);
    }

    private Scheme createHTTPSScheme() throws Exception {
        //cert store not set so far: take the preferences data
        if (this.certStore == null) {
            this.certStore = new KeystoreStorageImplFile(this.preferences.get(PreferencesAS2.KEYSTORE_HTTPS_SEND),
                    this.preferences.get(PreferencesAS2.KEYSTORE_HTTPS_SEND_PASS).toCharArray(),
                    BCCryptoHelper.KEYSTORE_JKS);
            this.trustStore = new KeystoreStorageImplFile(this.preferences.get(PreferencesAS2.KEYSTORE_HTTPS_SEND),
                    this.preferences.get(PreferencesAS2.KEYSTORE_HTTPS_SEND_PASS).toCharArray(),
                    BCCryptoHelper.KEYSTORE_JKS);
        }
        SSLSocketFactory socketFactory = new SSLSocketFactory(this.certStore.getKeystore(),
                new String(this.certStore.getKeystorePass()), this.trustStore.getKeystore());
        socketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        return (new Scheme("https", socketFactory, 443));
    }

    /**Updates the passed post HTTP headers with the headers defined for the sender*/
    private void updateUploadHttpHeader(HttpPost post, Partner receiver) {
        List<String> usedHeaderKeys = new ArrayList<String>();
        for (Header singleHeader : post.getAllHeaders()) {
            PartnerHttpHeader headerReplacement = receiver.getHttpHeader(singleHeader.getName());
            if (headerReplacement != null) {
                //a value to replace is set
                if (headerReplacement.getValue() != null && headerReplacement.getValue().length() > 0) {
                    post.setHeader(singleHeader.getName(), headerReplacement.getValue());
                } else {
                    //no value to replace is set: delete the header
                    post.removeHeader(singleHeader);
                }
                usedHeaderKeys.add(singleHeader.getName());
            }
        }
        //add additional user defined headers
        List<PartnerHttpHeader> additionalHeaders = receiver.getAllNonListedHttpHeader(usedHeaderKeys);
        for (PartnerHttpHeader additionalHeader : additionalHeaders) {
            //add the header if a value is set
            if (additionalHeader.getValue() != null && additionalHeader.getValue().length() > 0) {
                post.setHeader(additionalHeader.getKey(), additionalHeader.getValue());
            }
        }
    }

    /** Replaces the string tag by the string replacement in the sourceString
     * @param source Source string
     * @param tag   String that will be replaced
     * @param replacement String that will replace the tag
     * @return String that contains the replaced values
     */
    private String replace(String source, String tag, String replacement) {
        if (source == null) {
            return null;
        }
        StringBuilder buffer = new StringBuilder();
        while (true) {
            int index = source.indexOf(tag);
            if (index == -1) {
                buffer.append(source);
                return (buffer.toString());
            }
            buffer.append(source.substring(0, index));
            buffer.append(replacement);
            source = source.substring(index + tag.length());
        }
    }

    /**Returns the version of this class*/
    public static String getVersion() {
        String revision = "$Revision: 1.1 $";
        return (revision.substring(revision.indexOf(":") + 1, revision.lastIndexOf("$")).trim());
    }

    /**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[1024];
        int read = 1024;
        //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();
    }

    /**Returns the response data as byte array*/
    public byte[] getResponseData() {
        return (this.responseData);
    }

    /**Reads the data of a HTTP response entity*/
    public byte[] readEntityData(HttpResponse httpResponse) throws Exception {
        if (httpResponse == null) {
            return (null);
        }
        if (httpResponse.getEntity() == null) {
            return (null);
        }
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        httpResponse.getEntity().writeTo(outStream);
        outStream.flush();
        outStream.close();
        return (outStream.toByteArray());
    }

    /**Returns the array of response headers after the upload process has been performed
     * @return the responseHeader
     */
    public Header[] getResponseHeader() {
        if (this.responseHeader == null) {
            return (new Header[0]);
        }
        return (this.responseHeader);
    }

    static class PreemptiveAuth implements HttpRequestInterceptor {

        @Override
        public void process(final HttpRequest request, final HttpContext context)
                throws HttpException, IOException {

            AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);

            // If no auth scheme avaialble yet, try to initialize it preemptively
            if (authState.getAuthScheme() == null) {
                AuthScheme authScheme = (AuthScheme) context.getAttribute("preemptive-auth");
                CredentialsProvider credsProvider = (CredentialsProvider) context
                        .getAttribute(ClientContext.CREDS_PROVIDER);
                HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
                if (authScheme != null) {
                    Credentials creds = credsProvider
                            .getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()));
                    if (creds == null) {
                        throw new HttpException("No credentials for preemptive authentication");
                    }
                    authState.setAuthScheme(authScheme);
                    authState.setCredentials(creds);
                }
            }

        }
    }
}