/**
* (c) Copyright 2007-2010 by emarsys eMarketing Systems AG
*
* This file is part of dyson.
*
* dyson is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* dyson is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.emarsys.dyson;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emarsys.ecommon.util.Assertions;
import com.emarsys.ecommon.util.CollectionToStringBuilder;
/**
* <p>
* The {@link DysonStorage} is the service responsible for
* persistently storing messages delivered to dyson.
* </p><p>
* {@link DysonStorage} serves as an abstract super class for all
* possible implemenations.
* </p><p>
* Where the {@link DysonStorage} finally puts the mail files is
* up to its concrete implementation as well as its associated
* {@link MailStorageFileNamingScheme}.
* <br/>
* It's mandatory for all {@link DysonStorage} implementations to
* separate the locations where incoming mails are stored/buffered/cached
* from the place where the files are finally stored persistently.
* Said locations are called the {@link #getIncomingDirName() incoming}
* and {@link #getProcessedDirName() processed} directories.
* </p><p>
* Like all {@link GenericDysonPart dyson parts} every implementation of
* {@link DysonStorage} has to provide a public default constructor taking
* a {@link Dyson} instance.
* </p><p>
*
* </p>
*
* @author <a href="mailto:kulovits@emarsys.com">Michael "kULO" Kulovits</a>
*/
public abstract class DysonStorage extends GenericDysonPart
{
private static final Logger log =
LoggerFactory.getLogger( DysonStorage.class );
/**
* TODO implement an overall state for the dysonstorage
* possible states (still to discuss):
* - accepting/idle //running, nothing to do
* - accepting/processing //running, currentyl (still processing mails)
* - finishing_up //still processing after a stop request, but not accepting more mails
* - stopped/not running
*/
public enum State
{
}//enum State
/**
* factory method.
*
* hooks for concrete subclasses:
* - {@link #init()} <br/>
* - {@link #checkInitialization()}
*
*/
public static DysonStorage getInstance( Dyson dyson )
{
DysonStorage storage = dyson.newDysonPart(
DysonConfig.STORAGE_CLASS, DysonStorage.class );
storage.init();
storage.checkInitialization();
return storage;
}
//cached settings
protected String incomingDirName;
protected String processedDirName;
protected String mailFileSuffix;
protected String mailPartialFileSuffix;
//file system storage
protected MailStorageFileNamingScheme namingScheme;
/**
* Default constructor.
*
* All subclasses of {@link DysonStorage} must have a
* default public defaut constructor taking a {@link Dyson} instance.
*/
public DysonStorage( Dyson dyson )
{
super( dyson );
}
/**
* <p>
* initializes this storage instance after it's creation.
* </p><p>
* if this methods is redefined in subclasses then it should
* call it's super implementation as first statement unless
* there's a good reason not to do so.
* </p>
*/
protected abstract void init();
/**
* <p>
* initializes this storage instance after it's creation.
* </p><p>
* if this methods is redefined in subclasses then it should
* call it's super implementation as first statement unless
* there's a good reason not to do so.
* </p>
*
* @throws IllegalStateException
*/
protected void checkInitialization() throws IllegalStateException
{
try
{
Assertions.assertNotEmpty( this.incomingDirName );
Assertions.assertNotEmpty( this.processedDirName );
Assertions.assertNotEmpty( this.mailFileSuffix );
Assertions.assertNotEmpty( this.mailPartialFileSuffix );
Assertions.assertNotNull( this.namingScheme );
}
//TODO refactor "not empty checks" in order to omit this exception wrapping
catch( AssertionError ae )
{
throw new IllegalStateException(
"" + ae.getMessage(), ae );
}
}
/**
*
* @return
*/
public String getIncomingDirName()
{
return this.incomingDirName;
}
public String getProcessedDirName()
{
return this.processedDirName;
}
public Collection<File> getIncomingMailFiles()
{
return this.getMailFiles( this.getIncomingDirName() );
}
public Collection<File> getProcessedMailFiles()
{
return this.getMailFiles( this.getProcessedDirName() );
}
@SuppressWarnings("unchecked")
protected Collection<File> getMailFiles( String dirName )
{
File dir = new File( dirName );
Collection<File> mailFiles = FileUtils.listFiles(
dir, new String[] { this.getMailFileSuffix() }, true );
if( log.isTraceEnabled() )
{
log.trace(
"got mail files in \'{}\': {}",
dir.getAbsolutePath(),
new CollectionToStringBuilder().appendAll(
Arrays.asList( mailFiles ) )
);
}
return mailFiles;
}
/**
* @return the mailFileSuffix
*/
public String getMailFileSuffix()
{
return this.mailFileSuffix;
}
/**
* @return the mailPartialFileSuffix
*/
public String getMailPartialFileSuffix()
{
return this.mailPartialFileSuffix;
}
/**
*
* @return
*/
public MailStorageFileNamingScheme getProcessedMailFileNamingScheme()
{
return this.namingScheme;
}
/**
* TODO documentation
* @return
*/
public abstract boolean isRunning();
/**
* TODO documentation
*/
public abstract void start();
/**
* TODO documentation
*/
public abstract void stop();
/**
* waits for this dysonstorage to terminate until the timeout exceeded
*
*
* TODO proper specification
*
* @param i
* @param seconds
*/
public abstract void awaitTermination( int timeOut, TimeUnit unit );
/**
* deletes all files in the {@link #getIncomingDirName()
* incoming directory}
* @throws IOException
*/
public abstract void clearIncomingDir() throws IOException;
/**
* deletes all files in the {@link #getProcessedDirName()
* processed directory}
* @throws IOException
*/
public abstract void clearProcessedDir() throws IOException;
/**
* <p>
* Submits a task as {@link Runnable} to be executed asynchronously
* by the {@link DysonStorage}.
* </p>
* @param task
* @throws IllegalStateException - if the storage is not
* {@link #isRunning() running}
*/
public abstract void submitTask( Runnable task )
throws IllegalStateException;
}//class DysonStorage