/**
* 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.resender;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;
import java.util.Map;
import java.util.StringTokenizer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.helger.as2lib.exception.OpenAS2Exception;
import com.helger.as2lib.exception.WrappedOpenAS2Exception;
import com.helger.as2lib.message.IMessage;
import com.helger.as2lib.params.InvalidParameterException;
import com.helger.as2lib.processor.sender.IProcessorSenderModule;
import com.helger.as2lib.session.IAS2Session;
import com.helger.as2lib.util.DateHelper;
import com.helger.as2lib.util.IOHelper;
import com.helger.as2lib.util.IStringMap;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.collection.ext.CommonsArrayList;
import com.helger.commons.collection.ext.CommonsHashMap;
import com.helger.commons.collection.ext.ICommonsList;
import com.helger.commons.collection.ext.ICommonsMap;
/**
* An asynchronous, persisting, file based, polling resender module. Upon
* {@link #handle(String, IMessage, Map)} it writes the document into a file and
* there is a background poller task that checks for resending (see
* {@link #resend()}). If re-sending fails, the document is moved into an error
* folder.
*
* @author OpenAS2
*/
public class DirectoryResenderModule extends AbstractActiveResenderModule
{
public static final String ATTR_RESEND_DIRECTORY = "resenddir";
public static final String ATTR_ERROR_DIRECTORY = "errordir";
private static final String FILENAME_DATE_FORMAT = "MM-dd-yy-HH-mm-ss";
private static final Logger s_aLogger = LoggerFactory.getLogger (DirectoryResenderModule.class);
@Override
public void initDynamicComponent (@Nonnull final IAS2Session aSession,
@Nullable final IStringMap aOptions) throws OpenAS2Exception
{
super.initDynamicComponent (aSession, aOptions);
getAttributeAsStringRequired (ATTR_RESEND_DIRECTORY);
getAttributeAsStringRequired (ATTR_ERROR_DIRECTORY);
}
@Override
public boolean canHandle (@Nonnull final String sAction,
@Nonnull final IMessage aMsg,
@Nullable final Map <String, Object> aOptions)
{
return sAction.equals (IProcessorResenderModule.DO_RESEND);
}
@Override
public void handle (@Nonnull final String sAction,
@Nonnull final IMessage aMsg,
@Nullable final Map <String, Object> aOptions) throws OpenAS2Exception
{
try
{
final File aResendDir = IOHelper.getDirectoryFile (getAttributeAsStringRequired (ATTR_RESEND_DIRECTORY));
final File aResendFile = IOHelper.getUniqueFile (aResendDir, getFilename ());
try (final ObjectOutputStream aOOS = new ObjectOutputStream (new FileOutputStream (aResendFile)))
{
String sResendAction = (String) aOptions.get (IProcessorResenderModule.OPTION_RESEND_ACTION);
if (sResendAction == null)
{
s_aLogger.warn ("The resending method is missing - default to message sending!");
sResendAction = IProcessorSenderModule.DO_SEND;
}
String sRetries = (String) aOptions.get (IProcessorResenderModule.OPTION_RETRIES);
if (sRetries == null)
{
s_aLogger.warn ("The resending retry count is missing - default to " +
IProcessorResenderModule.DEFAULT_RETRIES +
"!");
sRetries = Integer.toString (IProcessorResenderModule.DEFAULT_RETRIES);
}
aOOS.writeObject (sResendAction);
aOOS.writeObject (sRetries);
aOOS.writeObject (aMsg);
}
s_aLogger.info ("Message put in resend queue" + aMsg.getLoggingText ());
}
catch (final IOException ioe)
{
throw WrappedOpenAS2Exception.wrap (ioe);
}
}
/**
* Build the filename for re-sending. The filename consists of the date and
* time when the document is to be re-send.
*
* @return The filename and never <code>null</code>.
* @throws InvalidParameterException
* Only theoretically
*/
@Nonnull
protected String getFilename () throws InvalidParameterException
{
final long nResendDelayMS = getResendDelayMS ();
final long nResendTime = new Date ().getTime () + nResendDelayMS;
return DateHelper.formatDate (FILENAME_DATE_FORMAT, new Date (nResendTime));
}
protected boolean isTimeToSend (@Nonnull final File aCurrentFile)
{
try
{
final StringTokenizer aFileTokens = new StringTokenizer (aCurrentFile.getName (), ".", false);
final Date aTimestamp = DateHelper.parseDate (FILENAME_DATE_FORMAT, aFileTokens.nextToken ());
return aTimestamp.before (new Date ());
}
catch (final Exception ex)
{
return true;
}
}
protected void resendFile (@Nonnull final File aFile) throws OpenAS2Exception
{
if (s_aLogger.isDebugEnabled ())
s_aLogger.debug ("Processing " + aFile.getAbsolutePath ());
IMessage aMsg = null;
try
{
try
{
String sResendAction;
String sRetries;
try (final ObjectInputStream aOIS = new ObjectInputStream (new FileInputStream (aFile)))
{
sResendAction = (String) aOIS.readObject ();
sRetries = (String) aOIS.readObject ();
aMsg = (IMessage) aOIS.readObject ();
}
// Decrement retries
sRetries = Integer.toString (Integer.parseInt (sRetries) - 1);
// Transmit the message
s_aLogger.info ("loaded message for resend." + aMsg.getLoggingText ());
final ICommonsMap <String, Object> aOptions = new CommonsHashMap<> ();
aOptions.put (IProcessorResenderModule.OPTION_RETRIES, sRetries);
getSession ().getMessageProcessor ().handle (sResendAction, aMsg, aOptions);
if (IOHelper.getFileOperationManager ().deleteFile (aFile).isFailure ())
{
// Delete the file, sender will re-queue if the transmission fails
// again
throw new OpenAS2Exception ("File was successfully sent but not deleted: " + aFile.getAbsolutePath ());
}
s_aLogger.info ("deleted " + aFile.getAbsolutePath () + aMsg.getLoggingText ());
}
catch (final IOException ex)
{
throw WrappedOpenAS2Exception.wrap (ex);
}
catch (final ClassNotFoundException ex)
{
throw WrappedOpenAS2Exception.wrap (ex);
}
}
catch (final OpenAS2Exception ex)
{
ex.addSource (OpenAS2Exception.SOURCE_MESSAGE, aMsg);
ex.addSource (OpenAS2Exception.SOURCE_FILE, aFile);
ex.terminate ();
IOHelper.handleError (aFile, getAttributeAsStringRequired (ATTR_ERROR_DIRECTORY));
}
}
/**
* @return A list with all files that are ready to be resend.
* @throws InvalidParameterException
* In case the directory listing fails
*/
@Nonnull
@ReturnsMutableCopy
protected ICommonsList <File> scanDirectory () throws InvalidParameterException
{
final File aResendDir = IOHelper.getDirectoryFile (getAttributeAsStringRequired (ATTR_RESEND_DIRECTORY));
final File [] aFiles = aResendDir.listFiles ();
if (aFiles == null)
{
throw new InvalidParameterException ("Error getting list of files in directory",
this,
ATTR_RESEND_DIRECTORY,
aResendDir.getAbsolutePath ());
}
final ICommonsList <File> ret = new CommonsArrayList<> ();
if (aFiles.length > 0)
for (final File aCurrentFile : aFiles)
if (aCurrentFile.exists () && aCurrentFile.isFile () && aCurrentFile.canWrite () && isTimeToSend (aCurrentFile))
ret.add (aCurrentFile);
return ret;
}
@Override
public void resend ()
{
try
{
// get a list of files that need to be sent now
final ICommonsList <File> aSendFiles = scanDirectory ();
// iterator through and send each file
for (final File aCurrentFile : aSendFiles)
resendFile (aCurrentFile);
}
catch (final OpenAS2Exception ex)
{
ex.terminate ();
forceStop (ex);
}
}
}