/**
* The FreeBSD Copyright
* Copyright 1994-2008 The FreeBSD Project. All rights reserved.
* Copyright (C) 2013-2017 Philip Helger philip[at]helger[dot]com
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation
* are those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of the FreeBSD Project.
*/
package com.helger.as2lib.processor.sender;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.Enumeration;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.mail.Header;
import javax.mail.MessagingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.helger.as2lib.disposition.DispositionType;
import com.helger.as2lib.exception.OpenAS2Exception;
import com.helger.as2lib.exception.WrappedOpenAS2Exception;
import com.helger.as2lib.message.AS2Message;
import com.helger.as2lib.message.IMessage;
import com.helger.as2lib.message.IMessageMDN;
import com.helger.as2lib.processor.NoModuleException;
import com.helger.as2lib.processor.storage.IProcessorStorageModule;
import com.helger.as2lib.session.ComponentNotFoundException;
import com.helger.as2lib.util.CAS2Header;
import com.helger.as2lib.util.IOHelper;
import com.helger.as2lib.util.http.AS2HttpHeaderWrapperHttpURLConnection;
import com.helger.as2lib.util.http.HTTPHelper;
import com.helger.as2lib.util.http.IHTTPOutgoingDumper;
import com.helger.commons.io.stream.StreamHelper;
import com.helger.commons.io.stream.WrappedOutputStream;
import com.helger.commons.timing.StopWatch;
import com.helger.http.EHTTPMethod;
public class AsynchMDNSenderModule extends AbstractHttpSenderModule
{
private static final Logger s_aLogger = LoggerFactory.getLogger (AsynchMDNSenderModule.class);
public AsynchMDNSenderModule ()
{}
public boolean canHandle (@Nonnull final String sAction,
@Nonnull final IMessage aMsg,
@Nullable final Map <String, Object> aOptions)
{
return sAction.equals (IProcessorSenderModule.DO_SENDMDN) && aMsg instanceof AS2Message;
}
private void _sendViaHTTP (@Nonnull final AS2Message aMsg,
@Nonnull final DispositionType aDisposition) throws OpenAS2Exception,
IOException,
MessagingException,
HttpResponseException
{
final IMessageMDN aMdn = aMsg.getMDN ();
// Create a HTTP connection
final String sUrl = aMsg.getAsyncMDNurl ();
final boolean bOutput = true;
final boolean bInput = true;
final boolean bUseCaches = false;
final EHTTPMethod eRequestMethod = EHTTPMethod.POST;
final HttpURLConnection aConn = getConnection (sUrl,
bOutput,
bInput,
bUseCaches,
eRequestMethod,
getSession ().getHttpProxy ());
try
{
s_aLogger.info ("connected to " + sUrl + aMsg.getLoggingText ());
final AS2HttpHeaderWrapperHttpURLConnection aHeaderWrapper = new AS2HttpHeaderWrapperHttpURLConnection (aConn);
aHeaderWrapper.setHttpHeader (CAS2Header.HEADER_CONNECTION, CAS2Header.DEFAULT_CONNECTION);
aHeaderWrapper.setHttpHeader (CAS2Header.HEADER_USER_AGENT, CAS2Header.DEFAULT_USER_AGENT);
// Copy all the header from mdn to the RequestProperties of conn
final Enumeration <?> aHeaders = aMdn.getHeaders ().getAllHeaders ();
while (aHeaders.hasMoreElements ())
{
final Header aHeader = (Header) aHeaders.nextElement ();
aHeaderWrapper.setHttpHeader (aHeader.getName (), aHeader.getValue ());
}
// Note: closing this stream causes connection abort errors on some AS2
// servers
OutputStream aMsgOS = aConn.getOutputStream ();
// This stream dumps the HTTP
OutputStream aDebugOS = null;
final IHTTPOutgoingDumper aHttpDumper = HTTPHelper.getHTTPOutgoingDumper ();
if (aHttpDumper != null)
{
aDebugOS = aHttpDumper.dumpOutgoingRequest (aMdn);
if (aDebugOS != null)
{
// Overwrite the used OutputStream to additionally log to the debug
// OutputStream
final OutputStream aFinalDebugOS = aDebugOS;
aMsgOS = new WrappedOutputStream (aMsgOS)
{
@Override
public final void write (final int b) throws IOException
{
super.write (b);
aFinalDebugOS.write (b);
}
};
}
}
// Transfer the data
final InputStream aMessageIS = aMdn.getData ().getInputStream ();
final StopWatch aSW = StopWatch.createdStarted ();
final long nBytes = IOHelper.copy (aMessageIS, aMsgOS);
aSW.stop ();
s_aLogger.info ("transferred " + IOHelper.getTransferRate (nBytes, aSW) + aMsg.getLoggingText ());
// Close debug OS (if used)
StreamHelper.close (aDebugOS);
// Check the HTTP Response code
final int nResponseCode = aConn.getResponseCode ();
if (nResponseCode != HttpURLConnection.HTTP_OK &&
nResponseCode != HttpURLConnection.HTTP_CREATED &&
nResponseCode != HttpURLConnection.HTTP_ACCEPTED &&
nResponseCode != HttpURLConnection.HTTP_PARTIAL &&
nResponseCode != HttpURLConnection.HTTP_NO_CONTENT)
{
s_aLogger.error ("sent AsyncMDN [" + aDisposition.getAsString () + "] Fail " + aMsg.getLoggingText ());
throw new HttpResponseException (sUrl, nResponseCode, aConn.getResponseMessage ());
}
s_aLogger.info ("sent AsyncMDN [" + aDisposition.getAsString () + "] OK " + aMsg.getLoggingText ());
// log & store mdn into backup folder.
try
{
getSession ().getMessageProcessor ().handle (IProcessorStorageModule.DO_STOREMDN, aMsg, null);
}
catch (final ComponentNotFoundException ex)
{
// No message processor found
}
catch (final NoModuleException ex)
{
// No module found in message processor
}
}
finally
{
aConn.disconnect ();
}
}
public void handle (@Nonnull final String sAction,
@Nonnull final IMessage aBaseMsg,
@Nullable final Map <String, Object> aOptions) throws OpenAS2Exception
{
try
{
final AS2Message aMsg = (AS2Message) aBaseMsg;
s_aLogger.info ("Async MDN submitted" + aMsg.getLoggingText ());
final DispositionType aDisposition = DispositionType.createSuccess ();
final int nRetries = getRetryCount (aMsg.getPartnership (), aOptions);
try
{
_sendViaHTTP (aMsg, aDisposition);
}
catch (final HttpResponseException ex)
{
s_aLogger.error ("Http Response Error " + ex.getMessage ());
// Resend if the HTTP Response has an error code
ex.terminate ();
if (!doResend (IProcessorSenderModule.DO_SENDMDN, aMsg, ex, nRetries))
throw ex;
}
catch (final IOException ex)
{
// Resend if a network error occurs during transmission
final OpenAS2Exception wioe = WrappedOpenAS2Exception.wrap (ex);
wioe.addSource (OpenAS2Exception.SOURCE_MESSAGE, aMsg);
wioe.terminate ();
if (!doResend (IProcessorSenderModule.DO_SENDMDN, aMsg, wioe, nRetries))
throw wioe;
}
catch (final Exception ex)
{
// Propagate error if it can't be handled by a resend
throw WrappedOpenAS2Exception.wrap (ex);
}
}
finally
{
if (s_aLogger.isDebugEnabled ())
s_aLogger.debug ("Async MDN message sent");
}
}
}