# HG changeset patch
# User cli
# Date 1283098657 -7200
# Node ID 74139325d3051a756dc438d794d1c789bb85c674
# Parent  c404a87db5b7483e418e175170066fd01faebf40
Switch intent style to Original K&R / Linux / Kernel.

diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/Main.java
--- a/src/org/sonews/Main.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/Main.java	Sun Aug 29 18:17:37 2010 +0200
@@ -44,155 +44,123 @@
  */
 public final class Main
 {
-  
-  private Main()
-  {
-  }
 
-  /** Version information of the sonews daemon */
-  public static final String VERSION = "sonews/1.1.0";
-  public static final Date   STARTDATE = new Date();
-  
-  /**
-   * The main entrypoint.
-   * @param args
-   * @throws Exception
-   */
-  public static void main(String[] args) throws Exception
-  {
-    System.out.println(VERSION);
-    Thread.currentThread().setName("Mainthread");
+	/** Version information of the sonews daemon */
+	public static final String VERSION = "sonews/1.1.0";
+	public static final Date STARTDATE = new Date();
 
-    // Command line arguments
-    boolean feed    = false;  // Enable feeding?
-    boolean mlgw    = false;  // Enable Mailinglist gateway?
-    int     port    = -1;
-    
-    for(int n = 0; n < args.length; n++)
-    {
-      if(args[n].equals("-c") || args[n].equals("-config"))
-      {
-        Config.inst().set(Config.LEVEL_CLI, Config.CONFIGFILE, args[++n]);
-        System.out.println("Using config file " + args[n]);
-      }
-      else if(args[n].equals("-dumpjdbcdriver"))
-      {
-        System.out.println("Available JDBC drivers:");
-        Enumeration<Driver> drvs =  DriverManager.getDrivers();
-        while(drvs.hasMoreElements())
-        {
-          System.out.println(drvs.nextElement());
-        }
-        return;
-      }
-      else if(args[n].equals("-feed"))
-      {
-        feed = true;
-      }
-      else if(args[n].equals("-h") || args[n].equals("-help"))
-      {
-        printArguments();
-        return;
-      }
-      else if(args[n].equals("-mlgw"))
-      {
-        mlgw = true;
-      }
-      else if(args[n].equals("-p"))
-      {
-        port = Integer.parseInt(args[++n]);
-      }
-      else if(args[n].equals("-plugin"))
-      {
-        System.out.println("Warning: -plugin-storage is not implemented!");
-      }
-      else if(args[n].equals("-plugin-command"))
-      {
-        try
-        {
-          CommandSelector.addCommandHandler(args[++n]);
-        }
-        catch(Exception ex)
-        {
-          Log.get().warning("Could not load command plugin: " + args[n]);
-          Log.get().log(Level.INFO, "Main.java", ex);
-        }
-      }
-      else if(args[n].equals("-plugin-storage"))
-      {
-        System.out.println("Warning: -plugin-storage is not implemented!");
-      }
-      else if(args[n].equals("-v") || args[n].equals("-version"))
-      {
-        // Simply return as the version info is already printed above
-        return;
-      }
-    }
-    
-    // Try to load the JDBCDatabase;
-    // Do NOT USE BackendConfig or Log classes before this point because they require
-    // a working JDBCDatabase connection.
-    try
-    {
-      StorageProvider sprov =
-        StorageManager.loadProvider("org.sonews.storage.impl.JDBCDatabaseProvider");
-      StorageManager.enableProvider(sprov);
-      
-      // Make sure some elementary groups are existing
-      if(!StorageManager.current().isGroupExisting("control"))
-      {
-        StorageManager.current().addGroup("control", 0);
-        Log.get().info("Group 'control' created.");
-      }
-    }
-    catch(StorageBackendException ex)
-    {
-      ex.printStackTrace();
-      System.err.println("Database initialization failed with " + ex.toString());
-      System.err.println("Make sure you have specified the correct database" +
-        " settings in sonews.conf!");
-      return;
-    }
-    
-    ChannelLineBuffers.allocateDirect();
-    
-    // Add shutdown hook
-    Runtime.getRuntime().addShutdownHook(new ShutdownHook());
-    
-    // Start the listening daemon
-    if(port <= 0)
-    {
-      port = Config.inst().get(Config.PORT, 119);
-    }
-    final NNTPDaemon daemon = NNTPDaemon.createInstance(port);
-    daemon.start();
-    
-    // Start Connections purger thread...
-    Connections.getInstance().start();
-    
-    // Start mailinglist gateway...
-    if(mlgw)
-    {
-      new MailPoller().start();
-    }
-    
-    // Start feeds
-    if(feed)
-    {
-      FeedManager.startFeeding();
-    }
+	/**
+	 * The main entrypoint.
+	 * @param args
+	 * @throws Exception
+	 */
+	public static void main(String[] args) throws Exception
+	{
+		System.out.println(VERSION);
+		Thread.currentThread().setName("Mainthread");
 
-    Purger purger = new Purger();
-    purger.start();
-    
-    // Wait for main thread to exit (setDaemon(false))
-    daemon.join();
-  }
-  
-  private static void printArguments()
-  {
-    String usage = Resource.getAsString("helpers/usage", true);
-    System.out.println(usage);
-  }
+		// Command line arguments
+		boolean feed = false;  // Enable feeding?
+		boolean mlgw = false;  // Enable Mailinglist gateway?
+		int port = -1;
 
+		for (int n = 0; n < args.length; n++) {
+			if (args[n].equals("-c") || args[n].equals("-config")) {
+				Config.inst().set(Config.LEVEL_CLI, Config.CONFIGFILE, args[++n]);
+				System.out.println("Using config file " + args[n]);
+			} else if (args[n].equals("-dumpjdbcdriver")) {
+				System.out.println("Available JDBC drivers:");
+				Enumeration<Driver> drvs = DriverManager.getDrivers();
+				while (drvs.hasMoreElements()) {
+					System.out.println(drvs.nextElement());
+				}
+				return;
+			} else if (args[n].equals("-feed")) {
+				feed = true;
+			} else if (args[n].equals("-h") || args[n].equals("-help")) {
+				printArguments();
+				return;
+			} else if (args[n].equals("-mlgw")) {
+				mlgw = true;
+			} else if (args[n].equals("-p")) {
+				port = Integer.parseInt(args[++n]);
+			} else if (args[n].equals("-plugin")) {
+				System.out.println("Warning: -plugin-storage is not implemented!");
+			} else if (args[n].equals("-plugin-command")) {
+				try {
+					CommandSelector.addCommandHandler(args[++n]);
+				} catch (Exception ex) {
+					Log.get().warning("Could not load command plugin: " + args[n]);
+					Log.get().log(Level.INFO, "Main.java", ex);
+				}
+			} else if (args[n].equals("-plugin-storage")) {
+				System.out.println("Warning: -plugin-storage is not implemented!");
+			} else if (args[n].equals("-v") || args[n].equals("-version")) {
+				// Simply return as the version info is already printed above
+				return;
+			}
+		}
+
+		// Try to load the JDBCDatabase;
+		// Do NOT USE BackendConfig or Log classes before this point because they require
+		// a working JDBCDatabase connection.
+		try {
+			StorageProvider sprov =
+				StorageManager.loadProvider("org.sonews.storage.impl.JDBCDatabaseProvider");
+			StorageManager.enableProvider(sprov);
+
+			// Make sure some elementary groups are existing
+			if (!StorageManager.current().isGroupExisting("control")) {
+				StorageManager.current().addGroup("control", 0);
+				Log.get().info("Group 'control' created.");
+			}
+		} catch (StorageBackendException ex) {
+			ex.printStackTrace();
+			System.err.println("Database initialization failed with " + ex.toString());
+			System.err.println("Make sure you have specified the correct database"
+				+ " settings in sonews.conf!");
+			return;
+		}
+
+		ChannelLineBuffers.allocateDirect();
+
+		// Add shutdown hook
+		Runtime.getRuntime().addShutdownHook(new ShutdownHook());
+
+		// Start the listening daemon
+		if (port <= 0) {
+			port = Config.inst().get(Config.PORT, 119);
+		}
+		final NNTPDaemon daemon = NNTPDaemon.createInstance(port);
+		daemon.start();
+
+		// Start Connections purger thread...
+		Connections.getInstance().start();
+
+		// Start mailinglist gateway...
+		if (mlgw) {
+			new MailPoller().start();
+		}
+
+		// Start feeds
+		if (feed) {
+			FeedManager.startFeeding();
+		}
+
+		Purger purger = new Purger();
+		purger.start();
+
+		// Wait for main thread to exit (setDaemon(false))
+		daemon.join();
+	}
+
+	private static void printArguments()
+	{
+		String usage = Resource.getAsString("helpers/usage", true);
+		System.out.println(usage);
+	}
+
+	private Main()
+	{
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/ShutdownHook.java
--- a/src/org/sonews/ShutdownHook.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/ShutdownHook.java	Sun Aug 29 18:17:37 2010 +0200
@@ -30,55 +30,44 @@
 class ShutdownHook extends Thread
 {
 
-  /**
-   * Called when the JVM exits.
-   */
-  @Override
-  public void run()
-  {
-    System.out.println("sonews: Trying to shutdown all threads...");
+	/**
+	 * Called when the JVM exits.
+	 */
+	@Override
+	public void run()
+	{
+		System.out.println("sonews: Trying to shutdown all threads...");
 
-    Map<Thread, StackTraceElement[]> threadsMap = Thread.getAllStackTraces();
-    for(Thread thread : threadsMap.keySet())
-    {
-      // Interrupt the thread if it's a AbstractDaemon
-      AbstractDaemon daemon;
-      if(thread instanceof AbstractDaemon && thread.isAlive())
-      {
-        try
-        {
-          daemon = (AbstractDaemon)thread;
-          daemon.shutdownNow();
-        }
-        catch(SQLException ex)
-        {
-          System.out.println("sonews: " + ex);
-        }
-      }
-    }
-    
-    for(Thread thread : threadsMap.keySet())
-    {
-      AbstractDaemon daemon;
-      if(thread instanceof AbstractDaemon && thread.isAlive())
-      {
-        daemon = (AbstractDaemon)thread;
-        System.out.println("sonews: Waiting for " + daemon + " to exit...");
-        try
-        {
-          daemon.join(500);
-        }
-        catch(InterruptedException ex)
-        {
-          System.out.println(ex.getLocalizedMessage());
-        }
-      }
-    }
-    
-    // We have notified all not-sleeping AbstractDaemons of the shutdown;
-    // all other threads can be simply purged on VM shutdown
-    
-    System.out.println("sonews: Clean shutdown.");
-  }
-  
+		Map<Thread, StackTraceElement[]> threadsMap = Thread.getAllStackTraces();
+		for (Thread thread : threadsMap.keySet()) {
+			// Interrupt the thread if it's a AbstractDaemon
+			AbstractDaemon daemon;
+			if (thread instanceof AbstractDaemon && thread.isAlive()) {
+				try {
+					daemon = (AbstractDaemon) thread;
+					daemon.shutdownNow();
+				} catch (SQLException ex) {
+					System.out.println("sonews: " + ex);
+				}
+			}
+		}
+
+		for (Thread thread : threadsMap.keySet()) {
+			AbstractDaemon daemon;
+			if (thread instanceof AbstractDaemon && thread.isAlive()) {
+				daemon = (AbstractDaemon) thread;
+				System.out.println("sonews: Waiting for " + daemon + " to exit...");
+				try {
+					daemon.join(500);
+				} catch (InterruptedException ex) {
+					System.out.println(ex.getLocalizedMessage());
+				}
+			}
+		}
+
+		// We have notified all not-sleeping AbstractDaemons of the shutdown;
+		// all other threads can be simply purged on VM shutdown
+
+		System.out.println("sonews: Clean shutdown.");
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/acl/AccessControl.java
--- a/src/org/sonews/acl/AccessControl.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/acl/AccessControl.java	Sun Aug 29 18:17:37 2010 +0200
@@ -26,6 +26,5 @@
 public interface AccessControl
 {
 
-  boolean hasPermission(String user, char[] secret, String permission);
-
+	boolean hasPermission(String user, char[] secret, String permission);
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/acl/AuthInfoCommand.java
--- a/src/org/sonews/acl/AuthInfoCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/acl/AuthInfoCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -31,34 +31,34 @@
 public class AuthInfoCommand implements Command
 {
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    throw new UnsupportedOperationException("Not supported yet.");
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		throw new UnsupportedOperationException("Not supported yet.");
+	}
 
-  @Override
-  public boolean hasFinished()
-  {
-    throw new UnsupportedOperationException("Not supported yet.");
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		throw new UnsupportedOperationException("Not supported yet.");
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    throw new UnsupportedOperationException("Not supported yet.");
-  }
+	@Override
+	public String impliedCapability()
+	{
+		throw new UnsupportedOperationException("Not supported yet.");
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    throw new UnsupportedOperationException("Not supported yet.");
-  }
+	@Override
+	public boolean isStateful()
+	{
+		throw new UnsupportedOperationException("Not supported yet.");
+	}
 
-  @Override
-  public void processLine(NNTPConnection conn, String line, byte[] rawLine) throws IOException, StorageBackendException
-  {
-    throw new UnsupportedOperationException("Not supported yet.");
-  }
-
+	@Override
+	public void processLine(NNTPConnection conn, String line, byte[] rawLine)
+		throws IOException, StorageBackendException
+	{
+		throw new UnsupportedOperationException("Not supported yet.");
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/config/AbstractConfig.java
--- a/src/org/sonews/config/AbstractConfig.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/config/AbstractConfig.java	Sun Aug 29 18:17:37 2010 +0200
@@ -23,35 +23,34 @@
  * @author Christian Lins
  * @since sonews/0.5.0
  */
-public abstract class AbstractConfig 
+public abstract class AbstractConfig
 {
-  
-  public abstract String get(String key, String defVal);
-  
-  public int get(final String key, final int defVal)
-  {
-    return Integer.parseInt(
-      get(key, Integer.toString(defVal)));
-  }
-  
-  public boolean get(String key, boolean defVal)
-  {
-    String val = get(key, Boolean.toString(defVal));
-    return Boolean.parseBoolean(val);
-  }
 
-  /**
-   * Returns a long config value specified via the given key.
-   * @param key
-   * @param defVal
-   * @return
-   */
-  public long get(String key, long defVal)
-  {
-    String val = get(key, Long.toString(defVal));
-    return Long.parseLong(val);
-  }
+	public abstract String get(String key, String defVal);
 
-  protected abstract void set(String key, String val);
-  
+	public int get(final String key, final int defVal)
+	{
+		return Integer.parseInt(
+			get(key, Integer.toString(defVal)));
+	}
+
+	public boolean get(String key, boolean defVal)
+	{
+		String val = get(key, Boolean.toString(defVal));
+		return Boolean.parseBoolean(val);
+	}
+
+	/**
+	 * Returns a long config value specified via the given key.
+	 * @param key
+	 * @param defVal
+	 * @return
+	 */
+	public long get(String key, long defVal)
+	{
+		String val = get(key, Long.toString(defVal));
+		return Long.parseLong(val);
+	}
+
+	protected abstract void set(String key, String val);
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/config/BackendConfig.java
--- a/src/org/sonews/config/BackendConfig.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/config/BackendConfig.java	Sun Aug 29 18:17:37 2010 +0200
@@ -33,83 +33,67 @@
 class BackendConfig extends AbstractConfig
 {
 
-  private static BackendConfig instance = new BackendConfig();
-  
-  public static BackendConfig getInstance()
-  {
-    return instance;
-  }
-  
-  private final TimeoutMap<String, String> values 
-    = new TimeoutMap<String, String>();
-  
-  private BackendConfig()
-  {
-    super();
-  }
-  
-  /**
-   * Returns the config value for the given key or the defaultValue if the
-   * key is not found in config.
-   * @param key
-   * @param defaultValue
-   * @return
-   */
-  @Override
-  public String get(String key, String defaultValue)
-  {
-    try
-    {
-      String configValue = values.get(key);
-      if(configValue == null)
-      {
-        if(StorageManager.current() == null)
-        {
-          Log.get().warning("BackendConfig not available, using default.");
-          return defaultValue;
-        }
+	private static BackendConfig instance = new BackendConfig();
 
-        configValue = StorageManager.current().getConfigValue(key);
-        if(configValue == null)
-        {
-          return defaultValue;
-        }
-        else
-        {
-          values.put(key, configValue);
-          return configValue;
-        }
-      }
-      else
-      {
-        return configValue;
-      }
-    }
-    catch(StorageBackendException ex)
-    {
-      Log.get().log(Level.SEVERE, "Storage backend problem", ex);
-      return defaultValue;
-    }
-  }
-  
-  /**
-   * Sets the config value which is identified by the given key.
-   * @param key
-   * @param value
-   */
-  public void set(String key, String value)
-  {
-    values.put(key, value);
-    
-    try
-    {
-      // Write values to database
-      StorageManager.current().setConfigValue(key, value);
-    }
-    catch(StorageBackendException ex)
-    {
-      ex.printStackTrace();
-    }
-  }
-  
+	public static BackendConfig getInstance()
+	{
+		return instance;
+	}
+	private final TimeoutMap<String, String> values = new TimeoutMap<String, String>();
+
+	private BackendConfig()
+	{
+		super();
+	}
+
+	/**
+	 * Returns the config value for the given key or the defaultValue if the
+	 * key is not found in config.
+	 * @param key
+	 * @param defaultValue
+	 * @return
+	 */
+	@Override
+	public String get(String key, String defaultValue)
+	{
+		try {
+			String configValue = values.get(key);
+			if (configValue == null) {
+				if (StorageManager.current() == null) {
+					Log.get().warning("BackendConfig not available, using default.");
+					return defaultValue;
+				}
+
+				configValue = StorageManager.current().getConfigValue(key);
+				if (configValue == null) {
+					return defaultValue;
+				} else {
+					values.put(key, configValue);
+					return configValue;
+				}
+			} else {
+				return configValue;
+			}
+		} catch (StorageBackendException ex) {
+			Log.get().log(Level.SEVERE, "Storage backend problem", ex);
+			return defaultValue;
+		}
+	}
+
+	/**
+	 * Sets the config value which is identified by the given key.
+	 * @param key
+	 * @param value
+	 */
+	public void set(String key, String value)
+	{
+		values.put(key, value);
+
+		try {
+			// Write values to database
+			StorageManager.current().setConfigValue(key, value);
+		} catch (StorageBackendException ex) {
+			ex.printStackTrace();
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/config/CommandLineConfig.java
--- a/src/org/sonews/config/CommandLineConfig.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/config/CommandLineConfig.java	Sun Aug 29 18:17:37 2010 +0200
@@ -28,37 +28,34 @@
 class CommandLineConfig extends AbstractConfig
 {
 
-  private static final CommandLineConfig instance = new CommandLineConfig();
+	private static final CommandLineConfig instance = new CommandLineConfig();
 
-  public static CommandLineConfig getInstance()
-  {
-    return instance;
-  }
+	public static CommandLineConfig getInstance()
+	{
+		return instance;
+	}
+	private final Map<String, String> values = new HashMap<String, String>();
 
-  private final Map<String, String> values = new HashMap<String, String>();
-  
-  private CommandLineConfig() {}
+	private CommandLineConfig()
+	{
+	}
 
-  @Override
-  public String get(String key, String def)
-  {
-    synchronized(this.values)
-    {
-      if(this.values.containsKey(key))
-      {
-        def = this.values.get(key);
-      }
-    }
-    return def;
-  }
+	@Override
+	public String get(String key, String def)
+	{
+		synchronized (this.values) {
+			if (this.values.containsKey(key)) {
+				def = this.values.get(key);
+			}
+		}
+		return def;
+	}
 
-  @Override
-  public void set(String key, String val)
-  {
-    synchronized(this.values)
-    {
-      this.values.put(key, val);
-    }
-  }
-
+	@Override
+	public void set(String key, String val)
+	{
+		synchronized (this.values) {
+			this.values.put(key, val);
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/config/Config.java
--- a/src/org/sonews/config/Config.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/config/Config.java	Sun Aug 29 18:17:37 2010 +0200
@@ -25,151 +25,132 @@
  */
 public class Config extends AbstractConfig
 {
-  
-  public static final int LEVEL_CLI     = 1;
-  public static final int LEVEL_FILE    = 2;
-  public static final int LEVEL_BACKEND = 3;
 
-  public static final String CONFIGFILE = "sonews.configfile";
-  
-    /** BackendConfig key constant. Value is the maximum article size in kilobytes. */
-  public static final String ARTICLE_MAXSIZE   = "sonews.article.maxsize";
+	public static final int LEVEL_CLI = 1;
+	public static final int LEVEL_FILE = 2;
+	public static final int LEVEL_BACKEND = 3;
+	public static final String CONFIGFILE = "sonews.configfile";
+	/** BackendConfig key constant. Value is the maximum article size in kilobytes. */
+	public static final String ARTICLE_MAXSIZE = "sonews.article.maxsize";
+	/** BackendConfig key constant. Value: Amount of news that are feeded per run. */
+	public static final String EVENTLOG = "sonews.eventlog";
+	public static final String FEED_NEWSPERRUN = "sonews.feed.newsperrun";
+	public static final String FEED_PULLINTERVAL = "sonews.feed.pullinterval";
+	public static final String HOSTNAME = "sonews.hostname";
+	public static final String PORT = "sonews.port";
+	public static final String TIMEOUT = "sonews.timeout";
+	public static final String LOGLEVEL = "sonews.loglevel";
+	public static final String MLPOLL_DELETEUNKNOWN = "sonews.mlpoll.deleteunknown";
+	public static final String MLPOLL_HOST = "sonews.mlpoll.host";
+	public static final String MLPOLL_PASSWORD = "sonews.mlpoll.password";
+	public static final String MLPOLL_USER = "sonews.mlpoll.user";
+	public static final String MLSEND_ADDRESS = "sonews.mlsend.address";
+	public static final String MLSEND_RW_FROM = "sonews.mlsend.rewrite.from";
+	public static final String MLSEND_RW_SENDER = "sonews.mlsend.rewrite.sender";
+	public static final String MLSEND_HOST = "sonews.mlsend.host";
+	public static final String MLSEND_PASSWORD = "sonews.mlsend.password";
+	public static final String MLSEND_PORT = "sonews.mlsend.port";
+	public static final String MLSEND_USER = "sonews.mlsend.user";
+	/** Key constant. If value is "true" every I/O is written to logfile
+	 * (which is a lot!)
+	 */
+	public static final String DEBUG = "sonews.debug";
+	/** Key constant. Value is classname of the JDBC driver */
+	public static final String STORAGE_DBMSDRIVER = "sonews.storage.dbmsdriver";
+	/** Key constant. Value is JDBC connect String to the database. */
+	public static final String STORAGE_DATABASE = "sonews.storage.database";
+	/** Key constant. Value is the username for the DBMS. */
+	public static final String STORAGE_USER = "sonews.storage.user";
+	/** Key constant. Value is the password for the DBMS. */
+	public static final String STORAGE_PASSWORD = "sonews.storage.password";
+	/** Key constant. Value is the name of the host which is allowed to use the
+	 *  XDAEMON command; default: "localhost" */
+	public static final String XDAEMON_HOST = "sonews.xdaemon.host";
+	/** The config key for the filename of the logfile */
+	public static final String LOGFILE = "sonews.log";
+	public static final String[] AVAILABLE_KEYS = {
+		ARTICLE_MAXSIZE,
+		EVENTLOG,
+		FEED_NEWSPERRUN,
+		FEED_PULLINTERVAL,
+		HOSTNAME,
+		MLPOLL_DELETEUNKNOWN,
+		MLPOLL_HOST,
+		MLPOLL_PASSWORD,
+		MLPOLL_USER,
+		MLSEND_ADDRESS,
+		MLSEND_HOST,
+		MLSEND_PASSWORD,
+		MLSEND_PORT,
+		MLSEND_RW_FROM,
+		MLSEND_RW_SENDER,
+		MLSEND_USER,
+		PORT,
+		TIMEOUT,
+		XDAEMON_HOST
+	};
+	private static Config instance = new Config();
 
-  /** BackendConfig key constant. Value: Amount of news that are feeded per run. */
-  public static final String EVENTLOG          = "sonews.eventlog";
-  public static final String FEED_NEWSPERRUN   = "sonews.feed.newsperrun";
-  public static final String FEED_PULLINTERVAL = "sonews.feed.pullinterval";
-  public static final String HOSTNAME          = "sonews.hostname";
-  public static final String PORT              = "sonews.port";
-  public static final String TIMEOUT           = "sonews.timeout";
-  public static final String LOGLEVEL          = "sonews.loglevel";
-  public static final String MLPOLL_DELETEUNKNOWN = "sonews.mlpoll.deleteunknown";
-  public static final String MLPOLL_HOST       = "sonews.mlpoll.host";
-  public static final String MLPOLL_PASSWORD   = "sonews.mlpoll.password";
-  public static final String MLPOLL_USER       = "sonews.mlpoll.user";
-  public static final String MLSEND_ADDRESS    = "sonews.mlsend.address";
-  public static final String MLSEND_RW_FROM    = "sonews.mlsend.rewrite.from";
-  public static final String MLSEND_RW_SENDER  = "sonews.mlsend.rewrite.sender";
-  public static final String MLSEND_HOST       = "sonews.mlsend.host";
-  public static final String MLSEND_PASSWORD   = "sonews.mlsend.password";
-  public static final String MLSEND_PORT       = "sonews.mlsend.port";
-  public static final String MLSEND_USER       = "sonews.mlsend.user";
-  
-  /** Key constant. If value is "true" every I/O is written to logfile
-   * (which is a lot!)
-   */
-  public static final String DEBUG              = "sonews.debug";
+	public static Config inst()
+	{
+		return instance;
+	}
 
-  /** Key constant. Value is classname of the JDBC driver */
-  public static final String STORAGE_DBMSDRIVER = "sonews.storage.dbmsdriver";
+	private Config()
+	{
+	}
 
-  /** Key constant. Value is JDBC connect String to the database. */
-  public static final String STORAGE_DATABASE   = "sonews.storage.database";
+	@Override
+	public String get(String key, String def)
+	{
+		String val = CommandLineConfig.getInstance().get(key, null);
 
-  /** Key constant. Value is the username for the DBMS. */
-  public static final String STORAGE_USER       = "sonews.storage.user";
+		if (val == null) {
+			val = FileConfig.getInstance().get(key, null);
+		}
 
-  /** Key constant. Value is the password for the DBMS. */
-  public static final String STORAGE_PASSWORD   = "sonews.storage.password";
+		if (val == null) {
+			val = BackendConfig.getInstance().get(key, def);
+		}
 
-  /** Key constant. Value is the name of the host which is allowed to use the
-   *  XDAEMON command; default: "localhost" */
-  public static final String XDAEMON_HOST       = "sonews.xdaemon.host";
+		return val;
+	}
 
-  /** The config key for the filename of the logfile */
-  public static final String LOGFILE = "sonews.log";
+	public String get(int maxLevel, String key, String def)
+	{
+		String val = CommandLineConfig.getInstance().get(key, null);
 
-  public static final String[] AVAILABLE_KEYS = {
-      ARTICLE_MAXSIZE,
-      EVENTLOG,
-      FEED_NEWSPERRUN,
-      FEED_PULLINTERVAL,
-      HOSTNAME,
-      MLPOLL_DELETEUNKNOWN,
-      MLPOLL_HOST,
-      MLPOLL_PASSWORD,
-      MLPOLL_USER,
-      MLSEND_ADDRESS,
-      MLSEND_HOST,
-      MLSEND_PASSWORD,
-      MLSEND_PORT,
-      MLSEND_RW_FROM,
-      MLSEND_RW_SENDER,
-      MLSEND_USER,
-      PORT,
-      TIMEOUT,
-      XDAEMON_HOST
-  };
+		if (val == null && maxLevel >= LEVEL_FILE) {
+			val = FileConfig.getInstance().get(key, null);
+			if (val == null && maxLevel >= LEVEL_BACKEND) {
+				val = BackendConfig.getInstance().get(key, def);
+			}
+		}
 
-  private static Config instance = new Config();
-  
-  public static Config inst()
-  {
-    return instance;
-  }
-  
-  private Config(){}
+		return val != null ? val : def;
+	}
 
-  @Override
-  public String get(String key, String def)
-  {
-    String val = CommandLineConfig.getInstance().get(key, null);
-    
-    if(val == null)
-    {
-      val = FileConfig.getInstance().get(key, null);
-    }
+	@Override
+	public void set(String key, String val)
+	{
+		set(LEVEL_BACKEND, key, val);
+	}
 
-    if(val == null)
-    {
-      val = BackendConfig.getInstance().get(key, def);
-    }
-
-    return val;
-  }
-
-  public String get(int maxLevel, String key, String def)
-  {
-    String val = CommandLineConfig.getInstance().get(key, null);
-
-    if(val == null && maxLevel >= LEVEL_FILE)
-    {
-      val = FileConfig.getInstance().get(key, null);
-      if(val == null && maxLevel >= LEVEL_BACKEND)
-      {
-        val = BackendConfig.getInstance().get(key, def);
-      }
-    }
-
-    return val != null ? val : def;
-  }
-
-  @Override
-  public void set(String key, String val)
-  {
-    set(LEVEL_BACKEND, key, val);
-  }
-
-  public void set(int level, String key, String val)
-  {
-    switch(level)
-    {
-      case LEVEL_CLI:
-      {
-        CommandLineConfig.getInstance().set(key, val);
-        break;
-      }
-      case LEVEL_FILE:
-      {
-        FileConfig.getInstance().set(key, val);
-        break;
-      }
-      case LEVEL_BACKEND:
-      {
-        BackendConfig.getInstance().set(key, val);
-        break;
-      }
-    }
-  }
-
+	public void set(int level, String key, String val)
+	{
+		switch (level) {
+			case LEVEL_CLI: {
+				CommandLineConfig.getInstance().set(key, val);
+				break;
+			}
+			case LEVEL_FILE: {
+				FileConfig.getInstance().set(key, val);
+				break;
+			}
+			case LEVEL_BACKEND: {
+				BackendConfig.getInstance().set(key, val);
+				break;
+			}
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/config/FileConfig.java
--- a/src/org/sonews/config/FileConfig.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/config/FileConfig.java	Sun Aug 29 18:17:37 2010 +0200
@@ -35,136 +35,120 @@
 class FileConfig extends AbstractConfig
 {
 
-  private static final Properties defaultConfig = new Properties();
-  
-  private static FileConfig instance = null;
-  
-  static
-  {
-    // Set some default values
-    defaultConfig.setProperty(Config.STORAGE_DATABASE, "jdbc:mysql://localhost/sonews");
-    defaultConfig.setProperty(Config.STORAGE_DBMSDRIVER, "com.mysql.jdbc.Driver");
-    defaultConfig.setProperty(Config.STORAGE_USER, "sonews_user");
-    defaultConfig.setProperty(Config.STORAGE_PASSWORD, "mysecret");
-    defaultConfig.setProperty(Config.DEBUG, "false");
-  }
-  
-  /**
-   * Note: this method is not thread-safe
-   * @return A Config instance
-   */
-  public static synchronized FileConfig getInstance()
-  {
-    if(instance == null)
-    {
-      instance = new FileConfig();
-    }
-    return instance;
-  }
+	private static final Properties defaultConfig = new Properties();
+	private static FileConfig instance = null;
 
-  // Every config instance is initialized with the default values.
-  private final Properties settings = (Properties)defaultConfig.clone();
+	static {
+		// Set some default values
+		defaultConfig.setProperty(Config.STORAGE_DATABASE, "jdbc:mysql://localhost/sonews");
+		defaultConfig.setProperty(Config.STORAGE_DBMSDRIVER, "com.mysql.jdbc.Driver");
+		defaultConfig.setProperty(Config.STORAGE_USER, "sonews_user");
+		defaultConfig.setProperty(Config.STORAGE_PASSWORD, "mysecret");
+		defaultConfig.setProperty(Config.DEBUG, "false");
+	}
 
-  /**
-   * Config is a singelton class with only one instance at time.
-   * So the constructor is private to prevent the creation of more
-   * then one Config instance.
-   * @see Config.getInstance() to retrieve an instance of Config
-   */
-  private FileConfig()
-  {
-    try
-    {
-      // Load settings from file
-      load();
-    }
-    catch(IOException ex)
-    {
-      ex.printStackTrace();
-    }
-  }
+	/**
+	 * Note: this method is not thread-safe
+	 * @return A Config instance
+	 */
+	public static synchronized FileConfig getInstance()
+	{
+		if (instance == null) {
+			instance = new FileConfig();
+		}
+		return instance;
+	}
+	// Every config instance is initialized with the default values.
+	private final Properties settings = (Properties) defaultConfig.clone();
 
-  /**
-   * Loads the configuration from the config file. By default this is done
-   * by the (private) constructor but it can be useful to reload the config
-   * by invoking this method.
-   * @throws IOException
-   */
-  public void load() 
-    throws IOException
-  {
-    FileInputStream in = null;
-    
-    try
-    {
-      in = new FileInputStream(
-        Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
-      settings.load(in);
-    }
-    catch (FileNotFoundException e)
-    {
-      // MUST NOT use Log otherwise endless loop
-      System.err.println(e.getMessage());
-      save();
-    }
-    finally
-    {
-      if(in != null)
-        in.close();
-    }
-  }
+	/**
+	 * Config is a singelton class with only one instance at time.
+	 * So the constructor is private to prevent the creation of more
+	 * then one Config instance.
+	 * @see Config.getInstance() to retrieve an instance of Config
+	 */
+	private FileConfig()
+	{
+		try {
+			// Load settings from file
+			load();
+		} catch (IOException ex) {
+			ex.printStackTrace();
+		}
+	}
 
-  /**
-   * Saves this Config to the config file. By default this is done
-   * at program end.
-   * @throws FileNotFoundException
-   * @throws IOException
-   */
-  public void save() throws FileNotFoundException, IOException
-  {
-    FileOutputStream out = null;
-    try
-    {
-      out = new FileOutputStream(
-        Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
-      settings.store(out, "SONEWS Config File");
-      out.flush();
-    }
-    catch(IOException ex)
-    {
-      throw ex;
-    }
-    finally
-    {
-      if(out != null)
-        out.close();
-    }
-  }
-  
-  /**
-   * Returns the value that is stored within this config
-   * identified by the given key. If the key cannot be found
-   * the default value is returned.
-   * @param key Key to identify the value.
-   * @param def The default value that is returned if the key
-   * is not found in this Config.
-   * @return
-   */
-  @Override
-  public String get(String key, String def)
-  {
-    return settings.getProperty(key, def);
-  }
+	/**
+	 * Loads the configuration from the config file. By default this is done
+	 * by the (private) constructor but it can be useful to reload the config
+	 * by invoking this method.
+	 * @throws IOException
+	 */
+	public void load()
+		throws IOException
+	{
+		FileInputStream in = null;
 
-  /**
-   * Sets the value for a given key.
-   * @param key
-   * @param value
-   */
-  @Override
-  public void set(final String key, final String value)
-  {
-    settings.setProperty(key, value);
-  }
+		try {
+			in = new FileInputStream(
+				Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
+			settings.load(in);
+		} catch (FileNotFoundException e) {
+			// MUST NOT use Log otherwise endless loop
+			System.err.println(e.getMessage());
+			save();
+		} finally {
+			if (in != null) {
+				in.close();
+			}
+		}
+	}
 
+	/**
+	 * Saves this Config to the config file. By default this is done
+	 * at program end.
+	 * @throws FileNotFoundException
+	 * @throws IOException
+	 */
+	public void save() throws FileNotFoundException, IOException
+	{
+		FileOutputStream out = null;
+		try {
+			out = new FileOutputStream(
+				Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
+			settings.store(out, "SONEWS Config File");
+			out.flush();
+		} catch (IOException ex) {
+			throw ex;
+		} finally {
+			if (out != null) {
+				out.close();
+			}
+		}
+	}
+
+	/**
+	 * Returns the value that is stored within this config
+	 * identified by the given key. If the key cannot be found
+	 * the default value is returned.
+	 * @param key Key to identify the value.
+	 * @param def The default value that is returned if the key
+	 * is not found in this Config.
+	 * @return
+	 */
+	@Override
+	public String get(String key, String def)
+	{
+		return settings.getProperty(key, def);
+	}
+
+	/**
+	 * Sets the value for a given key.
+	 * @param key
+	 * @param value
+	 */
+	@Override
+	public void set(final String key, final String value)
+	{
+		settings.setProperty(key, value);
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/AbstractDaemon.java
--- a/src/org/sonews/daemon/AbstractDaemon.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/AbstractDaemon.java	Sun Aug 29 18:17:37 2010 +0200
@@ -32,70 +32,63 @@
 public abstract class AbstractDaemon extends Thread
 {
 
-  /** This variable is write synchronized through setRunning */
-  private boolean isRunning = false;
+	/** This variable is write synchronized through setRunning */
+	private boolean isRunning = false;
 
-  /**
-   * Protected constructor. Will be called by derived classes.
-   */
-  protected AbstractDaemon()
-  {
-    setDaemon(true); // VM will exit when all threads are daemons
-    setName(getClass().getSimpleName());
-  }
-  
-  /**
-   * @return true if shutdown() was not yet called.
-   */
-  public boolean isRunning()
-  {
-    synchronized(this)
-    {
-      return this.isRunning;
-    }
-  }
-  
-  /**
-   * Marks this thread to exit soon. Closes the associated JDBCDatabase connection
-   * if available.
-   * @throws java.sql.SQLException
-   */
-  public void shutdownNow()
-    throws SQLException
-  {
-    synchronized(this)
-    {
-      this.isRunning = false;
-      StorageManager.disableProvider();
-    }
-  }
-  
-  /**
-   * Calls shutdownNow() but catches SQLExceptions if occurring.
-   */
-  public void shutdown()
-  {
-    try
-    {
-      shutdownNow();
-    }
-    catch(SQLException ex)
-    {
-      Log.get().warning(ex.toString());
-    }
-  }
-  
-  /**
-   * Starts this daemon.
-   */
-  @Override
-  public void start()
-  {
-    synchronized(this)
-    {
-      this.isRunning = true;
-    }
-    super.start();
-  }
-  
+	/**
+	 * Protected constructor. Will be called by derived classes.
+	 */
+	protected AbstractDaemon()
+	{
+		setDaemon(true); // VM will exit when all threads are daemons
+		setName(getClass().getSimpleName());
+	}
+
+	/**
+	 * @return true if shutdown() was not yet called.
+	 */
+	public boolean isRunning()
+	{
+		synchronized (this) {
+			return this.isRunning;
+		}
+	}
+
+	/**
+	 * Marks this thread to exit soon. Closes the associated JDBCDatabase connection
+	 * if available.
+	 * @throws java.sql.SQLException
+	 */
+	public void shutdownNow()
+		throws SQLException
+	{
+		synchronized (this) {
+			this.isRunning = false;
+			StorageManager.disableProvider();
+		}
+	}
+
+	/**
+	 * Calls shutdownNow() but catches SQLExceptions if occurring.
+	 */
+	public void shutdown()
+	{
+		try {
+			shutdownNow();
+		} catch (SQLException ex) {
+			Log.get().warning(ex.toString());
+		}
+	}
+
+	/**
+	 * Starts this daemon.
+	 */
+	@Override
+	public void start()
+	{
+		synchronized (this) {
+			this.isRunning = true;
+		}
+		super.start();
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/ChannelLineBuffers.java
--- a/src/org/sonews/daemon/ChannelLineBuffers.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/ChannelLineBuffers.java	Sun Aug 29 18:17:37 2010 +0200
@@ -30,254 +30,223 @@
  * @author Christian Lins
  * @since sonews/0.5.0
  */
-public class ChannelLineBuffers 
+public class ChannelLineBuffers
 {
-  
-  /**
-   * Size of one small buffer; 
-   * per default this is 512 bytes to fit one standard line.
-   */
-  public static final int BUFFER_SIZE = 512;
-  
-  private static int maxCachedBuffers = 2048; // Cached buffers maximum
-  
-  private static final List<ByteBuffer> freeSmallBuffers
-    = new ArrayList<ByteBuffer>(maxCachedBuffers);
-  
-  /**
-   * Allocates a predefined number of direct ByteBuffers (allocated via
-   * ByteBuffer.allocateDirect()). This method is Thread-safe, but should only
-   * called at startup.
-   */
-  public static void allocateDirect()
-  {
-    synchronized(freeSmallBuffers)
-    {
-      for(int n = 0; n < maxCachedBuffers; n++)
-      {
-        ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
-        freeSmallBuffers.add(buffer);
-      }
-    }
-  }
-  
-  private ByteBuffer       inputBuffer   = newLineBuffer();
-  private List<ByteBuffer> outputBuffers = new ArrayList<ByteBuffer>();
-  
-  /**
-   * Add the given ByteBuffer to the list of buffers to be send to the client.
-   * This method is Thread-safe.
-   * @param buffer
-   * @throws java.nio.channels.ClosedChannelException If the client channel was
-   * already closed.
-   */
-  public void addOutputBuffer(ByteBuffer buffer)
-    throws ClosedChannelException
-  {
-    if(outputBuffers == null)
-    {
-      throw new ClosedChannelException();
-    }
-    
-    synchronized(outputBuffers)
-    {
-      outputBuffers.add(buffer);
-    }
-  }
-  
-  /**
-   * Currently a channel has only one input buffer. This *may* be a bottleneck
-   * and should investigated in the future.
-   * @param channel
-   * @return The input buffer associated with given channel.
-   */
-  public ByteBuffer getInputBuffer()
-  {
-    return inputBuffer;
-  }
-  
-  /**
-   * Returns the current output buffer for writing(!) to SocketChannel.
-   * @param channel
-   * @return The next input buffer that contains unprocessed data or null
-   * if the connection was closed or there are no more unprocessed buffers.
-   */
-  public ByteBuffer getOutputBuffer()
-  {
-    synchronized(outputBuffers)
-    {
-      if(outputBuffers == null || outputBuffers.isEmpty())
-      {
-        return null;
-      }
-      else
-      {
-        ByteBuffer buffer = outputBuffers.get(0);
-        if(buffer.remaining() == 0)
-        {
-          outputBuffers.remove(0);
-          // Add old buffers to the list of free buffers
-          recycleBuffer(buffer);
-          buffer = getOutputBuffer();
-        }
-        return buffer;
-      }
-    }
-  }
 
-  /**
-   * @return false if there are output buffers pending to be written to the
-   * client.
-   */
-  boolean isOutputBufferEmpty()
-  {
-    synchronized(outputBuffers)
-    {
-      return outputBuffers.isEmpty();
-    }
-  }
-  
-  /**
-   * Goes through the input buffer of the given channel and searches
-   * for next line terminator. If a '\n' is found, the bytes up to the
-   * line terminator are returned as array of bytes (the line terminator
-   * is omitted). If none is found the method returns null.
-   * @param channel
-   * @return A ByteBuffer wrapping the line.
-   */
-  ByteBuffer nextInputLine()
-  {
-    if(inputBuffer == null)
-    {
-      return null;
-    }
-    
-    synchronized(inputBuffer)
-    {
-      ByteBuffer buffer = inputBuffer;
+	/**
+	 * Size of one small buffer;
+	 * per default this is 512 bytes to fit one standard line.
+	 */
+	public static final int BUFFER_SIZE = 512;
+	private static int maxCachedBuffers = 2048; // Cached buffers maximum
+	private static final List<ByteBuffer> freeSmallBuffers = new ArrayList<ByteBuffer>(maxCachedBuffers);
 
-      // Mark the current write position
-      int mark = buffer.position();
+	/**
+	 * Allocates a predefined number of direct ByteBuffers (allocated via
+	 * ByteBuffer.allocateDirect()). This method is Thread-safe, but should only
+	 * called at startup.
+	 */
+	public static void allocateDirect()
+	{
+		synchronized (freeSmallBuffers) {
+			for (int n = 0; n < maxCachedBuffers; n++) {
+				ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
+				freeSmallBuffers.add(buffer);
+			}
+		}
+	}
+	private ByteBuffer inputBuffer = newLineBuffer();
+	private List<ByteBuffer> outputBuffers = new ArrayList<ByteBuffer>();
 
-      // Set position to 0 and limit to current position
-      buffer.flip();
+	/**
+	 * Add the given ByteBuffer to the list of buffers to be send to the client.
+	 * This method is Thread-safe.
+	 * @param buffer
+	 * @throws java.nio.channels.ClosedChannelException If the client channel was
+	 * already closed.
+	 */
+	public void addOutputBuffer(ByteBuffer buffer)
+		throws ClosedChannelException
+	{
+		if (outputBuffers == null) {
+			throw new ClosedChannelException();
+		}
 
-      ByteBuffer lineBuffer = newLineBuffer();
+		synchronized (outputBuffers) {
+			outputBuffers.add(buffer);
+		}
+	}
 
-      while (buffer.position() < buffer.limit())
-      {
-        byte b = buffer.get();
-        if (b == 10) // '\n'
-        {
-          // The bytes between the buffer's current position and its limit, 
-          // if any, are copied to the beginning of the buffer. That is, the 
-          // byte at index p = position() is copied to index zero, the byte at 
-          // index p + 1 is copied to index one, and so forth until the byte 
-          // at index limit() - 1 is copied to index n = limit() - 1 - p. 
-          // The buffer's position is then set to n+1 and its limit is set to 
-          // its capacity.
-          buffer.compact();
+	/**
+	 * Currently a channel has only one input buffer. This *may* be a bottleneck
+	 * and should investigated in the future.
+	 * @param channel
+	 * @return The input buffer associated with given channel.
+	 */
+	public ByteBuffer getInputBuffer()
+	{
+		return inputBuffer;
+	}
 
-          lineBuffer.flip(); // limit to position, position to 0
-          return lineBuffer;
-        }
-        else
-        {
-          lineBuffer.put(b);
-        }
-      }
+	/**
+	 * Returns the current output buffer for writing(!) to SocketChannel.
+	 * @param channel
+	 * @return The next input buffer that contains unprocessed data or null
+	 * if the connection was closed or there are no more unprocessed buffers.
+	 */
+	public ByteBuffer getOutputBuffer()
+	{
+		synchronized (outputBuffers) {
+			if (outputBuffers == null || outputBuffers.isEmpty()) {
+				return null;
+			} else {
+				ByteBuffer buffer = outputBuffers.get(0);
+				if (buffer.remaining() == 0) {
+					outputBuffers.remove(0);
+					// Add old buffers to the list of free buffers
+					recycleBuffer(buffer);
+					buffer = getOutputBuffer();
+				}
+				return buffer;
+			}
+		}
+	}
 
-      buffer.limit(BUFFER_SIZE);
-      buffer.position(mark);
+	/**
+	 * @return false if there are output buffers pending to be written to the
+	 * client.
+	 */
+	boolean isOutputBufferEmpty()
+	{
+		synchronized (outputBuffers) {
+			return outputBuffers.isEmpty();
+		}
+	}
 
-      if(buffer.hasRemaining())
-      {
-        return null;
-      }
-      else
-      {
-        // In the first 512 was no newline found, so the input is not standard
-        // compliant. We return the current buffer as new line and add a space
-        // to the beginning of the next line which corrects some overlong header
-        // lines.
-        inputBuffer = newLineBuffer();
-        inputBuffer.put((byte)' ');
-        buffer.flip();
-        return buffer;
-      }
-    }
-  }
-  
-  /**
-   * Returns a at least 512 bytes long ByteBuffer ready for usage.
-   * The method first try to reuse an already allocated (cached) buffer but
-   * if that fails returns a newly allocated direct buffer.
-   * Use recycleBuffer() method when you do not longer use the allocated buffer.
-   */
-  static ByteBuffer newLineBuffer()
-  {
-    ByteBuffer buf = null;
-    synchronized(freeSmallBuffers)
-    {
-      if(!freeSmallBuffers.isEmpty())
-      {
-        buf = freeSmallBuffers.remove(0);
-      }
-    }
-      
-    if(buf == null)
-    {
-      // Allocate a non-direct buffer
-      buf = ByteBuffer.allocate(BUFFER_SIZE);
-    }
-    
-    assert buf.position() == 0;
-    assert buf.limit() >= BUFFER_SIZE;
-    
-    return buf;
-  }
-  
-  /**
-   * Adds the given buffer to the list of free buffers if it is a valuable
-   * direct allocated buffer.
-   * @param buffer
-   */
-  public static void recycleBuffer(ByteBuffer buffer)
-  {
-    assert buffer != null;
+	/**
+	 * Goes through the input buffer of the given channel and searches
+	 * for next line terminator. If a '\n' is found, the bytes up to the
+	 * line terminator are returned as array of bytes (the line terminator
+	 * is omitted). If none is found the method returns null.
+	 * @param channel
+	 * @return A ByteBuffer wrapping the line.
+	 */
+	ByteBuffer nextInputLine()
+	{
+		if (inputBuffer == null) {
+			return null;
+		}
 
-    if(buffer.isDirect())
-    {
-      assert buffer.capacity() >= BUFFER_SIZE;
-      
-      // Add old buffers to the list of free buffers
-      synchronized(freeSmallBuffers)
-      {
-        buffer.clear(); // Set position to 0 and limit to capacity
-        freeSmallBuffers.add(buffer);
-      }
-    } // if(buffer.isDirect())
-  }
-  
-  /**
-   * Recycles all buffers of this ChannelLineBuffers object.
-   */
-  public void recycleBuffers()
-  {
-    synchronized(inputBuffer)
-    {
-      recycleBuffer(inputBuffer);
-      this.inputBuffer = null;
-    }
-    
-    synchronized(outputBuffers)
-    {
-      for(ByteBuffer buf : outputBuffers)
-      {
-        recycleBuffer(buf);
-      }
-      outputBuffers = null;
-    }
-  }
-  
+		synchronized (inputBuffer) {
+			ByteBuffer buffer = inputBuffer;
+
+			// Mark the current write position
+			int mark = buffer.position();
+
+			// Set position to 0 and limit to current position
+			buffer.flip();
+
+			ByteBuffer lineBuffer = newLineBuffer();
+
+			while (buffer.position() < buffer.limit()) {
+				byte b = buffer.get();
+				if (b == 10) // '\n'
+				{
+					// The bytes between the buffer's current position and its limit,
+					// if any, are copied to the beginning of the buffer. That is, the
+					// byte at index p = position() is copied to index zero, the byte at
+					// index p + 1 is copied to index one, and so forth until the byte
+					// at index limit() - 1 is copied to index n = limit() - 1 - p.
+					// The buffer's position is then set to n+1 and its limit is set to
+					// its capacity.
+					buffer.compact();
+
+					lineBuffer.flip(); // limit to position, position to 0
+					return lineBuffer;
+				} else {
+					lineBuffer.put(b);
+				}
+			}
+
+			buffer.limit(BUFFER_SIZE);
+			buffer.position(mark);
+
+			if (buffer.hasRemaining()) {
+				return null;
+			} else {
+				// In the first 512 was no newline found, so the input is not standard
+				// compliant. We return the current buffer as new line and add a space
+				// to the beginning of the next line which corrects some overlong header
+				// lines.
+				inputBuffer = newLineBuffer();
+				inputBuffer.put((byte) ' ');
+				buffer.flip();
+				return buffer;
+			}
+		}
+	}
+
+	/**
+	 * Returns a at least 512 bytes long ByteBuffer ready for usage.
+	 * The method first try to reuse an already allocated (cached) buffer but
+	 * if that fails returns a newly allocated direct buffer.
+	 * Use recycleBuffer() method when you do not longer use the allocated buffer.
+	 */
+	static ByteBuffer newLineBuffer()
+	{
+		ByteBuffer buf = null;
+		synchronized (freeSmallBuffers) {
+			if (!freeSmallBuffers.isEmpty()) {
+				buf = freeSmallBuffers.remove(0);
+			}
+		}
+
+		if (buf == null) {
+			// Allocate a non-direct buffer
+			buf = ByteBuffer.allocate(BUFFER_SIZE);
+		}
+
+		assert buf.position() == 0;
+		assert buf.limit() >= BUFFER_SIZE;
+
+		return buf;
+	}
+
+	/**
+	 * Adds the given buffer to the list of free buffers if it is a valuable
+	 * direct allocated buffer.
+	 * @param buffer
+	 */
+	public static void recycleBuffer(ByteBuffer buffer)
+	{
+		assert buffer != null;
+
+		if (buffer.isDirect()) {
+			assert buffer.capacity() >= BUFFER_SIZE;
+
+			// Add old buffers to the list of free buffers
+			synchronized (freeSmallBuffers) {
+				buffer.clear(); // Set position to 0 and limit to capacity
+				freeSmallBuffers.add(buffer);
+			}
+		} // if(buffer.isDirect())
+	}
+
+	/**
+	 * Recycles all buffers of this ChannelLineBuffers object.
+	 */
+	public void recycleBuffers()
+	{
+		synchronized (inputBuffer) {
+			recycleBuffer(inputBuffer);
+			this.inputBuffer = null;
+		}
+
+		synchronized (outputBuffers) {
+			for (ByteBuffer buf : outputBuffers) {
+				recycleBuffer(buf);
+			}
+			outputBuffers = null;
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/ChannelReader.java
--- a/src/org/sonews/daemon/ChannelReader.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/ChannelReader.java	Sun Aug 29 18:17:37 2010 +0200
@@ -37,166 +37,141 @@
 class ChannelReader extends AbstractDaemon
 {
 
-  private static ChannelReader instance = new ChannelReader();
+	private static ChannelReader instance = new ChannelReader();
 
-  /**
-   * @return Active ChannelReader instance.
-   */
-  public static ChannelReader getInstance()
-  {
-    return instance;
-  }
-  
-  private Selector selector = null;
-  
-  protected ChannelReader()
-  {
-  }
-  
-  /**
-   * Sets the selector which is used by this reader to determine the channel
-   * to read from.
-   * @param selector
-   */
-  public void setSelector(final Selector selector)
-  {
-    this.selector = selector;
-  }
-  
-  /**
-   * Run loop. Blocks until some data is available in a channel.
-   */
-  @Override
-  public void run()
-  {
-    assert selector != null;
+	/**
+	 * @return Active ChannelReader instance.
+	 */
+	public static ChannelReader getInstance()
+	{
+		return instance;
+	}
+	private Selector selector = null;
 
-    while(isRunning())
-    {
-      try
-      {
-        // select() blocks until some SelectableChannels are ready for
-        // processing. There is no need to lock the selector as we have only
-        // one thread per selector.
-        selector.select();
+	protected ChannelReader()
+	{
+	}
 
-        // Get list of selection keys with pending events.
-        // Note: the selected key set is not thread-safe
-        SocketChannel channel = null;
-        NNTPConnection conn = null;
-        final Set<SelectionKey> selKeys = selector.selectedKeys();
-        SelectionKey selKey = null;
+	/**
+	 * Sets the selector which is used by this reader to determine the channel
+	 * to read from.
+	 * @param selector
+	 */
+	public void setSelector(final Selector selector)
+	{
+		this.selector = selector;
+	}
 
-        synchronized (selKeys)
-        {
-          Iterator it = selKeys.iterator();
+	/**
+	 * Run loop. Blocks until some data is available in a channel.
+	 */
+	@Override
+	public void run()
+	{
+		assert selector != null;
 
-          // Process the first pending event
-          while (it.hasNext())
-          {
-            selKey = (SelectionKey) it.next();
-            channel = (SocketChannel) selKey.channel();
-            conn = Connections.getInstance().get(channel);
+		while (isRunning()) {
+			try {
+				// select() blocks until some SelectableChannels are ready for
+				// processing. There is no need to lock the selector as we have only
+				// one thread per selector.
+				selector.select();
 
-            // Because we cannot lock the selKey as that would cause a deadlock
-            // we lock the connection. To preserve the order of the received
-            // byte blocks a selection key for a connection that has pending
-            // read events is skipped.
-            if (conn == null || conn.tryReadLock())
-            {
-              // Remove from set to indicate that it's being processed
-              it.remove();
-              if (conn != null)
-              {
-                break; // End while loop
-              }
-            }
-            else
-            {
-              selKey = null;
-              channel = null;
-              conn = null;
-            }
-          }
-        }
+				// Get list of selection keys with pending events.
+				// Note: the selected key set is not thread-safe
+				SocketChannel channel = null;
+				NNTPConnection conn = null;
+				final Set<SelectionKey> selKeys = selector.selectedKeys();
+				SelectionKey selKey = null;
 
-        // Do not lock the selKeys while processing because this causes
-        // a deadlock in sun.nio.ch.SelectorImpl.lockAndDoSelect()
-        if (selKey != null && channel != null && conn != null)
-        {
-          processSelectionKey(conn, channel, selKey);
-          conn.unlockReadLock();
-        }
+				synchronized (selKeys) {
+					Iterator it = selKeys.iterator();
 
-      }
-      catch(CancelledKeyException ex)
-      {
-        Log.get().warning("ChannelReader.run(): " + ex);
-        Log.get().log(Level.INFO, "", ex);
-      }
-      catch(Exception ex)
-      {
-        ex.printStackTrace();
-      }
-      
-      // Eventually wait for a register operation
-      synchronized (NNTPDaemon.RegisterGate)
-      {
-      // Do nothing; FindBugs may warn about an empty synchronized 
-      // statement, but we cannot use a wait()/notify() mechanism here.
-      // If we used something like RegisterGate.wait() we block here
-      // until the NNTPDaemon calls notify(). But the daemon only
-      // calls notify() if itself is NOT blocked in the listening socket.
-      }
-    } // while(isRunning())
-  }
-  
-  private void processSelectionKey(final NNTPConnection connection,
-    final SocketChannel socketChannel, final SelectionKey selKey)
-    throws InterruptedException, IOException
-  {
-    assert selKey != null;
-    assert selKey.isReadable();
-    
-    // Some bytes are available for reading
-    if(selKey.isValid())
-    {   
-      // Lock the channel
-      //synchronized(socketChannel)
-      {
-        // Read the data into the appropriate buffer
-        ByteBuffer buf = connection.getInputBuffer();
-        int read = -1;
-        try 
-        {
-          read = socketChannel.read(buf);
-        }
-        catch(IOException ex)
-        {
-          // The connection was probably closed by the remote host
-          // in a non-clean fashion
-          Log.get().info("ChannelReader.processSelectionKey(): " + ex);
-        }
-        catch(Exception ex) 
-        {
-          Log.get().warning("ChannelReader.processSelectionKey(): " + ex);
-        }
-        
-        if(read == -1) // End of stream
-        {
-          selKey.cancel();
-        }
-        else if(read > 0) // If some data was read
-        {
-          ConnectionWorker.addChannel(socketChannel);
-        }
-      }
-    }
-    else
-    {
-      // Should not happen
-      Log.get().severe("Should not happen: " + selKey.toString());
-    }
-  }
-  
+					// Process the first pending event
+					while (it.hasNext()) {
+						selKey = (SelectionKey) it.next();
+						channel = (SocketChannel) selKey.channel();
+						conn = Connections.getInstance().get(channel);
+
+						// Because we cannot lock the selKey as that would cause a deadlock
+						// we lock the connection. To preserve the order of the received
+						// byte blocks a selection key for a connection that has pending
+						// read events is skipped.
+						if (conn == null || conn.tryReadLock()) {
+							// Remove from set to indicate that it's being processed
+							it.remove();
+							if (conn != null) {
+								break; // End while loop
+							}
+						} else {
+							selKey = null;
+							channel = null;
+							conn = null;
+						}
+					}
+				}
+
+				// Do not lock the selKeys while processing because this causes
+				// a deadlock in sun.nio.ch.SelectorImpl.lockAndDoSelect()
+				if (selKey != null && channel != null && conn != null) {
+					processSelectionKey(conn, channel, selKey);
+					conn.unlockReadLock();
+				}
+
+			} catch (CancelledKeyException ex) {
+				Log.get().warning("ChannelReader.run(): " + ex);
+				Log.get().log(Level.INFO, "", ex);
+			} catch (Exception ex) {
+				ex.printStackTrace();
+			}
+
+			// Eventually wait for a register operation
+			synchronized (NNTPDaemon.RegisterGate) {
+				// Do nothing; FindBugs may warn about an empty synchronized
+				// statement, but we cannot use a wait()/notify() mechanism here.
+				// If we used something like RegisterGate.wait() we block here
+				// until the NNTPDaemon calls notify(). But the daemon only
+				// calls notify() if itself is NOT blocked in the listening socket.
+			}
+		} // while(isRunning())
+	}
+
+	private void processSelectionKey(final NNTPConnection connection,
+		final SocketChannel socketChannel, final SelectionKey selKey)
+		throws InterruptedException, IOException
+	{
+		assert selKey != null;
+		assert selKey.isReadable();
+
+		// Some bytes are available for reading
+		if (selKey.isValid()) {
+			// Lock the channel
+			//synchronized(socketChannel)
+			{
+				// Read the data into the appropriate buffer
+				ByteBuffer buf = connection.getInputBuffer();
+				int read = -1;
+				try {
+					read = socketChannel.read(buf);
+				} catch (IOException ex) {
+					// The connection was probably closed by the remote host
+					// in a non-clean fashion
+					Log.get().info("ChannelReader.processSelectionKey(): " + ex);
+				} catch (Exception ex) {
+					Log.get().warning("ChannelReader.processSelectionKey(): " + ex);
+				}
+
+				if (read == -1) // End of stream
+				{
+					selKey.cancel();
+				} else if (read > 0) // If some data was read
+				{
+					ConnectionWorker.addChannel(socketChannel);
+				}
+			}
+		} else {
+			// Should not happen
+			Log.get().severe("Should not happen: " + selKey.toString());
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/ChannelWriter.java
--- a/src/org/sonews/daemon/ChannelWriter.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/ChannelWriter.java	Sun Aug 29 18:17:37 2010 +0200
@@ -35,176 +35,151 @@
 class ChannelWriter extends AbstractDaemon
 {
 
-  private static ChannelWriter instance = new ChannelWriter();
+	private static ChannelWriter instance = new ChannelWriter();
 
-  /**
-   * @return Returns the active ChannelWriter instance.
-   */
-  public static ChannelWriter getInstance()
-  {
-    return instance;
-  }
-  
-  private Selector selector = null;
-  
-  protected ChannelWriter()
-  {
-  }
-  
-  /**
-   * @return Selector associated with this instance.
-   */
-  public Selector getSelector()
-  {
-    return this.selector;
-  }
-  
-  /**
-   * Sets the selector that is used by this ChannelWriter.
-   * @param selector
-   */
-  public void setSelector(final Selector selector)
-  {
-    this.selector = selector;
-  }
-  
-  /**
-   * Run loop.
-   */
-  @Override
-  public void run()
-  {
-    assert selector != null;
+	/**
+	 * @return Returns the active ChannelWriter instance.
+	 */
+	public static ChannelWriter getInstance()
+	{
+		return instance;
+	}
+	private Selector selector = null;
 
-    while(isRunning())
-    {
-      try
-      {
-        SelectionKey   selKey        = null;
-        SocketChannel  socketChannel = null;
-        NNTPConnection connection    = null;
+	protected ChannelWriter()
+	{
+	}
 
-        // select() blocks until some SelectableChannels are ready for
-        // processing. There is no need to synchronize the selector as we
-        // have only one thread per selector.
-        selector.select(); // The return value of select can be ignored
+	/**
+	 * @return Selector associated with this instance.
+	 */
+	public Selector getSelector()
+	{
+		return this.selector;
+	}
 
-        // Get list of selection keys with pending OP_WRITE events.
-        // The keySET is not thread-safe whereas the keys itself are.
-        Iterator it = selector.selectedKeys().iterator();
+	/**
+	 * Sets the selector that is used by this ChannelWriter.
+	 * @param selector
+	 */
+	public void setSelector(final Selector selector)
+	{
+		this.selector = selector;
+	}
 
-        while (it.hasNext())
-        {
-          // We remove the first event from the set and store it for
-          // later processing.
-          selKey = (SelectionKey) it.next();
-          socketChannel = (SocketChannel) selKey.channel();
-          connection = Connections.getInstance().get(socketChannel);
+	/**
+	 * Run loop.
+	 */
+	@Override
+	public void run()
+	{
+		assert selector != null;
 
-          it.remove();
-          if (connection != null)
-          {
-            break;
-          }
-          else
-          {
-            selKey = null;
-          }
-        }
-        
-        if (selKey != null)
-        {
-          try
-          {
-            // Process the selected key.
-            // As there is only one OP_WRITE key for a given channel, we need
-            // not to synchronize this processing to retain the order.
-            processSelectionKey(connection, socketChannel, selKey);
-          }
-          catch (IOException ex)
-          {
-            Log.get().warning("Error writing to channel: " + ex);
+		while (isRunning()) {
+			try {
+				SelectionKey selKey = null;
+				SocketChannel socketChannel = null;
+				NNTPConnection connection = null;
 
-            // Cancel write events for this channel
-            selKey.cancel();
-            connection.shutdownInput();
-            connection.shutdownOutput();
-          }
-        }
-        
-        // Eventually wait for a register operation
-        synchronized(NNTPDaemon.RegisterGate) { /* do nothing */ }
-      }
-      catch(CancelledKeyException ex)
-      {
-        Log.get().info("ChannelWriter.run(): " + ex);
-      }
-      catch(Exception ex)
-      {
-        ex.printStackTrace();
-      }
-    } // while(isRunning())
-  }
-  
-  private void processSelectionKey(final NNTPConnection connection,
-    final SocketChannel socketChannel, final SelectionKey selKey)
-    throws InterruptedException, IOException
-  {
-    assert connection != null;
-    assert socketChannel != null;
-    assert selKey != null;
-    assert selKey.isWritable();
+				// select() blocks until some SelectableChannels are ready for
+				// processing. There is no need to synchronize the selector as we
+				// have only one thread per selector.
+				selector.select(); // The return value of select can be ignored
 
-    // SocketChannel is ready for writing
-    if(selKey.isValid())
-    {
-      // Lock the socket channel
-      synchronized(socketChannel)
-      {
-        // Get next output buffer
-        ByteBuffer buf = connection.getOutputBuffer();
-        if(buf == null)
-        {
-          // Currently we have nothing to write, so we stop the writeable
-          // events until we have something to write to the socket channel
-          //selKey.cancel();
-          selKey.interestOps(0);
-          // Update activity timestamp to prevent too early disconnects
-          // on slow client connections
-          connection.setLastActivity(System.currentTimeMillis());
-          return;
-        }
- 
-        while(buf != null) // There is data to be send
-        {
-          // Write buffer to socket channel; this method does not block
-          if(socketChannel.write(buf) <= 0)
-          {
-            // Perhaps there is data to be written, but the SocketChannel's
-            // buffer is full, so we stop writing to until the next event.
-            break;
-          }
-          else
-          {
-            // Retrieve next buffer if available; method may return the same
-            // buffer instance if it still have some bytes remaining
-            buf = connection.getOutputBuffer();
-          }
-        }
-      }
-    }
-    else
-    {
-      Log.get().warning("Invalid OP_WRITE key: " + selKey);
+				// Get list of selection keys with pending OP_WRITE events.
+				// The keySET is not thread-safe whereas the keys itself are.
+				Iterator it = selector.selectedKeys().iterator();
 
-      if(socketChannel.socket().isClosed())
-      {
-        connection.shutdownInput();
-        connection.shutdownOutput();
-        socketChannel.close();
-        Log.get().info("Connection closed.");
-      }
-    }
-  }
-  
+				while (it.hasNext()) {
+					// We remove the first event from the set and store it for
+					// later processing.
+					selKey = (SelectionKey) it.next();
+					socketChannel = (SocketChannel) selKey.channel();
+					connection = Connections.getInstance().get(socketChannel);
+
+					it.remove();
+					if (connection != null) {
+						break;
+					} else {
+						selKey = null;
+					}
+				}
+
+				if (selKey != null) {
+					try {
+						// Process the selected key.
+						// As there is only one OP_WRITE key for a given channel, we need
+						// not to synchronize this processing to retain the order.
+						processSelectionKey(connection, socketChannel, selKey);
+					} catch (IOException ex) {
+						Log.get().warning("Error writing to channel: " + ex);
+
+						// Cancel write events for this channel
+						selKey.cancel();
+						connection.shutdownInput();
+						connection.shutdownOutput();
+					}
+				}
+
+				// Eventually wait for a register operation
+				synchronized (NNTPDaemon.RegisterGate) { /* do nothing */ }
+			} catch (CancelledKeyException ex) {
+				Log.get().info("ChannelWriter.run(): " + ex);
+			} catch (Exception ex) {
+				ex.printStackTrace();
+			}
+		} // while(isRunning())
+	}
+
+	private void processSelectionKey(final NNTPConnection connection,
+		final SocketChannel socketChannel, final SelectionKey selKey)
+		throws InterruptedException, IOException
+	{
+		assert connection != null;
+		assert socketChannel != null;
+		assert selKey != null;
+		assert selKey.isWritable();
+
+		// SocketChannel is ready for writing
+		if (selKey.isValid()) {
+			// Lock the socket channel
+			synchronized (socketChannel) {
+				// Get next output buffer
+				ByteBuffer buf = connection.getOutputBuffer();
+				if (buf == null) {
+					// Currently we have nothing to write, so we stop the writeable
+					// events until we have something to write to the socket channel
+					//selKey.cancel();
+					selKey.interestOps(0);
+					// Update activity timestamp to prevent too early disconnects
+					// on slow client connections
+					connection.setLastActivity(System.currentTimeMillis());
+					return;
+				}
+
+				while (buf != null) // There is data to be send
+				{
+					// Write buffer to socket channel; this method does not block
+					if (socketChannel.write(buf) <= 0) {
+						// Perhaps there is data to be written, but the SocketChannel's
+						// buffer is full, so we stop writing to until the next event.
+						break;
+					} else {
+						// Retrieve next buffer if available; method may return the same
+						// buffer instance if it still have some bytes remaining
+						buf = connection.getOutputBuffer();
+					}
+				}
+			}
+		} else {
+			Log.get().warning("Invalid OP_WRITE key: " + selKey);
+
+			if (socketChannel.socket().isClosed()) {
+				connection.shutdownInput();
+				connection.shutdownOutput();
+				socketChannel.close();
+				Log.get().info("Connection closed.");
+			}
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/CommandSelector.java
--- a/src/org/sonews/daemon/CommandSelector.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/CommandSelector.java	Sun Aug 29 18:17:37 2010 +0200
@@ -35,107 +35,84 @@
 public class CommandSelector
 {
 
-  private static Map<Thread, CommandSelector> instances
-    = new ConcurrentHashMap<Thread, CommandSelector>();
-  private static Map<String, Class<?>> commandClassesMapping
-    = new ConcurrentHashMap<String, Class<?>>();
+	private static Map<Thread, CommandSelector> instances = new ConcurrentHashMap<Thread, CommandSelector>();
+	private static Map<String, Class<?>> commandClassesMapping = new ConcurrentHashMap<String, Class<?>>();
 
-  static
-  {
-    String[] classes = Resource.getAsString("helpers/commands.list", true).split("\n");
-    for(String className : classes)
-    {
-      if(className.charAt(0) == '#')
-      {
-        // Skip comments
-        continue;
-      }
+	static {
+		String[] classes = Resource.getAsString("helpers/commands.list", true).split("\n");
+		for (String className : classes) {
+			if (className.charAt(0) == '#') {
+				// Skip comments
+				continue;
+			}
 
-      try
-      {
-        addCommandHandler(className);
-      }
-      catch(ClassNotFoundException ex)
-      {
-        Log.get().warning("Could not load command class: " + ex);
-      }
-      catch(InstantiationException ex)
-      {
-        Log.get().severe("Could not instantiate command class: " + ex);
-      }
-      catch(IllegalAccessException ex)
-      {
-        Log.get().severe("Could not access command class: " + ex);
-      }
-    }
-  }
+			try {
+				addCommandHandler(className);
+			} catch (ClassNotFoundException ex) {
+				Log.get().warning("Could not load command class: " + ex);
+			} catch (InstantiationException ex) {
+				Log.get().severe("Could not instantiate command class: " + ex);
+			} catch (IllegalAccessException ex) {
+				Log.get().severe("Could not access command class: " + ex);
+			}
+		}
+	}
 
-  public static void addCommandHandler(String className)
-    throws ClassNotFoundException, InstantiationException, IllegalAccessException
-  {
-    Class<?> clazz = Class.forName(className);
-    Command cmd = (Command)clazz.newInstance();
-    String[] cmdStrs = cmd.getSupportedCommandStrings();
-    for (String cmdStr : cmdStrs)
-    {
-      commandClassesMapping.put(cmdStr, clazz);
-    }
-  }
+	public static void addCommandHandler(String className)
+		throws ClassNotFoundException, InstantiationException,
+		IllegalAccessException
+	{
+		Class<?> clazz = Class.forName(className);
+		Command cmd = (Command) clazz.newInstance();
+		String[] cmdStrs = cmd.getSupportedCommandStrings();
+		for (String cmdStr : cmdStrs) {
+			commandClassesMapping.put(cmdStr, clazz);
+		}
+	}
 
-  public static Set<String> getCommandNames()
-  {
-    return commandClassesMapping.keySet();
-  }
+	public static Set<String> getCommandNames()
+	{
+		return commandClassesMapping.keySet();
+	}
 
-  public static CommandSelector getInstance()
-  {
-    CommandSelector csel = instances.get(Thread.currentThread());
-    if(csel == null)
-    {
-      csel = new CommandSelector();
-      instances.put(Thread.currentThread(), csel);
-    }
-    return csel;
-  }
+	public static CommandSelector getInstance()
+	{
+		CommandSelector csel = instances.get(Thread.currentThread());
+		if (csel == null) {
+			csel = new CommandSelector();
+			instances.put(Thread.currentThread(), csel);
+		}
+		return csel;
+	}
+	private Map<String, Command> commandMapping = new HashMap<String, Command>();
+	private Command unsupportedCmd = new UnsupportedCommand();
 
-  private Map<String, Command> commandMapping = new HashMap<String, Command>();
-  private Command              unsupportedCmd = new UnsupportedCommand();
+	private CommandSelector()
+	{
+	}
 
-  private CommandSelector()
-  {}
+	public Command get(String commandName)
+	{
+		try {
+			commandName = commandName.toUpperCase();
+			Command cmd = this.commandMapping.get(commandName);
 
-  public Command get(String commandName)
-  {
-    try
-    {
-      commandName = commandName.toUpperCase();
-      Command cmd = this.commandMapping.get(commandName);
+			if (cmd == null) {
+				Class<?> clazz = commandClassesMapping.get(commandName);
+				if (clazz == null) {
+					cmd = this.unsupportedCmd;
+				} else {
+					cmd = (Command) clazz.newInstance();
+					this.commandMapping.put(commandName, cmd);
+				}
+			} else if (cmd.isStateful()) {
+				cmd = cmd.getClass().newInstance();
+			}
 
-      if(cmd == null)
-      {
-        Class<?> clazz = commandClassesMapping.get(commandName);
-        if(clazz == null)
-        {
-          cmd = this.unsupportedCmd;
-        }
-        else
-        {
-          cmd = (Command)clazz.newInstance();
-          this.commandMapping.put(commandName, cmd);
-        }
-      }
-      else if(cmd.isStateful())
-      {
-        cmd = cmd.getClass().newInstance();
-      }
-
-      return cmd;
-    }
-    catch(Exception ex)
-    {
-      ex.printStackTrace();
-      return this.unsupportedCmd;
-    }
-  }
-
+			return cmd;
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			return this.unsupportedCmd;
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/ConnectionWorker.java
--- a/src/org/sonews/daemon/ConnectionWorker.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/ConnectionWorker.java	Sun Aug 29 18:17:37 2010 +0200
@@ -31,72 +31,60 @@
 class ConnectionWorker extends AbstractDaemon
 {
 
-  // 256 pending events should be enough
-  private static ArrayBlockingQueue<SocketChannel> pendingChannels
-    = new ArrayBlockingQueue<SocketChannel>(256, true);
-  
-  /**
-   * Registers the given channel for further event processing.
-   * @param channel
-   */
-  public static void addChannel(SocketChannel channel)
-    throws InterruptedException
-  {
-    pendingChannels.put(channel);
-  }
-  
-  /**
-   * Processing loop.
-   */
-  @Override
-  public void run()
-  {
-    while(isRunning())
-    {
-      try
-      {
-        // Retrieve and remove if available, otherwise wait.
-        SocketChannel channel = pendingChannels.take();
+	// 256 pending events should be enough
+	private static ArrayBlockingQueue<SocketChannel> pendingChannels = new ArrayBlockingQueue<SocketChannel>(256, true);
 
-        if(channel != null)
-        {
-          // Connections.getInstance().get() MAY return null
-          NNTPConnection conn = Connections.getInstance().get(channel);
-          
-          // Try to lock the connection object
-          if(conn != null && conn.tryReadLock())
-          {
-            ByteBuffer buf = conn.getBuffers().nextInputLine();
-            while(buf != null) // Complete line was received
-            {
-              final byte[] line = new byte[buf.limit()];
-              buf.get(line);
-              ChannelLineBuffers.recycleBuffer(buf);
-              
-              // Here is the actual work done
-              conn.lineReceived(line);
+	/**
+	 * Registers the given channel for further event processing.
+	 * @param channel
+	 */
+	public static void addChannel(SocketChannel channel)
+		throws InterruptedException
+	{
+		pendingChannels.put(channel);
+	}
 
-              // Read next line as we could have already received the next line
-              buf = conn.getBuffers().nextInputLine();
-            }
-            conn.unlockReadLock();
-          }
-          else
-          {
-            addChannel(channel);
-          }
-        }
-      }
-      catch(InterruptedException ex)
-      {
-        Log.get().info("ConnectionWorker interrupted: " + ex);
-      }
-      catch(Exception ex)
-      {
-        Log.get().severe("Exception in ConnectionWorker: " + ex);
-        ex.printStackTrace();
-      }
-    } // end while(isRunning())
-  }
-  
+	/**
+	 * Processing loop.
+	 */
+	@Override
+	public void run()
+	{
+		while (isRunning()) {
+			try {
+				// Retrieve and remove if available, otherwise wait.
+				SocketChannel channel = pendingChannels.take();
+
+				if (channel != null) {
+					// Connections.getInstance().get() MAY return null
+					NNTPConnection conn = Connections.getInstance().get(channel);
+
+					// Try to lock the connection object
+					if (conn != null && conn.tryReadLock()) {
+						ByteBuffer buf = conn.getBuffers().nextInputLine();
+						while (buf != null) // Complete line was received
+						{
+							final byte[] line = new byte[buf.limit()];
+							buf.get(line);
+							ChannelLineBuffers.recycleBuffer(buf);
+
+							// Here is the actual work done
+							conn.lineReceived(line);
+
+							// Read next line as we could have already received the next line
+							buf = conn.getBuffers().nextInputLine();
+						}
+						conn.unlockReadLock();
+					} else {
+						addChannel(channel);
+					}
+				}
+			} catch (InterruptedException ex) {
+				Log.get().info("ConnectionWorker interrupted: " + ex);
+			} catch (Exception ex) {
+				Log.get().severe("Exception in ConnectionWorker: " + ex);
+				ex.printStackTrace();
+			}
+		} // end while(isRunning())
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/Connections.java
--- a/src/org/sonews/daemon/Connections.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/Connections.java	Sun Aug 29 18:17:37 2010 +0200
@@ -41,141 +41,120 @@
 public final class Connections extends AbstractDaemon
 {
 
-  private static final Connections instance = new Connections();
-  
-  /**
-   * @return Active Connections instance.
-   */
-  public static Connections getInstance()
-  {
-    return Connections.instance;
-  }
-  
-  private final List<NNTPConnection> connections 
-    = new ArrayList<NNTPConnection>();
-  private final Map<SocketChannel, NNTPConnection> connByChannel 
-    = new HashMap<SocketChannel, NNTPConnection>();
-  
-  private Connections()
-  {
-    setName("Connections");
-  }
-  
-  /**
-   * Adds the given NNTPConnection to the Connections management.
-   * @param conn
-   * @see org.sonews.daemon.NNTPConnection
-   */
-  public void add(final NNTPConnection conn)
-  {
-    synchronized(this.connections)
-    {
-      this.connections.add(conn);
-      this.connByChannel.put(conn.getSocketChannel(), conn);
-    }
-  }
-  
-  /**
-   * @param channel
-   * @return NNTPConnection instance that is associated with the given
-   * SocketChannel.
-   */
-  public NNTPConnection get(final SocketChannel channel)
-  {
-    synchronized(this.connections)
-    {
-      return this.connByChannel.get(channel);
-    }
-  }
+	private static final Connections instance = new Connections();
 
-  int getConnectionCount(String remote)
-  {
-    int cnt = 0;
-    synchronized(this.connections)
-    {
-      for(NNTPConnection conn : this.connections)
-      {
-        assert conn != null;
-        assert conn.getSocketChannel() != null;
+	/**
+	 * @return Active Connections instance.
+	 */
+	public static Connections getInstance()
+	{
+		return Connections.instance;
+	}
+	private final List<NNTPConnection> connections = new ArrayList<NNTPConnection>();
+	private final Map<SocketChannel, NNTPConnection> connByChannel = new HashMap<SocketChannel, NNTPConnection>();
 
-        Socket socket = conn.getSocketChannel().socket();
-        if(socket != null)
-        {
-          InetSocketAddress sockAddr = (InetSocketAddress)socket.getRemoteSocketAddress();
-          if(sockAddr != null)
-          {
-            if(sockAddr.getHostName().equals(remote))
-            {
-              cnt++;
-            }
-          }
-        } // if(socket != null)
-      }
-    }
-    return cnt;
-  }
-  
-  /**
-   * Run loops. Checks periodically for timed out connections and purged them
-   * from the lists.
-   */
-  @Override
-  public void run()
-  {
-    while(isRunning())
-    {
-      int timeoutMillis = 1000 * Config.inst().get(Config.TIMEOUT, 180);
-      
-      synchronized (this.connections)
-      {
-        final ListIterator<NNTPConnection> iter = this.connections.listIterator();
-        NNTPConnection conn;
+	private Connections()
+	{
+		setName("Connections");
+	}
 
-        while (iter.hasNext())
-        {
-          conn = iter.next();
-          if((System.currentTimeMillis() - conn.getLastActivity()) > timeoutMillis
-              && conn.getBuffers().isOutputBufferEmpty())
-          {
-            // A connection timeout has occurred so purge the connection
-            iter.remove();
+	/**
+	 * Adds the given NNTPConnection to the Connections management.
+	 * @param conn
+	 * @see org.sonews.daemon.NNTPConnection
+	 */
+	public void add(final NNTPConnection conn)
+	{
+		synchronized (this.connections) {
+			this.connections.add(conn);
+			this.connByChannel.put(conn.getSocketChannel(), conn);
+		}
+	}
 
-            // Close and remove the channel
-            SocketChannel channel = conn.getSocketChannel();
-            connByChannel.remove(channel);
-            
-            try
-            {
-              assert channel != null;
-              assert channel.socket() != null;
-      
-              // Close the channel; implicitely cancels all selectionkeys
-              channel.close();
-              Log.get().info("Disconnected: " + channel.socket().getRemoteSocketAddress() +
-                " (timeout)");
-            }
-            catch(IOException ex)
-            {
-              Log.get().warning("Connections.run(): " + ex);
-            }
+	/**
+	 * @param channel
+	 * @return NNTPConnection instance that is associated with the given
+	 * SocketChannel.
+	 */
+	public NNTPConnection get(final SocketChannel channel)
+	{
+		synchronized (this.connections) {
+			return this.connByChannel.get(channel);
+		}
+	}
 
-            // Recycle the used buffers
-            conn.getBuffers().recycleBuffers();
-            
-            Stats.getInstance().clientDisconnect();
-          }
-        }
-      }
+	int getConnectionCount(String remote)
+	{
+		int cnt = 0;
+		synchronized (this.connections) {
+			for (NNTPConnection conn : this.connections) {
+				assert conn != null;
+				assert conn.getSocketChannel() != null;
 
-      try
-      {
-        Thread.sleep(10000); // Sleep ten seconds
-      }
-      catch(InterruptedException ex)
-      {
-        Log.get().warning("Connections Thread was interrupted: " + ex.getMessage());
-      }
-    }
-  }
-  
+				Socket socket = conn.getSocketChannel().socket();
+				if (socket != null) {
+					InetSocketAddress sockAddr = (InetSocketAddress) socket.getRemoteSocketAddress();
+					if (sockAddr != null) {
+						if (sockAddr.getHostName().equals(remote)) {
+							cnt++;
+						}
+					}
+				} // if(socket != null)
+			}
+		}
+		return cnt;
+	}
+
+	/**
+	 * Run loops. Checks periodically for timed out connections and purged them
+	 * from the lists.
+	 */
+	@Override
+	public void run()
+	{
+		while (isRunning()) {
+			int timeoutMillis = 1000 * Config.inst().get(Config.TIMEOUT, 180);
+
+			synchronized (this.connections) {
+				final ListIterator<NNTPConnection> iter = this.connections.listIterator();
+				NNTPConnection conn;
+
+				while (iter.hasNext()) {
+					conn = iter.next();
+					if ((System.currentTimeMillis() - conn.getLastActivity()) > timeoutMillis
+						&& conn.getBuffers().isOutputBufferEmpty()) {
+						// A connection timeout has occurred so purge the connection
+						iter.remove();
+
+						// Close and remove the channel
+						SocketChannel channel = conn.getSocketChannel();
+						connByChannel.remove(channel);
+
+						try {
+							assert channel != null;
+							assert channel.socket() != null;
+
+							// Close the channel; implicitely cancels all selectionkeys
+							channel.close();
+							Log.get().info("Disconnected: " + channel.socket().getRemoteSocketAddress()
+								+ " (timeout)");
+						} catch (IOException ex) {
+							Log.get().warning("Connections.run(): " + ex);
+						}
+
+						// Recycle the used buffers
+						conn.getBuffers().recycleBuffers();
+
+						Stats.getInstance().clientDisconnect();
+					}
+				}
+			}
+
+			try {
+				Thread.sleep(10000); // Sleep ten seconds
+			} catch (InterruptedException ex) {
+				Log.get().warning("Connections Thread was interrupted: " + ex.getMessage());
+			}
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/LineEncoder.java
--- a/src/org/sonews/daemon/LineEncoder.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/LineEncoder.java	Sun Aug 29 18:17:37 2010 +0200
@@ -33,48 +33,46 @@
 class LineEncoder
 {
 
-  private CharBuffer    characters;
-  private Charset       charset;
-  
-  /**
-   * Constructs new LineEncoder.
-   * @param characters
-   * @param charset
-   */
-  public LineEncoder(CharBuffer characters, Charset charset)
-  {
-    this.characters = characters;
-    this.charset    = charset;
-  }
-  
-  /**
-   * Encodes the characters of this instance to the given ChannelLineBuffers
-   * using the Charset of this instance.
-   * @param buffer
-   * @throws java.nio.channels.ClosedChannelException
-   */
-  public void encode(ChannelLineBuffers buffer)
-    throws ClosedChannelException
-  {
-    CharsetEncoder encoder = charset.newEncoder();
-    while (characters.hasRemaining())
-    {
-      ByteBuffer buf = ChannelLineBuffers.newLineBuffer();
-      assert buf.position() == 0;
-      assert buf.capacity() >= 512;
+	private CharBuffer characters;
+	private Charset charset;
 
-      CoderResult res = encoder.encode(characters, buf, true);
+	/**
+	 * Constructs new LineEncoder.
+	 * @param characters
+	 * @param charset
+	 */
+	public LineEncoder(CharBuffer characters, Charset charset)
+	{
+		this.characters = characters;
+		this.charset = charset;
+	}
 
-      // Set limit to current position and current position to 0;
-      // means make ready for read from buffer
-      buf.flip();
-      buffer.addOutputBuffer(buf);
+	/**
+	 * Encodes the characters of this instance to the given ChannelLineBuffers
+	 * using the Charset of this instance.
+	 * @param buffer
+	 * @throws java.nio.channels.ClosedChannelException
+	 */
+	public void encode(ChannelLineBuffers buffer)
+		throws ClosedChannelException
+	{
+		CharsetEncoder encoder = charset.newEncoder();
+		while (characters.hasRemaining()) {
+			ByteBuffer buf = ChannelLineBuffers.newLineBuffer();
+			assert buf.position() == 0;
+			assert buf.capacity() >= 512;
 
-      if (res.isUnderflow()) // All input processed
-      {
-        break;
-      }
-    }
-  }
-  
+			CoderResult res = encoder.encode(characters, buf, true);
+
+			// Set limit to current position and current position to 0;
+			// means make ready for read from buffer
+			buf.flip();
+			buffer.addOutputBuffer(buf);
+
+			if (res.isUnderflow()) // All input processed
+			{
+				break;
+			}
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/NNTPConnection.java
--- a/src/org/sonews/daemon/NNTPConnection.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/NNTPConnection.java	Sun Aug 29 18:17:37 2010 +0200
@@ -46,383 +46,341 @@
 public final class NNTPConnection
 {
 
-  public static final String NEWLINE            = "\r\n";    // RFC defines this as newline
-  public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
-  
-  private static final Timer cancelTimer = new Timer(true); // Thread-safe? True for run as daemon
-  
-  /** SocketChannel is generally thread-safe */
-  private SocketChannel   channel        = null;
-  private Charset         charset        = Charset.forName("UTF-8");
-  private Command         command        = null;
-  private Article         currentArticle = null;
-  private Channel         currentGroup   = null;
-  private volatile long   lastActivity   = System.currentTimeMillis();
-  private ChannelLineBuffers lineBuffers = new ChannelLineBuffers();
-  private int             readLock       = 0;
-  private final Object    readLockGate   = new Object();
-  private SelectionKey    writeSelKey    = null;
-  
-  public NNTPConnection(final SocketChannel channel)
-    throws IOException
-  {
-    if(channel == null)
-    {
-      throw new IllegalArgumentException("channel is null");
-    }
+	public static final String NEWLINE = "\r\n";    // RFC defines this as newline
+	public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
+	private static final Timer cancelTimer = new Timer(true); // Thread-safe? True for run as daemon
+	/** SocketChannel is generally thread-safe */
+	private SocketChannel channel = null;
+	private Charset charset = Charset.forName("UTF-8");
+	private Command command = null;
+	private Article currentArticle = null;
+	private Channel currentGroup = null;
+	private volatile long lastActivity = System.currentTimeMillis();
+	private ChannelLineBuffers lineBuffers = new ChannelLineBuffers();
+	private int readLock = 0;
+	private final Object readLockGate = new Object();
+	private SelectionKey writeSelKey = null;
 
-    this.channel = channel;
-    Stats.getInstance().clientConnect();
-  }
-  
-  /**
-   * Tries to get the read lock for this NNTPConnection. This method is Thread-
-   * safe and returns true of the read lock was successfully set. If the lock
-   * is still hold by another Thread the method returns false.
-   */
-  boolean tryReadLock()
-  {
-    // As synchronizing simple types may cause deadlocks,
-    // we use a gate object.
-    synchronized(readLockGate)
-    {
-      if(readLock != 0)
-      {
-        return false;
-      }
-      else
-      {
-        readLock = Thread.currentThread().hashCode();
-        return true;
-      }
-    }
-  }
-  
-  /**
-   * Releases the read lock in a Thread-safe way.
-   * @throws IllegalMonitorStateException if a Thread not holding the lock
-   * tries to release it.
-   */
-  void unlockReadLock()
-  {
-    synchronized(readLockGate)
-    {
-      if(readLock == Thread.currentThread().hashCode())
-      {
-        readLock = 0;
-      }
-      else
-      {
-        throw new IllegalMonitorStateException();
-      }
-    }
-  }
-  
-  /**
-   * @return Current input buffer of this NNTPConnection instance.
-   */
-  public ByteBuffer getInputBuffer()
-  {
-    return this.lineBuffers.getInputBuffer();
-  }
-  
-  /**
-   * @return Output buffer of this NNTPConnection which has at least one byte
-   * free storage.
-   */
-  public ByteBuffer getOutputBuffer()
-  {
-    return this.lineBuffers.getOutputBuffer();
-  }
-  
-  /**
-   * @return ChannelLineBuffers instance associated with this NNTPConnection.
-   */
-  public ChannelLineBuffers getBuffers()
-  {
-    return this.lineBuffers;
-  }
-  
-  /**
-   * @return true if this connection comes from a local remote address.
-   */
-  public boolean isLocalConnection()
-  {
-    return ((InetSocketAddress)this.channel.socket().getRemoteSocketAddress())
-      .getHostName().equalsIgnoreCase("localhost");
-  }
+	public NNTPConnection(final SocketChannel channel)
+		throws IOException
+	{
+		if (channel == null) {
+			throw new IllegalArgumentException("channel is null");
+		}
 
-  void setWriteSelectionKey(SelectionKey selKey)
-  {
-    this.writeSelKey = selKey;
-  }
+		this.channel = channel;
+		Stats.getInstance().clientConnect();
+	}
 
-  public void shutdownInput()
-  {
-    try
-    {
-      // Closes the input line of the channel's socket, so no new data
-      // will be received and a timeout can be triggered.
-      this.channel.socket().shutdownInput();
-    }
-    catch(IOException ex)
-    {
-      Log.get().warning("Exception in NNTPConnection.shutdownInput(): " + ex);
-    }
-  }
-  
-  public void shutdownOutput()
-  {
-    cancelTimer.schedule(new TimerTask() 
-    {
-      @Override
-      public void run()
-      {
-        try
-        {
-          // Closes the output line of the channel's socket.
-          channel.socket().shutdownOutput();
-          channel.close();
-        }
-        catch(SocketException ex)
-        {
-          // Socket was already disconnected
-          Log.get().info("NNTPConnection.shutdownOutput(): " + ex);
-        }
-        catch(Exception ex)
-        {
-          Log.get().warning("NNTPConnection.shutdownOutput(): " + ex);
-        }
-      }
-    }, 3000);
-  }
-  
-  public SocketChannel getSocketChannel()
-  {
-    return this.channel;
-  }
-  
-  public Article getCurrentArticle()
-  {
-    return this.currentArticle;
-  }
-  
-  public Charset getCurrentCharset()
-  {
-    return this.charset;
-  }
+	/**
+	 * Tries to get the read lock for this NNTPConnection. This method is Thread-
+	 * safe and returns true of the read lock was successfully set. If the lock
+	 * is still hold by another Thread the method returns false.
+	 */
+	boolean tryReadLock()
+	{
+		// As synchronizing simple types may cause deadlocks,
+		// we use a gate object.
+		synchronized (readLockGate) {
+			if (readLock != 0) {
+				return false;
+			} else {
+				readLock = Thread.currentThread().hashCode();
+				return true;
+			}
+		}
+	}
 
-  /**
-   * @return The currently selected communication channel (not SocketChannel)
-   */
-  public Channel getCurrentChannel()
-  {
-    return this.currentGroup;
-  }
-  
-  public void setCurrentArticle(final Article article)
-  {
-    this.currentArticle = article;
-  }
-  
-  public void setCurrentGroup(final Channel group)
-  {
-    this.currentGroup = group;
-  }
-  
-  public long getLastActivity()
-  {
-    return this.lastActivity;
-  }
-  
-  /**
-   * Due to the readLockGate there is no need to synchronize this method.
-   * @param raw
-   * @throws IllegalArgumentException if raw is null.
-   * @throws IllegalStateException if calling thread does not own the readLock.
-   */
-  void lineReceived(byte[] raw)
-  {
-    if(raw == null)
-    {
-      throw new IllegalArgumentException("raw is null");
-    }
-    
-    if(readLock == 0 || readLock != Thread.currentThread().hashCode())
-    {
-      throw new IllegalStateException("readLock not properly set");
-    }
+	/**
+	 * Releases the read lock in a Thread-safe way.
+	 * @throws IllegalMonitorStateException if a Thread not holding the lock
+	 * tries to release it.
+	 */
+	void unlockReadLock()
+	{
+		synchronized (readLockGate) {
+			if (readLock == Thread.currentThread().hashCode()) {
+				readLock = 0;
+			} else {
+				throw new IllegalMonitorStateException();
+			}
+		}
+	}
 
-    this.lastActivity = System.currentTimeMillis();
-    
-    String line = new String(raw, this.charset);
-    
-    // There might be a trailing \r, but trim() is a bad idea
-    // as it removes also leading spaces from long header lines.
-    if(line.endsWith("\r"))
-    {
-      line = line.substring(0, line.length() - 1);
-      raw  = Arrays.copyOf(raw, raw.length - 1);
-    }
-    
-    Log.get().fine("<< " + line);
-    
-    if(command == null)
-    {
-      command = parseCommandLine(line);
-      assert command != null;
-    }
+	/**
+	 * @return Current input buffer of this NNTPConnection instance.
+	 */
+	public ByteBuffer getInputBuffer()
+	{
+		return this.lineBuffers.getInputBuffer();
+	}
 
-    try
-    {
-      // The command object will process the line we just received
-      try
-      {
-        command.processLine(this, line, raw);
-      }
-      catch(StorageBackendException ex)
-      {
-        Log.get().info("Retry command processing after StorageBackendException");
+	/**
+	 * @return Output buffer of this NNTPConnection which has at least one byte
+	 * free storage.
+	 */
+	public ByteBuffer getOutputBuffer()
+	{
+		return this.lineBuffers.getOutputBuffer();
+	}
 
-        // Try it a second time, so that the backend has time to recover
-        command.processLine(this, line, raw);
-      }
-    }
-    catch(ClosedChannelException ex0)
-    {
-      try
-      {
-        Log.get().info("Connection to " + channel.socket().getRemoteSocketAddress()
-            + " closed: " + ex0);
-      }
-      catch(Exception ex0a)
-      {
-        ex0a.printStackTrace();
-      }
-    }
-    catch(Exception ex1) // This will catch a second StorageBackendException
-    {
-      try
-      {
-        command = null;
-        ex1.printStackTrace();
-        println("500 Internal server error");
-      }
-      catch(Exception ex2)
-      {
-        ex2.printStackTrace();
-      }
-    }
+	/**
+	 * @return ChannelLineBuffers instance associated with this NNTPConnection.
+	 */
+	public ChannelLineBuffers getBuffers()
+	{
+		return this.lineBuffers;
+	}
 
-    if(command == null || command.hasFinished())
-    {
-      command = null;
-      charset = Charset.forName("UTF-8"); // Reset to default
-    }
-  }
-  
-  /**
-   * This method determines the fitting command processing class.
-   * @param line
-   * @return
-   */
-  private Command parseCommandLine(String line)
-  {
-    String cmdStr = line.split(" ")[0];
-    return CommandSelector.getInstance().get(cmdStr);
-  }
-  
-  /**
-   * Puts the given line into the output buffer, adds a newline character
-   * and returns. The method returns immediately and does not block until
-   * the line was sent. If line is longer than 510 octets it is split up in
-   * several lines. Each line is terminated by \r\n (NNTPConnection.NEWLINE).
-   * @param line
-   */
-  public void println(final CharSequence line, final Charset charset)
-    throws IOException
-  {    
-    writeToChannel(CharBuffer.wrap(line), charset, line);
-    writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
-  }
+	/**
+	 * @return true if this connection comes from a local remote address.
+	 */
+	public boolean isLocalConnection()
+	{
+		return ((InetSocketAddress) this.channel.socket().getRemoteSocketAddress()).getHostName().equalsIgnoreCase("localhost");
+	}
 
-  /**
-   * Writes the given raw lines to the output buffers and finishes with
-   * a newline character (\r\n).
-   * @param rawLines
-   */
-  public void println(final byte[] rawLines)
-    throws IOException
-  {
-    this.lineBuffers.addOutputBuffer(ByteBuffer.wrap(rawLines));
-    writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
-  }
-  
-  /**
-   * Encodes the given CharBuffer using the given Charset to a bunch of
-   * ByteBuffers (each 512 bytes large) and enqueues them for writing at the
-   * connected SocketChannel.
-   * @throws java.io.IOException
-   */
-  private void writeToChannel(CharBuffer characters, final Charset charset,
-    CharSequence debugLine)
-    throws IOException
-  {
-    if(!charset.canEncode())
-    {
-      Log.get().severe("FATAL: Charset " + charset + " cannot encode!");
-      return;
-    }
-    
-    // Write characters to output buffers
-    LineEncoder lenc = new LineEncoder(characters, charset);
-    lenc.encode(lineBuffers);
-    
-    enableWriteEvents(debugLine);
-  }
+	void setWriteSelectionKey(SelectionKey selKey)
+	{
+		this.writeSelKey = selKey;
+	}
 
-  private void enableWriteEvents(CharSequence debugLine)
-  {
-    // Enable OP_WRITE events so that the buffers are processed
-    try
-    {
-      this.writeSelKey.interestOps(SelectionKey.OP_WRITE);
-      ChannelWriter.getInstance().getSelector().wakeup();
-    }
-    catch(Exception ex) // CancelledKeyException and ChannelCloseException
-    {
-      Log.get().warning("NNTPConnection.writeToChannel(): " + ex);
-      return;
-    }
+	public void shutdownInput()
+	{
+		try {
+			// Closes the input line of the channel's socket, so no new data
+			// will be received and a timeout can be triggered.
+			this.channel.socket().shutdownInput();
+		} catch (IOException ex) {
+			Log.get().warning("Exception in NNTPConnection.shutdownInput(): " + ex);
+		}
+	}
 
-    // Update last activity timestamp
-    this.lastActivity = System.currentTimeMillis();
-    if(debugLine != null)
-    {
-      Log.get().fine(">> " + debugLine);
-    }
-  }
-  
-  public void println(final CharSequence line)
-    throws IOException
-  {
-    println(line, charset);
-  }
-  
-  public void print(final String line)
-    throws IOException
-  {
-    writeToChannel(CharBuffer.wrap(line), charset, line);
-  }
-  
-  public void setCurrentCharset(final Charset charset)
-  {
-    this.charset = charset;
-  }
+	public void shutdownOutput()
+	{
+		cancelTimer.schedule(new TimerTask()
+		{
 
-  void setLastActivity(long timestamp)
-  {
-    this.lastActivity = timestamp;
-  }
-  
+			@Override
+			public void run()
+			{
+				try {
+					// Closes the output line of the channel's socket.
+					channel.socket().shutdownOutput();
+					channel.close();
+				} catch (SocketException ex) {
+					// Socket was already disconnected
+					Log.get().info("NNTPConnection.shutdownOutput(): " + ex);
+				} catch (Exception ex) {
+					Log.get().warning("NNTPConnection.shutdownOutput(): " + ex);
+				}
+			}
+		}, 3000);
+	}
+
+	public SocketChannel getSocketChannel()
+	{
+		return this.channel;
+	}
+
+	public Article getCurrentArticle()
+	{
+		return this.currentArticle;
+	}
+
+	public Charset getCurrentCharset()
+	{
+		return this.charset;
+	}
+
+	/**
+	 * @return The currently selected communication channel (not SocketChannel)
+	 */
+	public Channel getCurrentChannel()
+	{
+		return this.currentGroup;
+	}
+
+	public void setCurrentArticle(final Article article)
+	{
+		this.currentArticle = article;
+	}
+
+	public void setCurrentGroup(final Channel group)
+	{
+		this.currentGroup = group;
+	}
+
+	public long getLastActivity()
+	{
+		return this.lastActivity;
+	}
+
+	/**
+	 * Due to the readLockGate there is no need to synchronize this method.
+	 * @param raw
+	 * @throws IllegalArgumentException if raw is null.
+	 * @throws IllegalStateException if calling thread does not own the readLock.
+	 */
+	void lineReceived(byte[] raw)
+	{
+		if (raw == null) {
+			throw new IllegalArgumentException("raw is null");
+		}
+
+		if (readLock == 0 || readLock != Thread.currentThread().hashCode()) {
+			throw new IllegalStateException("readLock not properly set");
+		}
+
+		this.lastActivity = System.currentTimeMillis();
+
+		String line = new String(raw, this.charset);
+
+		// There might be a trailing \r, but trim() is a bad idea
+		// as it removes also leading spaces from long header lines.
+		if (line.endsWith("\r")) {
+			line = line.substring(0, line.length() - 1);
+			raw = Arrays.copyOf(raw, raw.length - 1);
+		}
+
+		Log.get().fine("<< " + line);
+
+		if (command == null) {
+			command = parseCommandLine(line);
+			assert command != null;
+		}
+
+		try {
+			// The command object will process the line we just received
+			try {
+				command.processLine(this, line, raw);
+			} catch (StorageBackendException ex) {
+				Log.get().info("Retry command processing after StorageBackendException");
+
+				// Try it a second time, so that the backend has time to recover
+				command.processLine(this, line, raw);
+			}
+		} catch (ClosedChannelException ex0) {
+			try {
+				Log.get().info("Connection to " + channel.socket().getRemoteSocketAddress()
+					+ " closed: " + ex0);
+			} catch (Exception ex0a) {
+				ex0a.printStackTrace();
+			}
+		} catch (Exception ex1) // This will catch a second StorageBackendException
+		{
+			try {
+				command = null;
+				ex1.printStackTrace();
+				println("500 Internal server error");
+			} catch (Exception ex2) {
+				ex2.printStackTrace();
+			}
+		}
+
+		if (command == null || command.hasFinished()) {
+			command = null;
+			charset = Charset.forName("UTF-8"); // Reset to default
+		}
+	}
+
+	/**
+	 * This method determines the fitting command processing class.
+	 * @param line
+	 * @return
+	 */
+	private Command parseCommandLine(String line)
+	{
+		String cmdStr = line.split(" ")[0];
+		return CommandSelector.getInstance().get(cmdStr);
+	}
+
+	/**
+	 * Puts the given line into the output buffer, adds a newline character
+	 * and returns. The method returns immediately and does not block until
+	 * the line was sent. If line is longer than 510 octets it is split up in
+	 * several lines. Each line is terminated by \r\n (NNTPConnection.NEWLINE).
+	 * @param line
+	 */
+	public void println(final CharSequence line, final Charset charset)
+		throws IOException
+	{
+		writeToChannel(CharBuffer.wrap(line), charset, line);
+		writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
+	}
+
+	/**
+	 * Writes the given raw lines to the output buffers and finishes with
+	 * a newline character (\r\n).
+	 * @param rawLines
+	 */
+	public void println(final byte[] rawLines)
+		throws IOException
+	{
+		this.lineBuffers.addOutputBuffer(ByteBuffer.wrap(rawLines));
+		writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
+	}
+
+	/**
+	 * Encodes the given CharBuffer using the given Charset to a bunch of
+	 * ByteBuffers (each 512 bytes large) and enqueues them for writing at the
+	 * connected SocketChannel.
+	 * @throws java.io.IOException
+	 */
+	private void writeToChannel(CharBuffer characters, final Charset charset,
+		CharSequence debugLine)
+		throws IOException
+	{
+		if (!charset.canEncode()) {
+			Log.get().severe("FATAL: Charset " + charset + " cannot encode!");
+			return;
+		}
+
+		// Write characters to output buffers
+		LineEncoder lenc = new LineEncoder(characters, charset);
+		lenc.encode(lineBuffers);
+
+		enableWriteEvents(debugLine);
+	}
+
+	private void enableWriteEvents(CharSequence debugLine)
+	{
+		// Enable OP_WRITE events so that the buffers are processed
+		try {
+			this.writeSelKey.interestOps(SelectionKey.OP_WRITE);
+			ChannelWriter.getInstance().getSelector().wakeup();
+		} catch (Exception ex) // CancelledKeyException and ChannelCloseException
+		{
+			Log.get().warning("NNTPConnection.writeToChannel(): " + ex);
+			return;
+		}
+
+		// Update last activity timestamp
+		this.lastActivity = System.currentTimeMillis();
+		if (debugLine != null) {
+			Log.get().fine(">> " + debugLine);
+		}
+	}
+
+	public void println(final CharSequence line)
+		throws IOException
+	{
+		println(line, charset);
+	}
+
+	public void print(final String line)
+		throws IOException
+	{
+		writeToChannel(CharBuffer.wrap(line), charset, line);
+	}
+
+	public void setCurrentCharset(final Charset charset)
+	{
+		this.charset = charset;
+	}
+
+	void setLastActivity(long timestamp)
+	{
+		this.lastActivity = timestamp;
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/NNTPDaemon.java
--- a/src/org/sonews/daemon/NNTPDaemon.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/NNTPDaemon.java	Sun Aug 29 18:17:37 2010 +0200
@@ -40,158 +40,130 @@
 public final class NNTPDaemon extends AbstractDaemon
 {
 
-  public static final Object RegisterGate = new Object();
-  
-  private static NNTPDaemon instance = null;
-  
-  public static synchronized NNTPDaemon createInstance(int port)
-  {
-    if(instance == null)
-    {
-      instance = new NNTPDaemon(port);
-      return instance;
-    }
-    else
-    {
-      throw new RuntimeException("NNTPDaemon.createInstance() called twice");
-    }
-  }
-  
-  private int port;
-  
-  private NNTPDaemon(final int port)
-  {
-    Log.get().info("Server listening on port " + port);
-    this.port = port;
-  }
+	public static final Object RegisterGate = new Object();
+	private static NNTPDaemon instance = null;
 
-  @Override
-  public void run()
-  {
-    try
-    {
-      // Create a Selector that handles the SocketChannel multiplexing
-      final Selector readSelector  = Selector.open();
-      final Selector writeSelector = Selector.open();
-      
-      // Start working threads
-      final int workerThreads = Runtime.getRuntime().availableProcessors() * 4;
-      ConnectionWorker[] cworkers = new ConnectionWorker[workerThreads];
-      for(int n = 0; n < workerThreads; n++)
-      {
-        cworkers[n] = new ConnectionWorker();
-        cworkers[n].start();
-      }
-      
-      ChannelWriter.getInstance().setSelector(writeSelector);
-      ChannelReader.getInstance().setSelector(readSelector);
-      ChannelWriter.getInstance().start();
-      ChannelReader.getInstance().start();
-      
-      final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
-      serverSocketChannel.configureBlocking(true);  // Set to blocking mode
-      
-      // Configure ServerSocket; bind to socket...
-      final ServerSocket serverSocket = serverSocketChannel.socket();
-      serverSocket.bind(new InetSocketAddress(this.port));
-      
-      while(isRunning())
-      {
-        SocketChannel socketChannel;
-        
-        try
-        {
-          // As we set the server socket channel to blocking mode the accept()
-          // method will block.
-          socketChannel = serverSocketChannel.accept();
-          socketChannel.configureBlocking(false);
-          assert socketChannel.isConnected();
-          assert socketChannel.finishConnect();
-        }
-        catch(IOException ex)
-        {
-          // Under heavy load an IOException "Too many open files may
-          // be thrown. It most cases we should slow down the connection
-          // accepting, to give the worker threads some time to process work.
-          Log.get().severe("IOException while accepting connection: " + ex.getMessage());
-          Log.get().info("Connection accepting sleeping for seconds...");
-          Thread.sleep(5000); // 5 seconds
-          continue;
-        }
-        
-        final NNTPConnection conn;
-        try
-        {
-          conn = new NNTPConnection(socketChannel);
-          Connections.getInstance().add(conn);
-        }
-        catch(IOException ex)
-        {
-          Log.get().warning(ex.toString());
-          socketChannel.close();
-          continue;
-        }
-        
-        try
-        {
-          SelectionKey selKeyWrite =
-            registerSelector(writeSelector, socketChannel, SelectionKey.OP_WRITE);
-          registerSelector(readSelector, socketChannel, SelectionKey.OP_READ);
-          
-          Log.get().info("Connected: " + socketChannel.socket().getRemoteSocketAddress());
+	public static synchronized NNTPDaemon createInstance(int port)
+	{
+		if (instance == null) {
+			instance = new NNTPDaemon(port);
+			return instance;
+		} else {
+			throw new RuntimeException("NNTPDaemon.createInstance() called twice");
+		}
+	}
+	private int port;
 
-          // Set write selection key and send hello to client
-          conn.setWriteSelectionKey(selKeyWrite);
-          conn.println("200 " + Config.inst().get(Config.HOSTNAME, "localhost")
-              + " " + Main.VERSION + " news server ready - (posting ok).");
-        }
-        catch(CancelledKeyException cke)
-        {
-          Log.get().warning("CancelledKeyException " + cke.getMessage() + " was thrown: "
-            + socketChannel.socket());
-        }
-        catch(ClosedChannelException cce)
-        {
-          Log.get().warning("ClosedChannelException " + cce.getMessage() + " was thrown: "
-            + socketChannel.socket());
-        }
-      }
-    }
-    catch(BindException ex)
-    {
-      // Could not bind to socket; this is a fatal problem; so perform shutdown
-      ex.printStackTrace();
-      System.exit(1);
-    }
-    catch(IOException ex)
-    {
-      ex.printStackTrace();
-    }
-    catch(Exception ex)
-    {
-      ex.printStackTrace();
-    }
-  }
-  
-  public static SelectionKey registerSelector(final Selector selector,
-    final SocketChannel channel, final int op)
-    throws CancelledKeyException, ClosedChannelException
-  {
-    // Register the selector at the channel, so that it will be notified
-    // on the socket's events
-    synchronized(RegisterGate)
-    {
-      // Wakeup the currently blocking reader/writer thread; we have locked
-      // the RegisterGate to prevent the awakened thread to block again
-      selector.wakeup();
-      
-      // Lock the selector to prevent the waiting worker threads going into
-      // selector.select() which would block the selector.
-      synchronized (selector)
-      {
-        return channel.register(selector, op, null);
-      }
-    }
-  }
-  
+	private NNTPDaemon(final int port)
+	{
+		Log.get().info("Server listening on port " + port);
+		this.port = port;
+	}
+
+	@Override
+	public void run()
+	{
+		try {
+			// Create a Selector that handles the SocketChannel multiplexing
+			final Selector readSelector = Selector.open();
+			final Selector writeSelector = Selector.open();
+
+			// Start working threads
+			final int workerThreads = Runtime.getRuntime().availableProcessors() * 4;
+			ConnectionWorker[] cworkers = new ConnectionWorker[workerThreads];
+			for (int n = 0; n < workerThreads; n++) {
+				cworkers[n] = new ConnectionWorker();
+				cworkers[n].start();
+			}
+
+			ChannelWriter.getInstance().setSelector(writeSelector);
+			ChannelReader.getInstance().setSelector(readSelector);
+			ChannelWriter.getInstance().start();
+			ChannelReader.getInstance().start();
+
+			final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
+			serverSocketChannel.configureBlocking(true);  // Set to blocking mode
+
+			// Configure ServerSocket; bind to socket...
+			final ServerSocket serverSocket = serverSocketChannel.socket();
+			serverSocket.bind(new InetSocketAddress(this.port));
+
+			while (isRunning()) {
+				SocketChannel socketChannel;
+
+				try {
+					// As we set the server socket channel to blocking mode the accept()
+					// method will block.
+					socketChannel = serverSocketChannel.accept();
+					socketChannel.configureBlocking(false);
+					assert socketChannel.isConnected();
+					assert socketChannel.finishConnect();
+				} catch (IOException ex) {
+					// Under heavy load an IOException "Too many open files may
+					// be thrown. It most cases we should slow down the connection
+					// accepting, to give the worker threads some time to process work.
+					Log.get().severe("IOException while accepting connection: " + ex.getMessage());
+					Log.get().info("Connection accepting sleeping for seconds...");
+					Thread.sleep(5000); // 5 seconds
+					continue;
+				}
+
+				final NNTPConnection conn;
+				try {
+					conn = new NNTPConnection(socketChannel);
+					Connections.getInstance().add(conn);
+				} catch (IOException ex) {
+					Log.get().warning(ex.toString());
+					socketChannel.close();
+					continue;
+				}
+
+				try {
+					SelectionKey selKeyWrite =
+						registerSelector(writeSelector, socketChannel, SelectionKey.OP_WRITE);
+					registerSelector(readSelector, socketChannel, SelectionKey.OP_READ);
+
+					Log.get().info("Connected: " + socketChannel.socket().getRemoteSocketAddress());
+
+					// Set write selection key and send hello to client
+					conn.setWriteSelectionKey(selKeyWrite);
+					conn.println("200 " + Config.inst().get(Config.HOSTNAME, "localhost")
+						+ " " + Main.VERSION + " news server ready - (posting ok).");
+				} catch (CancelledKeyException cke) {
+					Log.get().warning("CancelledKeyException " + cke.getMessage() + " was thrown: "
+						+ socketChannel.socket());
+				} catch (ClosedChannelException cce) {
+					Log.get().warning("ClosedChannelException " + cce.getMessage() + " was thrown: "
+						+ socketChannel.socket());
+				}
+			}
+		} catch (BindException ex) {
+			// Could not bind to socket; this is a fatal problem; so perform shutdown
+			ex.printStackTrace();
+			System.exit(1);
+		} catch (IOException ex) {
+			ex.printStackTrace();
+		} catch (Exception ex) {
+			ex.printStackTrace();
+		}
+	}
+
+	public static SelectionKey registerSelector(final Selector selector,
+		final SocketChannel channel, final int op)
+		throws CancelledKeyException, ClosedChannelException
+	{
+		// Register the selector at the channel, so that it will be notified
+		// on the socket's events
+		synchronized (RegisterGate) {
+			// Wakeup the currently blocking reader/writer thread; we have locked
+			// the RegisterGate to prevent the awakened thread to block again
+			selector.wakeup();
+
+			// Lock the selector to prevent the waiting worker threads going into
+			// selector.select() which would block the selector.
+			synchronized (selector) {
+				return channel.register(selector, op, null);
+			}
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/ArticleCommand.java
--- a/src/org/sonews/daemon/command/ArticleCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/ArticleCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -33,142 +33,120 @@
 public class ArticleCommand implements Command
 {
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[] {"ARTICLE", "BODY", "HEAD"};
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"ARTICLE", "BODY", "HEAD"};
+	}
 
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return false;
-  }
+	@Override
+	public boolean isStateful()
+	{
+		return false;
+	}
 
-  // TODO: Refactor this method to reduce its complexity!
-  @Override
-  public void processLine(NNTPConnection conn, final String line, byte[] raw)
-    throws IOException
-  {
-    final String[] command = line.split(" ");
-    
-    Article article  = null;
-    long    artIndex = -1;
-    if (command.length == 1)
-    {
-      article = conn.getCurrentArticle();
-      if (article == null)
-      {
-        conn.println("420 no current article has been selected");
-        return;
-      }
-    }
-    else if (command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN))
-    {
-      // Message-ID
-      article = Article.getByMessageID(command[1]);
-      if (article == null)
-      {
-        conn.println("430 no such article found");
-        return;
-      }
-    }
-    else
-    {
-      // Message Number
-      try
-      {
-        Channel currentGroup = conn.getCurrentChannel();
-        if(currentGroup == null)
-        {
-          conn.println("400 no group selected");
-          return;
-        }
-        
-        artIndex = Long.parseLong(command[1]);
-        article  = currentGroup.getArticle(artIndex);
-      }
-      catch(NumberFormatException ex)
-      {
-        ex.printStackTrace();
-      }
-      catch(StorageBackendException ex)
-      {
-        ex.printStackTrace();
-      }
+	// TODO: Refactor this method to reduce its complexity!
+	@Override
+	public void processLine(NNTPConnection conn, final String line, byte[] raw)
+		throws IOException
+	{
+		final String[] command = line.split(" ");
 
-      if (article == null)
-      {
-        conn.println("423 no such article number in this group");
-        return;
-      }
-      conn.setCurrentArticle(article);
-    }
+		Article article = null;
+		long artIndex = -1;
+		if (command.length == 1) {
+			article = conn.getCurrentArticle();
+			if (article == null) {
+				conn.println("420 no current article has been selected");
+				return;
+			}
+		} else if (command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN)) {
+			// Message-ID
+			article = Article.getByMessageID(command[1]);
+			if (article == null) {
+				conn.println("430 no such article found");
+				return;
+			}
+		} else {
+			// Message Number
+			try {
+				Channel currentGroup = conn.getCurrentChannel();
+				if (currentGroup == null) {
+					conn.println("400 no group selected");
+					return;
+				}
 
-    if(command[0].equalsIgnoreCase("ARTICLE"))
-    {
-      conn.println("220 " + artIndex + " " + article.getMessageID()
-          + " article retrieved - head and body follow");
-      conn.println(article.getHeaderSource());
-      conn.println("");
-      conn.println(article.getBody());
-      conn.println(".");
-    }
-    else if(command[0].equalsIgnoreCase("BODY"))
-    {
-      conn.println("222 " + artIndex + " " + article.getMessageID() + " body");
-      conn.println(article.getBody());
-      conn.println(".");
-    }
-    
-    /*
-     * HEAD: This command is mandatory.
-     *
-     * Syntax
-     *    HEAD message-id
-     *    HEAD number
-     *    HEAD
-     *
-     * Responses
-     *
-     * First form (message-id specified)
-     *  221 0|n message-id    Headers follow (multi-line)
-     *  430                   No article with that message-id
-     *
-     * Second form (article number specified)
-     *  221 n message-id      Headers follow (multi-line)
-     *  412                   No newsgroup selected
-     *  423                   No article with that number
-     *
-     * Third form (current article number used)
-     *  221 n message-id      Headers follow (multi-line)
-     *  412                   No newsgroup selected
-     *  420                   Current article number is invalid
-     *
-     * Parameters
-     *  number        Requested article number
-     *  n             Returned article number
-     *  message-id    Article message-id
-     */
-    else if(command[0].equalsIgnoreCase("HEAD"))
-    {
-      conn.println("221 " + artIndex + " " + article.getMessageID()
-          + " Headers follow (multi-line)");
-      conn.println(article.getHeaderSource());
-      conn.println(".");
-    }
-  }  
-  
+				artIndex = Long.parseLong(command[1]);
+				article = currentGroup.getArticle(artIndex);
+			} catch (NumberFormatException ex) {
+				ex.printStackTrace();
+			} catch (StorageBackendException ex) {
+				ex.printStackTrace();
+			}
+
+			if (article == null) {
+				conn.println("423 no such article number in this group");
+				return;
+			}
+			conn.setCurrentArticle(article);
+		}
+
+		if (command[0].equalsIgnoreCase("ARTICLE")) {
+			conn.println("220 " + artIndex + " " + article.getMessageID()
+				+ " article retrieved - head and body follow");
+			conn.println(article.getHeaderSource());
+			conn.println("");
+			conn.println(article.getBody());
+			conn.println(".");
+		} else if (command[0].equalsIgnoreCase("BODY")) {
+			conn.println("222 " + artIndex + " " + article.getMessageID() + " body");
+			conn.println(article.getBody());
+			conn.println(".");
+		} /*
+		 * HEAD: This command is mandatory.
+		 *
+		 * Syntax
+		 *    HEAD message-id
+		 *    HEAD number
+		 *    HEAD
+		 *
+		 * Responses
+		 *
+		 * First form (message-id specified)
+		 *  221 0|n message-id    Headers follow (multi-line)
+		 *  430                   No article with that message-id
+		 *
+		 * Second form (article number specified)
+		 *  221 n message-id      Headers follow (multi-line)
+		 *  412                   No newsgroup selected
+		 *  423                   No article with that number
+		 *
+		 * Third form (current article number used)
+		 *  221 n message-id      Headers follow (multi-line)
+		 *  412                   No newsgroup selected
+		 *  420                   Current article number is invalid
+		 *
+		 * Parameters
+		 *  number        Requested article number
+		 *  n             Returned article number
+		 *  message-id    Article message-id
+		 */ else if (command[0].equalsIgnoreCase("HEAD")) {
+			conn.println("221 " + artIndex + " " + article.getMessageID()
+				+ " Headers follow (multi-line)");
+			conn.println(article.getHeaderSource());
+			conn.println(".");
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/CapabilitiesCommand.java
--- a/src/org/sonews/daemon/command/CapabilitiesCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/CapabilitiesCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -42,52 +42,49 @@
 public class CapabilitiesCommand implements Command
 {
 
-  static final String[] CAPABILITIES = new String[]
-    {
-      "VERSION 2", // MUST be the first one; VERSION 2 refers to RFC3977
-      "READER",    // Server implements commands for reading
-      "POST",      // Server implements POST command
-      "OVER"       // Server implements OVER command
-    };
+	static final String[] CAPABILITIES = new String[] {
+		"VERSION 2", // MUST be the first one; VERSION 2 refers to RFC3977
+		"READER", // Server implements commands for reading
+		"POST", // Server implements POST command
+		"OVER" // Server implements OVER command
+	};
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[] {"CAPABILITIES"};
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"CAPABILITIES"};
+	}
 
-  /**
-   * First called after one call to processLine().
-   * @return
-   */
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	/**
+	 * First called after one call to processLine().
+	 * @return
+	 */
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
-  
-  @Override
-  public boolean isStateful()
-  {
-    return false;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public void processLine(NNTPConnection conn, final String line, byte[] raw)
-    throws IOException
-  {
-    conn.println("101 Capabilities list:");
-    for(String cap : CAPABILITIES)
-    {
-      conn.println(cap);
-    }
-    conn.println(".");
-  }
+	@Override
+	public boolean isStateful()
+	{
+		return false;
+	}
 
+	@Override
+	public void processLine(NNTPConnection conn, final String line, byte[] raw)
+		throws IOException
+	{
+		conn.println("101 Capabilities list:");
+		for (String cap : CAPABILITIES) {
+			conn.println(cap);
+		}
+		conn.println(".");
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/Command.java
--- a/src/org/sonews/daemon/command/Command.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/Command.java	Sun Aug 29 18:17:37 2010 +0200
@@ -30,22 +30,21 @@
 public interface Command
 {
 
-  /**
-   * @return true if this instance can be reused.
-   */
-  boolean hasFinished();
+	/**
+	 * @return true if this instance can be reused.
+	 */
+	boolean hasFinished();
 
-  /**
-   * Returns capability string that is implied by this command class.
-   * MAY return null if the command is required by the NNTP standard.
-   */
-  String impliedCapability();
+	/**
+	 * Returns capability string that is implied by this command class.
+	 * MAY return null if the command is required by the NNTP standard.
+	 */
+	String impliedCapability();
 
-  boolean isStateful();
+	boolean isStateful();
 
-  String[] getSupportedCommandStrings();
+	String[] getSupportedCommandStrings();
 
-  void processLine(NNTPConnection conn, String line, byte[] rawLine)
-    throws IOException, StorageBackendException;
-
+	void processLine(NNTPConnection conn, String line, byte[] rawLine)
+		throws IOException, StorageBackendException;
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/GroupCommand.java
--- a/src/org/sonews/daemon/command/GroupCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/GroupCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -48,55 +48,48 @@
 public class GroupCommand implements Command
 {
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[]{"GROUP"};
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"GROUP"};
+	}
 
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return true;
-  }
-  
-  @Override
-  public void processLine(NNTPConnection conn, final String line, byte[] raw)
-    throws IOException, StorageBackendException
-  {
-    final String[] command = line.split(" ");
+	@Override
+	public boolean isStateful()
+	{
+		return true;
+	}
 
-    Channel group;
-    if(command.length >= 2)
-    {
-      group = Channel.getByName(command[1]);
-      if(group == null || group.isDeleted())
-      {
-        conn.println("411 no such news group");
-      }
-      else
-      {
-        conn.setCurrentGroup(group);
-        conn.println("211 " + group.getPostingsCount() + " " + group.getFirstArticleNumber()
-          + " " + group.getLastArticleNumber() + " " + group.getName() + " group selected");
-      }
-    }
-    else
-    {
-      conn.println("500 no group name given");
-    }
-  }
+	@Override
+	public void processLine(NNTPConnection conn, final String line, byte[] raw)
+		throws IOException, StorageBackendException
+	{
+		final String[] command = line.split(" ");
 
+		Channel group;
+		if (command.length >= 2) {
+			group = Channel.getByName(command[1]);
+			if (group == null || group.isDeleted()) {
+				conn.println("411 no such news group");
+			} else {
+				conn.setCurrentGroup(group);
+				conn.println("211 " + group.getPostingsCount() + " " + group.getFirstArticleNumber()
+					+ " " + group.getLastArticleNumber() + " " + group.getName() + " group selected");
+			}
+		} else {
+			conn.println("500 no group name given");
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/HelpCommand.java
--- a/src/org/sonews/daemon/command/HelpCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/HelpCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -35,66 +35,56 @@
 public class HelpCommand implements Command
 {
 
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return true;
-  }
+	@Override
+	public boolean isStateful()
+	{
+		return true;
+	}
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[]{"HELP"};
-  }
-  
-  @Override
-  public void processLine(NNTPConnection conn, final String line, byte[] raw)
-    throws IOException
-  {
-    final String[] command = line.split(" ");
-    conn.println("100 help text follows");
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"HELP"};
+	}
 
-    if(line.length() <= 1)
-    {
-      final String[] help = Resource
-        .getAsString("helpers/helptext", true).split("\n");
-      for(String hstr : help)
-      {
-        conn.println(hstr);
-      }
+	@Override
+	public void processLine(NNTPConnection conn, final String line, byte[] raw)
+		throws IOException
+	{
+		final String[] command = line.split(" ");
+		conn.println("100 help text follows");
 
-      Set<String> commandNames = CommandSelector.getCommandNames();
-      for(String cmdName : commandNames)
-      {
-        conn.println(cmdName);
-      }
-    }
-    else
-    {
-      Command cmd = CommandSelector.getInstance().get(command[1]);
-      if(cmd instanceof HelpfulCommand)
-      {
-        conn.println(((HelpfulCommand)cmd).getHelpString());
-      }
-      else
-      {
-        conn.println("No further help information available.");
-      }
-    }
-    
-    conn.println(".");
-  }
-  
+		if (line.length() <= 1) {
+			final String[] help = Resource.getAsString("helpers/helptext", true).split("\n");
+			for (String hstr : help) {
+				conn.println(hstr);
+			}
+
+			Set<String> commandNames = CommandSelector.getCommandNames();
+			for (String cmdName : commandNames) {
+				conn.println(cmdName);
+			}
+		} else {
+			Command cmd = CommandSelector.getInstance().get(command[1]);
+			if (cmd instanceof HelpfulCommand) {
+				conn.println(((HelpfulCommand) cmd).getHelpString());
+			} else {
+				conn.println("No further help information available.");
+			}
+		}
+
+		conn.println(".");
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/HelpfulCommand.java
--- a/src/org/sonews/daemon/command/HelpfulCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/HelpfulCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -26,10 +26,9 @@
 public interface HelpfulCommand extends Command
 {
 
-  /**
-   * @return A short description of this command, that is
-   * used within the output of the HELP command.
-   */
-  String getHelpString();
-
+	/**
+	 * @return A short description of this command, that is
+	 * used within the output of the HELP command.
+	 */
+	String getHelpString();
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/ListCommand.java
--- a/src/org/sonews/daemon/command/ListCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/ListCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -37,117 +37,93 @@
 public class ListCommand implements Command
 {
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[]{"LIST"};
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"LIST"};
+	}
 
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return false;
-  }
-  
-  @Override
-  public void processLine(NNTPConnection conn, final String line, byte[] raw)
-    throws IOException, StorageBackendException
-  {
-    final String[] command = line.split(" ");
-    
-    if(command.length >= 2)
-    {
-      if(command[1].equalsIgnoreCase("OVERVIEW.FMT"))
-      {
-        conn.println("215 information follows");
-        conn.println("Subject:\nFrom:\nDate:\nMessage-ID:\nReferences:\nBytes:\nLines:\nXref");
-        conn.println(".");
-      }
-      else if(command[1].equalsIgnoreCase("NEWSGROUPS"))
-      {
-        conn.println("215 information follows");
-        final List<Channel> list = Channel.getAll();
-        for (Channel g : list)
-        {
-          conn.println(g.getName() + "\t" + "-");
-        }
-        conn.println(".");
-      }
-      else if(command[1].equalsIgnoreCase("SUBSCRIPTIONS"))
-      {
-        conn.println("215 information follows");
-        conn.println(".");
-      }
-      else if(command[1].equalsIgnoreCase("EXTENSIONS"))
-      {
-        conn.println("202 Supported NNTP extensions.");
-        conn.println("LISTGROUP");
-        conn.println("XDAEMON");
-        conn.println("XPAT");
-        conn.println(".");
-      }
-      else if(command[1].equalsIgnoreCase("ACTIVE"))
-      {
-        String  pattern  = command.length == 2
-          ? null : command[2].replace("*", "\\w*");
-        printGroupInfo(conn, pattern);
-      }
-      else
-      {
-        conn.println("500 unknown argument to LIST command");
-      }
-    }
-    else
-    {
-      printGroupInfo(conn, null);
-    }
-  }
+	@Override
+	public boolean isStateful()
+	{
+		return false;
+	}
 
-  private void printGroupInfo(NNTPConnection conn, String pattern)
-    throws IOException, StorageBackendException
-  {
-    final List<Channel> groups = Channel.getAll();
-    if(groups != null)
-    {
-      conn.println("215 list of newsgroups follows");
-      for(Channel g : groups)
-      {
-        try
-        {
-          Matcher matcher = pattern == null ?
-            null : Pattern.compile(pattern).matcher(g.getName());
-          if(!g.isDeleted() &&
-            (matcher == null || matcher.find()))
-          {
-            String writeable = g.isWriteable() ? " y" : " n";
-            // Indeed first the higher article number then the lower
-            conn.println(g.getName() + " " + g.getLastArticleNumber() + " "
-              + g.getFirstArticleNumber() + writeable);
-          }
-        }
-        catch(PatternSyntaxException ex)
-        {
-          Log.get().info(ex.toString());
-        }
-      }
-      conn.println(".");
-    }
-    else
-    {
-      conn.println("500 server backend malfunction");
-    }
-  }
+	@Override
+	public void processLine(NNTPConnection conn, final String line, byte[] raw)
+		throws IOException, StorageBackendException
+	{
+		final String[] command = line.split(" ");
 
+		if (command.length >= 2) {
+			if (command[1].equalsIgnoreCase("OVERVIEW.FMT")) {
+				conn.println("215 information follows");
+				conn.println("Subject:\nFrom:\nDate:\nMessage-ID:\nReferences:\nBytes:\nLines:\nXref");
+				conn.println(".");
+			} else if (command[1].equalsIgnoreCase("NEWSGROUPS")) {
+				conn.println("215 information follows");
+				final List<Channel> list = Channel.getAll();
+				for (Channel g : list) {
+					conn.println(g.getName() + "\t" + "-");
+				}
+				conn.println(".");
+			} else if (command[1].equalsIgnoreCase("SUBSCRIPTIONS")) {
+				conn.println("215 information follows");
+				conn.println(".");
+			} else if (command[1].equalsIgnoreCase("EXTENSIONS")) {
+				conn.println("202 Supported NNTP extensions.");
+				conn.println("LISTGROUP");
+				conn.println("XDAEMON");
+				conn.println("XPAT");
+				conn.println(".");
+			} else if (command[1].equalsIgnoreCase("ACTIVE")) {
+				String pattern = command.length == 2
+					? null : command[2].replace("*", "\\w*");
+				printGroupInfo(conn, pattern);
+			} else {
+				conn.println("500 unknown argument to LIST command");
+			}
+		} else {
+			printGroupInfo(conn, null);
+		}
+	}
+
+	private void printGroupInfo(NNTPConnection conn, String pattern)
+		throws IOException, StorageBackendException
+	{
+		final List<Channel> groups = Channel.getAll();
+		if (groups != null) {
+			conn.println("215 list of newsgroups follows");
+			for (Channel g : groups) {
+				try {
+					Matcher matcher = pattern == null
+						? null : Pattern.compile(pattern).matcher(g.getName());
+					if (!g.isDeleted()
+						&& (matcher == null || matcher.find())) {
+						String writeable = g.isWriteable() ? " y" : " n";
+						// Indeed first the higher article number then the lower
+						conn.println(g.getName() + " " + g.getLastArticleNumber() + " "
+							+ g.getFirstArticleNumber() + writeable);
+					}
+				} catch (PatternSyntaxException ex) {
+					Log.get().info(ex.toString());
+				}
+			}
+			conn.println(".");
+		} else {
+			conn.println("500 server backend malfunction");
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/ListGroupCommand.java
--- a/src/org/sonews/daemon/command/ListGroupCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/ListGroupCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -33,62 +33,56 @@
 public class ListGroupCommand implements Command
 {
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[]{"LISTGROUP"};
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"LISTGROUP"};
+	}
 
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return false;
-  }
+	@Override
+	public boolean isStateful()
+	{
+		return false;
+	}
 
-  @Override
-  public void processLine(NNTPConnection conn, final String commandName, byte[] raw)
-    throws IOException, StorageBackendException
-  {
-    final String[] command = commandName.split(" ");
+	@Override
+	public void processLine(NNTPConnection conn, final String commandName, byte[] raw)
+		throws IOException, StorageBackendException
+	{
+		final String[] command = commandName.split(" ");
 
-    Channel group;
-    if(command.length >= 2)
-    {
-      group = Channel.getByName(command[1]);
-    }
-    else
-    {
-      group = conn.getCurrentChannel();
-    }
+		Channel group;
+		if (command.length >= 2) {
+			group = Channel.getByName(command[1]);
+		} else {
+			group = conn.getCurrentChannel();
+		}
 
-    if (group == null)
-    {
-      conn.println("412 no group selected; use GROUP <group> command");
-      return;
-    }
+		if (group == null) {
+			conn.println("412 no group selected; use GROUP <group> command");
+			return;
+		}
 
-    List<Long> ids = group.getArticleNumbers();
-    conn.println("211 " + ids.size() + " " +
-      group.getFirstArticleNumber() + " " + 
-      group.getLastArticleNumber() + " list of article numbers follow");
-    for(long id : ids)
-    {
-      // One index number per line
-      conn.println(Long.toString(id));
-    }
-    conn.println(".");
-  }
-
+		List<Long> ids = group.getArticleNumbers();
+		conn.println("211 " + ids.size() + " "
+			+ group.getFirstArticleNumber() + " "
+			+ group.getLastArticleNumber() + " list of article numbers follow");
+		for (long id : ids) {
+			// One index number per line
+			conn.println(Long.toString(id));
+		}
+		conn.println(".");
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/ModeReaderCommand.java
--- a/src/org/sonews/daemon/command/ModeReaderCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/ModeReaderCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -30,43 +30,39 @@
  */
 public class ModeReaderCommand implements Command
 {
-  
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[]{"MODE"};
-  }
 
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"MODE"};
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return false;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public void processLine(NNTPConnection conn, final String line, byte[] raw)
-    throws IOException, StorageBackendException
-  {
-    if(line.equalsIgnoreCase("MODE READER"))
-    {
-      conn.println("200 hello you can post");
-    }
-    else
-    {
-      conn.println("500 I do not know this mode command");
-    }
-  }
+	@Override
+	public boolean isStateful()
+	{
+		return false;
+	}
 
+	@Override
+	public void processLine(NNTPConnection conn, final String line, byte[] raw)
+		throws IOException, StorageBackendException
+	{
+		if (line.equalsIgnoreCase("MODE READER")) {
+			conn.println("200 hello you can post");
+		} else {
+			conn.println("500 I do not know this mode command");
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/NewGroupsCommand.java
--- a/src/org/sonews/daemon/command/NewGroupsCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/NewGroupsCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -31,48 +31,44 @@
 public class NewGroupsCommand implements Command
 {
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[]{"NEWGROUPS"};
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"NEWGROUPS"};
+	}
 
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return false;
-  }
+	@Override
+	public boolean isStateful()
+	{
+		return false;
+	}
 
-  @Override
-  public void processLine(NNTPConnection conn, final String line, byte[] raw)
-    throws IOException, StorageBackendException
-  {
-    final String[] command = line.split(" ");
+	@Override
+	public void processLine(NNTPConnection conn, final String line, byte[] raw)
+		throws IOException, StorageBackendException
+	{
+		final String[] command = line.split(" ");
 
-    if(command.length == 3)
-    {
-      conn.println("231 list of new newsgroups follows");
+		if (command.length == 3) {
+			conn.println("231 list of new newsgroups follows");
 
-      // Currently we do not store a group's creation date;
-      // so we return an empty list which is a valid response
-      conn.println(".");
-    }
-    else
-    {
-      conn.println("500 invalid command usage");
-    }
-  }
-
+			// Currently we do not store a group's creation date;
+			// so we return an empty list which is a valid response
+			conn.println(".");
+		} else {
+			conn.println("500 invalid command usage");
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/NextPrevCommand.java
--- a/src/org/sonews/daemon/command/NextPrevCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/NextPrevCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -33,84 +33,73 @@
 public class NextPrevCommand implements Command
 {
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[]{"NEXT", "PREV"};
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"NEXT", "PREV"};
+	}
 
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return false;
-  }
+	@Override
+	public boolean isStateful()
+	{
+		return false;
+	}
 
-  @Override
-  public void processLine(NNTPConnection conn, final String line, byte[] raw)
-    throws IOException, StorageBackendException
-  {
-    final Article currA = conn.getCurrentArticle();
-    final Channel currG = conn.getCurrentChannel();
-    
-    if (currA == null)
-    {
-      conn.println("420 no current article has been selected");
-      return;
-    }
-    
-    if (currG == null)
-    {
-      conn.println("412 no newsgroup selected");
-      return;
-    }
-    
-    final String[] command = line.split(" ");
+	@Override
+	public void processLine(NNTPConnection conn, final String line, byte[] raw)
+		throws IOException, StorageBackendException
+	{
+		final Article currA = conn.getCurrentArticle();
+		final Channel currG = conn.getCurrentChannel();
 
-    if(command[0].equalsIgnoreCase("NEXT"))
-    {
-      selectNewArticle(conn, currA, currG, 1);
-    }
-    else if(command[0].equalsIgnoreCase("PREV"))
-    {
-      selectNewArticle(conn, currA, currG, -1);
-    }
-    else
-    {
-      conn.println("500 internal server error");
-    }
-  }
-  
-  private void selectNewArticle(NNTPConnection conn, Article article, Channel grp,
-    final int delta)
-    throws IOException, StorageBackendException
-  {
-    assert article != null;
+		if (currA == null) {
+			conn.println("420 no current article has been selected");
+			return;
+		}
 
-    article = grp.getArticle(grp.getIndexOf(article) + delta);
+		if (currG == null) {
+			conn.println("412 no newsgroup selected");
+			return;
+		}
 
-    if(article == null)
-    {
-      conn.println("421 no next article in this group");
-    }
-    else
-    {
-      conn.setCurrentArticle(article);
-      conn.println("223 " + conn.getCurrentChannel().getIndexOf(article)
-                    + " " + article.getMessageID()
-                    + " article retrieved - request text separately");
-    }
-  }
+		final String[] command = line.split(" ");
 
+		if (command[0].equalsIgnoreCase("NEXT")) {
+			selectNewArticle(conn, currA, currG, 1);
+		} else if (command[0].equalsIgnoreCase("PREV")) {
+			selectNewArticle(conn, currA, currG, -1);
+		} else {
+			conn.println("500 internal server error");
+		}
+	}
+
+	private void selectNewArticle(NNTPConnection conn, Article article, Channel grp,
+		final int delta)
+		throws IOException, StorageBackendException
+	{
+		assert article != null;
+
+		article = grp.getArticle(grp.getIndexOf(article) + delta);
+
+		if (article == null) {
+			conn.println("421 no next article in this group");
+		} else {
+			conn.setCurrentArticle(article);
+			conn.println("223 " + conn.getCurrentChannel().getIndexOf(article)
+				+ " " + article.getMessageID()
+				+ " article retrieved - request text separately");
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/OverCommand.java
--- a/src/org/sonews/daemon/command/OverCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/OverCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -109,186 +109,153 @@
 public class OverCommand implements Command
 {
 
-  public static final int MAX_LINES_PER_DBREQUEST = 200;
+	public static final int MAX_LINES_PER_DBREQUEST = 200;
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[]{"OVER", "XOVER"};
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"OVER", "XOVER"};
+	}
 
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return false;
-  }
+	@Override
+	public boolean isStateful()
+	{
+		return false;
+	}
 
-  @Override
-  public void processLine(NNTPConnection conn, final String line, byte[] raw)
-    throws IOException, StorageBackendException
-  {
-    if(conn.getCurrentChannel() == null)
-    {
-      conn.println("412 no newsgroup selected");
-    }
-    else
-    {
-      String[] command = line.split(" ");
+	@Override
+	public void processLine(NNTPConnection conn, final String line, byte[] raw)
+		throws IOException, StorageBackendException
+	{
+		if (conn.getCurrentChannel() == null) {
+			conn.println("412 no newsgroup selected");
+		} else {
+			String[] command = line.split(" ");
 
-      // If no parameter was specified, show information about
-      // the currently selected article(s)
-      if(command.length == 1)
-      {
-        final Article art = conn.getCurrentArticle();
-        if(art == null)
-        {
-          conn.println("420 no article(s) selected");
-          return;
-        }
+			// If no parameter was specified, show information about
+			// the currently selected article(s)
+			if (command.length == 1) {
+				final Article art = conn.getCurrentArticle();
+				if (art == null) {
+					conn.println("420 no article(s) selected");
+					return;
+				}
 
-        conn.println(buildOverview(art, -1));
-      }
-      // otherwise print information about the specified range
-      else
-      {
-        long artStart;
-        long artEnd   = conn.getCurrentChannel().getLastArticleNumber();
-        String[] nums = command[1].split("-");
-        if(nums.length >= 1)
-        {
-          try
-          {
-            artStart = Integer.parseInt(nums[0]);
-          }
-          catch(NumberFormatException e) 
-          {
-            Log.get().info(e.getMessage());
-            artStart = Integer.parseInt(command[1]);
-          }
-        }
-        else
-        {
-          artStart = conn.getCurrentChannel().getFirstArticleNumber();
-        }
+				conn.println(buildOverview(art, -1));
+			} // otherwise print information about the specified range
+			else {
+				long artStart;
+				long artEnd = conn.getCurrentChannel().getLastArticleNumber();
+				String[] nums = command[1].split("-");
+				if (nums.length >= 1) {
+					try {
+						artStart = Integer.parseInt(nums[0]);
+					} catch (NumberFormatException e) {
+						Log.get().info(e.getMessage());
+						artStart = Integer.parseInt(command[1]);
+					}
+				} else {
+					artStart = conn.getCurrentChannel().getFirstArticleNumber();
+				}
 
-        if(nums.length >=2)
-        {
-          try
-          {
-            artEnd = Integer.parseInt(nums[1]);
-          }
-          catch(NumberFormatException e) 
-          {
-            e.printStackTrace();
-          }
-        }
+				if (nums.length >= 2) {
+					try {
+						artEnd = Integer.parseInt(nums[1]);
+					} catch (NumberFormatException e) {
+						e.printStackTrace();
+					}
+				}
 
-        if(artStart > artEnd)
-        {
-          if(command[0].equalsIgnoreCase("OVER"))
-          {
-            conn.println("423 no articles in that range");
-          }
-          else
-          {
-            conn.println("224 (empty) overview information follows:");
-            conn.println(".");
-          }
-        }
-        else
-        {
-          for(long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST)
-          {
-            long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
-            List<Pair<Long, ArticleHead>> articleHeads = conn.getCurrentChannel()
-              .getArticleHeads(n, nEnd);
-            if(articleHeads.isEmpty() && n == artStart
-              && command[0].equalsIgnoreCase("OVER"))
-            {
-              // This reply is only valid for OVER, not for XOVER command
-              conn.println("423 no articles in that range");
-              return;
-            }
-            else if(n == artStart)
-            {
-              // XOVER replies this although there is no data available
-              conn.println("224 overview information follows");
-            }
+				if (artStart > artEnd) {
+					if (command[0].equalsIgnoreCase("OVER")) {
+						conn.println("423 no articles in that range");
+					} else {
+						conn.println("224 (empty) overview information follows:");
+						conn.println(".");
+					}
+				} else {
+					for (long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST) {
+						long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
+						List<Pair<Long, ArticleHead>> articleHeads = conn.getCurrentChannel().getArticleHeads(n, nEnd);
+						if (articleHeads.isEmpty() && n == artStart
+							&& command[0].equalsIgnoreCase("OVER")) {
+							// This reply is only valid for OVER, not for XOVER command
+							conn.println("423 no articles in that range");
+							return;
+						} else if (n == artStart) {
+							// XOVER replies this although there is no data available
+							conn.println("224 overview information follows");
+						}
 
-            for(Pair<Long, ArticleHead> article : articleHeads)
-            {
-              String overview = buildOverview(article.getB(), article.getA());
-              conn.println(overview);
-            }
-          } // for
-          conn.println(".");
-        }
-      }
-    }
-  }
-  
-  private String buildOverview(ArticleHead art, long nr)
-  {
-    StringBuilder overview = new StringBuilder();
-    overview.append(nr);
-    overview.append('\t');
+						for (Pair<Long, ArticleHead> article : articleHeads) {
+							String overview = buildOverview(article.getB(), article.getA());
+							conn.println(overview);
+						}
+					} // for
+					conn.println(".");
+				}
+			}
+		}
+	}
 
-    String subject = art.getHeader(Headers.SUBJECT)[0];
-    if("".equals(subject))
-    {
-      subject = "<empty>";
-    }
-    overview.append(escapeString(subject));
-    overview.append('\t');
+	private String buildOverview(ArticleHead art, long nr)
+	{
+		StringBuilder overview = new StringBuilder();
+		overview.append(nr);
+		overview.append('\t');
 
-    overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
-    overview.append('\t');
-    overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
-    overview.append('\t');
-    overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
-    overview.append('\t');
-    overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
-    overview.append('\t');
+		String subject = art.getHeader(Headers.SUBJECT)[0];
+		if ("".equals(subject)) {
+			subject = "<empty>";
+		}
+		overview.append(escapeString(subject));
+		overview.append('\t');
 
-    String bytes = art.getHeader(Headers.BYTES)[0];
-    if("".equals(bytes))
-    {
-      bytes = "0";
-    }
-    overview.append(escapeString(bytes));
-    overview.append('\t');
+		overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
+		overview.append('\t');
+		overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
+		overview.append('\t');
+		overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
+		overview.append('\t');
+		overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
+		overview.append('\t');
 
-    String lines = art.getHeader(Headers.LINES)[0];
-    if("".equals(lines))
-    {
-      lines = "0";
-    }
-    overview.append(escapeString(lines));
-    overview.append('\t');
-    overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
+		String bytes = art.getHeader(Headers.BYTES)[0];
+		if ("".equals(bytes)) {
+			bytes = "0";
+		}
+		overview.append(escapeString(bytes));
+		overview.append('\t');
 
-    // Remove trailing tabs if some data is empty
-    return overview.toString().trim();
-  }
-  
-  private String escapeString(String str)
-  {
-    String nstr = str.replace("\r", "");
-    nstr = nstr.replace('\n', ' ');
-    nstr = nstr.replace('\t', ' ');
-    return nstr.trim();
-  }
-  
+		String lines = art.getHeader(Headers.LINES)[0];
+		if ("".equals(lines)) {
+			lines = "0";
+		}
+		overview.append(escapeString(lines));
+		overview.append('\t');
+		overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
+
+		// Remove trailing tabs if some data is empty
+		return overview.toString().trim();
+	}
+
+	private String escapeString(String str)
+	{
+		String nstr = str.replace("\r", "");
+		nstr = nstr.replace('\n', ' ');
+		nstr = nstr.replace('\t', ' ');
+		return nstr.trim();
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/PostCommand.java
--- a/src/org/sonews/daemon/command/PostCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/PostCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -47,286 +47,237 @@
  */
 public class PostCommand implements Command
 {
-  
-  private final Article article   = new Article();
-  private int           lineCount = 0;
-  private long          bodySize  = 0;
-  private InternetHeaders headers = null;
-  private long          maxBodySize  = 
-    Config.inst().get(Config.ARTICLE_MAXSIZE, 128) * 1024L; // Size in bytes
-  private PostState     state     = PostState.WaitForLineOne;
-  private final ByteArrayOutputStream bufBody   = new ByteArrayOutputStream();
-  private final StringBuilder         strHead   = new StringBuilder();
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[]{"POST"};
-  }
+	private final Article article = new Article();
+	private int lineCount = 0;
+	private long bodySize = 0;
+	private InternetHeaders headers = null;
+	private long maxBodySize =
+		Config.inst().get(Config.ARTICLE_MAXSIZE, 128) * 1024L; // Size in bytes
+	private PostState state = PostState.WaitForLineOne;
+	private final ByteArrayOutputStream bufBody = new ByteArrayOutputStream();
+	private final StringBuilder strHead = new StringBuilder();
 
-  @Override
-  public boolean hasFinished()
-  {
-    return this.state == PostState.Finished;
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"POST"};
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return this.state == PostState.Finished;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return true;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  /**
-   * Process the given line String. line.trim() was called by NNTPConnection.
-   * @param line
-   * @throws java.io.IOException
-   * @throws java.sql.SQLException
-   */
-  @Override // TODO: Refactor this method to reduce complexity!
-  public void processLine(NNTPConnection conn, String line, byte[] raw)
-    throws IOException, StorageBackendException
-  {
-    switch(state)
-    {
-      case WaitForLineOne:
-      {
-        if(line.equalsIgnoreCase("POST"))
-        {
-          conn.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
-          state = PostState.ReadingHeaders;
-        }
-        else
-        {
-          conn.println("500 invalid command usage");
-        }
-        break;
-      }
-      case ReadingHeaders:
-      {
-        strHead.append(line);
-        strHead.append(NNTPConnection.NEWLINE);
-        
-        if("".equals(line) || ".".equals(line))
-        {
-          // we finally met the blank line
-          // separating headers from body
-          
-          try
-          {
-            // Parse the header using the InternetHeader class from JavaMail API
-            headers = new InternetHeaders(
-              new ByteArrayInputStream(strHead.toString().trim()
-                .getBytes(conn.getCurrentCharset())));
+	@Override
+	public boolean isStateful()
+	{
+		return true;
+	}
 
-            // add the header entries for the article
-            article.setHeaders(headers);
-          }
-          catch (MessagingException e)
-          {
-            e.printStackTrace();
-            conn.println("500 posting failed - invalid header");
-            state = PostState.Finished;
-            break;
-          }
+	/**
+	 * Process the given line String. line.trim() was called by NNTPConnection.
+	 * @param line
+	 * @throws java.io.IOException
+	 * @throws java.sql.SQLException
+	 */
+	@Override // TODO: Refactor this method to reduce complexity!
+	public void processLine(NNTPConnection conn, String line, byte[] raw)
+		throws IOException, StorageBackendException
+	{
+		switch (state) {
+			case WaitForLineOne: {
+				if (line.equalsIgnoreCase("POST")) {
+					conn.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
+					state = PostState.ReadingHeaders;
+				} else {
+					conn.println("500 invalid command usage");
+				}
+				break;
+			}
+			case ReadingHeaders: {
+				strHead.append(line);
+				strHead.append(NNTPConnection.NEWLINE);
 
-          // Change charset for reading body; 
-          // for multipart messages UTF-8 is returned
-          //conn.setCurrentCharset(article.getBodyCharset());
-          
-          state = PostState.ReadingBody;
-          
-          if(".".equals(line))
-          {
-            // Post an article without body
-            postArticle(conn, article);
-            state = PostState.Finished;
-          }
-        }
-        break;
-      }
-      case ReadingBody:
-      {
-        if(".".equals(line))
-        {    
-          // Set some headers needed for Over command
-          headers.setHeader(Headers.LINES, Integer.toString(lineCount));
-          headers.setHeader(Headers.BYTES, Long.toString(bodySize));
+				if ("".equals(line) || ".".equals(line)) {
+					// we finally met the blank line
+					// separating headers from body
 
-          byte[] body = bufBody.toByteArray();
-          if(body.length >= 2)
-          {
-            // Remove trailing CRLF
-            body = Arrays.copyOf(body, body.length - 2);
-          }
-          article.setBody(body); // set the article body
-          
-          postArticle(conn, article);
-          state = PostState.Finished;
-        }
-        else
-        {
-          bodySize += line.length() + 1;
-          lineCount++;
-          
-          // Add line to body buffer
-          bufBody.write(raw, 0, raw.length);
-          bufBody.write(NNTPConnection.NEWLINE.getBytes());
-          
-          if(bodySize > maxBodySize)
-          {
-            conn.println("500 article is too long");
-            state = PostState.Finished;
-            break;
-          }
-        }
-        break;
-      }
-      default:
-      {
-        // Should never happen
-        Log.get().severe("PostCommand::processLine(): already finished...");
-      }
-    }
-  }
-  
-  /**
-   * Article is a control message and needs special handling.
-   * @param article
-   */
-  private void controlMessage(NNTPConnection conn, Article article)
-    throws IOException
-  {
-    String[] ctrl = article.getHeader(Headers.CONTROL)[0].split(" ");
-    if(ctrl.length == 2) // "cancel <mid>"
-    {
-      try
-      {
-        StorageManager.current().delete(ctrl[1]);
-        
-        // Move cancel message to "control" group
-        article.setHeader(Headers.NEWSGROUPS, "control");
-        StorageManager.current().addArticle(article);
-        conn.println("240 article cancelled");
-      }
-      catch(StorageBackendException ex)
-      {
-        Log.get().severe(ex.toString());
-        conn.println("500 internal server error");
-      }
-    }
-    else
-    {
-      conn.println("441 unknown control header");
-    }
-  }
-  
-  private void supersedeMessage(NNTPConnection conn, Article article)
-    throws IOException
-  {
-    try
-    {
-      String oldMsg = article.getHeader(Headers.SUPERSEDES)[0];
-      StorageManager.current().delete(oldMsg);
-      StorageManager.current().addArticle(article);
-      conn.println("240 article replaced");
-    }
-    catch(StorageBackendException ex)
-    {
-      Log.get().severe(ex.toString());
-      conn.println("500 internal server error");
-    }
-  }
-  
-  private void postArticle(NNTPConnection conn, Article article)
-    throws IOException
-  {
-    if(article.getHeader(Headers.CONTROL)[0].length() > 0)
-    {
-      controlMessage(conn, article);
-    }
-    else if(article.getHeader(Headers.SUPERSEDES)[0].length() > 0)
-    {
-      supersedeMessage(conn, article);
-    }
-    else // Post the article regularily
-    {
-      // Circle check; note that Path can already contain the hostname here
-      String host = Config.inst().get(Config.HOSTNAME, "localhost");
-      if(article.getHeader(Headers.PATH)[0].indexOf(host + "!", 1) > 0)
-      {
-        Log.get().info(article.getMessageID() + " skipped for host " + host);
-        conn.println("441 I know this article already");
-        return;
-      }
+					try {
+						// Parse the header using the InternetHeader class from JavaMail API
+						headers = new InternetHeaders(
+							new ByteArrayInputStream(strHead.toString().trim().getBytes(conn.getCurrentCharset())));
 
-      // Try to create the article in the database or post it to
-      // appropriate mailing list
-      try
-      {
-        boolean success = false;
-        String[] groupnames = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
-        for(String groupname : groupnames)
-        {          
-          Group group = StorageManager.current().getGroup(groupname);
-          if(group != null && !group.isDeleted())
-          {
-            if(group.isMailingList() && !conn.isLocalConnection())
-            {
-              // Send to mailing list; the Dispatcher writes 
-              // statistics to database
-              Dispatcher.toList(article, group.getName());
-              success = true;
-            }
-            else
-            {
-              // Store in database
-              if(!StorageManager.current().isArticleExisting(article.getMessageID()))
-              {
-                StorageManager.current().addArticle(article);
+						// add the header entries for the article
+						article.setHeaders(headers);
+					} catch (MessagingException e) {
+						e.printStackTrace();
+						conn.println("500 posting failed - invalid header");
+						state = PostState.Finished;
+						break;
+					}
 
-                // Log this posting to statistics
-                Stats.getInstance().mailPosted(
-                  article.getHeader(Headers.NEWSGROUPS)[0]);
-              }
-              success = true;
-            }
-          }
-        } // end for
+					// Change charset for reading body;
+					// for multipart messages UTF-8 is returned
+					//conn.setCurrentCharset(article.getBodyCharset());
 
-        if(success)
-        {
-          conn.println("240 article posted ok");
-          FeedManager.queueForPush(article);
-        }
-        else
-        {
-          conn.println("441 newsgroup not found");
-        }
-      }
-      catch(AddressException ex)
-      {
-        Log.get().warning(ex.getMessage());
-        conn.println("441 invalid sender address");
-      }
-      catch(MessagingException ex)
-      {
-        // A MessageException is thrown when the sender email address is
-        // invalid or something is wrong with the SMTP server.
-        System.err.println(ex.getLocalizedMessage());
-        conn.println("441 " + ex.getClass().getCanonicalName() + ": " + ex.getLocalizedMessage());
-      }
-      catch(StorageBackendException ex)
-      {
-        ex.printStackTrace();
-        conn.println("500 internal server error");
-      }
-    }
-  }
+					state = PostState.ReadingBody;
 
+					if (".".equals(line)) {
+						// Post an article without body
+						postArticle(conn, article);
+						state = PostState.Finished;
+					}
+				}
+				break;
+			}
+			case ReadingBody: {
+				if (".".equals(line)) {
+					// Set some headers needed for Over command
+					headers.setHeader(Headers.LINES, Integer.toString(lineCount));
+					headers.setHeader(Headers.BYTES, Long.toString(bodySize));
+
+					byte[] body = bufBody.toByteArray();
+					if (body.length >= 2) {
+						// Remove trailing CRLF
+						body = Arrays.copyOf(body, body.length - 2);
+					}
+					article.setBody(body); // set the article body
+
+					postArticle(conn, article);
+					state = PostState.Finished;
+				} else {
+					bodySize += line.length() + 1;
+					lineCount++;
+
+					// Add line to body buffer
+					bufBody.write(raw, 0, raw.length);
+					bufBody.write(NNTPConnection.NEWLINE.getBytes());
+
+					if (bodySize > maxBodySize) {
+						conn.println("500 article is too long");
+						state = PostState.Finished;
+						break;
+					}
+				}
+				break;
+			}
+			default: {
+				// Should never happen
+				Log.get().severe("PostCommand::processLine(): already finished...");
+			}
+		}
+	}
+
+	/**
+	 * Article is a control message and needs special handling.
+	 * @param article
+	 */
+	private void controlMessage(NNTPConnection conn, Article article)
+		throws IOException
+	{
+		String[] ctrl = article.getHeader(Headers.CONTROL)[0].split(" ");
+		if (ctrl.length == 2) // "cancel <mid>"
+		{
+			try {
+				StorageManager.current().delete(ctrl[1]);
+
+				// Move cancel message to "control" group
+				article.setHeader(Headers.NEWSGROUPS, "control");
+				StorageManager.current().addArticle(article);
+				conn.println("240 article cancelled");
+			} catch (StorageBackendException ex) {
+				Log.get().severe(ex.toString());
+				conn.println("500 internal server error");
+			}
+		} else {
+			conn.println("441 unknown control header");
+		}
+	}
+
+	private void supersedeMessage(NNTPConnection conn, Article article)
+		throws IOException
+	{
+		try {
+			String oldMsg = article.getHeader(Headers.SUPERSEDES)[0];
+			StorageManager.current().delete(oldMsg);
+			StorageManager.current().addArticle(article);
+			conn.println("240 article replaced");
+		} catch (StorageBackendException ex) {
+			Log.get().severe(ex.toString());
+			conn.println("500 internal server error");
+		}
+	}
+
+	private void postArticle(NNTPConnection conn, Article article)
+		throws IOException
+	{
+		if (article.getHeader(Headers.CONTROL)[0].length() > 0) {
+			controlMessage(conn, article);
+		} else if (article.getHeader(Headers.SUPERSEDES)[0].length() > 0) {
+			supersedeMessage(conn, article);
+		} else // Post the article regularily
+		{
+			// Circle check; note that Path can already contain the hostname here
+			String host = Config.inst().get(Config.HOSTNAME, "localhost");
+			if (article.getHeader(Headers.PATH)[0].indexOf(host + "!", 1) > 0) {
+				Log.get().info(article.getMessageID() + " skipped for host " + host);
+				conn.println("441 I know this article already");
+				return;
+			}
+
+			// Try to create the article in the database or post it to
+			// appropriate mailing list
+			try {
+				boolean success = false;
+				String[] groupnames = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
+				for (String groupname : groupnames) {
+					Group group = StorageManager.current().getGroup(groupname);
+					if (group != null && !group.isDeleted()) {
+						if (group.isMailingList() && !conn.isLocalConnection()) {
+							// Send to mailing list; the Dispatcher writes
+							// statistics to database
+							Dispatcher.toList(article, group.getName());
+							success = true;
+						} else {
+							// Store in database
+							if (!StorageManager.current().isArticleExisting(article.getMessageID())) {
+								StorageManager.current().addArticle(article);
+
+								// Log this posting to statistics
+								Stats.getInstance().mailPosted(
+									article.getHeader(Headers.NEWSGROUPS)[0]);
+							}
+							success = true;
+						}
+					}
+				} // end for
+
+				if (success) {
+					conn.println("240 article posted ok");
+					FeedManager.queueForPush(article);
+				} else {
+					conn.println("441 newsgroup not found");
+				}
+			} catch (AddressException ex) {
+				Log.get().warning(ex.getMessage());
+				conn.println("441 invalid sender address");
+			} catch (MessagingException ex) {
+				// A MessageException is thrown when the sender email address is
+				// invalid or something is wrong with the SMTP server.
+				System.err.println(ex.getLocalizedMessage());
+				conn.println("441 " + ex.getClass().getCanonicalName() + ": " + ex.getLocalizedMessage());
+			} catch (StorageBackendException ex) {
+				ex.printStackTrace();
+				conn.println("500 internal server error");
+			}
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/PostState.java
--- a/src/org/sonews/daemon/command/PostState.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/PostState.java	Sun Aug 29 18:17:37 2010 +0200
@@ -25,5 +25,6 @@
  */
 enum PostState
 {
-  WaitForLineOne, ReadingHeaders, ReadingBody, Finished
+
+	WaitForLineOne, ReadingHeaders, ReadingBody, Finished
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/QuitCommand.java
--- a/src/org/sonews/daemon/command/QuitCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/QuitCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -30,38 +30,37 @@
 public class QuitCommand implements Command
 {
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[]{"QUIT"};
-  }
-  
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"QUIT"};
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return false;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public void processLine(NNTPConnection conn, final String line, byte[] raw)
-    throws IOException, StorageBackendException
-  {    
-    conn.println("205 cya");
-    
-    conn.shutdownInput();
-    conn.shutdownOutput();
-  }
+	@Override
+	public boolean isStateful()
+	{
+		return false;
+	}
 
+	@Override
+	public void processLine(NNTPConnection conn, final String line, byte[] raw)
+		throws IOException, StorageBackendException
+	{
+		conn.println("205 cya");
+
+		conn.shutdownInput();
+		conn.shutdownOutput();
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/StatCommand.java
--- a/src/org/sonews/daemon/command/StatCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/StatCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -31,84 +31,70 @@
 public class StatCommand implements Command
 {
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[]{"STAT"};
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"STAT"};
+	}
 
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return false;
-  }
+	@Override
+	public boolean isStateful()
+	{
+		return false;
+	}
 
-  // TODO: Method has various exit points => Refactor!
-  @Override
-  public void processLine(NNTPConnection conn, final String line, byte[] raw)
-    throws IOException, StorageBackendException
-  {
-    final String[] command = line.split(" ");
+	// TODO: Method has various exit points => Refactor!
+	@Override
+	public void processLine(NNTPConnection conn, final String line, byte[] raw)
+		throws IOException, StorageBackendException
+	{
+		final String[] command = line.split(" ");
 
-    Article article = null;
-    if(command.length == 1)
-    {
-      article = conn.getCurrentArticle();
-      if(article == null)
-      {
-        conn.println("420 no current article has been selected");
-        return;
-      }
-    }
-    else if(command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN))
-    {
-      // Message-ID
-      article = Article.getByMessageID(command[1]);
-      if (article == null)
-      {
-        conn.println("430 no such article found");
-        return;
-      }
-    }
-    else
-    {
-      // Message Number
-      try
-      {
-        long aid = Long.parseLong(command[1]);
-        article = conn.getCurrentChannel().getArticle(aid);
-      }
-      catch(NumberFormatException ex)
-      {
-        ex.printStackTrace();
-      }
-      catch(StorageBackendException ex)
-      {
-        ex.printStackTrace();
-      }
-      if (article == null)
-      {
-        conn.println("423 no such article number in this group");
-        return;
-      }
-      conn.setCurrentArticle(article);
-    }
-    
-    conn.println("223 " + conn.getCurrentChannel().getIndexOf(article) + " "
-      + article.getMessageID()
-      + " article retrieved - request text separately");
-  }
-  
+		Article article = null;
+		if (command.length == 1) {
+			article = conn.getCurrentArticle();
+			if (article == null) {
+				conn.println("420 no current article has been selected");
+				return;
+			}
+		} else if (command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN)) {
+			// Message-ID
+			article = Article.getByMessageID(command[1]);
+			if (article == null) {
+				conn.println("430 no such article found");
+				return;
+			}
+		} else {
+			// Message Number
+			try {
+				long aid = Long.parseLong(command[1]);
+				article = conn.getCurrentChannel().getArticle(aid);
+			} catch (NumberFormatException ex) {
+				ex.printStackTrace();
+			} catch (StorageBackendException ex) {
+				ex.printStackTrace();
+			}
+			if (article == null) {
+				conn.println("423 no such article number in this group");
+				return;
+			}
+			conn.setCurrentArticle(article);
+		}
+
+		conn.println("223 " + conn.getCurrentChannel().getIndexOf(article) + " "
+			+ article.getMessageID()
+			+ " article retrieved - request text separately");
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/UnsupportedCommand.java
--- a/src/org/sonews/daemon/command/UnsupportedCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/UnsupportedCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -29,39 +29,38 @@
  */
 public class UnsupportedCommand implements Command
 {
-  
-  /**
-   * @return Always returns null.
-   */
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return null;
-  }
 
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	/**
+	 * @return Always returns null.
+	 */
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return null;
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return false;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public void processLine(NNTPConnection conn, final String line, byte[] raw)
-    throws IOException
-  {
-    conn.println("500 command not supported");
-  }
-  
+	@Override
+	public boolean isStateful()
+	{
+		return false;
+	}
+
+	@Override
+	public void processLine(NNTPConnection conn, final String line, byte[] raw)
+		throws IOException
+	{
+		conn.println("500 command not supported");
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/XDaemonCommand.java
--- a/src/org/sonews/daemon/command/XDaemonCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/XDaemonCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -43,228 +43,162 @@
 public class XDaemonCommand implements Command
 {
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[]{"XDAEMON"};
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"XDAEMON"};
+	}
 
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return false;
-  }
+	@Override
+	public boolean isStateful()
+	{
+		return false;
+	}
 
-  private void channelAdd(String[] commands, NNTPConnection conn)
-    throws IOException, StorageBackendException
-  {
-    String groupName = commands[2];
-    if(StorageManager.current().isGroupExisting(groupName))
-    {
-      conn.println("400 group " + groupName + " already existing!");
-    }
-    else
-    {
-      StorageManager.current().addGroup(groupName, Integer.parseInt(commands[3]));
-      conn.println("200 group " + groupName + " created");
-    }
-  }
+	private void channelAdd(String[] commands, NNTPConnection conn)
+		throws IOException, StorageBackendException
+	{
+		String groupName = commands[2];
+		if (StorageManager.current().isGroupExisting(groupName)) {
+			conn.println("400 group " + groupName + " already existing!");
+		} else {
+			StorageManager.current().addGroup(groupName, Integer.parseInt(commands[3]));
+			conn.println("200 group " + groupName + " created");
+		}
+	}
 
-  // TODO: Refactor this method to reduce complexity!
-  @Override
-  public void processLine(NNTPConnection conn, String line, byte[] raw)
-    throws IOException, StorageBackendException
-  {
-    InetSocketAddress addr = (InetSocketAddress)conn.getSocketChannel().socket()
-      .getRemoteSocketAddress();
-    if(addr.getHostName().equals(
-      Config.inst().get(Config.XDAEMON_HOST, "localhost")))
-    {
-      String[] commands = line.split(" ", 4);
-      if(commands.length == 3 && commands[1].equalsIgnoreCase("LIST"))
-      {
-        if(commands[2].equalsIgnoreCase("CONFIGKEYS"))
-        {
-          conn.println("100 list of available config keys follows");
-          for(String key : Config.AVAILABLE_KEYS)
-          {
-            conn.println(key);
-          }
-          conn.println(".");
-        }
-        else if(commands[2].equalsIgnoreCase("PEERINGRULES"))
-        {
-          List<Subscription> pull = 
-            StorageManager.current().getSubscriptions(FeedManager.TYPE_PULL);
-          List<Subscription> push =
-            StorageManager.current().getSubscriptions(FeedManager.TYPE_PUSH);
-          conn.println("100 list of peering rules follows");
-          for(Subscription sub : pull)
-          {
-            conn.println("PULL " + sub.getHost() + ":" + sub.getPort()
-              + " " + sub.getGroup());
-          }
-          for(Subscription sub : push)
-          {
-            conn.println("PUSH " + sub.getHost() + ":" + sub.getPort()
-              + " " + sub.getGroup());
-          }
-          conn.println(".");
-        }
-        else
-        {
-          conn.println("401 unknown sub command");
-        }
-      }
-      else if(commands.length == 3 && commands[1].equalsIgnoreCase("DELETE"))
-      {
-        StorageManager.current().delete(commands[2]);
-        conn.println("200 article " + commands[2] + " deleted");
-      }
-      else if(commands.length == 4 && commands[1].equalsIgnoreCase("GROUPADD"))
-      {
-        channelAdd(commands, conn);
-      }
-      else if(commands.length == 3 && commands[1].equalsIgnoreCase("GROUPDEL"))
-      {
-        Group group = StorageManager.current().getGroup(commands[2]);
-        if(group == null)
-        {
-          conn.println("400 group not found");
-        }
-        else
-        {
-          group.setFlag(Group.DELETED);
-          group.update();
-          conn.println("200 group " + commands[2] + " marked as deleted");
-        }
-      }
-      else if(commands.length == 4 && commands[1].equalsIgnoreCase("SET"))
-      {
-        String key = commands[2];
-        String val = commands[3];
-        Config.inst().set(key, val);
-        conn.println("200 new config value set");
-      }
-      else if(commands.length == 3 && commands[1].equalsIgnoreCase("GET"))
-      {
-        String key = commands[2];
-        String val = Config.inst().get(key, null);
-        if(val != null)
-        {
-          conn.println("100 config value for " + key + " follows");
-          conn.println(val);
-          conn.println(".");
-        }
-        else
-        {
-          conn.println("400 config value not set");
-        }
-      }
-      else if(commands.length >= 3 && commands[1].equalsIgnoreCase("LOG"))
-      {
-        Group group = null;
-        if(commands.length > 3)
-        {
-          group = (Group)Channel.getByName(commands[3]);
-        }
+	// TODO: Refactor this method to reduce complexity!
+	@Override
+	public void processLine(NNTPConnection conn, String line, byte[] raw)
+		throws IOException, StorageBackendException
+	{
+		InetSocketAddress addr = (InetSocketAddress) conn.getSocketChannel().socket().getRemoteSocketAddress();
+		if (addr.getHostName().equals(
+			Config.inst().get(Config.XDAEMON_HOST, "localhost"))) {
+			String[] commands = line.split(" ", 4);
+			if (commands.length == 3 && commands[1].equalsIgnoreCase("LIST")) {
+				if (commands[2].equalsIgnoreCase("CONFIGKEYS")) {
+					conn.println("100 list of available config keys follows");
+					for (String key : Config.AVAILABLE_KEYS) {
+						conn.println(key);
+					}
+					conn.println(".");
+				} else if (commands[2].equalsIgnoreCase("PEERINGRULES")) {
+					List<Subscription> pull =
+						StorageManager.current().getSubscriptions(FeedManager.TYPE_PULL);
+					List<Subscription> push =
+						StorageManager.current().getSubscriptions(FeedManager.TYPE_PUSH);
+					conn.println("100 list of peering rules follows");
+					for (Subscription sub : pull) {
+						conn.println("PULL " + sub.getHost() + ":" + sub.getPort()
+							+ " " + sub.getGroup());
+					}
+					for (Subscription sub : push) {
+						conn.println("PUSH " + sub.getHost() + ":" + sub.getPort()
+							+ " " + sub.getGroup());
+					}
+					conn.println(".");
+				} else {
+					conn.println("401 unknown sub command");
+				}
+			} else if (commands.length == 3 && commands[1].equalsIgnoreCase("DELETE")) {
+				StorageManager.current().delete(commands[2]);
+				conn.println("200 article " + commands[2] + " deleted");
+			} else if (commands.length == 4 && commands[1].equalsIgnoreCase("GROUPADD")) {
+				channelAdd(commands, conn);
+			} else if (commands.length == 3 && commands[1].equalsIgnoreCase("GROUPDEL")) {
+				Group group = StorageManager.current().getGroup(commands[2]);
+				if (group == null) {
+					conn.println("400 group not found");
+				} else {
+					group.setFlag(Group.DELETED);
+					group.update();
+					conn.println("200 group " + commands[2] + " marked as deleted");
+				}
+			} else if (commands.length == 4 && commands[1].equalsIgnoreCase("SET")) {
+				String key = commands[2];
+				String val = commands[3];
+				Config.inst().set(key, val);
+				conn.println("200 new config value set");
+			} else if (commands.length == 3 && commands[1].equalsIgnoreCase("GET")) {
+				String key = commands[2];
+				String val = Config.inst().get(key, null);
+				if (val != null) {
+					conn.println("100 config value for " + key + " follows");
+					conn.println(val);
+					conn.println(".");
+				} else {
+					conn.println("400 config value not set");
+				}
+			} else if (commands.length >= 3 && commands[1].equalsIgnoreCase("LOG")) {
+				Group group = null;
+				if (commands.length > 3) {
+					group = (Group) Channel.getByName(commands[3]);
+				}
 
-        if(commands[2].equalsIgnoreCase("CONNECTED_CLIENTS"))
-        {
-          conn.println("100 number of connections follow");
-          conn.println(Integer.toString(Stats.getInstance().connectedClients()));
-          conn.println(".");
-        }
-        else if(commands[2].equalsIgnoreCase("POSTED_NEWS"))
-        {
-          conn.println("100 hourly numbers of posted news yesterday");
-          for(int n = 0; n < 24; n++)
-          {
-            conn.println(n + " " + Stats.getInstance()
-              .getYesterdaysEvents(Stats.POSTED_NEWS, n, group));
-          }
-          conn.println(".");
-        }
-        else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS"))
-        {
-          conn.println("100 hourly numbers of gatewayed news yesterday");
-          for(int n = 0; n < 24; n++)
-          {
-            conn.println(n + " " + Stats.getInstance()
-              .getYesterdaysEvents(Stats.GATEWAYED_NEWS, n, group));
-          }
-          conn.println(".");
-        }
-        else if(commands[2].equalsIgnoreCase("TRANSMITTED_NEWS"))
-        {
-          conn.println("100 hourly numbers of news transmitted to peers yesterday");
-          for(int n = 0; n < 24; n++)
-          {
-            conn.println(n + " " + Stats.getInstance()
-              .getYesterdaysEvents(Stats.FEEDED_NEWS, n, group));
-          }
-          conn.println(".");
-        }
-        else if(commands[2].equalsIgnoreCase("HOSTED_NEWS"))
-        {
-          conn.println("100 number of overall hosted news");
-          conn.println(Integer.toString(Stats.getInstance().getNumberOfNews()));
-          conn.println(".");
-        }
-        else if(commands[2].equalsIgnoreCase("HOSTED_GROUPS"))
-        {
-          conn.println("100 number of hosted groups");
-          conn.println(Integer.toString(Stats.getInstance().getNumberOfGroups()));
-          conn.println(".");
-        }
-        else if(commands[2].equalsIgnoreCase("POSTED_NEWS_PER_HOUR"))
-        {
-          conn.println("100 posted news per hour");
-          conn.println(Double.toString(Stats.getInstance().postedPerHour(-1)));
-          conn.println(".");
-        }
-        else if(commands[2].equalsIgnoreCase("FEEDED_NEWS_PER_HOUR"))
-        {
-          conn.println("100 feeded news per hour");
-          conn.println(Double.toString(Stats.getInstance().feededPerHour(-1)));
-          conn.println(".");
-        }
-        else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS_PER_HOUR"))
-        {
-          conn.println("100 gatewayed news per hour");
-          conn.println(Double.toString(Stats.getInstance().gatewayedPerHour(-1)));
-          conn.println(".");
-        }
-        else
-        {
-          conn.println("401 unknown sub command");
-        }
-      }
-      else if(commands.length >= 3 && commands[1].equalsIgnoreCase("PLUGIN"))
-      {
-        
-      }
-      else
-      {
-        conn.println("400 invalid command usage");
-      }
-    }
-    else
-    {
-      conn.println("501 not allowed");
-    }
-  }
-  
+				if (commands[2].equalsIgnoreCase("CONNECTED_CLIENTS")) {
+					conn.println("100 number of connections follow");
+					conn.println(Integer.toString(Stats.getInstance().connectedClients()));
+					conn.println(".");
+				} else if (commands[2].equalsIgnoreCase("POSTED_NEWS")) {
+					conn.println("100 hourly numbers of posted news yesterday");
+					for (int n = 0; n < 24; n++) {
+						conn.println(n + " " + Stats.getInstance().getYesterdaysEvents(Stats.POSTED_NEWS, n, group));
+					}
+					conn.println(".");
+				} else if (commands[2].equalsIgnoreCase("GATEWAYED_NEWS")) {
+					conn.println("100 hourly numbers of gatewayed news yesterday");
+					for (int n = 0; n < 24; n++) {
+						conn.println(n + " " + Stats.getInstance().getYesterdaysEvents(Stats.GATEWAYED_NEWS, n, group));
+					}
+					conn.println(".");
+				} else if (commands[2].equalsIgnoreCase("TRANSMITTED_NEWS")) {
+					conn.println("100 hourly numbers of news transmitted to peers yesterday");
+					for (int n = 0; n < 24; n++) {
+						conn.println(n + " " + Stats.getInstance().getYesterdaysEvents(Stats.FEEDED_NEWS, n, group));
+					}
+					conn.println(".");
+				} else if (commands[2].equalsIgnoreCase("HOSTED_NEWS")) {
+					conn.println("100 number of overall hosted news");
+					conn.println(Integer.toString(Stats.getInstance().getNumberOfNews()));
+					conn.println(".");
+				} else if (commands[2].equalsIgnoreCase("HOSTED_GROUPS")) {
+					conn.println("100 number of hosted groups");
+					conn.println(Integer.toString(Stats.getInstance().getNumberOfGroups()));
+					conn.println(".");
+				} else if (commands[2].equalsIgnoreCase("POSTED_NEWS_PER_HOUR")) {
+					conn.println("100 posted news per hour");
+					conn.println(Double.toString(Stats.getInstance().postedPerHour(-1)));
+					conn.println(".");
+				} else if (commands[2].equalsIgnoreCase("FEEDED_NEWS_PER_HOUR")) {
+					conn.println("100 feeded news per hour");
+					conn.println(Double.toString(Stats.getInstance().feededPerHour(-1)));
+					conn.println(".");
+				} else if (commands[2].equalsIgnoreCase("GATEWAYED_NEWS_PER_HOUR")) {
+					conn.println("100 gatewayed news per hour");
+					conn.println(Double.toString(Stats.getInstance().gatewayedPerHour(-1)));
+					conn.println(".");
+				} else {
+					conn.println("401 unknown sub command");
+				}
+			} else if (commands.length >= 3 && commands[1].equalsIgnoreCase("PLUGIN")) {
+			} else {
+				conn.println("400 invalid command usage");
+			}
+		} else {
+			conn.println("501 not allowed");
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/daemon/command/XPatCommand.java
--- a/src/org/sonews/daemon/command/XPatCommand.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/daemon/command/XPatCommand.java	Sun Aug 29 18:17:37 2010 +0200
@@ -78,93 +78,79 @@
 public class XPatCommand implements Command
 {
 
-  @Override
-  public String[] getSupportedCommandStrings()
-  {
-    return new String[]{"XPAT"};
-  }
-  
-  @Override
-  public boolean hasFinished()
-  {
-    return true;
-  }
+	@Override
+	public String[] getSupportedCommandStrings()
+	{
+		return new String[] {"XPAT"};
+	}
 
-  @Override
-  public String impliedCapability()
-  {
-    return null;
-  }
+	@Override
+	public boolean hasFinished()
+	{
+		return true;
+	}
 
-  @Override
-  public boolean isStateful()
-  {
-    return false;
-  }
+	@Override
+	public String impliedCapability()
+	{
+		return null;
+	}
 
-  @Override
-  public void processLine(NNTPConnection conn, final String line, byte[] raw)
-    throws IOException, StorageBackendException
-  {
-    if(conn.getCurrentChannel() == null)
-    {
-      conn.println("430 no group selected");
-      return;
-    }
+	@Override
+	public boolean isStateful()
+	{
+		return false;
+	}
 
-    String[] command = line.split("\\p{Space}+");
+	@Override
+	public void processLine(NNTPConnection conn, final String line, byte[] raw)
+		throws IOException, StorageBackendException
+	{
+		if (conn.getCurrentChannel() == null) {
+			conn.println("430 no group selected");
+			return;
+		}
 
-    // There may be multiple patterns and Thunderbird produces
-    // additional spaces between range and pattern
-    if(command.length >= 4)
-    {
-      String header  = command[1].toLowerCase(Locale.US);
-      String range   = command[2];
-      String pattern = command[3];
+		String[] command = line.split("\\p{Space}+");
 
-      long start = -1;
-      long end   = -1;
-      if(range.contains("-"))
-      {
-        String[] rsplit = range.split("-", 2);
-        start = Long.parseLong(rsplit[0]);
-        if(rsplit[1].length() > 0)
-        {
-          end = Long.parseLong(rsplit[1]);
-        }
-      }
-      else // TODO: Handle Message-IDs
-      {
-        start = Long.parseLong(range);
-      }
+		// There may be multiple patterns and Thunderbird produces
+		// additional spaces between range and pattern
+		if (command.length >= 4) {
+			String header = command[1].toLowerCase(Locale.US);
+			String range = command[2];
+			String pattern = command[3];
 
-      try
-      {
-        List<Pair<Long, String>> heads = StorageManager.current().
-          getArticleHeaders(conn.getCurrentChannel(), start, end, header, pattern);
-        
-        conn.println("221 header follows");
-        for(Pair<Long, String> head : heads)
-        {
-          conn.println(head.getA() + " " + head.getB());
-        }
-        conn.println(".");
-      }
-      catch(PatternSyntaxException ex)
-      {
-        ex.printStackTrace();
-        conn.println("500 invalid pattern syntax");
-      }
-      catch(StorageBackendException ex)
-      {
-        ex.printStackTrace();
-        conn.println("500 internal server error");
-      }
-    }
-    else
-    {
-      conn.println("430 invalid command usage");
-    }
-  }
+			long start = -1;
+			long end = -1;
+			if (range.contains("-")) {
+				String[] rsplit = range.split("-", 2);
+				start = Long.parseLong(rsplit[0]);
+				if (rsplit[1].length() > 0) {
+					end = Long.parseLong(rsplit[1]);
+				}
+			} else // TODO: Handle Message-IDs
+			{
+				start = Long.parseLong(range);
+			}
 
+			try {
+				List<Pair<Long, String>> heads = StorageManager.current().
+					getArticleHeaders(conn.getCurrentChannel(), start, end, header, pattern);
+
+				conn.println("221 header follows");
+				for (Pair<Long, String> head : heads) {
+					conn.println(head.getA() + " " + head.getB());
+				}
+				conn.println(".");
+			} catch (PatternSyntaxException ex) {
+				ex.printStackTrace();
+				conn.println("500 invalid pattern syntax");
+			} catch (StorageBackendException ex) {
+				ex.printStackTrace();
+				conn.println("500 internal server error");
+			}
+		} else {
+			conn.println("430 invalid command usage");
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/feed/FeedManager.java
--- a/src/org/sonews/feed/FeedManager.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/feed/FeedManager.java	Sun Aug 29 18:17:37 2010 +0200
@@ -25,30 +25,30 @@
  * @author Christian Lins
  * @since sonews/0.5.0
  */
-public final class FeedManager 
+public final class FeedManager
 {
 
-  public static final int TYPE_PULL = 0;
-  public static final int TYPE_PUSH = 1;
-  
-  private static PullFeeder pullFeeder = new PullFeeder();
-  private static PushFeeder pushFeeder = new PushFeeder();
-  
-  /**
-   * Reads the peer subscriptions from database and starts the appropriate
-   * PullFeeder or PushFeeder.
-   */
-  public static synchronized void startFeeding()
-  {
-    pullFeeder.start();
-    pushFeeder.start();
-  }
-  
-  public static void queueForPush(Article article)
-  {
-    pushFeeder.queueForPush(article);
-  }
-  
-  private FeedManager() {}
-  
+	public static final int TYPE_PULL = 0;
+	public static final int TYPE_PUSH = 1;
+	private static PullFeeder pullFeeder = new PullFeeder();
+	private static PushFeeder pushFeeder = new PushFeeder();
+
+	/**
+	 * Reads the peer subscriptions from database and starts the appropriate
+	 * PullFeeder or PushFeeder.
+	 */
+	public static synchronized void startFeeding()
+	{
+		pullFeeder.start();
+		pushFeeder.start();
+	}
+
+	public static void queueForPush(Article article)
+	{
+		pushFeeder.queueForPush(article);
+	}
+
+	private FeedManager()
+	{
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/feed/PullFeeder.java
--- a/src/org/sonews/feed/PullFeeder.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/feed/PullFeeder.java	Sun Aug 29 18:17:37 2010 +0200
@@ -49,228 +49,192 @@
  */
 class PullFeeder extends AbstractDaemon
 {
-  
-  private Map<Subscription, Integer> highMarks = new HashMap<Subscription, Integer>();
-  private BufferedReader             in;
-  private PrintWriter                out;
-  private Set<Subscription>          subscriptions = new HashSet<Subscription>();
-  
-  private void addSubscription(final Subscription sub)
-  {
-    subscriptions.add(sub);
 
-    if(!highMarks.containsKey(sub))
-    {
-      // Set a initial highMark
-      this.highMarks.put(sub, 0);
-    }
-  }
-  
-  /**
-   * Changes to the given group and returns its high mark.
-   * @param groupName
-   * @return
-   */
-  private int changeGroup(String groupName)
-    throws IOException
-  {
-    this.out.print("GROUP " + groupName + "\r\n");
-    this.out.flush();
-    
-    String line = this.in.readLine();
-    if(line.startsWith("211 "))
-    {
-      int highmark = Integer.parseInt(line.split(" ")[3]);
-      return highmark;
-    }
-    else
-    {
-      throw new IOException("GROUP " + groupName + " returned: " + line);
-    }
-  }
-  
-  private void connectTo(final String host, final int port)
-    throws IOException, UnknownHostException
-  {
-    Socket socket = new Socket(host, port);
-    this.out = new PrintWriter(socket.getOutputStream());
-    this.in  = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+	private Map<Subscription, Integer> highMarks = new HashMap<Subscription, Integer>();
+	private BufferedReader in;
+	private PrintWriter out;
+	private Set<Subscription> subscriptions = new HashSet<Subscription>();
 
-    String line = in.readLine();
-    if(!(line.charAt(0) == '2')) // Could be 200 or 2xx if posting is not allowed
-    {
-      throw new IOException(line);
-    }
+	private void addSubscription(final Subscription sub)
+	{
+		subscriptions.add(sub);
 
-    // Send MODE READER to peer, some newsservers are friendlier then
-    this.out.println("MODE READER\r\n");
-    this.out.flush();
-    line = this.in.readLine();
-  }
-  
-  private void disconnect()
-    throws IOException
-  {
-    this.out.print("QUIT\r\n");
-    this.out.flush();
-    this.out.close();
-    this.in.close();
-    
-    this.out = null;
-    this.in  = null;
-  }
-  
-  /**
-   * Uses the OVER or XOVER command to get a list of message overviews that
-   * may be unknown to this feeder and are about to be peered.
-   * @param start
-   * @param end
-   * @return A list of message ids with potentially interesting messages.
-   */
-  private List<String> over(int start, int end)
-    throws IOException
-  {
-    this.out.print("OVER " + start + "-" + end + "\r\n");
-    this.out.flush();
-    
-    String line = this.in.readLine();
-    if(line.startsWith("500 ")) // OVER not supported
-    {
-      this.out.print("XOVER " + start + "-" + end + "\r\n");
-      this.out.flush();
-      
-      line = this.in.readLine();
-    }
-    
-    if(line.startsWith("224 "))
-    {
-      List<String> messages = new ArrayList<String>();
-      line = this.in.readLine();
-      while(!".".equals(line))
-      {
-        String mid = line.split("\t")[4]; // 5th should be the Message-ID
-        messages.add(mid);
-        line = this.in.readLine();
-      }
-      return messages;
-    }
-    else
-    {
-      throw new IOException("Server return for OVER/XOVER: " + line);
-    }
-  }
-  
-  @Override
-  public void run()
-  {
-    while(isRunning())
-    {
-      int pullInterval = 1000 * 
-        Config.inst().get(Config.FEED_PULLINTERVAL, 3600);
-      String host = "localhost";
-      int    port = 119;
-      
-      Log.get().info("Start PullFeeder run...");
+		if (!highMarks.containsKey(sub)) {
+			// Set a initial highMark
+			this.highMarks.put(sub, 0);
+		}
+	}
 
-      try
-      {
-        this.subscriptions.clear();
-        List<Subscription> subsPull = StorageManager.current()
-          .getSubscriptions(FeedManager.TYPE_PULL);
-        for(Subscription sub : subsPull)
-        {
-          addSubscription(sub);
-        }
-      }
-      catch(StorageBackendException ex)
-      {
-        Log.get().log(Level.SEVERE, host, ex);
-      }
+	/**
+	 * Changes to the given group and returns its high mark.
+	 * @param groupName
+	 * @return
+	 */
+	private int changeGroup(String groupName)
+		throws IOException
+	{
+		this.out.print("GROUP " + groupName + "\r\n");
+		this.out.flush();
 
-      try
-      {
-        for(Subscription sub : this.subscriptions)
-        {
-          host = sub.getHost();
-          port = sub.getPort();
+		String line = this.in.readLine();
+		if (line.startsWith("211 ")) {
+			int highmark = Integer.parseInt(line.split(" ")[3]);
+			return highmark;
+		} else {
+			throw new IOException("GROUP " + groupName + " returned: " + line);
+		}
+	}
 
-          try
-          {
-            Log.get().info("Feeding " + sub.getGroup() + " from " + sub.getHost());
-            try
-            {
-              connectTo(host, port);
-            }
-            catch(SocketException ex)
-            {
-              Log.get().info("Skipping " + sub.getHost() + ": " + ex);
-              continue;
-            }
-            
-            int oldMark = this.highMarks.get(sub);
-            int newMark = changeGroup(sub.getGroup());
-            
-            if(oldMark != newMark)
-            {
-              List<String> messageIDs = over(oldMark, newMark);
+	private void connectTo(final String host, final int port)
+		throws IOException, UnknownHostException
+	{
+		Socket socket = new Socket(host, port);
+		this.out = new PrintWriter(socket.getOutputStream());
+		this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
 
-              for(String messageID : messageIDs)
-              {
-                if(!StorageManager.current().isArticleExisting(messageID))
-                {
-                  try
-                  {
-                    // Post the message via common socket connection
-                    ArticleReader aread =
-                      new ArticleReader(sub.getHost(), sub.getPort(), messageID);
-                    byte[] abuf = aread.getArticleData();
-                    if(abuf == null)
-                    {
-                      Log.get().warning("Could not feed " + messageID
-                        + " from " + sub.getHost());
-                    }
-                    else
-                    {
-                      Log.get().info("Feeding " + messageID);
-                      ArticleWriter awrite = new ArticleWriter(
-                        "localhost", Config.inst().get(Config.PORT, 119));
-                      awrite.writeArticle(abuf);
-                      awrite.close();
-                    }
-                    Stats.getInstance().mailFeeded(sub.getGroup());
-                  }
-                  catch(IOException ex)
-                  {
-                    // There may be a temporary network failure
-                    ex.printStackTrace();
-                    Log.get().warning("Skipping mail " + messageID + " due to exception.");
-                  }
-                }
-              } // for(;;)
-              this.highMarks.put(sub, newMark);
-            }
-            
-            disconnect();
-          }
-          catch(StorageBackendException ex)
-          {
-            ex.printStackTrace();
-          }
-          catch(IOException ex)
-          {
-            ex.printStackTrace();
-            Log.get().severe("PullFeeder run stopped due to exception.");
-          }
-        } // for(Subscription sub : subscriptions)
-        
-        Log.get().info("PullFeeder run ended. Waiting " + pullInterval / 1000 + "s");
-        Thread.sleep(pullInterval);
-      }
-      catch(InterruptedException ex)
-      {
-        Log.get().warning(ex.getMessage());
-      }
-    }
-  }
-  
+		String line = in.readLine();
+		if (!(line.charAt(0) == '2')) // Could be 200 or 2xx if posting is not allowed
+		{
+			throw new IOException(line);
+		}
+
+		// Send MODE READER to peer, some newsservers are friendlier then
+		this.out.println("MODE READER\r\n");
+		this.out.flush();
+		line = this.in.readLine();
+	}
+
+	private void disconnect()
+		throws IOException
+	{
+		this.out.print("QUIT\r\n");
+		this.out.flush();
+		this.out.close();
+		this.in.close();
+
+		this.out = null;
+		this.in = null;
+	}
+
+	/**
+	 * Uses the OVER or XOVER command to get a list of message overviews that
+	 * may be unknown to this feeder and are about to be peered.
+	 * @param start
+	 * @param end
+	 * @return A list of message ids with potentially interesting messages.
+	 */
+	private List<String> over(int start, int end)
+		throws IOException
+	{
+		this.out.print("OVER " + start + "-" + end + "\r\n");
+		this.out.flush();
+
+		String line = this.in.readLine();
+		if (line.startsWith("500 ")) // OVER not supported
+		{
+			this.out.print("XOVER " + start + "-" + end + "\r\n");
+			this.out.flush();
+
+			line = this.in.readLine();
+		}
+
+		if (line.startsWith("224 ")) {
+			List<String> messages = new ArrayList<String>();
+			line = this.in.readLine();
+			while (!".".equals(line)) {
+				String mid = line.split("\t")[4]; // 5th should be the Message-ID
+				messages.add(mid);
+				line = this.in.readLine();
+			}
+			return messages;
+		} else {
+			throw new IOException("Server return for OVER/XOVER: " + line);
+		}
+	}
+
+	@Override
+	public void run()
+	{
+		while (isRunning()) {
+			int pullInterval = 1000
+				* Config.inst().get(Config.FEED_PULLINTERVAL, 3600);
+			String host = "localhost";
+			int port = 119;
+
+			Log.get().info("Start PullFeeder run...");
+
+			try {
+				this.subscriptions.clear();
+				List<Subscription> subsPull = StorageManager.current().getSubscriptions(FeedManager.TYPE_PULL);
+				for (Subscription sub : subsPull) {
+					addSubscription(sub);
+				}
+			} catch (StorageBackendException ex) {
+				Log.get().log(Level.SEVERE, host, ex);
+			}
+
+			try {
+				for (Subscription sub : this.subscriptions) {
+					host = sub.getHost();
+					port = sub.getPort();
+
+					try {
+						Log.get().info("Feeding " + sub.getGroup() + " from " + sub.getHost());
+						try {
+							connectTo(host, port);
+						} catch (SocketException ex) {
+							Log.get().info("Skipping " + sub.getHost() + ": " + ex);
+							continue;
+						}
+
+						int oldMark = this.highMarks.get(sub);
+						int newMark = changeGroup(sub.getGroup());
+
+						if (oldMark != newMark) {
+							List<String> messageIDs = over(oldMark, newMark);
+
+							for (String messageID : messageIDs) {
+								if (!StorageManager.current().isArticleExisting(messageID)) {
+									try {
+										// Post the message via common socket connection
+										ArticleReader aread =
+											new ArticleReader(sub.getHost(), sub.getPort(), messageID);
+										byte[] abuf = aread.getArticleData();
+										if (abuf == null) {
+											Log.get().warning("Could not feed " + messageID
+												+ " from " + sub.getHost());
+										} else {
+											Log.get().info("Feeding " + messageID);
+											ArticleWriter awrite = new ArticleWriter(
+												"localhost", Config.inst().get(Config.PORT, 119));
+											awrite.writeArticle(abuf);
+											awrite.close();
+										}
+										Stats.getInstance().mailFeeded(sub.getGroup());
+									} catch (IOException ex) {
+										// There may be a temporary network failure
+										ex.printStackTrace();
+										Log.get().warning("Skipping mail " + messageID + " due to exception.");
+									}
+								}
+							} // for(;;)
+							this.highMarks.put(sub, newMark);
+						}
+
+						disconnect();
+					} catch (StorageBackendException ex) {
+						ex.printStackTrace();
+					} catch (IOException ex) {
+						ex.printStackTrace();
+						Log.get().severe("PullFeeder run stopped due to exception.");
+					}
+				} // for(Subscription sub : subscriptions)
+
+				Log.get().info("PullFeeder run ended. Waiting " + pullInterval / 1000 + "s");
+				Thread.sleep(pullInterval);
+			} catch (InterruptedException ex) {
+				Log.get().warning(ex.getMessage());
+			}
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/feed/PushFeeder.java
--- a/src/org/sonews/feed/PushFeeder.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/feed/PushFeeder.java	Sun Aug 29 18:17:37 2010 +0200
@@ -37,82 +37,65 @@
  */
 class PushFeeder extends AbstractDaemon
 {
-  
-  private ConcurrentLinkedQueue<Article> articleQueue = 
-    new ConcurrentLinkedQueue<Article>();
-  
-  @Override
-  public void run()
-  {
-    while(isRunning())
-    {
-      try
-      {
-        synchronized(this)
-        {
-          this.wait();
-        }
-        
-        List<Subscription> subscriptions = StorageManager.current()
-          .getSubscriptions(FeedManager.TYPE_PUSH);
 
-        Article  article = this.articleQueue.poll();
-        String[] groups  = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
-        Log.get().info("PushFeed: " + article.getMessageID());
-        for(Subscription sub : subscriptions)
-        {
-          // Circle check
-          if(article.getHeader(Headers.PATH)[0].contains(sub.getHost()))
-          {
-            Log.get().info(article.getMessageID() + " skipped for host "
-              + sub.getHost());
-            continue;
-          }
+	private ConcurrentLinkedQueue<Article> articleQueue =
+		new ConcurrentLinkedQueue<Article>();
 
-          try
-          {
-            for(String group : groups)
-            {
-              if(sub.getGroup().equals(group))
-              {
-                // Delete headers that may cause problems
-                article.removeHeader(Headers.NNTP_POSTING_DATE);
-                article.removeHeader(Headers.NNTP_POSTING_HOST);
-                article.removeHeader(Headers.X_COMPLAINTS_TO);
-                article.removeHeader(Headers.X_TRACE);
-                article.removeHeader(Headers.XREF);
-                
-                // POST the message to remote server
-                ArticleWriter awriter = new ArticleWriter(sub.getHost(), sub.getPort());
-                awriter.writeArticle(article);
-                break;
-              }
-            }
-          }
-          catch(IOException ex)
-          {
-            Log.get().warning(ex.toString());
-          }
-        }
-      }
-      catch(StorageBackendException ex)
-      {
-        Log.get().severe(ex.toString());
-      }
-      catch(InterruptedException ex)
-      {
-        Log.get().warning("PushFeeder interrupted: " + ex);
-      }
-    }
-  }
-  
-  public void queueForPush(Article article)
-  {
-    this.articleQueue.add(article);
-    synchronized(this)
-    {
-      this.notifyAll();
-    }
-  }
-  
+	@Override
+	public void run()
+	{
+		while (isRunning()) {
+			try {
+				synchronized (this) {
+					this.wait();
+				}
+
+				List<Subscription> subscriptions = StorageManager.current().getSubscriptions(FeedManager.TYPE_PUSH);
+
+				Article article = this.articleQueue.poll();
+				String[] groups = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
+				Log.get().info("PushFeed: " + article.getMessageID());
+				for (Subscription sub : subscriptions) {
+					// Circle check
+					if (article.getHeader(Headers.PATH)[0].contains(sub.getHost())) {
+						Log.get().info(article.getMessageID() + " skipped for host "
+							+ sub.getHost());
+						continue;
+					}
+
+					try {
+						for (String group : groups) {
+							if (sub.getGroup().equals(group)) {
+								// Delete headers that may cause problems
+								article.removeHeader(Headers.NNTP_POSTING_DATE);
+								article.removeHeader(Headers.NNTP_POSTING_HOST);
+								article.removeHeader(Headers.X_COMPLAINTS_TO);
+								article.removeHeader(Headers.X_TRACE);
+								article.removeHeader(Headers.XREF);
+
+								// POST the message to remote server
+								ArticleWriter awriter = new ArticleWriter(sub.getHost(), sub.getPort());
+								awriter.writeArticle(article);
+								break;
+							}
+						}
+					} catch (IOException ex) {
+						Log.get().warning(ex.toString());
+					}
+				}
+			} catch (StorageBackendException ex) {
+				Log.get().severe(ex.toString());
+			} catch (InterruptedException ex) {
+				Log.get().warning("PushFeeder interrupted: " + ex);
+			}
+		}
+	}
+
+	public void queueForPush(Article article)
+	{
+		this.articleQueue.add(article);
+		synchronized (this) {
+			this.notifyAll();
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/feed/Subscription.java
--- a/src/org/sonews/feed/Subscription.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/feed/Subscription.java	Sun Aug 29 18:17:37 2010 +0200
@@ -24,61 +24,57 @@
  * @author Christian Lins
  * @since sonews/0.5.0
  */
-public class Subscription 
+public class Subscription
 {
 
-  private String host;
-  private int    port;
-  private int    feedtype;
-  private String group;
-  
-  public Subscription(String host, int port, int feedtype, String group)
-  {
-    this.host     = host;
-    this.port     = port;
-    this.feedtype = feedtype;
-    this.group    = group;
-  }
+	private String host;
+	private int port;
+	private int feedtype;
+	private String group;
 
-  @Override
-  public boolean equals(Object obj)
-  {
-    if(obj instanceof Subscription)
-    {
-      Subscription sub = (Subscription)obj;
-      return sub.host.equals(host) && sub.group.equals(group) 
-        && sub.port == port && sub.feedtype == feedtype;
-    }
-    else
-    {
-      return false;
-    }
-  }
+	public Subscription(String host, int port, int feedtype, String group)
+	{
+		this.host = host;
+		this.port = port;
+		this.feedtype = feedtype;
+		this.group = group;
+	}
 
-  @Override
-  public int hashCode()
-  {
-    return host.hashCode() + port + feedtype + group.hashCode();
-  }
+	@Override
+	public boolean equals(Object obj)
+	{
+		if (obj instanceof Subscription) {
+			Subscription sub = (Subscription) obj;
+			return sub.host.equals(host) && sub.group.equals(group)
+				&& sub.port == port && sub.feedtype == feedtype;
+		} else {
+			return false;
+		}
+	}
 
-  public int getFeedtype()
-  {
-    return feedtype;
-  }
+	@Override
+	public int hashCode()
+	{
+		return host.hashCode() + port + feedtype + group.hashCode();
+	}
 
-  public String getGroup()
-  {
-    return group;
-  }
+	public int getFeedtype()
+	{
+		return feedtype;
+	}
 
-  public String getHost()
-  {
-    return host;
-  }
+	public String getGroup()
+	{
+		return group;
+	}
 
-  public int getPort()
-  {
-    return port;
-  }
-  
+	public String getHost()
+	{
+		return host;
+	}
+
+	public int getPort()
+	{
+		return port;
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/mlgw/Dispatcher.java
--- a/src/org/sonews/mlgw/Dispatcher.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/mlgw/Dispatcher.java	Sun Aug 29 18:17:37 2010 +0200
@@ -43,267 +43,233 @@
  * @author Christian Lins
  * @since sonews/0.5.0
  */
-public class Dispatcher 
+public class Dispatcher
 {
 
-  static class PasswordAuthenticator extends Authenticator
-  {
-    
-    @Override
-    public PasswordAuthentication getPasswordAuthentication()
-    {
-      final String username = 
-        Config.inst().get(Config.MLSEND_USER, "user");
-      final String password = 
-        Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
+	static class PasswordAuthenticator extends Authenticator
+	{
 
-      return new PasswordAuthentication(username, password);
-    }
-    
-  }
+		@Override
+		public PasswordAuthentication getPasswordAuthentication()
+		{
+			final String username =
+				Config.inst().get(Config.MLSEND_USER, "user");
+			final String password =
+				Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
 
-  /**
-   * Chunks out the email address of the full List-Post header field.
-   * @param listPostValue
-   * @return The matching email address or null
-   */
-  private static String chunkListPost(String listPostValue)
-  {
-    // listPostValue is of form "<mailto:dev@openoffice.org>"
-    Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
-    Matcher mailMatcher = mailPattern.matcher(listPostValue);
-    if(mailMatcher.find())
-    {
-      return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
-    }
-    else
-    {
-      return null;
-    }
-  }
+			return new PasswordAuthentication(username, password);
+		}
+	}
 
-  /**
-   * This method inspects the header of the given message, trying
-   * to find the most appropriate recipient.
-   * @param msg
-   * @param fallback If this is false only List-Post and X-List-Post headers
-   *                 are examined.
-   * @return null or fitting group name for the given message.
-   */
-  private static List<String> getGroupFor(final Message msg, final boolean fallback)
-    throws MessagingException, StorageBackendException
-  {
-    List<String> groups = null;
+	/**
+	 * Chunks out the email address of the full List-Post header field.
+	 * @param listPostValue
+	 * @return The matching email address or null
+	 */
+	private static String chunkListPost(String listPostValue)
+	{
+		// listPostValue is of form "<mailto:dev@openoffice.org>"
+		Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
+		Matcher mailMatcher = mailPattern.matcher(listPostValue);
+		if (mailMatcher.find()) {
+			return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
+		} else {
+			return null;
+		}
+	}
 
-    // Is there a List-Post header?
-    String[]        listPost = msg.getHeader(Headers.LIST_POST);
-    InternetAddress listPostAddr;
+	/**
+	 * This method inspects the header of the given message, trying
+	 * to find the most appropriate recipient.
+	 * @param msg
+	 * @param fallback If this is false only List-Post and X-List-Post headers
+	 *                 are examined.
+	 * @return null or fitting group name for the given message.
+	 */
+	private static List<String> getGroupFor(final Message msg, final boolean fallback)
+		throws MessagingException, StorageBackendException
+	{
+		List<String> groups = null;
 
-    if(listPost == null || listPost.length == 0 || "".equals(listPost[0]))
-    {
-      // Is there a X-List-Post header?
-      listPost = msg.getHeader(Headers.X_LIST_POST);
-    }
+		// Is there a List-Post header?
+		String[] listPost = msg.getHeader(Headers.LIST_POST);
+		InternetAddress listPostAddr;
 
-    if(listPost != null && listPost.length > 0 
-      && !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null)
-    {
-      // listPost[0] is of form "<mailto:dev@openoffice.org>"
-      listPost[0]  = chunkListPost(listPost[0]);
-      listPostAddr = new InternetAddress(listPost[0], false);
-      groups = StorageManager.current().getGroupsForList(listPostAddr.getAddress());
-    }
-    else if(fallback)
-    {
-      Log.get().info("Using fallback recipient discovery for: " + msg.getSubject());
-      groups = new ArrayList<String>();
-      // Fallback to TO/CC/BCC addresses
-      Address[] to = msg.getAllRecipients();
-      for(Address toa : to) // Address can have '<' '>' around
-      {
-        if(toa instanceof InternetAddress)
-        {
-          List<String> g = StorageManager.current()
-            .getGroupsForList(((InternetAddress)toa).getAddress());
-          groups.addAll(g);
-        }
-      }
-    }
-    
-    return groups;
-  }
-  
-  /**
-   * Posts a message that was received from a mailing list to the 
-   * appropriate newsgroup.
-   * If the message already exists in the storage, this message checks
-   * if it must be posted in an additional group. This can happen for
-   * crosspostings in different mailing lists.
-   * @param msg
-   */
-  public static boolean toGroup(final Message msg)
-  {
-    if(msg == null)
+		if (listPost == null || listPost.length == 0 || "".equals(listPost[0])) {
+			// Is there a X-List-Post header?
+			listPost = msg.getHeader(Headers.X_LIST_POST);
+		}
+
+		if (listPost != null && listPost.length > 0
+			&& !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null) {
+			// listPost[0] is of form "<mailto:dev@openoffice.org>"
+			listPost[0] = chunkListPost(listPost[0]);
+			listPostAddr = new InternetAddress(listPost[0], false);
+			groups = StorageManager.current().getGroupsForList(listPostAddr.getAddress());
+		} else if (fallback) {
+			Log.get().info("Using fallback recipient discovery for: " + msg.getSubject());
+			groups = new ArrayList<String>();
+			// Fallback to TO/CC/BCC addresses
+			Address[] to = msg.getAllRecipients();
+			for (Address toa : to) // Address can have '<' '>' around
+			{
+				if (toa instanceof InternetAddress) {
+					List<String> g = StorageManager.current().getGroupsForList(((InternetAddress) toa).getAddress());
+					groups.addAll(g);
+				}
+			}
+		}
+
+		return groups;
+	}
+
+	/**
+	 * Posts a message that was received from a mailing list to the
+	 * appropriate newsgroup.
+	 * If the message already exists in the storage, this message checks
+	 * if it must be posted in an additional group. This can happen for
+	 * crosspostings in different mailing lists.
+	 * @param msg
+	 */
+	public static boolean toGroup(final Message msg)
 	{
-      throw new IllegalArgumentException("Argument 'msg' must not be null!");
-    }
+		if (msg == null) {
+			throw new IllegalArgumentException("Argument 'msg' must not be null!");
+		}
 
-    try
-    {
-      // Create new Article object
-      Article article = new Article(msg);
-      boolean posted  = false;
+		try {
+			// Create new Article object
+			Article article = new Article(msg);
+			boolean posted = false;
 
-      // Check if this mail is already existing the storage
-      boolean updateReq = 
-        StorageManager.current().isArticleExisting(article.getMessageID());
+			// Check if this mail is already existing the storage
+			boolean updateReq =
+				StorageManager.current().isArticleExisting(article.getMessageID());
 
-      List<String> newsgroups = getGroupFor(msg, !updateReq);
-      List<String> oldgroups  = new ArrayList<String>();
-      if(updateReq)
-      {
-        // Check for duplicate entries of the same group
-        Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
-        if(oldArticle != null)
-		{
-          List<Group> oldGroups = oldArticle.getGroups();
-          for(Group oldGroup : oldGroups)
-          {
-            if(!newsgroups.contains(oldGroup.getName()))
-            {
-              oldgroups.add(oldGroup.getName());
-            }
-          }
+			List<String> newsgroups = getGroupFor(msg, !updateReq);
+			List<String> oldgroups = new ArrayList<String>();
+			if (updateReq) {
+				// Check for duplicate entries of the same group
+				Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
+				if (oldArticle != null) {
+					List<Group> oldGroups = oldArticle.getGroups();
+					for (Group oldGroup : oldGroups) {
+						if (!newsgroups.contains(oldGroup.getName())) {
+							oldgroups.add(oldGroup.getName());
+						}
+					}
+				}
+			}
+
+			if (newsgroups.size() > 0) {
+				newsgroups.addAll(oldgroups);
+				StringBuilder groups = new StringBuilder();
+				for (int n = 0; n < newsgroups.size(); n++) {
+					groups.append(newsgroups.get(n));
+					if (n + 1 != newsgroups.size()) {
+						groups.append(',');
+					}
+				}
+				Log.get().info("Posting to group " + groups.toString());
+
+				article.setGroup(groups.toString());
+				//article.removeHeader(Headers.REPLY_TO);
+				//article.removeHeader(Headers.TO);
+
+				// Write article to database
+				if (updateReq) {
+					Log.get().info("Updating " + article.getMessageID()
+						+ " with additional groups");
+					StorageManager.current().delete(article.getMessageID());
+					StorageManager.current().addArticle(article);
+				} else {
+					Log.get().info("Gatewaying " + article.getMessageID() + " to "
+						+ article.getHeader(Headers.NEWSGROUPS)[0]);
+					StorageManager.current().addArticle(article);
+					Stats.getInstance().mailGatewayed(
+						article.getHeader(Headers.NEWSGROUPS)[0]);
+				}
+				posted = true;
+			} else {
+				StringBuilder buf = new StringBuilder();
+				for (Address toa : msg.getAllRecipients()) {
+					buf.append(' ');
+					buf.append(toa.toString());
+				}
+				buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
+				Log.get().warning("No group for" + buf.toString());
+			}
+			return posted;
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			return false;
 		}
-      }
+	}
 
-      if(newsgroups.size() > 0)
-      {
-        newsgroups.addAll(oldgroups);
-        StringBuilder groups = new StringBuilder();
-        for(int n = 0; n < newsgroups.size(); n++)
-        {
-          groups.append(newsgroups.get(n));
-          if (n + 1 != newsgroups.size())
-          {
-            groups.append(',');
-          }
-        }
-        Log.get().info("Posting to group " + groups.toString());
+	/**
+	 * Mails a message received through NNTP to the appropriate mailing list.
+	 * This method MAY be called several times by PostCommand for the same
+	 * article.
+	 */
+	public static void toList(Article article, String group)
+		throws IOException, MessagingException, StorageBackendException
+	{
+		// Get mailing lists for the group of this article
+		List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
 
-        article.setGroup(groups.toString());
-        //article.removeHeader(Headers.REPLY_TO);
-        //article.removeHeader(Headers.TO);
+		if (rcptAddresses == null || rcptAddresses.size() == 0) {
+			Log.get().warning("No ML-address for " + group + " found.");
+			return;
+		}
 
-        // Write article to database
-        if(updateReq)
-        {
-          Log.get().info("Updating " + article.getMessageID()
-            + " with additional groups");
-          StorageManager.current().delete(article.getMessageID());
-          StorageManager.current().addArticle(article);
-        }
-        else
-        {
-          Log.get().info("Gatewaying " + article.getMessageID() + " to "
-            + article.getHeader(Headers.NEWSGROUPS)[0]);
-          StorageManager.current().addArticle(article);
-          Stats.getInstance().mailGatewayed(
-            article.getHeader(Headers.NEWSGROUPS)[0]);
-        }
-        posted = true;
-      }
-      else
-      {
-        StringBuilder buf = new StringBuilder();
-        for (Address toa : msg.getAllRecipients())
-        {
-          buf.append(' ');
-          buf.append(toa.toString());
-        }
-        buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
-        Log.get().warning("No group for" + buf.toString());
-      }
-      return posted;
-    }
-    catch(Exception ex)
-    {
-      ex.printStackTrace();
-      return false;
-    }
-  }
-  
-  /**
-   * Mails a message received through NNTP to the appropriate mailing list.
-   * This method MAY be called several times by PostCommand for the same
-   * article.
-   */
-  public static void toList(Article article, String group)
-    throws IOException, MessagingException, StorageBackendException
-  {
-    // Get mailing lists for the group of this article
-    List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
+		for (String rcptAddress : rcptAddresses) {
+			// Compose message and send it via given SMTP-Host
+			String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
+			int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
+			String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
+			String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
+			String smtpFrom = Config.inst().get(
+				Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
 
-    if(rcptAddresses == null || rcptAddresses.size() == 0)
-    {
-      Log.get().warning("No ML-address for " + group + " found.");
-      return;
-    }
+			// TODO: Make Article cloneable()
+			article.getMessageID(); // Make sure an ID is existing
+			article.removeHeader(Headers.NEWSGROUPS);
+			article.removeHeader(Headers.PATH);
+			article.removeHeader(Headers.LINES);
+			article.removeHeader(Headers.BYTES);
 
-    for(String rcptAddress : rcptAddresses)
-    {
-      // Compose message and send it via given SMTP-Host
-      String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
-      int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
-      String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
-      String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
-      String smtpFrom = Config.inst().get(
-        Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
+			article.setHeader("To", rcptAddress);
+			//article.setHeader("Reply-To", listAddress);
 
-      // TODO: Make Article cloneable()
-      article.getMessageID(); // Make sure an ID is existing
-      article.removeHeader(Headers.NEWSGROUPS);
-      article.removeHeader(Headers.PATH);
-      article.removeHeader(Headers.LINES);
-      article.removeHeader(Headers.BYTES);
+			if (Config.inst().get(Config.MLSEND_RW_SENDER, false)) {
+				rewriteSenderAddress(article); // Set the SENDER address
+			}
 
-      article.setHeader("To", rcptAddress);
-      //article.setHeader("Reply-To", listAddress);
+			SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
+			smtpTransport.send(article, smtpFrom, rcptAddress);
+			smtpTransport.close();
 
-      if (Config.inst().get(Config.MLSEND_RW_SENDER, false))
-      {
-        rewriteSenderAddress(article); // Set the SENDER address
-      }
+			Stats.getInstance().mailGatewayed(group);
+			Log.get().info("MLGateway: Mail " + article.getHeader("Subject")[0]
+				+ " was delivered to " + rcptAddress + ".");
+		}
+	}
 
-      SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
-      smtpTransport.send(article, smtpFrom, rcptAddress);
-      smtpTransport.close();
+	/**
+	 * Sets the SENDER header of the given MimeMessage. This might be necessary
+	 * for moderated groups that does not allow the "normal" FROM sender.
+	 * @param msg
+	 * @throws javax.mail.MessagingException
+	 */
+	private static void rewriteSenderAddress(Article msg)
+		throws MessagingException
+	{
+		String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
 
-      Stats.getInstance().mailGatewayed(group);
-      Log.get().info("MLGateway: Mail " + article.getHeader("Subject")[0]
-        + " was delivered to " + rcptAddress + ".");
-    }
-  }
-  
-  /**
-   * Sets the SENDER header of the given MimeMessage. This might be necessary
-   * for moderated groups that does not allow the "normal" FROM sender.
-   * @param msg
-   * @throws javax.mail.MessagingException
-   */
-  private static void rewriteSenderAddress(Article msg)
-    throws MessagingException
-  {
-    String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
-
-    if(mlAddress != null)
-    {
-      msg.setHeader(Headers.SENDER, mlAddress);
-    }
-    else
-    {
-      throw new MessagingException("Cannot rewrite SENDER header!");
-    }
-  }
-  
+		if (mlAddress != null) {
+			msg.setHeader(Headers.SENDER, mlAddress);
+		} else {
+			throw new MessagingException("Cannot rewrite SENDER header!");
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/mlgw/MailPoller.java
--- a/src/org/sonews/mlgw/MailPoller.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/mlgw/MailPoller.java	Sun Aug 29 18:17:37 2010 +0200
@@ -42,110 +42,94 @@
 public class MailPoller extends AbstractDaemon
 {
 
-  static class PasswordAuthenticator extends Authenticator
-  {
-    
-    @Override
-    public PasswordAuthentication getPasswordAuthentication()
-    {
-      final String username = 
-        Config.inst().get(Config.MLPOLL_USER, "user");
-      final String password = 
-        Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
+	static class PasswordAuthenticator extends Authenticator
+	{
 
-      return new PasswordAuthentication(username, password);
-    }
-    
-  }
-  
-  @Override
-  public void run()
-  {
-    Log.get().info("Starting Mailinglist Poller...");
-    int errors = 0;
-    while(isRunning())
-    {
-      try
-      {
-        // Wait some time between runs. At the beginning has advantages,
-        // because the wait is not skipped if an exception occurs.
-        Thread.sleep(60000 * (errors + 1)); // one minute * errors
-        
-        final String host     = 
-          Config.inst().get(Config.MLPOLL_HOST, "samplehost");
-        final String username = 
-          Config.inst().get(Config.MLPOLL_USER, "user");
-        final String password = 
-          Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
-        
-        Stats.getInstance().mlgwRunStart();
-        
-        // Create empty properties
-        Properties props = System.getProperties();
-        props.put("mail.pop3.host", host);
-        props.put("mail.mime.address.strict", "false");
+		@Override
+		public PasswordAuthentication getPasswordAuthentication()
+		{
+			final String username =
+				Config.inst().get(Config.MLPOLL_USER, "user");
+			final String password =
+				Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
 
-        // Get session
-        Session session = Session.getInstance(props);
+			return new PasswordAuthentication(username, password);
+		}
+	}
 
-        // Get the store
-        Store store = session.getStore("pop3");
-        store.connect(host, 110, username, password);
+	@Override
+	public void run()
+	{
+		Log.get().info("Starting Mailinglist Poller...");
+		int errors = 0;
+		while (isRunning()) {
+			try {
+				// Wait some time between runs. At the beginning has advantages,
+				// because the wait is not skipped if an exception occurs.
+				Thread.sleep(60000 * (errors + 1)); // one minute * errors
 
-        // Get folder
-        Folder folder = store.getFolder("INBOX");
-        folder.open(Folder.READ_WRITE);
+				final String host =
+					Config.inst().get(Config.MLPOLL_HOST, "samplehost");
+				final String username =
+					Config.inst().get(Config.MLPOLL_USER, "user");
+				final String password =
+					Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
 
-        // Get directory
-        Message[] messages = folder.getMessages();
+				Stats.getInstance().mlgwRunStart();
 
-        // Dispatch messages and delete it afterwards on the inbox
-        for(Message message : messages)
-        {
-          if(Dispatcher.toGroup(message)
-            || Config.inst().get(Config.MLPOLL_DELETEUNKNOWN, false))
-          {
-            // Delete the message
-            message.setFlag(Flag.DELETED, true);
-          }
-        }
+				// Create empty properties
+				Properties props = System.getProperties();
+				props.put("mail.pop3.host", host);
+				props.put("mail.mime.address.strict", "false");
 
-        // Close connection 
-        folder.close(true); // true to expunge deleted messages
-        store.close();
-        errors = 0;
-        
-        Stats.getInstance().mlgwRunEnd();
-      }
-      catch(NoSuchProviderException ex)
-      {
-        Log.get().severe(ex.toString());
-        shutdown();
-      }
-      catch(AuthenticationFailedException ex)
-      {
-        // AuthentificationFailedException may be thrown if credentials are
-        // bad or if the Mailbox is in use (locked).
-        ex.printStackTrace();
-        errors = errors < 5 ? errors + 1 : errors;
-      }
-      catch(InterruptedException ex)
-      {
-        System.out.println("sonews: " + this + " returns: " + ex);
-        return;
-      }
-      catch(MessagingException ex)
-      {
-        ex.printStackTrace();
-        errors = errors < 5 ? errors + 1 : errors;
-      }
-      catch(Exception ex)
-      {
-        ex.printStackTrace();
-        errors = errors < 5 ? errors + 1 : errors;
-      }
-    }
-    Log.get().severe("MailPoller exited.");
-  }
-  
+				// Get session
+				Session session = Session.getInstance(props);
+
+				// Get the store
+				Store store = session.getStore("pop3");
+				store.connect(host, 110, username, password);
+
+				// Get folder
+				Folder folder = store.getFolder("INBOX");
+				folder.open(Folder.READ_WRITE);
+
+				// Get directory
+				Message[] messages = folder.getMessages();
+
+				// Dispatch messages and delete it afterwards on the inbox
+				for (Message message : messages) {
+					if (Dispatcher.toGroup(message)
+						|| Config.inst().get(Config.MLPOLL_DELETEUNKNOWN, false)) {
+						// Delete the message
+						message.setFlag(Flag.DELETED, true);
+					}
+				}
+
+				// Close connection
+				folder.close(true); // true to expunge deleted messages
+				store.close();
+				errors = 0;
+
+				Stats.getInstance().mlgwRunEnd();
+			} catch (NoSuchProviderException ex) {
+				Log.get().severe(ex.toString());
+				shutdown();
+			} catch (AuthenticationFailedException ex) {
+				// AuthentificationFailedException may be thrown if credentials are
+				// bad or if the Mailbox is in use (locked).
+				ex.printStackTrace();
+				errors = errors < 5 ? errors + 1 : errors;
+			} catch (InterruptedException ex) {
+				System.out.println("sonews: " + this + " returns: " + ex);
+				return;
+			} catch (MessagingException ex) {
+				ex.printStackTrace();
+				errors = errors < 5 ? errors + 1 : errors;
+			} catch (Exception ex) {
+				ex.printStackTrace();
+				errors = errors < 5 ? errors + 1 : errors;
+			}
+		}
+		Log.get().severe("MailPoller exited.");
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/mlgw/SMTPTransport.java
--- a/src/org/sonews/mlgw/SMTPTransport.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/mlgw/SMTPTransport.java	Sun Aug 29 18:17:37 2010 +0200
@@ -36,98 +36,90 @@
 class SMTPTransport
 {
 
-  protected BufferedReader       in;
-  protected BufferedOutputStream out;
-  protected Socket               socket;
+	protected BufferedReader in;
+	protected BufferedOutputStream out;
+	protected Socket socket;
 
-  public SMTPTransport(String host, int port)
-    throws IOException, UnknownHostException
-  {
-    socket = new Socket(host, port);
-    this.in  = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-    this.out = new BufferedOutputStream(socket.getOutputStream());
+	public SMTPTransport(String host, int port)
+		throws IOException, UnknownHostException
+	{
+		socket = new Socket(host, port);
+		this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+		this.out = new BufferedOutputStream(socket.getOutputStream());
 
-    // Read helo from server
-    String line = this.in.readLine();
-    if(line == null || !line.startsWith("220 "))
-    {
-      throw new IOException("Invalid helo from server: " + line);
-    }
+		// Read helo from server
+		String line = this.in.readLine();
+		if (line == null || !line.startsWith("220 ")) {
+			throw new IOException("Invalid helo from server: " + line);
+		}
 
-    // Send HELO to server
-    this.out.write(
-      ("HELO " + Config.inst().get(Config.HOSTNAME, "localhost") + "\r\n").getBytes("UTF-8"));
-    this.out.flush();
-    line = this.in.readLine();
-    if(line == null || !line.startsWith("250 "))
-    {
-      throw new IOException("Unexpected reply: " + line);
-    }
-  }
+		// Send HELO to server
+		this.out.write(
+			("HELO " + Config.inst().get(Config.HOSTNAME, "localhost") + "\r\n").getBytes("UTF-8"));
+		this.out.flush();
+		line = this.in.readLine();
+		if (line == null || !line.startsWith("250 ")) {
+			throw new IOException("Unexpected reply: " + line);
+		}
+	}
 
-  public SMTPTransport(String host)
-    throws IOException
-  {
-    this(host, 25);
-  }
+	public SMTPTransport(String host)
+		throws IOException
+	{
+		this(host, 25);
+	}
 
-  public void close()
-    throws IOException
-  {
-    this.out.write("QUIT".getBytes("UTF-8"));
-    this.out.flush();
-    this.in.readLine();
+	public void close()
+		throws IOException
+	{
+		this.out.write("QUIT".getBytes("UTF-8"));
+		this.out.flush();
+		this.in.readLine();
 
-    this.socket.close();
-  }
+		this.socket.close();
+	}
 
-  public void send(Article article, String mailFrom, String rcptTo)
-    throws IOException
-  {
-    assert(article != null);
-    assert(mailFrom != null);
-    assert(rcptTo != null);
+	public void send(Article article, String mailFrom, String rcptTo)
+		throws IOException
+	{
+		assert (article != null);
+		assert (mailFrom != null);
+		assert (rcptTo != null);
 
-    this.out.write(("MAIL FROM: " + mailFrom).getBytes("UTF-8"));
-    this.out.flush();
-    String line = this.in.readLine();
-    if(line == null || !line.startsWith("250 "))
-    {
-      throw new IOException("Unexpected reply: " + line);
-    }
+		this.out.write(("MAIL FROM: " + mailFrom).getBytes("UTF-8"));
+		this.out.flush();
+		String line = this.in.readLine();
+		if (line == null || !line.startsWith("250 ")) {
+			throw new IOException("Unexpected reply: " + line);
+		}
 
-    this.out.write(("RCPT TO: " + rcptTo).getBytes("UTF-8"));
-    this.out.flush();
-    line  = this.in.readLine();
-    if(line == null || !line.startsWith("250 "))
-    {
-      throw new IOException("Unexpected reply: " + line);
-    }
+		this.out.write(("RCPT TO: " + rcptTo).getBytes("UTF-8"));
+		this.out.flush();
+		line = this.in.readLine();
+		if (line == null || !line.startsWith("250 ")) {
+			throw new IOException("Unexpected reply: " + line);
+		}
 
-    this.out.write("DATA".getBytes("UTF-8"));
-    this.out.flush();
-    line = this.in.readLine();
-    if(line == null || !line.startsWith("354 "))
-    {
-      throw new IOException("Unexpected reply: " + line);
-    }
+		this.out.write("DATA".getBytes("UTF-8"));
+		this.out.flush();
+		line = this.in.readLine();
+		if (line == null || !line.startsWith("354 ")) {
+			throw new IOException("Unexpected reply: " + line);
+		}
 
-    ArticleInputStream   artStream = new ArticleInputStream(article);
-    for(int b = artStream.read(); b >= 0; b = artStream.read())
-    {
-      this.out.write(b);
-    }
+		ArticleInputStream artStream = new ArticleInputStream(article);
+		for (int b = artStream.read(); b >= 0; b = artStream.read()) {
+			this.out.write(b);
+		}
 
-    // Flush the binary stream; important because otherwise the output
-    // will be mixed with the PrintWriter.
-    this.out.flush();
-    this.out.write("\r\n.\r\n".getBytes("UTF-8"));
-    this.out.flush();
-    line = this.in.readLine();
-    if(line == null || !line.startsWith("250 "))
-    {
-      throw new IOException("Unexpected reply: " + line);
-    }
-  }
-
+		// Flush the binary stream; important because otherwise the output
+		// will be mixed with the PrintWriter.
+		this.out.flush();
+		this.out.write("\r\n.\r\n".getBytes("UTF-8"));
+		this.out.flush();
+		line = this.in.readLine();
+		if (line == null || !line.startsWith("250 ")) {
+			throw new IOException("Unexpected reply: " + line);
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/plugin/Plugin.java
--- a/src/org/sonews/plugin/Plugin.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/plugin/Plugin.java	Sun Aug 29 18:17:37 2010 +0200
@@ -28,15 +28,14 @@
 public interface Plugin
 {
 
-  /**
-   * Called when the Plugin is loaded by sonews. This method can be used
-   * by implementing classes to install additional or required plugins.
-   */
-  void load();
+	/**
+	 * Called when the Plugin is loaded by sonews. This method can be used
+	 * by implementing classes to install additional or required plugins.
+	 */
+	void load();
 
-  /**
-   * Called when the Plugin is unloaded by sonews.
-   */
-  void unload();
-
+	/**
+	 * Called when the Plugin is unloaded by sonews.
+	 */
+	void unload();
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/storage/Article.java
--- a/src/org/sonews/storage/Article.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/storage/Article.java	Sun Aug 29 18:17:37 2010 +0200
@@ -41,213 +41,196 @@
  */
 public class Article extends ArticleHead
 {
-  
-  /**
-   * Loads the Article identified by the given ID from the JDBCDatabase.
-   * @param messageID
-   * @return null if Article is not found or if an error occurred.
-   */
-  public static Article getByMessageID(final String messageID)
-  {
-    try
-    {
-      return StorageManager.current().getArticle(messageID);
-    }
-    catch(StorageBackendException ex)
-    {
-      ex.printStackTrace();
-      return null;
-    }
-  }
-  
-  private byte[] body       = new byte[0];
-  
-  /**
-   * Default constructor.
-   */
-  public Article()
-  {
-  }
-  
-  /**
-   * Creates a new Article object using the date from the given
-   * raw data.
-   */
-  public Article(String headers, byte[] body)
-  {
-    try
-    {
-      this.body  = body;
 
-      // Parse the header
-      this.headers = new InternetHeaders(
-        new ByteArrayInputStream(headers.getBytes()));
-      
-      this.headerSrc = headers;
-    }
-    catch(MessagingException ex)
-    {
-      ex.printStackTrace();
-    }
-  }
+	/**
+	 * Loads the Article identified by the given ID from the JDBCDatabase.
+	 * @param messageID
+	 * @return null if Article is not found or if an error occurred.
+	 */
+	public static Article getByMessageID(final String messageID)
+	{
+		try {
+			return StorageManager.current().getArticle(messageID);
+		} catch (StorageBackendException ex) {
+			ex.printStackTrace();
+			return null;
+		}
+	}
+	private byte[] body = new byte[0];
 
-  /**
-   * Creates an Article instance using the data from the javax.mail.Message
-   * object. This constructor is called by the Mailinglist gateway.
-   * @see javax.mail.Message
-   * @param msg
-   * @throws IOException
-   * @throws MessagingException
-   */
-  public Article(final Message msg)
-    throws IOException, MessagingException
-  {
-    this.headers = new InternetHeaders();
+	/**
+	 * Default constructor.
+	 */
+	public Article()
+	{
+	}
 
-    for(Enumeration e = msg.getAllHeaders() ; e.hasMoreElements();) 
-    {
-      final Header header = (Header)e.nextElement();
-      this.headers.addHeader(header.getName(), header.getValue());
-    }
+	/**
+	 * Creates a new Article object using the date from the given
+	 * raw data.
+	 */
+	public Article(String headers, byte[] body)
+	{
+		try {
+			this.body = body;
 
-	// Reads the raw byte body using Message.writeTo(OutputStream out)
-	this.body = readContent(msg);
-    
-    // Validate headers
-    validateHeaders();
-  }
+			// Parse the header
+			this.headers = new InternetHeaders(
+				new ByteArrayInputStream(headers.getBytes()));
 
-  /**
-   * Reads from the given Message into a byte array.
-   * @param in
-   * @return
-   * @throws IOException
-   */
-  private byte[] readContent(Message in)
-    throws IOException, MessagingException
-  {
-    ByteArrayOutputStream out = new ByteArrayOutputStream();
-    in.writeTo(out);
-    return out.toByteArray();
-  }
+			this.headerSrc = headers;
+		} catch (MessagingException ex) {
+			ex.printStackTrace();
+		}
+	}
 
-  /**
-   * Removes the header identified by the given key.
-   * @param headerKey
-   */
-  public void removeHeader(final String headerKey)
-  {
-    this.headers.removeHeader(headerKey);
-    this.headerSrc = null;
-  }
+	/**
+	 * Creates an Article instance using the data from the javax.mail.Message
+	 * object. This constructor is called by the Mailinglist gateway.
+	 * @see javax.mail.Message
+	 * @param msg
+	 * @throws IOException
+	 * @throws MessagingException
+	 */
+	public Article(final Message msg)
+		throws IOException, MessagingException
+	{
+		this.headers = new InternetHeaders();
 
-  /**
-   * Generates a message id for this article and sets it into
-   * the header object. You have to update the JDBCDatabase manually to make this
-   * change persistent.
-   * Note: a Message-ID should never be changed and only generated once.
-   */
-  private String generateMessageID()
-  {
-    String randomString;
-    MessageDigest md5;
-    try
-    {
-      md5 = MessageDigest.getInstance("MD5");
-      md5.reset();
-      md5.update(getBody());
-      md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
-      md5.update(getHeader(Headers.FROM)[0].getBytes());
-      byte[] result = md5.digest();
-      StringBuffer hexString = new StringBuffer();
-      for (int i = 0; i < result.length; i++)
-      {
-        hexString.append(Integer.toHexString(0xFF & result[i]));
-      }
-      randomString = hexString.toString();
-    }
-    catch (NoSuchAlgorithmException e)
-    {
-      e.printStackTrace();
-      randomString = UUID.randomUUID().toString();
-    }
-    String msgID = "<" + randomString + "@"
-        + Config.inst().get(Config.HOSTNAME, "localhost") + ">";
-    
-    this.headers.setHeader(Headers.MESSAGE_ID, msgID);
-    
-    return msgID;
-  }
+		for (Enumeration e = msg.getAllHeaders(); e.hasMoreElements();) {
+			final Header header = (Header) e.nextElement();
+			this.headers.addHeader(header.getName(), header.getValue());
+		}
 
-  /**
-   * Returns the body string.
-   */
-  public byte[] getBody()
-  {
-    return body;
-  }
-  
-  /**
-   * @return Numerical IDs of the newsgroups this Article belongs to.
-   */
-  public List<Group> getGroups()
-  {
-    String[]         groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
-    ArrayList<Group> groups     = new ArrayList<Group>();
+		// Reads the raw byte body using Message.writeTo(OutputStream out)
+		this.body = readContent(msg);
 
-    try
-    {
-      for(String newsgroup : groupnames)
-      {
-        newsgroup = newsgroup.trim();
-        Group group = StorageManager.current().getGroup(newsgroup);
-        if(group != null &&         // If the server does not provide the group, ignore it
-          !groups.contains(group))  // Yes, there may be duplicates
-        {
-          groups.add(group);
-        }
-      }
-    }
-    catch(StorageBackendException ex)
-    {
-      ex.printStackTrace();
-      return null;
-    }
-    return groups;
-  }
+		// Validate headers
+		validateHeaders();
+	}
 
-  public void setBody(byte[] body)
-  {
-    this.body = body;
-  }
-  
-  /**
-   * 
-   * @param groupname Name(s) of newsgroups
-   */
-  public void setGroup(String groupname)
-  {
-    this.headers.setHeader(Headers.NEWSGROUPS, groupname);
-  }
+	/**
+	 * Reads from the given Message into a byte array.
+	 * @param in
+	 * @return
+	 * @throws IOException
+	 */
+	private byte[] readContent(Message in)
+		throws IOException, MessagingException
+	{
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		in.writeTo(out);
+		return out.toByteArray();
+	}
 
-  /**
-   * Returns the Message-ID of this Article. If the appropriate header
-   * is empty, a new Message-ID is created.
-   * @return Message-ID of this Article.
-   */
-  public String getMessageID()
-  {
-    String[] msgID = getHeader(Headers.MESSAGE_ID);
-    return msgID[0].equals("") ? generateMessageID() : msgID[0];
-  }
-  
-  /**
-   * @return String containing the Message-ID.
-   */
-  @Override
-  public String toString()
-  {
-    return getMessageID();
-  }
+	/**
+	 * Removes the header identified by the given key.
+	 * @param headerKey
+	 */
+	public void removeHeader(final String headerKey)
+	{
+		this.headers.removeHeader(headerKey);
+		this.headerSrc = null;
+	}
 
+	/**
+	 * Generates a message id for this article and sets it into
+	 * the header object. You have to update the JDBCDatabase manually to make this
+	 * change persistent.
+	 * Note: a Message-ID should never be changed and only generated once.
+	 */
+	private String generateMessageID()
+	{
+		String randomString;
+		MessageDigest md5;
+		try {
+			md5 = MessageDigest.getInstance("MD5");
+			md5.reset();
+			md5.update(getBody());
+			md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
+			md5.update(getHeader(Headers.FROM)[0].getBytes());
+			byte[] result = md5.digest();
+			StringBuffer hexString = new StringBuffer();
+			for (int i = 0; i < result.length; i++) {
+				hexString.append(Integer.toHexString(0xFF & result[i]));
+			}
+			randomString = hexString.toString();
+		} catch (NoSuchAlgorithmException e) {
+			e.printStackTrace();
+			randomString = UUID.randomUUID().toString();
+		}
+		String msgID = "<" + randomString + "@"
+			+ Config.inst().get(Config.HOSTNAME, "localhost") + ">";
+
+		this.headers.setHeader(Headers.MESSAGE_ID, msgID);
+
+		return msgID;
+	}
+
+	/**
+	 * Returns the body string.
+	 */
+	public byte[] getBody()
+	{
+		return body;
+	}
+
+	/**
+	 * @return Numerical IDs of the newsgroups this Article belongs to.
+	 */
+	public List<Group> getGroups()
+	{
+		String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
+		ArrayList<Group> groups = new ArrayList<Group>();
+
+		try {
+			for (String newsgroup : groupnames) {
+				newsgroup = newsgroup.trim();
+				Group group = StorageManager.current().getGroup(newsgroup);
+				if (group != null && // If the server does not provide the group, ignore it
+					!groups.contains(group)) // Yes, there may be duplicates
+				{
+					groups.add(group);
+				}
+			}
+		} catch (StorageBackendException ex) {
+			ex.printStackTrace();
+			return null;
+		}
+		return groups;
+	}
+
+	public void setBody(byte[] body)
+	{
+		this.body = body;
+	}
+
+	/**
+	 *
+	 * @param groupname Name(s) of newsgroups
+	 */
+	public void setGroup(String groupname)
+	{
+		this.headers.setHeader(Headers.NEWSGROUPS, groupname);
+	}
+
+	/**
+	 * Returns the Message-ID of this Article. If the appropriate header
+	 * is empty, a new Message-ID is created.
+	 * @return Message-ID of this Article.
+	 */
+	public String getMessageID()
+	{
+		String[] msgID = getHeader(Headers.MESSAGE_ID);
+		return msgID[0].equals("") ? generateMessageID() : msgID[0];
+	}
+
+	/**
+	 * @return String containing the Message-ID.
+	 */
+	@Override
+	public String toString()
+	{
+		return getMessageID();
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/storage/ArticleHead.java
--- a/src/org/sonews/storage/ArticleHead.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/storage/ArticleHead.java	Sun Aug 29 18:17:37 2010 +0200
@@ -31,131 +31,122 @@
  * @author Christian Lins
  * @since sonews/0.5.0
  */
-public class ArticleHead 
+public class ArticleHead
 {
 
-  protected InternetHeaders headers   = null;
-  protected String          headerSrc = null;
-  
-  protected ArticleHead()
-  {
-  }
-  
-  public ArticleHead(String headers)
-  {
-    try
-    {
-      // Parse the header
-      this.headers = new InternetHeaders(
-          new ByteArrayInputStream(headers.getBytes()));
-    }
-    catch(MessagingException ex)
-    {
-      ex.printStackTrace();
-    }
-  }
-  
-  /**
-   * Returns the header field with given name.
-   * @param name Name of the header field(s).
-   * @param returnNull If set to true, this method will return null instead
-   *                   of an empty array if there is no header field found.
-   * @return Header values or empty string.
-   */
-  public String[] getHeader(String name, boolean returnNull)
-  {
-    String[] ret = this.headers.getHeader(name);
-    if(ret == null && !returnNull)
-    {
-      ret = new String[]{""};
-    }
-    return ret;
-  }
+	protected InternetHeaders headers = null;
+	protected String headerSrc = null;
 
-  public String[] getHeader(String name)
-  {
-    return getHeader(name, false);
-  }
-  
-  /**
-   * Sets the header value identified through the header name.
-   * @param name
-   * @param value
-   */
-  public void setHeader(String name, String value)
-  {
-    this.headers.setHeader(name, value);
-    this.headerSrc = null;
-  }
+	protected ArticleHead()
+	{
+	}
 
-    public Enumeration getAllHeaders()
-  {
-    return this.headers.getAllHeaders();
-  }
+	public ArticleHead(String headers)
+	{
+		try {
+			// Parse the header
+			this.headers = new InternetHeaders(
+				new ByteArrayInputStream(headers.getBytes()));
+		} catch (MessagingException ex) {
+			ex.printStackTrace();
+		}
+	}
 
-  /**
-   * @return Header source code of this Article.
-   */
-  public String getHeaderSource()
-  {
-    if(this.headerSrc != null)
-    {
-      return this.headerSrc;
-    }
+	/**
+	 * Returns the header field with given name.
+	 * @param name Name of the header field(s).
+	 * @param returnNull If set to true, this method will return null instead
+	 *                   of an empty array if there is no header field found.
+	 * @return Header values or empty string.
+	 */
+	public String[] getHeader(String name, boolean returnNull)
+	{
+		String[] ret = this.headers.getHeader(name);
+		if (ret == null && !returnNull) {
+			ret = new String[] {""};
+		}
+		return ret;
+	}
 
-    StringBuffer buf = new StringBuffer();
+	public String[] getHeader(String name)
+	{
+		return getHeader(name, false);
+	}
 
-    for(Enumeration en = this.headers.getAllHeaders(); en.hasMoreElements();)
-    {
-      Header entry = (Header)en.nextElement();
+	/**
+	 * Sets the header value identified through the header name.
+	 * @param name
+	 * @param value
+	 */
+	public void setHeader(String name, String value)
+	{
+		this.headers.setHeader(name, value);
+		this.headerSrc = null;
+	}
 
-      String value = entry.getValue().replaceAll("[\r\n]", " ");
-      buf.append(entry.getName());
-      buf.append(": ");
-      buf.append(MimeUtility.fold(entry.getName().length() + 2, value));
+	public Enumeration getAllHeaders()
+	{
+		return this.headers.getAllHeaders();
+	}
 
-      if(en.hasMoreElements())
-      {
-        buf.append("\r\n");
-      }
-    }
+	/**
+	 * @return Header source code of this Article.
+	 */
+	public String getHeaderSource()
+	{
+		if (this.headerSrc != null) {
+			return this.headerSrc;
+		}
 
-    this.headerSrc = buf.toString();
-    return this.headerSrc;
-  }
+		StringBuffer buf = new StringBuffer();
 
-  /**
-   * Sets the headers of this Article. If headers contain no
-   * Message-Id a new one is created.
-   * @param headers
-   */
-  public void setHeaders(InternetHeaders headers)
-  {
-    this.headers   = headers;
-    this.headerSrc = null;
-    validateHeaders();
-  }
+		for (Enumeration en = this.headers.getAllHeaders(); en.hasMoreElements();) {
+			Header entry = (Header) en.nextElement();
 
-  /**
-   * Checks some headers for their validity and generates an
-   * appropriate Path-header for this host if not yet existing.
-   * This method is called by some Article constructors and the
-   * method setHeaders().
-   * @return true if something on the headers was changed.
-   */
-  protected void validateHeaders()
-  {
-    // Check for valid Path-header
-    final String path = getHeader(Headers.PATH)[0];
-    final String host = Config.inst().get(Config.HOSTNAME, "localhost");
-    if(!path.startsWith(host))
-    {
-      StringBuffer pathBuf = new StringBuffer();
-      pathBuf.append(host);
-      pathBuf.append('!');
-      pathBuf.append(path);
-      this.headers.setHeader(Headers.PATH, pathBuf.toString());
-    }
-  }
-  
+			String value = entry.getValue().replaceAll("[\r\n]", " ");
+			buf.append(entry.getName());
+			buf.append(": ");
+			buf.append(MimeUtility.fold(entry.getName().length() + 2, value));
+
+			if (en.hasMoreElements()) {
+				buf.append("\r\n");
+			}
+		}
+
+		this.headerSrc = buf.toString();
+		return this.headerSrc;
+	}
+
+	/**
+	 * Sets the headers of this Article. If headers contain no
+	 * Message-Id a new one is created.
+	 * @param headers
+	 */
+	public void setHeaders(InternetHeaders headers)
+	{
+		this.headers = headers;
+		this.headerSrc = null;
+		validateHeaders();
+	}
+
+	/**
+	 * Checks some headers for their validity and generates an
+	 * appropriate Path-header for this host if not yet existing.
+	 * This method is called by some Article constructors and the
+	 * method setHeaders().
+	 * @return true if something on the headers was changed.
+	 */
+	protected void validateHeaders()
+	{
+		// Check for valid Path-header
+		final String path = getHeader(Headers.PATH)[0];
+		final String host = Config.inst().get(Config.HOSTNAME, "localhost");
+		if (!path.startsWith(host)) {
+			StringBuffer pathBuf = new StringBuffer();
+			pathBuf.append(host);
+			pathBuf.append('!');
+			pathBuf.append(path);
+			this.headers.setHeader(Headers.PATH, pathBuf.toString());
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/storage/Channel.java
--- a/src/org/sonews/storage/Channel.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/storage/Channel.java	Sun Aug 29 18:17:37 2010 +0200
@@ -33,79 +33,75 @@
 public abstract class Channel
 {
 
-  /**
-   * If this flag is set the Group is no real newsgroup but a mailing list
-   * mirror. In that case every posting and receiving mails must go through
-   * the mailing list gateway.
-   */
-  public static final int MAILINGLIST = 0x1;
+	/**
+	 * If this flag is set the Group is no real newsgroup but a mailing list
+	 * mirror. In that case every posting and receiving mails must go through
+	 * the mailing list gateway.
+	 */
+	public static final int MAILINGLIST = 0x1;
+	/**
+	 * If this flag is set the Group is marked as readonly and the posting
+	 * is prohibited. This can be useful for groups that are synced only in
+	 * one direction.
+	 */
+	public static final int READONLY = 0x2;
+	/**
+	 * If this flag is set the Group is marked as deleted and must not occur
+	 * in any output. The deletion is done lazily by a low priority daemon.
+	 */
+	public static final int DELETED = 0x80;
 
-  /**
-   * If this flag is set the Group is marked as readonly and the posting
-   * is prohibited. This can be useful for groups that are synced only in
-   * one direction.
-   */
-  public static final int READONLY    = 0x2;
+	public static List<Channel> getAll()
+	{
+		List<Channel> all = new ArrayList<Channel>();
 
-  /**
-   * If this flag is set the Group is marked as deleted and must not occur
-   * in any output. The deletion is done lazily by a low priority daemon.
-   */
-  public static final int DELETED     = 0x80;
+		/*List<Channel> agroups = AggregatedGroup.getAll();
+		if(agroups != null)
+		{
+		all.addAll(agroups);
+		}*/
 
-  public static List<Channel> getAll()
-  {
-    List<Channel> all = new ArrayList<Channel>();
+		List<Channel> groups = Group.getAll();
+		if (groups != null) {
+			all.addAll(groups);
+		}
 
-    /*List<Channel> agroups = AggregatedGroup.getAll();
-    if(agroups != null)
-    {
-      all.addAll(agroups);
-    }*/
+		return all;
+	}
 
-    List<Channel> groups = Group.getAll();
-    if(groups != null)
-    {
-      all.addAll(groups);
-    }
+	public static Channel getByName(String name)
+		throws StorageBackendException
+	{
+		return StorageManager.current().getGroup(name);
+	}
 
-    return all;
-  }
+	public abstract Article getArticle(long idx)
+		throws StorageBackendException;
 
-  public static Channel getByName(String name)
-    throws StorageBackendException
-  {
-    return StorageManager.current().getGroup(name);
-  }
+	public abstract List<Pair<Long, ArticleHead>> getArticleHeads(
+		final long first, final long last)
+		throws StorageBackendException;
 
-  public abstract Article getArticle(long idx)
-    throws StorageBackendException;
+	public abstract List<Long> getArticleNumbers()
+		throws StorageBackendException;
 
-  public abstract List<Pair<Long, ArticleHead>> getArticleHeads(
-    final long first, final long last)
-    throws StorageBackendException;
+	public abstract long getFirstArticleNumber()
+		throws StorageBackendException;
 
-  public abstract List<Long> getArticleNumbers()
-    throws StorageBackendException;
+	public abstract long getIndexOf(Article art)
+		throws StorageBackendException;
 
-  public abstract long getFirstArticleNumber()
-    throws StorageBackendException;
+	public abstract long getInternalID();
 
-  public abstract long getIndexOf(Article art)
-    throws StorageBackendException;
+	public abstract long getLastArticleNumber()
+		throws StorageBackendException;
 
-  public abstract long getInternalID();
+	public abstract String getName();
 
-  public abstract long getLastArticleNumber()
-    throws StorageBackendException;
+	public abstract long getPostingsCount()
+		throws StorageBackendException;
 
-  public abstract String getName();
-  
-  public abstract long getPostingsCount()
-    throws StorageBackendException;
+	public abstract boolean isDeleted();
 
-  public abstract boolean isDeleted();
-
-  public abstract boolean isWriteable();
-
+	public abstract boolean isWriteable();
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/storage/Group.java
--- a/src/org/sonews/storage/Group.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/storage/Group.java	Sun Aug 29 18:17:37 2010 +0200
@@ -31,154 +31,147 @@
 // TODO: This class should not be public!
 public class Group extends Channel
 {
-  
-  private long   id     = 0;
-  private int    flags  = -1;
-  private String name   = null;
 
-  /**
-   * @return List of all groups this server handles.
-   */
-  public static List<Channel> getAll()
-  {
-    try
-    {
-      return StorageManager.current().getGroups();
-    }
-    catch(StorageBackendException ex)
-    {
-      Log.get().severe(ex.getMessage());
-      return null;
-    }
-  }
-  
-  /**
-   * @param name
-   * @param id
-   */
-  public Group(final String name, final long id, final int flags)
-  {
-    this.id    = id;
-    this.flags = flags;
-    this.name  = name;
-  }
+	private long id = 0;
+	private int flags = -1;
+	private String name = null;
 
-  @Override
-  public boolean equals(Object obj)
-  {
-    if(obj instanceof Group)
-    {
-      return ((Group)obj).id == this.id;
-    }
-    else
-    {
-      return false;
-    }
-  }
+	/**
+	 * @return List of all groups this server handles.
+	 */
+	public static List<Channel> getAll()
+	{
+		try {
+			return StorageManager.current().getGroups();
+		} catch (StorageBackendException ex) {
+			Log.get().severe(ex.getMessage());
+			return null;
+		}
+	}
 
-  public Article getArticle(long idx)
-    throws StorageBackendException
-  {
-    return StorageManager.current().getArticle(idx, this.id);
-  }
+	/**
+	 * @param name
+	 * @param id
+	 */
+	public Group(final String name, final long id, final int flags)
+	{
+		this.id = id;
+		this.flags = flags;
+		this.name = name;
+	}
 
-  public List<Pair<Long, ArticleHead>> getArticleHeads(final long first, final long last)
-    throws StorageBackendException
-  {
-    return StorageManager.current().getArticleHeads(this, first, last);
-  }
-  
-  public List<Long> getArticleNumbers()
-    throws StorageBackendException
-  {
-    return StorageManager.current().getArticleNumbers(id);
-  }
+	@Override
+	public boolean equals(Object obj)
+	{
+		if (obj instanceof Group) {
+			return ((Group) obj).id == this.id;
+		} else {
+			return false;
+		}
+	}
 
-  public long getFirstArticleNumber()
-    throws StorageBackendException
-  {
-    return StorageManager.current().getFirstArticleNumber(this);
-  }
+	public Article getArticle(long idx)
+		throws StorageBackendException
+	{
+		return StorageManager.current().getArticle(idx, this.id);
+	}
 
-  public int getFlags()
-  {
-    return this.flags;
-  }
+	public List<Pair<Long, ArticleHead>> getArticleHeads(final long first, final long last)
+		throws StorageBackendException
+	{
+		return StorageManager.current().getArticleHeads(this, first, last);
+	}
 
-  public long getIndexOf(Article art)
-    throws StorageBackendException
-  {
-    return StorageManager.current().getArticleIndex(art, this);
-  }
+	public List<Long> getArticleNumbers()
+		throws StorageBackendException
+	{
+		return StorageManager.current().getArticleNumbers(id);
+	}
 
-  /**
-   * Returns the group id.
-   */
-  public long getInternalID()
-  {
-    assert id > 0;
+	public long getFirstArticleNumber()
+		throws StorageBackendException
+	{
+		return StorageManager.current().getFirstArticleNumber(this);
+	}
 
-    return id;
-  }
+	public int getFlags()
+	{
+		return this.flags;
+	}
 
-  public boolean isDeleted()
-  {
-    return (this.flags & DELETED) != 0;
-  }
+	public long getIndexOf(Article art)
+		throws StorageBackendException
+	{
+		return StorageManager.current().getArticleIndex(art, this);
+	}
 
-  public boolean isMailingList()
-  {
-    return (this.flags & MAILINGLIST) != 0;
-  }
+	/**
+	 * Returns the group id.
+	 */
+	public long getInternalID()
+	{
+		assert id > 0;
 
-  public boolean isWriteable()
-  {
-    return true;
-  }
+		return id;
+	}
 
-  public long getLastArticleNumber()
-    throws StorageBackendException
-  {
-    return StorageManager.current().getLastArticleNumber(this);
-  }
+	public boolean isDeleted()
+	{
+		return (this.flags & DELETED) != 0;
+	}
 
-  public String getName()
-  {
-    return name;
-  }
+	public boolean isMailingList()
+	{
+		return (this.flags & MAILINGLIST) != 0;
+	}
 
-  /**
-   * Performs this.flags |= flag to set a specified flag and updates the data
-   * in the JDBCDatabase.
-   * @param flag
-   */
-  public void setFlag(final int flag)
-  {
-    this.flags |= flag;
-  }
+	public boolean isWriteable()
+	{
+		return true;
+	}
 
-  public void setName(final String name)
-  {
-    this.name = name;
-  }
+	public long getLastArticleNumber()
+		throws StorageBackendException
+	{
+		return StorageManager.current().getLastArticleNumber(this);
+	}
 
-  /**
-   * @return Number of posted articles in this group.
-   * @throws java.sql.SQLException
-   */
-  public long getPostingsCount()
-    throws StorageBackendException
-  {
-    return StorageManager.current().getPostingsCount(this.name);
-  }
+	public String getName()
+	{
+		return name;
+	}
 
-  /**
-   * Updates flags and name in the backend.
-   */
-  public void update()
-    throws StorageBackendException
-  {
-    StorageManager.current().update(this);
-  }
+	/**
+	 * Performs this.flags |= flag to set a specified flag and updates the data
+	 * in the JDBCDatabase.
+	 * @param flag
+	 */
+	public void setFlag(final int flag)
+	{
+		this.flags |= flag;
+	}
 
+	public void setName(final String name)
+	{
+		this.name = name;
+	}
+
+	/**
+	 * @return Number of posted articles in this group.
+	 * @throws java.sql.SQLException
+	 */
+	public long getPostingsCount()
+		throws StorageBackendException
+	{
+		return StorageManager.current().getPostingsCount(this.name);
+	}
+
+	/**
+	 * Updates flags and name in the backend.
+	 */
+	public void update()
+		throws StorageBackendException
+	{
+		StorageManager.current().update(this);
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/storage/Headers.java
--- a/src/org/sonews/storage/Headers.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/storage/Headers.java	Sun Aug 29 18:17:37 2010 +0200
@@ -27,30 +27,30 @@
 public final class Headers
 {
 
-  public static final String BYTES             = "bytes";
-  public static final String CONTENT_TYPE      = "content-type";
-  public static final String CONTROL           = "control";
-  public static final String DATE              = "date";
-  public static final String FROM              = "from";
-  public static final String LINES             = "lines";
-  public static final String LIST_POST         = "list-post";
-  public static final String MESSAGE_ID        = "message-id";
-  public static final String NEWSGROUPS        = "newsgroups";
-  public static final String NNTP_POSTING_DATE = "nntp-posting-date";
-  public static final String NNTP_POSTING_HOST = "nntp-posting-host";
-  public static final String PATH              = "path";
-  public static final String REFERENCES        = "references";
-  public static final String REPLY_TO          = "reply-to";
-  public static final String SENDER            = "sender";
-  public static final String SUBJECT           = "subject";
-  public static final String SUPERSEDES        = "subersedes";
-  public static final String TO                = "to";
-  public static final String X_COMPLAINTS_TO   = "x-complaints-to";
-  public static final String X_LIST_POST       = "x-list-post";
-  public static final String X_TRACE           = "x-trace";
-  public static final String XREF              = "xref";
+	public static final String BYTES = "bytes";
+	public static final String CONTENT_TYPE = "content-type";
+	public static final String CONTROL = "control";
+	public static final String DATE = "date";
+	public static final String FROM = "from";
+	public static final String LINES = "lines";
+	public static final String LIST_POST = "list-post";
+	public static final String MESSAGE_ID = "message-id";
+	public static final String NEWSGROUPS = "newsgroups";
+	public static final String NNTP_POSTING_DATE = "nntp-posting-date";
+	public static final String NNTP_POSTING_HOST = "nntp-posting-host";
+	public static final String PATH = "path";
+	public static final String REFERENCES = "references";
+	public static final String REPLY_TO = "reply-to";
+	public static final String SENDER = "sender";
+	public static final String SUBJECT = "subject";
+	public static final String SUPERSEDES = "subersedes";
+	public static final String TO = "to";
+	public static final String X_COMPLAINTS_TO = "x-complaints-to";
+	public static final String X_LIST_POST = "x-list-post";
+	public static final String X_TRACE = "x-trace";
+	public static final String XREF = "xref";
 
-  private Headers()
-  {}
-
+	private Headers()
+	{
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/storage/Storage.java
--- a/src/org/sonews/storage/Storage.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/storage/Storage.java	Sun Aug 29 18:17:37 2010 +0200
@@ -30,121 +30,120 @@
 public interface Storage
 {
 
-  /**
-   * Stores the given Article in the storage.
-   * @param art
-   * @throws StorageBackendException
-   */
-  void addArticle(Article art)
-    throws StorageBackendException;
+	/**
+	 * Stores the given Article in the storage.
+	 * @param art
+	 * @throws StorageBackendException
+	 */
+	void addArticle(Article art)
+		throws StorageBackendException;
 
-  void addEvent(long timestamp, int type, long groupID)
-    throws StorageBackendException;
+	void addEvent(long timestamp, int type, long groupID)
+		throws StorageBackendException;
 
-  void addGroup(String groupname, int flags)
-    throws StorageBackendException;
+	void addGroup(String groupname, int flags)
+		throws StorageBackendException;
 
-  int countArticles()
-    throws StorageBackendException;
+	int countArticles()
+		throws StorageBackendException;
 
-  int countGroups()
-    throws StorageBackendException;
+	int countGroups()
+		throws StorageBackendException;
 
-  void delete(String messageID)
-    throws StorageBackendException;
+	void delete(String messageID)
+		throws StorageBackendException;
 
-  Article getArticle(String messageID)
-    throws StorageBackendException;
+	Article getArticle(String messageID)
+		throws StorageBackendException;
 
-  Article getArticle(long articleIndex, long groupID)
-    throws StorageBackendException;
+	Article getArticle(long articleIndex, long groupID)
+		throws StorageBackendException;
 
-  List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first, long last)
-    throws StorageBackendException;
+	List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first, long last)
+		throws StorageBackendException;
 
-  List<Pair<Long, String>> getArticleHeaders(Channel channel, long start, long end,
-    String header, String pattern)
-    throws StorageBackendException;
+	List<Pair<Long, String>> getArticleHeaders(Channel channel, long start, long end,
+		String header, String pattern)
+		throws StorageBackendException;
 
-  long getArticleIndex(Article art, Group group)
-    throws StorageBackendException;
+	long getArticleIndex(Article art, Group group)
+		throws StorageBackendException;
 
-  List<Long> getArticleNumbers(long groupID)
-    throws StorageBackendException;
+	List<Long> getArticleNumbers(long groupID)
+		throws StorageBackendException;
 
-  String getConfigValue(String key)
-    throws StorageBackendException;
+	String getConfigValue(String key)
+		throws StorageBackendException;
 
-  int getEventsCount(int eventType, long startTimestamp, long endTimestamp,
-    Channel channel)
-    throws StorageBackendException;
+	int getEventsCount(int eventType, long startTimestamp, long endTimestamp,
+		Channel channel)
+		throws StorageBackendException;
 
-  double getEventsPerHour(int key, long gid)
-    throws StorageBackendException;
+	double getEventsPerHour(int key, long gid)
+		throws StorageBackendException;
 
-  int getFirstArticleNumber(Group group)
-    throws StorageBackendException;
+	int getFirstArticleNumber(Group group)
+		throws StorageBackendException;
 
-  Group getGroup(String name)
-    throws StorageBackendException;
+	Group getGroup(String name)
+		throws StorageBackendException;
 
-  List<Channel> getGroups()
-    throws StorageBackendException;
+	List<Channel> getGroups()
+		throws StorageBackendException;
 
-  /**
-   * Retrieves the collection of groupnames that are associated with the
-   * given list address.
-   * @param inetaddress
-   * @return
-   * @throws StorageBackendException
-   */
-  List<String> getGroupsForList(String listAddress)
-    throws StorageBackendException;
+	/**
+	 * Retrieves the collection of groupnames that are associated with the
+	 * given list address.
+	 * @param inetaddress
+	 * @return
+	 * @throws StorageBackendException
+	 */
+	List<String> getGroupsForList(String listAddress)
+		throws StorageBackendException;
 
-  int getLastArticleNumber(Group group)
-    throws StorageBackendException;
+	int getLastArticleNumber(Group group)
+		throws StorageBackendException;
 
-  /**
-   * Returns a list of email addresses that are related to the given
-   * groupname. In most cases the list may contain only one entry.
-   * @param groupname
-   * @return
-   * @throws StorageBackendException
-   */
-  List<String> getListsForGroup(String groupname)
-    throws StorageBackendException;
+	/**
+	 * Returns a list of email addresses that are related to the given
+	 * groupname. In most cases the list may contain only one entry.
+	 * @param groupname
+	 * @return
+	 * @throws StorageBackendException
+	 */
+	List<String> getListsForGroup(String groupname)
+		throws StorageBackendException;
 
-  String getOldestArticle()
-    throws StorageBackendException;
+	String getOldestArticle()
+		throws StorageBackendException;
 
-  int getPostingsCount(String groupname)
-    throws StorageBackendException;
+	int getPostingsCount(String groupname)
+		throws StorageBackendException;
 
-  List<Subscription> getSubscriptions(int type)
-    throws StorageBackendException;
+	List<Subscription> getSubscriptions(int type)
+		throws StorageBackendException;
 
-  boolean isArticleExisting(String messageID)
-    throws StorageBackendException;
+	boolean isArticleExisting(String messageID)
+		throws StorageBackendException;
 
-  boolean isGroupExisting(String groupname)
-    throws StorageBackendException;
+	boolean isGroupExisting(String groupname)
+		throws StorageBackendException;
 
-  void purgeGroup(Group group)
-    throws StorageBackendException;
+	void purgeGroup(Group group)
+		throws StorageBackendException;
 
-  void setConfigValue(String key, String value)
-    throws StorageBackendException;
+	void setConfigValue(String key, String value)
+		throws StorageBackendException;
 
-  /**
-   * Updates headers and channel references of the given article.
-   * @param article
-   * @return
-   * @throws StorageBackendException
-   */
-  boolean update(Article article)
-    throws StorageBackendException;
+	/**
+	 * Updates headers and channel references of the given article.
+	 * @param article
+	 * @return
+	 * @throws StorageBackendException
+	 */
+	boolean update(Article article)
+		throws StorageBackendException;
 
-  boolean update(Group group)
-    throws StorageBackendException;
-
+	boolean update(Group group)
+		throws StorageBackendException;
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/storage/StorageBackendException.java
--- a/src/org/sonews/storage/StorageBackendException.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/storage/StorageBackendException.java	Sun Aug 29 18:17:37 2010 +0200
@@ -19,21 +19,20 @@
 package org.sonews.storage;
 
 /**
- *
+ * Exception caused by the storage backend.
  * @author Christian Lins
  * @since sonews/1.0
  */
 public class StorageBackendException extends Exception
 {
 
-  public StorageBackendException(Throwable cause)
-  {
-    super(cause);
-  }
+	public StorageBackendException(Throwable cause)
+	{
+		super(cause);
+	}
 
-  public StorageBackendException(String msg)
-  {
-    super(msg);
-  }
-
+	public StorageBackendException(String msg)
+	{
+		super(msg);
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/storage/StorageManager.java
--- a/src/org/sonews/storage/StorageManager.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/storage/StorageManager.java	Sun Aug 29 18:17:37 2010 +0200
@@ -26,64 +26,53 @@
 public final class StorageManager
 {
 
-  private static StorageProvider provider;
+	private static StorageProvider provider;
 
-  public static Storage current()
-    throws StorageBackendException
-  {
-    synchronized(StorageManager.class)
-    {
-      if(provider == null)
-      {
-        return null;
-      }
-      else
-      {
-        return provider.storage(Thread.currentThread());
-      }
-    }
-  }
+	public static Storage current()
+		throws StorageBackendException
+	{
+		synchronized (StorageManager.class) {
+			if (provider == null) {
+				return null;
+			} else {
+				return provider.storage(Thread.currentThread());
+			}
+		}
+	}
 
-  public static StorageProvider loadProvider(String pluginClassName)
-  {
-    try
-    {
-      Class<?> clazz = Class.forName(pluginClassName);
-      Object   inst  = clazz.newInstance();
-      return (StorageProvider)inst;
-    }
-    catch(Exception ex)
-    {
-      System.err.println(ex);
-      return null;
-    }
-  }
+	public static StorageProvider loadProvider(String pluginClassName)
+	{
+		try {
+			Class<?> clazz = Class.forName(pluginClassName);
+			Object inst = clazz.newInstance();
+			return (StorageProvider) inst;
+		} catch (Exception ex) {
+			System.err.println(ex);
+			return null;
+		}
+	}
 
-  /**
-   * Sets the current storage provider.
-   * @param provider
-   */
-  public static void enableProvider(StorageProvider provider)
-  {
-    synchronized(StorageManager.class)
-    {
-      if(StorageManager.provider != null)
-      {
-        disableProvider();
-      }
-      StorageManager.provider = provider;
-    }
-  }
+	/**
+	 * Sets the current storage provider.
+	 * @param provider
+	 */
+	public static void enableProvider(StorageProvider provider)
+	{
+		synchronized (StorageManager.class) {
+			if (StorageManager.provider != null) {
+				disableProvider();
+			}
+			StorageManager.provider = provider;
+		}
+	}
 
-  /**
-   * Disables the current provider.
-   */
-  public static void disableProvider()
-  {
-    synchronized(StorageManager.class)
-    {
-      provider = null;
-    }
-  }
-
+	/**
+	 * Disables the current provider.
+	 */
+	public static void disableProvider()
+	{
+		synchronized (StorageManager.class) {
+			provider = null;
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/storage/StorageProvider.java
--- a/src/org/sonews/storage/StorageProvider.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/storage/StorageProvider.java	Sun Aug 29 18:17:37 2010 +0200
@@ -26,15 +26,14 @@
 public interface StorageProvider
 {
 
-  public boolean isSupported(String uri);
+	public boolean isSupported(String uri);
 
-  /**
-   * This method returns the reference to the associated storage.
-   * The reference MAY be unique for each thread. In any case it MUST be
-   * thread-safe to use this method.
-   * @return The reference to the associated Storage.
-   */
-  public Storage storage(Thread thread)
-    throws StorageBackendException;
-
+	/**
+	 * This method returns the reference to the associated storage.
+	 * The reference MAY be unique for each thread. In any case it MUST be
+	 * thread-safe to use this method.
+	 * @return The reference to the associated Storage.
+	 */
+	public Storage storage(Thread thread)
+		throws StorageBackendException;
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/storage/impl/JDBCDatabase.java
--- a/src/org/sonews/storage/impl/JDBCDatabase.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/storage/impl/JDBCDatabase.java	Sun Aug 29 18:17:37 2010 +0200
@@ -52,1731 +52,1378 @@
 public class JDBCDatabase implements Storage
 {
 
-  public static final int MAX_RESTARTS = 2;
-  
-  private Connection        conn = null;
-  private PreparedStatement pstmtAddArticle1 = null;
-  private PreparedStatement pstmtAddArticle2 = null;
-  private PreparedStatement pstmtAddArticle3 = null;
-  private PreparedStatement pstmtAddArticle4 = null;
-  private PreparedStatement pstmtAddGroup0   = null;
-  private PreparedStatement pstmtAddEvent = null;
-  private PreparedStatement pstmtCountArticles = null;
-  private PreparedStatement pstmtCountGroups   = null;
-  private PreparedStatement pstmtDeleteArticle0 = null;
-  private PreparedStatement pstmtDeleteArticle1 = null;
-  private PreparedStatement pstmtDeleteArticle2 = null;
-  private PreparedStatement pstmtDeleteArticle3 = null;
-  private PreparedStatement pstmtGetArticle0 = null;
-  private PreparedStatement pstmtGetArticle1 = null;
-  private PreparedStatement pstmtGetArticleHeaders0 = null;
-  private PreparedStatement pstmtGetArticleHeaders1 = null;
-  private PreparedStatement pstmtGetArticleHeads = null;
-  private PreparedStatement pstmtGetArticleIDs   = null;
-  private PreparedStatement pstmtGetArticleIndex    = null;
-  private PreparedStatement pstmtGetConfigValue = null;
-  private PreparedStatement pstmtGetEventsCount0 = null;
-  private PreparedStatement pstmtGetEventsCount1 = null;
-  private PreparedStatement pstmtGetGroupForList = null;
-  private PreparedStatement pstmtGetGroup0     = null;
-  private PreparedStatement pstmtGetGroup1     = null;
-  private PreparedStatement pstmtGetFirstArticleNumber = null;
-  private PreparedStatement pstmtGetListForGroup       = null;
-  private PreparedStatement pstmtGetLastArticleNumber  = null;
-  private PreparedStatement pstmtGetMaxArticleID       = null;
-  private PreparedStatement pstmtGetMaxArticleIndex    = null;
-  private PreparedStatement pstmtGetOldestArticle      = null;
-  private PreparedStatement pstmtGetPostingsCount      = null;
-  private PreparedStatement pstmtGetSubscriptions  = null;
-  private PreparedStatement pstmtIsArticleExisting = null;
-  private PreparedStatement pstmtIsGroupExisting = null;
-  private PreparedStatement pstmtPurgeGroup0     = null;
-  private PreparedStatement pstmtPurgeGroup1     = null;
-  private PreparedStatement pstmtSetConfigValue0 = null;
-  private PreparedStatement pstmtSetConfigValue1 = null;
-  private PreparedStatement pstmtUpdateGroup     = null;
-  
-  /** How many times the database connection was reinitialized */
-  private int restarts = 0;
-  
-  /**
-   * Rises the database: reconnect and recreate all prepared statements.
-   * @throws java.lang.SQLException
-   */
-  protected void arise()
-    throws SQLException
-  {
-    try
-    {
-      // Load database driver
-      Class.forName(
-        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DBMSDRIVER, "java.lang.Object"));
+	public static final int MAX_RESTARTS = 2;
+	private Connection conn = null;
+	private PreparedStatement pstmtAddArticle1 = null;
+	private PreparedStatement pstmtAddArticle2 = null;
+	private PreparedStatement pstmtAddArticle3 = null;
+	private PreparedStatement pstmtAddArticle4 = null;
+	private PreparedStatement pstmtAddGroup0 = null;
+	private PreparedStatement pstmtAddEvent = null;
+	private PreparedStatement pstmtCountArticles = null;
+	private PreparedStatement pstmtCountGroups = null;
+	private PreparedStatement pstmtDeleteArticle0 = null;
+	private PreparedStatement pstmtDeleteArticle1 = null;
+	private PreparedStatement pstmtDeleteArticle2 = null;
+	private PreparedStatement pstmtDeleteArticle3 = null;
+	private PreparedStatement pstmtGetArticle0 = null;
+	private PreparedStatement pstmtGetArticle1 = null;
+	private PreparedStatement pstmtGetArticleHeaders0 = null;
+	private PreparedStatement pstmtGetArticleHeaders1 = null;
+	private PreparedStatement pstmtGetArticleHeads = null;
+	private PreparedStatement pstmtGetArticleIDs = null;
+	private PreparedStatement pstmtGetArticleIndex = null;
+	private PreparedStatement pstmtGetConfigValue = null;
+	private PreparedStatement pstmtGetEventsCount0 = null;
+	private PreparedStatement pstmtGetEventsCount1 = null;
+	private PreparedStatement pstmtGetGroupForList = null;
+	private PreparedStatement pstmtGetGroup0 = null;
+	private PreparedStatement pstmtGetGroup1 = null;
+	private PreparedStatement pstmtGetFirstArticleNumber = null;
+	private PreparedStatement pstmtGetListForGroup = null;
+	private PreparedStatement pstmtGetLastArticleNumber = null;
+	private PreparedStatement pstmtGetMaxArticleID = null;
+	private PreparedStatement pstmtGetMaxArticleIndex = null;
+	private PreparedStatement pstmtGetOldestArticle = null;
+	private PreparedStatement pstmtGetPostingsCount = null;
+	private PreparedStatement pstmtGetSubscriptions = null;
+	private PreparedStatement pstmtIsArticleExisting = null;
+	private PreparedStatement pstmtIsGroupExisting = null;
+	private PreparedStatement pstmtPurgeGroup0 = null;
+	private PreparedStatement pstmtPurgeGroup1 = null;
+	private PreparedStatement pstmtSetConfigValue0 = null;
+	private PreparedStatement pstmtSetConfigValue1 = null;
+	private PreparedStatement pstmtUpdateGroup = null;
+	/** How many times the database connection was reinitialized */
+	private int restarts = 0;
 
-      // Establish database connection
-      this.conn = DriverManager.getConnection(
-        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DATABASE, "<not specified>"),
-        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_USER, "root"),
-        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_PASSWORD, ""));
+	/**
+	 * Rises the database: reconnect and recreate all prepared statements.
+	 * @throws java.lang.SQLException
+	 */
+	protected void arise()
+		throws SQLException
+	{
+		try {
+			// Load database driver
+			Class.forName(
+				Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DBMSDRIVER, "java.lang.Object"));
 
-      this.conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
-      if(this.conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE)
-      {
-        Log.get().warning("Database is NOT fully serializable!");
-      }
+			// Establish database connection
+			this.conn = DriverManager.getConnection(
+				Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DATABASE, "<not specified>"),
+				Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_USER, "root"),
+				Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_PASSWORD, ""));
 
-      // Prepare statements for method addArticle()
-      this.pstmtAddArticle1 = conn.prepareStatement(
-        "INSERT INTO articles (article_id, body) VALUES(?, ?)");
-      this.pstmtAddArticle2 = conn.prepareStatement(
-        "INSERT INTO headers (article_id, header_key, header_value, header_index) " +
-        "VALUES (?, ?, ?, ?)");
-      this.pstmtAddArticle3 = conn.prepareStatement(
-        "INSERT INTO postings (group_id, article_id, article_index)" +
-        "VALUES (?, ?, ?)");
-      this.pstmtAddArticle4 = conn.prepareStatement(
-        "INSERT INTO article_ids (article_id, message_id) VALUES (?, ?)");
+			this.conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
+			if (this.conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE) {
+				Log.get().warning("Database is NOT fully serializable!");
+			}
 
-      // Prepare statement for method addStatValue()
-      this.pstmtAddEvent = conn.prepareStatement(
-        "INSERT INTO events VALUES (?, ?, ?)");
-     
-      // Prepare statement for method addGroup()
-      this.pstmtAddGroup0 = conn.prepareStatement(
-        "INSERT INTO groups (name, flags) VALUES (?, ?)");
-      
-      // Prepare statement for method countArticles()
-      this.pstmtCountArticles = conn.prepareStatement(
-        "SELECT Count(article_id) FROM article_ids");
-      
-      // Prepare statement for method countGroups()
-      this.pstmtCountGroups = conn.prepareStatement(
-        "SELECT Count(group_id) FROM groups WHERE " +
-        "flags & " + Channel.DELETED + " = 0");
-      
-      // Prepare statements for method delete(article)
-      this.pstmtDeleteArticle0 = conn.prepareStatement(
-        "DELETE FROM articles WHERE article_id = " +
-        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
-      this.pstmtDeleteArticle1 = conn.prepareStatement(
-        "DELETE FROM headers WHERE article_id = " +
-        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
-      this.pstmtDeleteArticle2 = conn.prepareStatement(
-        "DELETE FROM postings WHERE article_id = " +
-        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
-      this.pstmtDeleteArticle3 = conn.prepareStatement(
-        "DELETE FROM article_ids WHERE message_id = ?");
+			// Prepare statements for method addArticle()
+			this.pstmtAddArticle1 = conn.prepareStatement(
+				"INSERT INTO articles (article_id, body) VALUES(?, ?)");
+			this.pstmtAddArticle2 = conn.prepareStatement(
+				"INSERT INTO headers (article_id, header_key, header_value, header_index) "
+				+ "VALUES (?, ?, ?, ?)");
+			this.pstmtAddArticle3 = conn.prepareStatement(
+				"INSERT INTO postings (group_id, article_id, article_index)"
+				+ "VALUES (?, ?, ?)");
+			this.pstmtAddArticle4 = conn.prepareStatement(
+				"INSERT INTO article_ids (article_id, message_id) VALUES (?, ?)");
 
-      // Prepare statements for methods getArticle()
-      this.pstmtGetArticle0 = conn.prepareStatement(
-        "SELECT * FROM articles  WHERE article_id = " +
-        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
-      this.pstmtGetArticle1 = conn.prepareStatement(
-        "SELECT * FROM articles WHERE article_id = " +
-        "(SELECT article_id FROM postings WHERE " +
-        "article_index = ? AND group_id = ?)");
-      
-      // Prepare statement for method getArticleHeaders()
-      this.pstmtGetArticleHeaders0 = conn.prepareStatement(
-        "SELECT header_key, header_value FROM headers WHERE article_id = ? " +
-        "ORDER BY header_index ASC");
+			// Prepare statement for method addStatValue()
+			this.pstmtAddEvent = conn.prepareStatement(
+				"INSERT INTO events VALUES (?, ?, ?)");
 
-      // Prepare statement for method getArticleHeaders(regular expr pattern)
-      this.pstmtGetArticleHeaders1 = conn.prepareStatement(
-        "SELECT p.article_index, h.header_value FROM headers h " +
-          "INNER JOIN postings p ON h.article_id = p.article_id " +
-          "INNER JOIN groups g ON p.group_id = g.group_id " +
-            "WHERE g.name          =  ? AND " +
-                  "h.header_key    =  ? AND " +
-                  "p.article_index >= ? " +
-        "ORDER BY p.article_index ASC");
+			// Prepare statement for method addGroup()
+			this.pstmtAddGroup0 = conn.prepareStatement(
+				"INSERT INTO groups (name, flags) VALUES (?, ?)");
 
-      this.pstmtGetArticleIDs = conn.prepareStatement(
-        "SELECT article_index FROM postings WHERE group_id = ?");
-      
-      // Prepare statement for method getArticleIndex
-      this.pstmtGetArticleIndex = conn.prepareStatement(
-              "SELECT article_index FROM postings WHERE " +
-              "article_id = (SELECT article_id FROM article_ids " +
-              "WHERE message_id = ?) " +
-              " AND group_id = ?");
+			// Prepare statement for method countArticles()
+			this.pstmtCountArticles = conn.prepareStatement(
+				"SELECT Count(article_id) FROM article_ids");
 
-      // Prepare statements for method getArticleHeads()
-      this.pstmtGetArticleHeads = conn.prepareStatement(
-        "SELECT article_id, article_index FROM postings WHERE " +
-        "postings.group_id = ? AND article_index >= ? AND " +
-        "article_index <= ?");
+			// Prepare statement for method countGroups()
+			this.pstmtCountGroups = conn.prepareStatement(
+				"SELECT Count(group_id) FROM groups WHERE "
+				+ "flags & " + Channel.DELETED + " = 0");
 
-      // Prepare statements for method getConfigValue()
-      this.pstmtGetConfigValue = conn.prepareStatement(
-        "SELECT config_value FROM config WHERE config_key = ?");
+			// Prepare statements for method delete(article)
+			this.pstmtDeleteArticle0 = conn.prepareStatement(
+				"DELETE FROM articles WHERE article_id = "
+				+ "(SELECT article_id FROM article_ids WHERE message_id = ?)");
+			this.pstmtDeleteArticle1 = conn.prepareStatement(
+				"DELETE FROM headers WHERE article_id = "
+				+ "(SELECT article_id FROM article_ids WHERE message_id = ?)");
+			this.pstmtDeleteArticle2 = conn.prepareStatement(
+				"DELETE FROM postings WHERE article_id = "
+				+ "(SELECT article_id FROM article_ids WHERE message_id = ?)");
+			this.pstmtDeleteArticle3 = conn.prepareStatement(
+				"DELETE FROM article_ids WHERE message_id = ?");
 
-      // Prepare statements for method getEventsCount()
-      this.pstmtGetEventsCount0 = conn.prepareStatement(
-        "SELECT Count(*) FROM events WHERE event_key = ? AND " +
-        "event_time >= ? AND event_time < ?");
+			// Prepare statements for methods getArticle()
+			this.pstmtGetArticle0 = conn.prepareStatement(
+				"SELECT * FROM articles  WHERE article_id = "
+				+ "(SELECT article_id FROM article_ids WHERE message_id = ?)");
+			this.pstmtGetArticle1 = conn.prepareStatement(
+				"SELECT * FROM articles WHERE article_id = "
+				+ "(SELECT article_id FROM postings WHERE "
+				+ "article_index = ? AND group_id = ?)");
 
-      this.pstmtGetEventsCount1 = conn.prepareStatement(
-        "SELECT Count(*) FROM events WHERE event_key = ? AND " +
-        "event_time >= ? AND event_time < ? AND group_id = ?");
-      
-      // Prepare statement for method getGroupForList()
-      this.pstmtGetGroupForList = conn.prepareStatement(
-        "SELECT name FROM groups INNER JOIN groups2list " +
-        "ON groups.group_id = groups2list.group_id " +
-        "WHERE groups2list.listaddress = ?");
+			// Prepare statement for method getArticleHeaders()
+			this.pstmtGetArticleHeaders0 = conn.prepareStatement(
+				"SELECT header_key, header_value FROM headers WHERE article_id = ? "
+				+ "ORDER BY header_index ASC");
 
-      // Prepare statement for method getGroup()
-      this.pstmtGetGroup0 = conn.prepareStatement(
-        "SELECT group_id, flags FROM groups WHERE Name = ?");
-      this.pstmtGetGroup1 = conn.prepareStatement(
-        "SELECT name FROM groups WHERE group_id = ?");
+			// Prepare statement for method getArticleHeaders(regular expr pattern)
+			this.pstmtGetArticleHeaders1 = conn.prepareStatement(
+				"SELECT p.article_index, h.header_value FROM headers h "
+				+ "INNER JOIN postings p ON h.article_id = p.article_id "
+				+ "INNER JOIN groups g ON p.group_id = g.group_id "
+				+ "WHERE g.name          =  ? AND "
+				+ "h.header_key    =  ? AND "
+				+ "p.article_index >= ? "
+				+ "ORDER BY p.article_index ASC");
 
-      // Prepare statement for method getLastArticleNumber()
-      this.pstmtGetLastArticleNumber = conn.prepareStatement(
-        "SELECT Max(article_index) FROM postings WHERE group_id = ?");
+			this.pstmtGetArticleIDs = conn.prepareStatement(
+				"SELECT article_index FROM postings WHERE group_id = ?");
 
-      // Prepare statement for method getListForGroup()
-      this.pstmtGetListForGroup = conn.prepareStatement(
-        "SELECT listaddress FROM groups2list INNER JOIN groups " +
-        "ON groups.group_id = groups2list.group_id WHERE name = ?");
+			// Prepare statement for method getArticleIndex
+			this.pstmtGetArticleIndex = conn.prepareStatement(
+				"SELECT article_index FROM postings WHERE "
+				+ "article_id = (SELECT article_id FROM article_ids "
+				+ "WHERE message_id = ?) "
+				+ " AND group_id = ?");
 
-      // Prepare statement for method getMaxArticleID()
-      this.pstmtGetMaxArticleID = conn.prepareStatement(
-        "SELECT Max(article_id) FROM articles");
-      
-      // Prepare statement for method getMaxArticleIndex()
-      this.pstmtGetMaxArticleIndex = conn.prepareStatement(
-        "SELECT Max(article_index) FROM postings WHERE group_id = ?");
-      
-      // Prepare statement for method getOldestArticle()
-      this.pstmtGetOldestArticle = conn.prepareStatement(
-        "SELECT message_id FROM article_ids WHERE article_id = " +
-        "(SELECT Min(article_id) FROM article_ids)");
+			// Prepare statements for method getArticleHeads()
+			this.pstmtGetArticleHeads = conn.prepareStatement(
+				"SELECT article_id, article_index FROM postings WHERE "
+				+ "postings.group_id = ? AND article_index >= ? AND "
+				+ "article_index <= ?");
 
-      // Prepare statement for method getFirstArticleNumber()
-      this.pstmtGetFirstArticleNumber = conn.prepareStatement(
-        "SELECT Min(article_index) FROM postings WHERE group_id = ?");
-      
-      // Prepare statement for method getPostingsCount()
-      this.pstmtGetPostingsCount = conn.prepareStatement(
-        "SELECT Count(*) FROM postings NATURAL JOIN groups " +
-        "WHERE groups.name = ?");
-      
-      // Prepare statement for method getSubscriptions()
-      this.pstmtGetSubscriptions = conn.prepareStatement(
-        "SELECT host, port, name FROM peers NATURAL JOIN " +
-        "peer_subscriptions NATURAL JOIN groups WHERE feedtype = ?");
-      
-      // Prepare statement for method isArticleExisting()
-      this.pstmtIsArticleExisting = conn.prepareStatement(
-        "SELECT Count(article_id) FROM article_ids WHERE message_id = ?");
-      
-      // Prepare statement for method isGroupExisting()
-      this.pstmtIsGroupExisting = conn.prepareStatement(
-        "SELECT * FROM groups WHERE name = ?");
-      
-      // Prepare statement for method setConfigValue()
-      this.pstmtSetConfigValue0 = conn.prepareStatement(
-        "DELETE FROM config WHERE config_key = ?");
-      this.pstmtSetConfigValue1 = conn.prepareStatement(
-        "INSERT INTO config VALUES(?, ?)");
+			// Prepare statements for method getConfigValue()
+			this.pstmtGetConfigValue = conn.prepareStatement(
+				"SELECT config_value FROM config WHERE config_key = ?");
 
-      // Prepare statements for method purgeGroup()
-      this.pstmtPurgeGroup0 = conn.prepareStatement(
-        "DELETE FROM peer_subscriptions WHERE group_id = ?");
-      this.pstmtPurgeGroup1 = conn.prepareStatement(
-        "DELETE FROM groups WHERE group_id = ?");
+			// Prepare statements for method getEventsCount()
+			this.pstmtGetEventsCount0 = conn.prepareStatement(
+				"SELECT Count(*) FROM events WHERE event_key = ? AND "
+				+ "event_time >= ? AND event_time < ?");
 
-      // Prepare statement for method update(Group)
-      this.pstmtUpdateGroup = conn.prepareStatement(
-        "UPDATE groups SET flags = ?, name = ? WHERE group_id = ?");
-    }
-    catch(ClassNotFoundException ex)
-    {
-      throw new Error("JDBC Driver not found!", ex);
-    }
-  }
-  
-  /**
-   * Adds an article to the database.
-   * @param article
-   * @return
-   * @throws java.sql.SQLException
-   */
-  @Override
-  public void addArticle(final Article article)
-    throws StorageBackendException
-  {
-    try
-    {
-      this.conn.setAutoCommit(false);
+			this.pstmtGetEventsCount1 = conn.prepareStatement(
+				"SELECT Count(*) FROM events WHERE event_key = ? AND "
+				+ "event_time >= ? AND event_time < ? AND group_id = ?");
 
-      int newArticleID = getMaxArticleID() + 1;
+			// Prepare statement for method getGroupForList()
+			this.pstmtGetGroupForList = conn.prepareStatement(
+				"SELECT name FROM groups INNER JOIN groups2list "
+				+ "ON groups.group_id = groups2list.group_id "
+				+ "WHERE groups2list.listaddress = ?");
 
-      // Fill prepared statement with values;
-      // writes body to article table
-      pstmtAddArticle1.setInt(1, newArticleID);
-      pstmtAddArticle1.setBytes(2, article.getBody());
-      pstmtAddArticle1.execute();
+			// Prepare statement for method getGroup()
+			this.pstmtGetGroup0 = conn.prepareStatement(
+				"SELECT group_id, flags FROM groups WHERE Name = ?");
+			this.pstmtGetGroup1 = conn.prepareStatement(
+				"SELECT name FROM groups WHERE group_id = ?");
 
-      // Add headers
-      Enumeration headers = article.getAllHeaders();
-      for(int n = 0; headers.hasMoreElements(); n++)
-      {
-        Header header = (Header)headers.nextElement();
-        pstmtAddArticle2.setInt(1, newArticleID);
-        pstmtAddArticle2.setString(2, header.getName().toLowerCase());
-        pstmtAddArticle2.setString(3, 
-          header.getValue().replaceAll("[\r\n]", ""));
-        pstmtAddArticle2.setInt(4, n);
-        pstmtAddArticle2.execute();
-      }
-      
-      // For each newsgroup add a reference
-      List<Group> groups = article.getGroups();
-      for(Group group : groups)
-      {
-        pstmtAddArticle3.setLong(1, group.getInternalID());
-        pstmtAddArticle3.setInt(2, newArticleID);
-        pstmtAddArticle3.setLong(3, getMaxArticleIndex(group.getInternalID()) + 1);
-        pstmtAddArticle3.execute();
-      }
-      
-      // Write message-id to article_ids table
-      this.pstmtAddArticle4.setInt(1, newArticleID);
-      this.pstmtAddArticle4.setString(2, article.getMessageID());
-      this.pstmtAddArticle4.execute();
+			// Prepare statement for method getLastArticleNumber()
+			this.pstmtGetLastArticleNumber = conn.prepareStatement(
+				"SELECT Max(article_index) FROM postings WHERE group_id = ?");
 
-      this.conn.commit();
-      this.conn.setAutoCommit(true);
+			// Prepare statement for method getListForGroup()
+			this.pstmtGetListForGroup = conn.prepareStatement(
+				"SELECT listaddress FROM groups2list INNER JOIN groups "
+				+ "ON groups.group_id = groups2list.group_id WHERE name = ?");
 
-      this.restarts = 0; // Reset error count
-    }
-    catch(SQLException ex)
-    {
-      try
-      {
-        this.conn.rollback();  // Rollback changes
-      }
-      catch(SQLException ex2)
-      {
-        Log.get().severe("Rollback of addArticle() failed: " + ex2);
-      }
-      
-      try
-      {
-        this.conn.setAutoCommit(true); // and release locks
-      }
-      catch(SQLException ex2)
-      {
-        Log.get().severe("setAutoCommit(true) of addArticle() failed: " + ex2);
-      }
+			// Prepare statement for method getMaxArticleID()
+			this.pstmtGetMaxArticleID = conn.prepareStatement(
+				"SELECT Max(article_id) FROM articles");
 
-      restartConnection(ex);
-      addArticle(article);
-    }
-  }
-  
-  /**
-   * Adds a group to the JDBCDatabase. This method is not accessible via NNTP.
-   * @param name
-   * @throws java.sql.SQLException
-   */
-  @Override
-  public void addGroup(String name, int flags)
-    throws StorageBackendException
-  {
-    try
-    {
-      this.conn.setAutoCommit(false);
-      pstmtAddGroup0.setString(1, name);
-      pstmtAddGroup0.setInt(2, flags);
+			// Prepare statement for method getMaxArticleIndex()
+			this.pstmtGetMaxArticleIndex = conn.prepareStatement(
+				"SELECT Max(article_index) FROM postings WHERE group_id = ?");
 
-      pstmtAddGroup0.executeUpdate();
-      this.conn.commit();
-      this.conn.setAutoCommit(true);
-      this.restarts = 0; // Reset error count
-    }
-    catch(SQLException ex)
-    {
-      try
-      {
-        this.conn.rollback();
-        this.conn.setAutoCommit(true);
-      }
-      catch(SQLException ex2)
-      {
-        ex2.printStackTrace();
-      }
+			// Prepare statement for method getOldestArticle()
+			this.pstmtGetOldestArticle = conn.prepareStatement(
+				"SELECT message_id FROM article_ids WHERE article_id = "
+				+ "(SELECT Min(article_id) FROM article_ids)");
 
-      restartConnection(ex);
-      addGroup(name, flags);
-    }
-  }
+			// Prepare statement for method getFirstArticleNumber()
+			this.pstmtGetFirstArticleNumber = conn.prepareStatement(
+				"SELECT Min(article_index) FROM postings WHERE group_id = ?");
 
-  @Override
-  public void addEvent(long time, int type, long gid)
-    throws StorageBackendException
-  {
-    try
-    {
-      this.conn.setAutoCommit(false);
-      this.pstmtAddEvent.setLong(1, time);
-      this.pstmtAddEvent.setInt(2, type);
-      this.pstmtAddEvent.setLong(3, gid);
-      this.pstmtAddEvent.executeUpdate();
-      this.conn.commit();
-      this.conn.setAutoCommit(true);
-      this.restarts = 0;
-    }
-    catch(SQLException ex)
-    {
-      try
-      {
-        this.conn.rollback();
-        this.conn.setAutoCommit(true);
-      }
-      catch(SQLException ex2)
-      {
-        ex2.printStackTrace();
-      }
+			// Prepare statement for method getPostingsCount()
+			this.pstmtGetPostingsCount = conn.prepareStatement(
+				"SELECT Count(*) FROM postings NATURAL JOIN groups "
+				+ "WHERE groups.name = ?");
 
-      restartConnection(ex);
-      addEvent(time, type, gid);
-    }
-  }
+			// Prepare statement for method getSubscriptions()
+			this.pstmtGetSubscriptions = conn.prepareStatement(
+				"SELECT host, port, name FROM peers NATURAL JOIN "
+				+ "peer_subscriptions NATURAL JOIN groups WHERE feedtype = ?");
 
-  @Override
-  public int countArticles()
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
+			// Prepare statement for method isArticleExisting()
+			this.pstmtIsArticleExisting = conn.prepareStatement(
+				"SELECT Count(article_id) FROM article_ids WHERE message_id = ?");
 
-    try
-    {
-      rs = this.pstmtCountArticles.executeQuery();
-      if(rs.next())
-      {
-        return rs.getInt(1);
-      }
-      else
-      {
-        return -1;
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return countArticles();
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-        restarts = 0;
-      }
-    }
-  }
+			// Prepare statement for method isGroupExisting()
+			this.pstmtIsGroupExisting = conn.prepareStatement(
+				"SELECT * FROM groups WHERE name = ?");
 
-  @Override
-  public int countGroups()
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
+			// Prepare statement for method setConfigValue()
+			this.pstmtSetConfigValue0 = conn.prepareStatement(
+				"DELETE FROM config WHERE config_key = ?");
+			this.pstmtSetConfigValue1 = conn.prepareStatement(
+				"INSERT INTO config VALUES(?, ?)");
 
-    try
-    {
-      rs = this.pstmtCountGroups.executeQuery();
-      if(rs.next())
-      {
-        return rs.getInt(1);
-      }
-      else
-      {
-        return -1;
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return countGroups();
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-        restarts = 0;
-      }
-    }
-  }
+			// Prepare statements for method purgeGroup()
+			this.pstmtPurgeGroup0 = conn.prepareStatement(
+				"DELETE FROM peer_subscriptions WHERE group_id = ?");
+			this.pstmtPurgeGroup1 = conn.prepareStatement(
+				"DELETE FROM groups WHERE group_id = ?");
 
-  @Override
-  public void delete(final String messageID)
-    throws StorageBackendException
-  {
-    try
-    {
-      this.conn.setAutoCommit(false);
-      
-      this.pstmtDeleteArticle0.setString(1, messageID);
-      int rs = this.pstmtDeleteArticle0.executeUpdate();
-      
-      // We do not trust the ON DELETE CASCADE functionality to delete
-      // orphaned references...
-      this.pstmtDeleteArticle1.setString(1, messageID);
-      rs = this.pstmtDeleteArticle1.executeUpdate();
+			// Prepare statement for method update(Group)
+			this.pstmtUpdateGroup = conn.prepareStatement(
+				"UPDATE groups SET flags = ?, name = ? WHERE group_id = ?");
+		} catch (ClassNotFoundException ex) {
+			throw new Error("JDBC Driver not found!", ex);
+		}
+	}
 
-      this.pstmtDeleteArticle2.setString(1, messageID);
-      rs = this.pstmtDeleteArticle2.executeUpdate();
+	/**
+	 * Adds an article to the database.
+	 * @param article
+	 * @return
+	 * @throws java.sql.SQLException
+	 */
+	@Override
+	public void addArticle(final Article article)
+		throws StorageBackendException
+	{
+		try {
+			this.conn.setAutoCommit(false);
 
-      this.pstmtDeleteArticle3.setString(1, messageID);
-      rs = this.pstmtDeleteArticle3.executeUpdate();
-      
-      this.conn.commit();
-      this.conn.setAutoCommit(true);
-    }
-    catch(SQLException ex)
-    {
-      throw new StorageBackendException(ex);
-    }
-  }
+			int newArticleID = getMaxArticleID() + 1;
 
-  @Override
-  public Article getArticle(String messageID)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
-    try
-    {
-      pstmtGetArticle0.setString(1, messageID);
-      rs = pstmtGetArticle0.executeQuery();
+			// Fill prepared statement with values;
+			// writes body to article table
+			pstmtAddArticle1.setInt(1, newArticleID);
+			pstmtAddArticle1.setBytes(2, article.getBody());
+			pstmtAddArticle1.execute();
 
-      if(!rs.next())
-      {
-        return null;
-      }
-      else
-      {
-        byte[] body     = rs.getBytes("body");
-        String headers  = getArticleHeaders(rs.getInt("article_id"));
-        return new Article(headers, body);
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getArticle(messageID);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-        restarts = 0; // Reset error count
-      }
-    }
-  }
-  
-  /**
-   * Retrieves an article by its ID.
-   * @param articleID
-   * @return
-   * @throws StorageBackendException
-   */
-  @Override
-  public Article getArticle(long articleIndex, long gid)
-    throws StorageBackendException
-  {  
-    ResultSet rs = null;
+			// Add headers
+			Enumeration headers = article.getAllHeaders();
+			for (int n = 0; headers.hasMoreElements(); n++) {
+				Header header = (Header) headers.nextElement();
+				pstmtAddArticle2.setInt(1, newArticleID);
+				pstmtAddArticle2.setString(2, header.getName().toLowerCase());
+				pstmtAddArticle2.setString(3,
+					header.getValue().replaceAll("[\r\n]", ""));
+				pstmtAddArticle2.setInt(4, n);
+				pstmtAddArticle2.execute();
+			}
 
-    try
-    {
-      this.pstmtGetArticle1.setLong(1, articleIndex);
-      this.pstmtGetArticle1.setLong(2, gid);
+			// For each newsgroup add a reference
+			List<Group> groups = article.getGroups();
+			for (Group group : groups) {
+				pstmtAddArticle3.setLong(1, group.getInternalID());
+				pstmtAddArticle3.setInt(2, newArticleID);
+				pstmtAddArticle3.setLong(3, getMaxArticleIndex(group.getInternalID()) + 1);
+				pstmtAddArticle3.execute();
+			}
 
-      rs = this.pstmtGetArticle1.executeQuery();
+			// Write message-id to article_ids table
+			this.pstmtAddArticle4.setInt(1, newArticleID);
+			this.pstmtAddArticle4.setString(2, article.getMessageID());
+			this.pstmtAddArticle4.execute();
 
-      if(rs.next())
-      {
-        byte[] body    = rs.getBytes("body");
-        String headers = getArticleHeaders(rs.getInt("article_id"));
-        return new Article(headers, body);
-      }
-      else
-      {
-        return null;
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getArticle(articleIndex, gid);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-        restarts = 0;
-      }
-    }
-  }
+			this.conn.commit();
+			this.conn.setAutoCommit(true);
 
-  /**
-   * Searches for fitting header values using the given regular expression.
-   * @param group
-   * @param start
-   * @param end
-   * @param headerKey
-   * @param pattern
-   * @return
-   * @throws StorageBackendException
-   */
-  @Override
-  public List<Pair<Long, String>> getArticleHeaders(Channel group, long start,
-    long end, String headerKey, String patStr)
-    throws StorageBackendException, PatternSyntaxException
-  {
-    ResultSet rs = null;
-    List<Pair<Long, String>> heads = new ArrayList<Pair<Long, String>>();
+			this.restarts = 0; // Reset error count
+		} catch (SQLException ex) {
+			try {
+				this.conn.rollback();  // Rollback changes
+			} catch (SQLException ex2) {
+				Log.get().severe("Rollback of addArticle() failed: " + ex2);
+			}
 
-    try
-    {
-      this.pstmtGetArticleHeaders1.setString(1, group.getName());
-      this.pstmtGetArticleHeaders1.setString(2, headerKey);
-      this.pstmtGetArticleHeaders1.setLong(3, start);
+			try {
+				this.conn.setAutoCommit(true); // and release locks
+			} catch (SQLException ex2) {
+				Log.get().severe("setAutoCommit(true) of addArticle() failed: " + ex2);
+			}
 
-      rs = this.pstmtGetArticleHeaders1.executeQuery();
+			restartConnection(ex);
+			addArticle(article);
+		}
+	}
 
-      // Convert the "NNTP" regex to Java regex
-      patStr = patStr.replace("*", ".*");
-      Pattern pattern = Pattern.compile(patStr);
+	/**
+	 * Adds a group to the JDBCDatabase. This method is not accessible via NNTP.
+	 * @param name
+	 * @throws java.sql.SQLException
+	 */
+	@Override
+	public void addGroup(String name, int flags)
+		throws StorageBackendException
+	{
+		try {
+			this.conn.setAutoCommit(false);
+			pstmtAddGroup0.setString(1, name);
+			pstmtAddGroup0.setInt(2, flags);
 
-      while(rs.next())
-      {
-        Long articleIndex = rs.getLong(1);
-        if(end < 0 || articleIndex <= end) // Match start is done via SQL
-        {
-          String headerValue  = rs.getString(2);
-          Matcher matcher = pattern.matcher(headerValue);
-          if(matcher.matches())
-          {
-            heads.add(new Pair<Long, String>(articleIndex, headerValue));
-          }
-        }
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getArticleHeaders(group, start, end, headerKey, patStr);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
+			pstmtAddGroup0.executeUpdate();
+			this.conn.commit();
+			this.conn.setAutoCommit(true);
+			this.restarts = 0; // Reset error count
+		} catch (SQLException ex) {
+			try {
+				this.conn.rollback();
+				this.conn.setAutoCommit(true);
+			} catch (SQLException ex2) {
+				ex2.printStackTrace();
+			}
 
-    return heads;
-  }
+			restartConnection(ex);
+			addGroup(name, flags);
+		}
+	}
 
-  private String getArticleHeaders(long articleID)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
-    
-    try
-    {
-      this.pstmtGetArticleHeaders0.setLong(1, articleID);
-      rs = this.pstmtGetArticleHeaders0.executeQuery();
-      
-      StringBuilder buf = new StringBuilder();
-      if(rs.next())
-      {
-        for(;;)
-        {
-          buf.append(rs.getString(1)); // key
-          buf.append(": ");
-          String foldedValue = MimeUtility.fold(0, rs.getString(2));
-          buf.append(foldedValue); // value
-          if(rs.next())
-          {
-            buf.append("\r\n");
-          }
-          else
-          {
-            break;
-          }
-        }
-      }
-      
-      return buf.toString();
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getArticleHeaders(articleID);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
+	@Override
+	public void addEvent(long time, int type, long gid)
+		throws StorageBackendException
+	{
+		try {
+			this.conn.setAutoCommit(false);
+			this.pstmtAddEvent.setLong(1, time);
+			this.pstmtAddEvent.setInt(2, type);
+			this.pstmtAddEvent.setLong(3, gid);
+			this.pstmtAddEvent.executeUpdate();
+			this.conn.commit();
+			this.conn.setAutoCommit(true);
+			this.restarts = 0;
+		} catch (SQLException ex) {
+			try {
+				this.conn.rollback();
+				this.conn.setAutoCommit(true);
+			} catch (SQLException ex2) {
+				ex2.printStackTrace();
+			}
 
-  @Override
-  public long getArticleIndex(Article article, Group group)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
+			restartConnection(ex);
+			addEvent(time, type, gid);
+		}
+	}
 
-    try
-    {
-      this.pstmtGetArticleIndex.setString(1, article.getMessageID());
-      this.pstmtGetArticleIndex.setLong(2, group.getInternalID());
-      
-      rs = this.pstmtGetArticleIndex.executeQuery();
-      if(rs.next())
-      {
-        return rs.getLong(1);
-      }
-      else
-      {
-        return -1;
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getArticleIndex(article, group);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
-  
-  /**
-   * Returns a list of Long/Article Pairs.
-   * @throws java.sql.SQLException
-   */
-  @Override
-  public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first,
-    long last)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
+	@Override
+	public int countArticles()
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
 
-    try
-    {
-      this.pstmtGetArticleHeads.setLong(1, group.getInternalID());
-      this.pstmtGetArticleHeads.setLong(2, first);
-      this.pstmtGetArticleHeads.setLong(3, last);
-      rs = pstmtGetArticleHeads.executeQuery();
+		try {
+			rs = this.pstmtCountArticles.executeQuery();
+			if (rs.next()) {
+				return rs.getInt(1);
+			} else {
+				return -1;
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return countArticles();
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+				restarts = 0;
+			}
+		}
+	}
 
-      List<Pair<Long, ArticleHead>> articles 
-        = new ArrayList<Pair<Long, ArticleHead>>();
+	@Override
+	public int countGroups()
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
 
-      while (rs.next())
-      {
-        long aid  = rs.getLong("article_id");
-        long aidx = rs.getLong("article_index");
-        String headers = getArticleHeaders(aid);
-        articles.add(new Pair<Long, ArticleHead>(aidx, 
-                        new ArticleHead(headers)));
-      }
+		try {
+			rs = this.pstmtCountGroups.executeQuery();
+			if (rs.next()) {
+				return rs.getInt(1);
+			} else {
+				return -1;
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return countGroups();
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+				restarts = 0;
+			}
+		}
+	}
 
-      return articles;
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getArticleHeads(group, first, last);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
+	@Override
+	public void delete(final String messageID)
+		throws StorageBackendException
+	{
+		try {
+			this.conn.setAutoCommit(false);
 
-  @Override
-  public List<Long> getArticleNumbers(long gid)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
-    try
-    {
-      List<Long> ids = new ArrayList<Long>();
-      this.pstmtGetArticleIDs.setLong(1, gid);
-      rs = this.pstmtGetArticleIDs.executeQuery();
-      while(rs.next())
-      {
-        ids.add(rs.getLong(1));
-      }
-      return ids;
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getArticleNumbers(gid);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-          restarts = 0; // Clear the restart count after successful request
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
+			this.pstmtDeleteArticle0.setString(1, messageID);
+			int rs = this.pstmtDeleteArticle0.executeUpdate();
 
-  @Override
-  public String getConfigValue(String key)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
-    try
-    {
-      this.pstmtGetConfigValue.setString(1, key);
+			// We do not trust the ON DELETE CASCADE functionality to delete
+			// orphaned references...
+			this.pstmtDeleteArticle1.setString(1, messageID);
+			rs = this.pstmtDeleteArticle1.executeUpdate();
 
-      rs = this.pstmtGetConfigValue.executeQuery();
-      if(rs.next())
-      {
-        return rs.getString(1); // First data on index 1 not 0
-      }
-      else
-      {
-        return null;
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getConfigValue(key);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-        restarts = 0; // Clear the restart count after successful request
-      }
-    }
-  }
+			this.pstmtDeleteArticle2.setString(1, messageID);
+			rs = this.pstmtDeleteArticle2.executeUpdate();
 
-  @Override
-  public int getEventsCount(int type, long start, long end, Channel channel)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
-    
-    try
-    {
-      if(channel == null)
-      {
-        this.pstmtGetEventsCount0.setInt(1, type);
-        this.pstmtGetEventsCount0.setLong(2, start);
-        this.pstmtGetEventsCount0.setLong(3, end);
-        rs = this.pstmtGetEventsCount0.executeQuery();
-      }
-      else
-      {
-        this.pstmtGetEventsCount1.setInt(1, type);
-        this.pstmtGetEventsCount1.setLong(2, start);
-        this.pstmtGetEventsCount1.setLong(3, end);
-        this.pstmtGetEventsCount1.setLong(4, channel.getInternalID());
-        rs = this.pstmtGetEventsCount1.executeQuery();
-      }
-      
-      if(rs.next())
-      {
-        return rs.getInt(1);
-      }
-      else
-      {
-        return -1;
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getEventsCount(type, start, end, channel);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
-  
-  /**
-   * Reads all Groups from the JDBCDatabase.
-   * @return
-   * @throws StorageBackendException
-   */
-  @Override
-  public List<Channel> getGroups()
-    throws StorageBackendException
-  {
-    ResultSet   rs;
-    List<Channel> buffer = new ArrayList<Channel>();
-    Statement   stmt   = null;
+			this.pstmtDeleteArticle3.setString(1, messageID);
+			rs = this.pstmtDeleteArticle3.executeUpdate();
 
-    try
-    {
-      stmt = conn.createStatement();
-      rs = stmt.executeQuery("SELECT * FROM groups ORDER BY name");
+			this.conn.commit();
+			this.conn.setAutoCommit(true);
+		} catch (SQLException ex) {
+			throw new StorageBackendException(ex);
+		}
+	}
 
-      while(rs.next())
-      {
-        String name  = rs.getString("name");
-        long   id    = rs.getLong("group_id");
-        int    flags = rs.getInt("flags");
-        
-        Group group = new Group(name, id, flags);
-        buffer.add(group);
-      }
+	@Override
+	public Article getArticle(String messageID)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+		try {
+			pstmtGetArticle0.setString(1, messageID);
+			rs = pstmtGetArticle0.executeQuery();
 
-      return buffer;
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getGroups();
-    }
-    finally
-    {
-      if(stmt != null)
-      {
-        try
-        {
-          stmt.close(); // Implicitely closes ResultSets
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
+			if (!rs.next()) {
+				return null;
+			} else {
+				byte[] body = rs.getBytes("body");
+				String headers = getArticleHeaders(rs.getInt("article_id"));
+				return new Article(headers, body);
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getArticle(messageID);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+				restarts = 0; // Reset error count
+			}
+		}
+	}
 
-  @Override
-  public List<String> getGroupsForList(String listAddress)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
-    
-    try
-    {
-      this.pstmtGetGroupForList.setString(1, listAddress);
+	/**
+	 * Retrieves an article by its ID.
+	 * @param articleID
+	 * @return
+	 * @throws StorageBackendException
+	 */
+	@Override
+	public Article getArticle(long articleIndex, long gid)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
 
-      rs = this.pstmtGetGroupForList.executeQuery();
-      List<String> groups = new ArrayList<String>();
-      while(rs.next())
-      {
-        String group = rs.getString(1);
-        groups.add(group);
-      }
-      return groups;
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getGroupsForList(listAddress);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
-  
-  /**
-   * Returns the Group that is identified by the name.
-   * @param name
-   * @return
-   * @throws StorageBackendException
-   */
-  @Override
-  public Group getGroup(String name)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
-    
-    try
-    {
-      this.pstmtGetGroup0.setString(1, name);
-      rs = this.pstmtGetGroup0.executeQuery();
+		try {
+			this.pstmtGetArticle1.setLong(1, articleIndex);
+			this.pstmtGetArticle1.setLong(2, gid);
 
-      if (!rs.next())
-      {
-        return null;
-      }
-      else
-      {
-        long id = rs.getLong("group_id");
-        int flags = rs.getInt("flags");
-        return new Group(name, id, flags);
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getGroup(name);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
+			rs = this.pstmtGetArticle1.executeQuery();
 
-  @Override
-  public List<String> getListsForGroup(String group)
-    throws StorageBackendException
-  {
-    ResultSet     rs    = null;
-    List<String>  lists = new ArrayList<String>();
+			if (rs.next()) {
+				byte[] body = rs.getBytes("body");
+				String headers = getArticleHeaders(rs.getInt("article_id"));
+				return new Article(headers, body);
+			} else {
+				return null;
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getArticle(articleIndex, gid);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+				restarts = 0;
+			}
+		}
+	}
 
-    try
-    {
-      this.pstmtGetListForGroup.setString(1, group);
-      rs = this.pstmtGetListForGroup.executeQuery();
+	/**
+	 * Searches for fitting header values using the given regular expression.
+	 * @param group
+	 * @param start
+	 * @param end
+	 * @param headerKey
+	 * @param pattern
+	 * @return
+	 * @throws StorageBackendException
+	 */
+	@Override
+	public List<Pair<Long, String>> getArticleHeaders(Channel group, long start,
+		long end, String headerKey, String patStr)
+		throws StorageBackendException, PatternSyntaxException
+	{
+		ResultSet rs = null;
+		List<Pair<Long, String>> heads = new ArrayList<Pair<Long, String>>();
 
-      while(rs.next())
-      {
-        lists.add(rs.getString(1));
-      }
-      return lists;
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getListsForGroup(group);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
-  
-  private int getMaxArticleIndex(long groupID)
-    throws StorageBackendException
-  {
-    ResultSet rs    = null;
+		try {
+			this.pstmtGetArticleHeaders1.setString(1, group.getName());
+			this.pstmtGetArticleHeaders1.setString(2, headerKey);
+			this.pstmtGetArticleHeaders1.setLong(3, start);
 
-    try
-    {
-      this.pstmtGetMaxArticleIndex.setLong(1, groupID);
-      rs = this.pstmtGetMaxArticleIndex.executeQuery();
+			rs = this.pstmtGetArticleHeaders1.executeQuery();
 
-      int maxIndex = 0;
-      if (rs.next())
-      {
-        maxIndex = rs.getInt(1);
-      }
+			// Convert the "NNTP" regex to Java regex
+			patStr = patStr.replace("*", ".*");
+			Pattern pattern = Pattern.compile(patStr);
 
-      return maxIndex;
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getMaxArticleIndex(groupID);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
-  
-  private int getMaxArticleID()
-    throws StorageBackendException
-  {
-    ResultSet rs    = null;
+			while (rs.next()) {
+				Long articleIndex = rs.getLong(1);
+				if (end < 0 || articleIndex <= end) // Match start is done via SQL
+				{
+					String headerValue = rs.getString(2);
+					Matcher matcher = pattern.matcher(headerValue);
+					if (matcher.matches()) {
+						heads.add(new Pair<Long, String>(articleIndex, headerValue));
+					}
+				}
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getArticleHeaders(group, start, end, headerKey, patStr);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
 
-    try
-    {
-      rs = this.pstmtGetMaxArticleID.executeQuery();
+		return heads;
+	}
 
-      int maxIndex = 0;
-      if (rs.next())
-      {
-        maxIndex = rs.getInt(1);
-      }
+	private String getArticleHeaders(long articleID)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
 
-      return maxIndex;
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getMaxArticleID();
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
+		try {
+			this.pstmtGetArticleHeaders0.setLong(1, articleID);
+			rs = this.pstmtGetArticleHeaders0.executeQuery();
 
-  @Override
-  public int getLastArticleNumber(Group group)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
+			StringBuilder buf = new StringBuilder();
+			if (rs.next()) {
+				for (;;) {
+					buf.append(rs.getString(1)); // key
+					buf.append(": ");
+					String foldedValue = MimeUtility.fold(0, rs.getString(2));
+					buf.append(foldedValue); // value
+					if (rs.next()) {
+						buf.append("\r\n");
+					} else {
+						break;
+					}
+				}
+			}
 
-    try
-    {
-      this.pstmtGetLastArticleNumber.setLong(1, group.getInternalID());
-      rs = this.pstmtGetLastArticleNumber.executeQuery();
-      if (rs.next())
-      {
-        return rs.getInt(1);
-      }
-      else
-      {
-        return 0;
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getLastArticleNumber(group);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
+			return buf.toString();
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getArticleHeaders(articleID);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
 
-  @Override
-  public int getFirstArticleNumber(Group group)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
-    try
-    {
-      this.pstmtGetFirstArticleNumber.setLong(1, group.getInternalID());
-      rs = this.pstmtGetFirstArticleNumber.executeQuery();
-      if(rs.next())
-      {
-        return rs.getInt(1);
-      }
-      else
-      {
-        return 0;
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getFirstArticleNumber(group);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
-  
-  /**
-   * Returns a group name identified by the given id.
-   * @param id
-   * @return
-   * @throws StorageBackendException
-   */
-  public String getGroup(int id)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
+	@Override
+	public long getArticleIndex(Article article, Group group)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
 
-    try
-    {
-      this.pstmtGetGroup1.setInt(1, id);
-      rs = this.pstmtGetGroup1.executeQuery();
+		try {
+			this.pstmtGetArticleIndex.setString(1, article.getMessageID());
+			this.pstmtGetArticleIndex.setLong(2, group.getInternalID());
 
-      if (rs.next())
-      {
-        return rs.getString(1);
-      }
-      else
-      {
-        return null;
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getGroup(id);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
+			rs = this.pstmtGetArticleIndex.executeQuery();
+			if (rs.next()) {
+				return rs.getLong(1);
+			} else {
+				return -1;
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getArticleIndex(article, group);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
 
-  @Override
-  public double getEventsPerHour(int key, long gid)
-    throws StorageBackendException
-  {
-    String gidquery = "";
-    if(gid >= 0)
-    {
-      gidquery = " AND group_id = " + gid;
-    }
-    
-    Statement stmt = null;
-    ResultSet rs   = null;
-    
-    try
-    {
-      stmt = this.conn.createStatement();
-      rs = stmt.executeQuery("SELECT Count(*) / (Max(event_time) - Min(event_time))" +
-        " * 1000 * 60 * 60 FROM events WHERE event_key = " + key + gidquery);
-      
-      if(rs.next())
-      {
-        restarts = 0; // reset error count
-        return rs.getDouble(1);
-      }
-      else
-      {
-        return Double.NaN;
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getEventsPerHour(key, gid);
-    }
-    finally
-    {
-      try
-      {
-        if(stmt != null)
-        {
-          stmt.close(); // Implicitely closes the result sets
-        }
-      }
-      catch(SQLException ex)
-      {
-        ex.printStackTrace();
-      }
-    }
-  }
+	/**
+	 * Returns a list of Long/Article Pairs.
+	 * @throws java.sql.SQLException
+	 */
+	@Override
+	public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first,
+		long last)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
 
-  @Override
-  public String getOldestArticle()
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
+		try {
+			this.pstmtGetArticleHeads.setLong(1, group.getInternalID());
+			this.pstmtGetArticleHeads.setLong(2, first);
+			this.pstmtGetArticleHeads.setLong(3, last);
+			rs = pstmtGetArticleHeads.executeQuery();
 
-    try
-    {
-      rs = this.pstmtGetOldestArticle.executeQuery();
-      if(rs.next())
-      {
-        return rs.getString(1);
-      }
-      else
-      {
-        return null;
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getOldestArticle();
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
+			List<Pair<Long, ArticleHead>> articles = new ArrayList<Pair<Long, ArticleHead>>();
 
-  @Override
-  public int getPostingsCount(String groupname)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
-    
-    try
-    {
-      this.pstmtGetPostingsCount.setString(1, groupname);
-      rs = this.pstmtGetPostingsCount.executeQuery();
-      if(rs.next())
-      {
-        return rs.getInt(1);
-      }
-      else
-      {
-        Log.get().warning("Count on postings return nothing!");
-        return 0;
-      }
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getPostingsCount(groupname);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
+			while (rs.next()) {
+				long aid = rs.getLong("article_id");
+				long aidx = rs.getLong("article_index");
+				String headers = getArticleHeaders(aid);
+				articles.add(new Pair<Long, ArticleHead>(aidx,
+					new ArticleHead(headers)));
+			}
 
-  @Override
-  public List<Subscription> getSubscriptions(int feedtype)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
-    
-    try
-    {
-      List<Subscription> subs = new ArrayList<Subscription>();
-      this.pstmtGetSubscriptions.setInt(1, feedtype);
-      rs = this.pstmtGetSubscriptions.executeQuery();
-      
-      while(rs.next())
-      {
-        String host  = rs.getString("host");
-        String group = rs.getString("name");
-        int    port  = rs.getInt("port");
-        subs.add(new Subscription(host, port, feedtype, group));
-      }
-      
-      return subs;
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return getSubscriptions(feedtype);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
+			return articles;
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getArticleHeads(group, first, last);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
 
-  /**
-   * Checks if there is an article with the given messageid in the JDBCDatabase.
-   * @param name
-   * @return
-   * @throws StorageBackendException
-   */
-  @Override
-  public boolean isArticleExisting(String messageID)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
-    
-    try
-    {
-      this.pstmtIsArticleExisting.setString(1, messageID);
-      rs = this.pstmtIsArticleExisting.executeQuery();
-      return rs.next() && rs.getInt(1) == 1;
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return isArticleExisting(messageID);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
-  
-  /**
-   * Checks if there is a group with the given name in the JDBCDatabase.
-   * @param name
-   * @return
-   * @throws StorageBackendException
-   */
-  @Override
-  public boolean isGroupExisting(String name)
-    throws StorageBackendException
-  {
-    ResultSet rs = null;
-    
-    try
-    {
-      this.pstmtIsGroupExisting.setString(1, name);
-      rs = this.pstmtIsGroupExisting.executeQuery();
-      return rs.next();
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return isGroupExisting(name);
-    }
-    finally
-    {
-      if(rs != null)
-      {
-        try
-        {
-          rs.close();
-        }
-        catch(SQLException ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-    }
-  }
+	@Override
+	public List<Long> getArticleNumbers(long gid)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+		try {
+			List<Long> ids = new ArrayList<Long>();
+			this.pstmtGetArticleIDs.setLong(1, gid);
+			rs = this.pstmtGetArticleIDs.executeQuery();
+			while (rs.next()) {
+				ids.add(rs.getLong(1));
+			}
+			return ids;
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getArticleNumbers(gid);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+					restarts = 0; // Clear the restart count after successful request
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
 
-  @Override
-  public void setConfigValue(String key, String value)
-    throws StorageBackendException
-  {
-    try
-    {
-      conn.setAutoCommit(false);
-      this.pstmtSetConfigValue0.setString(1, key);
-      this.pstmtSetConfigValue0.execute();
-      this.pstmtSetConfigValue1.setString(1, key);
-      this.pstmtSetConfigValue1.setString(2, value);
-      this.pstmtSetConfigValue1.execute();
-      conn.commit();
-      conn.setAutoCommit(true);
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      setConfigValue(key, value);
-    }
-  }
-  
-  /**
-   * Closes the JDBCDatabase connection.
-   */
-  public void shutdown()
-    throws StorageBackendException
-  {
-    try
-    {
-      if(this.conn != null)
-      {
-        this.conn.close();
-      }
-    }
-    catch(SQLException ex)
-    {
-      throw new StorageBackendException(ex);
-    }
-  }
+	@Override
+	public String getConfigValue(String key)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+		try {
+			this.pstmtGetConfigValue.setString(1, key);
 
-  @Override
-  public void purgeGroup(Group group)
-    throws StorageBackendException
-  {
-    try
-    {
-      this.pstmtPurgeGroup0.setLong(1, group.getInternalID());
-      this.pstmtPurgeGroup0.executeUpdate();
+			rs = this.pstmtGetConfigValue.executeQuery();
+			if (rs.next()) {
+				return rs.getString(1); // First data on index 1 not 0
+			} else {
+				return null;
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getConfigValue(key);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+				restarts = 0; // Clear the restart count after successful request
+			}
+		}
+	}
 
-      this.pstmtPurgeGroup1.setLong(1, group.getInternalID());
-      this.pstmtPurgeGroup1.executeUpdate();
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      purgeGroup(group);
-    }
-  }
-  
-  private void restartConnection(SQLException cause)
-    throws StorageBackendException
-  {
-    restarts++;
-    Log.get().severe(Thread.currentThread()
-      + ": Database connection was closed (restart " + restarts + ").");
-    
-    if(restarts >= MAX_RESTARTS)
-    {
-      // Delete the current, probably broken JDBCDatabase instance.
-      // So no one can use the instance any more.
-      JDBCDatabaseProvider.instances.remove(Thread.currentThread());
-      
-      // Throw the exception upwards
-      throw new StorageBackendException(cause);
-    }
-    
-    try
-    {
-      Thread.sleep(1500L * restarts);
-    }
-    catch(InterruptedException ex)
-    {
-      Log.get().warning("Interrupted: " + ex.getMessage());
-    }
-    
-    // Try to properly close the old database connection
-    try
-    {
-      if(this.conn != null)
-      {
-        this.conn.close();
-      }
-    }
-    catch(SQLException ex)
-    {
-      Log.get().warning(ex.getMessage());
-    }
-    
-    try
-    {
-      // Try to reinitialize database connection
-      arise();
-    }
-    catch(SQLException ex)
-    {
-      Log.get().warning(ex.getMessage());
-      restartConnection(ex);
-    }
-  }
+	@Override
+	public int getEventsCount(int type, long start, long end, Channel channel)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
 
-  @Override
-  public boolean update(Article article)
-    throws StorageBackendException
-  {
-    // DELETE FROM headers WHERE article_id = ?
+		try {
+			if (channel == null) {
+				this.pstmtGetEventsCount0.setInt(1, type);
+				this.pstmtGetEventsCount0.setLong(2, start);
+				this.pstmtGetEventsCount0.setLong(3, end);
+				rs = this.pstmtGetEventsCount0.executeQuery();
+			} else {
+				this.pstmtGetEventsCount1.setInt(1, type);
+				this.pstmtGetEventsCount1.setLong(2, start);
+				this.pstmtGetEventsCount1.setLong(3, end);
+				this.pstmtGetEventsCount1.setLong(4, channel.getInternalID());
+				rs = this.pstmtGetEventsCount1.executeQuery();
+			}
 
-    // INSERT INTO headers ...
+			if (rs.next()) {
+				return rs.getInt(1);
+			} else {
+				return -1;
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getEventsCount(type, start, end, channel);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
 
-    // SELECT * FROM postings WHERE article_id = ? AND group_id = ?
-    return false;
-  }
+	/**
+	 * Reads all Groups from the JDBCDatabase.
+	 * @return
+	 * @throws StorageBackendException
+	 */
+	@Override
+	public List<Channel> getGroups()
+		throws StorageBackendException
+	{
+		ResultSet rs;
+		List<Channel> buffer = new ArrayList<Channel>();
+		Statement stmt = null;
 
-  /**
-   * Writes the flags and the name of the given group to the database.
-   * @param group
-   * @throws StorageBackendException
-   */
-  @Override
-  public boolean update(Group group)
-    throws StorageBackendException
-  {
-    try
-    {
-      this.pstmtUpdateGroup.setInt(1, group.getFlags());
-      this.pstmtUpdateGroup.setString(2, group.getName());
-      this.pstmtUpdateGroup.setLong(3, group.getInternalID());
-      int rs = this.pstmtUpdateGroup.executeUpdate();
-      return rs == 1;
-    }
-    catch(SQLException ex)
-    {
-      restartConnection(ex);
-      return update(group);
-    }
-  }
+		try {
+			stmt = conn.createStatement();
+			rs = stmt.executeQuery("SELECT * FROM groups ORDER BY name");
 
+			while (rs.next()) {
+				String name = rs.getString("name");
+				long id = rs.getLong("group_id");
+				int flags = rs.getInt("flags");
+
+				Group group = new Group(name, id, flags);
+				buffer.add(group);
+			}
+
+			return buffer;
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getGroups();
+		} finally {
+			if (stmt != null) {
+				try {
+					stmt.close(); // Implicitely closes ResultSets
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
+
+	@Override
+	public List<String> getGroupsForList(String listAddress)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+
+		try {
+			this.pstmtGetGroupForList.setString(1, listAddress);
+
+			rs = this.pstmtGetGroupForList.executeQuery();
+			List<String> groups = new ArrayList<String>();
+			while (rs.next()) {
+				String group = rs.getString(1);
+				groups.add(group);
+			}
+			return groups;
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getGroupsForList(listAddress);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
+
+	/**
+	 * Returns the Group that is identified by the name.
+	 * @param name
+	 * @return
+	 * @throws StorageBackendException
+	 */
+	@Override
+	public Group getGroup(String name)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+
+		try {
+			this.pstmtGetGroup0.setString(1, name);
+			rs = this.pstmtGetGroup0.executeQuery();
+
+			if (!rs.next()) {
+				return null;
+			} else {
+				long id = rs.getLong("group_id");
+				int flags = rs.getInt("flags");
+				return new Group(name, id, flags);
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getGroup(name);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
+
+	@Override
+	public List<String> getListsForGroup(String group)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+		List<String> lists = new ArrayList<String>();
+
+		try {
+			this.pstmtGetListForGroup.setString(1, group);
+			rs = this.pstmtGetListForGroup.executeQuery();
+
+			while (rs.next()) {
+				lists.add(rs.getString(1));
+			}
+			return lists;
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getListsForGroup(group);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
+
+	private int getMaxArticleIndex(long groupID)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+
+		try {
+			this.pstmtGetMaxArticleIndex.setLong(1, groupID);
+			rs = this.pstmtGetMaxArticleIndex.executeQuery();
+
+			int maxIndex = 0;
+			if (rs.next()) {
+				maxIndex = rs.getInt(1);
+			}
+
+			return maxIndex;
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getMaxArticleIndex(groupID);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
+
+	private int getMaxArticleID()
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+
+		try {
+			rs = this.pstmtGetMaxArticleID.executeQuery();
+
+			int maxIndex = 0;
+			if (rs.next()) {
+				maxIndex = rs.getInt(1);
+			}
+
+			return maxIndex;
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getMaxArticleID();
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
+
+	@Override
+	public int getLastArticleNumber(Group group)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+
+		try {
+			this.pstmtGetLastArticleNumber.setLong(1, group.getInternalID());
+			rs = this.pstmtGetLastArticleNumber.executeQuery();
+			if (rs.next()) {
+				return rs.getInt(1);
+			} else {
+				return 0;
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getLastArticleNumber(group);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
+
+	@Override
+	public int getFirstArticleNumber(Group group)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+		try {
+			this.pstmtGetFirstArticleNumber.setLong(1, group.getInternalID());
+			rs = this.pstmtGetFirstArticleNumber.executeQuery();
+			if (rs.next()) {
+				return rs.getInt(1);
+			} else {
+				return 0;
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getFirstArticleNumber(group);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
+
+	/**
+	 * Returns a group name identified by the given id.
+	 * @param id
+	 * @return
+	 * @throws StorageBackendException
+	 */
+	public String getGroup(int id)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+
+		try {
+			this.pstmtGetGroup1.setInt(1, id);
+			rs = this.pstmtGetGroup1.executeQuery();
+
+			if (rs.next()) {
+				return rs.getString(1);
+			} else {
+				return null;
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getGroup(id);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
+
+	@Override
+	public double getEventsPerHour(int key, long gid)
+		throws StorageBackendException
+	{
+		String gidquery = "";
+		if (gid >= 0) {
+			gidquery = " AND group_id = " + gid;
+		}
+
+		Statement stmt = null;
+		ResultSet rs = null;
+
+		try {
+			stmt = this.conn.createStatement();
+			rs = stmt.executeQuery("SELECT Count(*) / (Max(event_time) - Min(event_time))"
+				+ " * 1000 * 60 * 60 FROM events WHERE event_key = " + key + gidquery);
+
+			if (rs.next()) {
+				restarts = 0; // reset error count
+				return rs.getDouble(1);
+			} else {
+				return Double.NaN;
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getEventsPerHour(key, gid);
+		} finally {
+			try {
+				if (stmt != null) {
+					stmt.close(); // Implicitely closes the result sets
+				}
+			} catch (SQLException ex) {
+				ex.printStackTrace();
+			}
+		}
+	}
+
+	@Override
+	public String getOldestArticle()
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+
+		try {
+			rs = this.pstmtGetOldestArticle.executeQuery();
+			if (rs.next()) {
+				return rs.getString(1);
+			} else {
+				return null;
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getOldestArticle();
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
+
+	@Override
+	public int getPostingsCount(String groupname)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+
+		try {
+			this.pstmtGetPostingsCount.setString(1, groupname);
+			rs = this.pstmtGetPostingsCount.executeQuery();
+			if (rs.next()) {
+				return rs.getInt(1);
+			} else {
+				Log.get().warning("Count on postings return nothing!");
+				return 0;
+			}
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getPostingsCount(groupname);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
+
+	@Override
+	public List<Subscription> getSubscriptions(int feedtype)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+
+		try {
+			List<Subscription> subs = new ArrayList<Subscription>();
+			this.pstmtGetSubscriptions.setInt(1, feedtype);
+			rs = this.pstmtGetSubscriptions.executeQuery();
+
+			while (rs.next()) {
+				String host = rs.getString("host");
+				String group = rs.getString("name");
+				int port = rs.getInt("port");
+				subs.add(new Subscription(host, port, feedtype, group));
+			}
+
+			return subs;
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return getSubscriptions(feedtype);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
+
+	/**
+	 * Checks if there is an article with the given messageid in the JDBCDatabase.
+	 * @param name
+	 * @return
+	 * @throws StorageBackendException
+	 */
+	@Override
+	public boolean isArticleExisting(String messageID)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+
+		try {
+			this.pstmtIsArticleExisting.setString(1, messageID);
+			rs = this.pstmtIsArticleExisting.executeQuery();
+			return rs.next() && rs.getInt(1) == 1;
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return isArticleExisting(messageID);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
+
+	/**
+	 * Checks if there is a group with the given name in the JDBCDatabase.
+	 * @param name
+	 * @return
+	 * @throws StorageBackendException
+	 */
+	@Override
+	public boolean isGroupExisting(String name)
+		throws StorageBackendException
+	{
+		ResultSet rs = null;
+
+		try {
+			this.pstmtIsGroupExisting.setString(1, name);
+			rs = this.pstmtIsGroupExisting.executeQuery();
+			return rs.next();
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return isGroupExisting(name);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException ex) {
+					ex.printStackTrace();
+				}
+			}
+		}
+	}
+
+	@Override
+	public void setConfigValue(String key, String value)
+		throws StorageBackendException
+	{
+		try {
+			conn.setAutoCommit(false);
+			this.pstmtSetConfigValue0.setString(1, key);
+			this.pstmtSetConfigValue0.execute();
+			this.pstmtSetConfigValue1.setString(1, key);
+			this.pstmtSetConfigValue1.setString(2, value);
+			this.pstmtSetConfigValue1.execute();
+			conn.commit();
+			conn.setAutoCommit(true);
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			setConfigValue(key, value);
+		}
+	}
+
+	/**
+	 * Closes the JDBCDatabase connection.
+	 */
+	public void shutdown()
+		throws StorageBackendException
+	{
+		try {
+			if (this.conn != null) {
+				this.conn.close();
+			}
+		} catch (SQLException ex) {
+			throw new StorageBackendException(ex);
+		}
+	}
+
+	@Override
+	public void purgeGroup(Group group)
+		throws StorageBackendException
+	{
+		try {
+			this.pstmtPurgeGroup0.setLong(1, group.getInternalID());
+			this.pstmtPurgeGroup0.executeUpdate();
+
+			this.pstmtPurgeGroup1.setLong(1, group.getInternalID());
+			this.pstmtPurgeGroup1.executeUpdate();
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			purgeGroup(group);
+		}
+	}
+
+	private void restartConnection(SQLException cause)
+		throws StorageBackendException
+	{
+		restarts++;
+		Log.get().severe(Thread.currentThread()
+			+ ": Database connection was closed (restart " + restarts + ").");
+
+		if (restarts >= MAX_RESTARTS) {
+			// Delete the current, probably broken JDBCDatabase instance.
+			// So no one can use the instance any more.
+			JDBCDatabaseProvider.instances.remove(Thread.currentThread());
+
+			// Throw the exception upwards
+			throw new StorageBackendException(cause);
+		}
+
+		try {
+			Thread.sleep(1500L * restarts);
+		} catch (InterruptedException ex) {
+			Log.get().warning("Interrupted: " + ex.getMessage());
+		}
+
+		// Try to properly close the old database connection
+		try {
+			if (this.conn != null) {
+				this.conn.close();
+			}
+		} catch (SQLException ex) {
+			Log.get().warning(ex.getMessage());
+		}
+
+		try {
+			// Try to reinitialize database connection
+			arise();
+		} catch (SQLException ex) {
+			Log.get().warning(ex.getMessage());
+			restartConnection(ex);
+		}
+	}
+
+	@Override
+	public boolean update(Article article)
+		throws StorageBackendException
+	{
+		// DELETE FROM headers WHERE article_id = ?
+
+		// INSERT INTO headers ...
+
+		// SELECT * FROM postings WHERE article_id = ? AND group_id = ?
+		return false;
+	}
+
+	/**
+	 * Writes the flags and the name of the given group to the database.
+	 * @param group
+	 * @throws StorageBackendException
+	 */
+	@Override
+	public boolean update(Group group)
+		throws StorageBackendException
+	{
+		try {
+			this.pstmtUpdateGroup.setInt(1, group.getFlags());
+			this.pstmtUpdateGroup.setString(2, group.getName());
+			this.pstmtUpdateGroup.setLong(3, group.getInternalID());
+			int rs = this.pstmtUpdateGroup.executeUpdate();
+			return rs == 1;
+		} catch (SQLException ex) {
+			restartConnection(ex);
+			return update(group);
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/storage/impl/JDBCDatabaseProvider.java
--- a/src/org/sonews/storage/impl/JDBCDatabaseProvider.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/storage/impl/JDBCDatabaseProvider.java	Sun Aug 29 18:17:37 2010 +0200
@@ -33,37 +33,29 @@
 public class JDBCDatabaseProvider implements StorageProvider
 {
 
-  protected static final Map<Thread, JDBCDatabase> instances
-    = new ConcurrentHashMap<Thread, JDBCDatabase>();
+	protected static final Map<Thread, JDBCDatabase> instances = new ConcurrentHashMap<Thread, JDBCDatabase>();
 
-  @Override
-  public boolean isSupported(String uri)
-  {
-    throw new UnsupportedOperationException("Not supported yet.");
-  }
+	@Override
+	public boolean isSupported(String uri)
+	{
+		throw new UnsupportedOperationException("Not supported yet.");
+	}
 
-  @Override
-  public Storage storage(Thread thread)
-    throws StorageBackendException
-  {
-    try
-    {
-    if(!instances.containsKey(Thread.currentThread()))
-    {
-      JDBCDatabase db = new JDBCDatabase();
-      db.arise();
-      instances.put(Thread.currentThread(), db);
-      return db;
-    }
-    else
-    {
-      return instances.get(Thread.currentThread());
-    }
-    }
-    catch(SQLException ex)
-    {
-      throw new StorageBackendException(ex);
-    }
-  }
-
+	@Override
+	public Storage storage(Thread thread)
+		throws StorageBackendException
+	{
+		try {
+			if (!instances.containsKey(Thread.currentThread())) {
+				JDBCDatabase db = new JDBCDatabase();
+				db.arise();
+				instances.put(Thread.currentThread(), db);
+				return db;
+			} else {
+				return instances.get(Thread.currentThread());
+			}
+		} catch (SQLException ex) {
+			throw new StorageBackendException(ex);
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/util/DatabaseSetup.java
--- a/src/org/sonews/util/DatabaseSetup.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/util/DatabaseSetup.java	Sun Aug 29 18:17:37 2010 +0200
@@ -25,7 +25,6 @@
 import java.sql.Statement;
 import java.util.HashMap;
 import java.util.Map;
-import org.sonews.config.Config;
 import org.sonews.util.io.Resource;
 
 /**
@@ -33,95 +32,85 @@
  * @author Christian Lins
  * @since sonews/0.5.0
  */
-public final class DatabaseSetup 
+public final class DatabaseSetup
 {
 
-  private static final Map<String, String> templateMap 
-    = new HashMap<String, String>();
-  private static final Map<String, StringTemplate> urlMap
-    = new HashMap<String, StringTemplate>();
-  private static final Map<String, String> driverMap
-    = new HashMap<String, String>();
-  
-  static
-  {
-    templateMap.put("1", "helpers/database_mysql5_tmpl.sql");
-    templateMap.put("2", "helpers/database_postgresql8_tmpl.sql");
-    
-    urlMap.put("1", new StringTemplate("jdbc:mysql://%HOSTNAME/%DB"));
-    urlMap.put("2", new StringTemplate("jdbc:postgresql://%HOSTNAME/%DB"));
-    
-    driverMap.put("1", "com.mysql.jdbc.Driver");
-    driverMap.put("2", "org.postgresql.Driver");
-  }
-  
-  public static void main(String[] args)
-    throws Exception
-  {
-    System.out.println("sonews Database setup helper");
-    System.out.println("This program will create a initial database table structure");
-    System.out.println("for the sonews Newsserver.");
-    System.out.println("You need to create a database and a db user manually before!");
-    
-    System.out.println("Select DBMS type:");
-    System.out.println("[1] MySQL 5.x or higher");
-    System.out.println("[2] PostgreSQL 8.x or higher");
-    System.out.print("Your choice: ");
-    
-    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
-    String dbmsType = in.readLine();
-    String tmplName = templateMap.get(dbmsType);
-    if(tmplName == null)
-    {
-      System.err.println("Invalid choice. Try again you fool!");
-      main(args);
-      return;
-    }
-    
-    // Load JDBC Driver class
-    Class.forName(driverMap.get(dbmsType));
-    
-    String tmpl = Resource.getAsString(tmplName, true);
-    
-    System.out.print("Database server hostname (e.g. localhost): ");
-    String dbHostname = in.readLine();
-    
-    System.out.print("Database name: ");
-    String dbName = in.readLine();
+	private static final Map<String, String> templateMap = new HashMap<String, String>();
+	private static final Map<String, StringTemplate> urlMap = new HashMap<String, StringTemplate>();
+	private static final Map<String, String> driverMap = new HashMap<String, String>();
 
-    System.out.print("Give name of DB user that can create tables: ");
-    String dbUser = in.readLine();
+	static {
+		templateMap.put("1", "helpers/database_mysql5_tmpl.sql");
+		templateMap.put("2", "helpers/database_postgresql8_tmpl.sql");
 
-    System.out.print("Password: ");
-    String dbPassword = in.readLine();
-    
-    String url = urlMap.get(dbmsType)
-      .set("HOSTNAME", dbHostname)
-      .set("DB", dbName).toString();
-    
-    Connection conn = 
-      DriverManager.getConnection(url, dbUser, dbPassword);
-    conn.setAutoCommit(false);
-    
-    String[] tmplChunks = tmpl.split(";");
-    
-    for(String chunk : tmplChunks)
-    {
-      if(chunk.trim().equals(""))
-      {
-        continue;
-      }
-      
-      Statement stmt = conn.createStatement();
-      stmt.execute(chunk);
-    }
-    
-    conn.commit();
-    conn.setAutoCommit(true);
-    
-    // Create config file
-    
-    System.out.println("Ok");
-  }
-  
+		urlMap.put("1", new StringTemplate("jdbc:mysql://%HOSTNAME/%DB"));
+		urlMap.put("2", new StringTemplate("jdbc:postgresql://%HOSTNAME/%DB"));
+
+		driverMap.put("1", "com.mysql.jdbc.Driver");
+		driverMap.put("2", "org.postgresql.Driver");
+	}
+
+	public static void main(String[] args)
+		throws Exception
+	{
+		System.out.println("sonews Database setup helper");
+		System.out.println("This program will create a initial database table structure");
+		System.out.println("for the sonews Newsserver.");
+		System.out.println("You need to create a database and a db user manually before!");
+
+		System.out.println("Select DBMS type:");
+		System.out.println("[1] MySQL 5.x or higher");
+		System.out.println("[2] PostgreSQL 8.x or higher");
+		System.out.print("Your choice: ");
+
+		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
+		String dbmsType = in.readLine();
+		String tmplName = templateMap.get(dbmsType);
+		if (tmplName == null) {
+			System.err.println("Invalid choice. Try again you fool!");
+			main(args);
+			return;
+		}
+
+		// Load JDBC Driver class
+		Class.forName(driverMap.get(dbmsType));
+
+		String tmpl = Resource.getAsString(tmplName, true);
+
+		System.out.print("Database server hostname (e.g. localhost): ");
+		String dbHostname = in.readLine();
+
+		System.out.print("Database name: ");
+		String dbName = in.readLine();
+
+		System.out.print("Give name of DB user that can create tables: ");
+		String dbUser = in.readLine();
+
+		System.out.print("Password: ");
+		String dbPassword = in.readLine();
+
+		String url = urlMap.get(dbmsType).set("HOSTNAME", dbHostname).set("DB", dbName).toString();
+
+		Connection conn =
+			DriverManager.getConnection(url, dbUser, dbPassword);
+		conn.setAutoCommit(false);
+
+		String[] tmplChunks = tmpl.split(";");
+
+		for (String chunk : tmplChunks) {
+			if (chunk.trim().equals("")) {
+				continue;
+			}
+
+			Statement stmt = conn.createStatement();
+			stmt.execute(chunk);
+		}
+
+		conn.commit();
+		conn.setAutoCommit(true);
+
+		// Create config file
+
+		System.out.println("Ok");
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/util/Log.java
--- a/src/org/sonews/util/Log.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/util/Log.java	Sun Aug 29 18:17:37 2010 +0200
@@ -33,25 +33,24 @@
 public class Log extends Logger
 {
 
-  private static Log instance = new Log();
+	private static Log instance = new Log();
 
-  private Log()
-  {
-    super("org.sonews", null);
+	private Log()
+	{
+		super("org.sonews", null);
 
-    StreamHandler handler = new StreamHandler(System.out, new SimpleFormatter());
-    Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
-    handler.setLevel(level);
-    addHandler(handler);
-    setLevel(level);
-    LogManager.getLogManager().addLogger(this);
-  }
+		StreamHandler handler = new StreamHandler(System.out, new SimpleFormatter());
+		Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
+		handler.setLevel(level);
+		addHandler(handler);
+		setLevel(level);
+		LogManager.getLogManager().addLogger(this);
+	}
 
-  public static Logger get()
-  {
-    Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
-    instance.setLevel(level);
-    return instance;
-  }
-
+	public static Logger get()
+	{
+		Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
+		instance.setLevel(level);
+		return instance;
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/util/Pair.java
--- a/src/org/sonews/util/Pair.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/util/Pair.java	Sun Aug 29 18:17:37 2010 +0200
@@ -23,26 +23,25 @@
  * @author Christian Lins
  * @since sonews/0.5.0
  */
-public class Pair<T1, T2> 
+public class Pair<T1, T2>
 {
- 
-  private T1 a;
-  private T2 b;
-  
-  public Pair(T1 a, T2 b)
-  {
-    this.a = a;
-    this.b = b;
-  }
 
-  public T1 getA()
-  {
-    return a;
-  }
+	private T1 a;
+	private T2 b;
 
-  public T2 getB()
-  {
-    return b;
-  } 
- 
+	public Pair(T1 a, T2 b)
+	{
+		this.a = a;
+		this.b = b;
+	}
+
+	public T1 getA()
+	{
+		return a;
+	}
+
+	public T2 getB()
+	{
+		return b;
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/util/Purger.java
--- a/src/org/sonews/util/Purger.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/util/Purger.java	Sun Aug 29 18:17:37 2010 +0200
@@ -40,110 +40,91 @@
 public class Purger extends AbstractDaemon
 {
 
-  /**
-   * Loops through all messages and deletes them if their time
-   * has come.
-   */
-  @Override
-  public void run()
-  {
-    try
-    {
-      while(isRunning())
-      {
-        purgeDeleted();
-        purgeOutdated();
+	/**
+	 * Loops through all messages and deletes them if their time
+	 * has come.
+	 */
+	@Override
+	public void run()
+	{
+		try {
+			while (isRunning()) {
+				purgeDeleted();
+				purgeOutdated();
 
-        Thread.sleep(120000); // Sleep for two minutes
-      }
-    }
-    catch(StorageBackendException ex)
-    {
-      ex.printStackTrace();
-    }
-    catch(InterruptedException ex)
-    {
-      Log.get().warning("Purger interrupted: " + ex);
-    }
-  }
+				Thread.sleep(120000); // Sleep for two minutes
+			}
+		} catch (StorageBackendException ex) {
+			ex.printStackTrace();
+		} catch (InterruptedException ex) {
+			Log.get().warning("Purger interrupted: " + ex);
+		}
+	}
 
-  private void purgeDeleted()
-    throws StorageBackendException
-  {
-    List<Channel> groups = StorageManager.current().getGroups();
-    for(Channel channel : groups)
-    {
-      if(!(channel instanceof Group))
-        continue;
-      
-      Group group = (Group)channel;
-      // Look for groups that are marked as deleted
-      if(group.isDeleted())
-      {
-        List<Long> ids = StorageManager.current().getArticleNumbers(group.getInternalID());
-        if(ids.size() == 0)
-        {
-          StorageManager.current().purgeGroup(group);
-          Log.get().info("Group " + group.getName() + " purged.");
-        }
+	private void purgeDeleted()
+		throws StorageBackendException
+	{
+		List<Channel> groups = StorageManager.current().getGroups();
+		for (Channel channel : groups) {
+			if (!(channel instanceof Group)) {
+				continue;
+			}
 
-        for(int n = 0; n < ids.size() && n < 10; n++)
-        {
-          Article art = StorageManager.current().getArticle(ids.get(n), group.getInternalID());
-          StorageManager.current().delete(art.getMessageID());
-          Log.get().info("Article " + art.getMessageID() + " purged.");
-        }
-      }
-    }
-  }
+			Group group = (Group) channel;
+			// Look for groups that are marked as deleted
+			if (group.isDeleted()) {
+				List<Long> ids = StorageManager.current().getArticleNumbers(group.getInternalID());
+				if (ids.size() == 0) {
+					StorageManager.current().purgeGroup(group);
+					Log.get().info("Group " + group.getName() + " purged.");
+				}
 
-  private void purgeOutdated()
-    throws InterruptedException, StorageBackendException
-  {
-    long articleMaximum =
-      Config.inst().get("sonews.article.maxnum", Long.MAX_VALUE);
-    long lifetime =
-      Config.inst().get("sonews.article.lifetime", -1);
+				for (int n = 0; n < ids.size() && n < 10; n++) {
+					Article art = StorageManager.current().getArticle(ids.get(n), group.getInternalID());
+					StorageManager.current().delete(art.getMessageID());
+					Log.get().info("Article " + art.getMessageID() + " purged.");
+				}
+			}
+		}
+	}
 
-    if(lifetime > 0 || articleMaximum < Stats.getInstance().getNumberOfNews())
-    {
-      Log.get().info("Purging old messages...");
-      String mid = StorageManager.current().getOldestArticle();
-      if (mid == null) // No articles in the database
-      {
-        return;
-      }
+	private void purgeOutdated()
+		throws InterruptedException, StorageBackendException
+	{
+		long articleMaximum =
+			Config.inst().get("sonews.article.maxnum", Long.MAX_VALUE);
+		long lifetime =
+			Config.inst().get("sonews.article.lifetime", -1);
 
-      Article art = StorageManager.current().getArticle(mid);
-      long artDate = 0;
-      String dateStr = art.getHeader(Headers.DATE)[0];
-      try
-      {
-        artDate = Date.parse(dateStr) / 1000 / 60 / 60 / 24;
-      }
-      catch (IllegalArgumentException ex)
-      {
-        Log.get().warning("Could not parse date string: " + dateStr + " " + ex);
-      }
+		if (lifetime > 0 || articleMaximum < Stats.getInstance().getNumberOfNews()) {
+			Log.get().info("Purging old messages...");
+			String mid = StorageManager.current().getOldestArticle();
+			if (mid == null) // No articles in the database
+			{
+				return;
+			}
 
-      // Should we delete the message because of its age or because the
-      // article maximum was reached?
-      if (lifetime < 0 || artDate < (new Date().getTime() + lifetime))
-      {
-        StorageManager.current().delete(mid);
-        System.out.println("Deleted: " + mid);
-      }
-      else
-      {
-        Thread.sleep(1000 * 60); // Wait 60 seconds
-        return;
-      }
-    }
-    else
-    {
-      Log.get().info("Lifetime purger is disabled");
-      Thread.sleep(1000 * 60 * 30); // Wait 30 minutes
-    }
-  }
+			Article art = StorageManager.current().getArticle(mid);
+			long artDate = 0;
+			String dateStr = art.getHeader(Headers.DATE)[0];
+			try {
+				artDate = Date.parse(dateStr) / 1000 / 60 / 60 / 24;
+			} catch (IllegalArgumentException ex) {
+				Log.get().warning("Could not parse date string: " + dateStr + " " + ex);
+			}
 
+			// Should we delete the message because of its age or because the
+			// article maximum was reached?
+			if (lifetime < 0 || artDate < (new Date().getTime() + lifetime)) {
+				StorageManager.current().delete(mid);
+				System.out.println("Deleted: " + mid);
+			} else {
+				Thread.sleep(1000 * 60); // Wait 60 seconds
+				return;
+			}
+		} else {
+			Log.get().info("Lifetime purger is disabled");
+			Thread.sleep(1000 * 60 * 30); // Wait 30 minutes
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/util/Stats.java
--- a/src/org/sonews/util/Stats.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/util/Stats.java	Sun Aug 29 18:17:37 2010 +0200
@@ -29,178 +29,157 @@
  * @author Christian Lins
  * @since sonews/0.5.0
  */
-public final class Stats 
+public final class Stats
 {
-      
-  public static final byte CONNECTIONS    = 1;
-  public static final byte POSTED_NEWS    = 2;
-  public static final byte GATEWAYED_NEWS = 3;
-  public static final byte FEEDED_NEWS    = 4;
-  public static final byte MLGW_RUNSTART  = 5;
-  public static final byte MLGW_RUNEND    = 6;
 
-  private static Stats instance = new Stats();
-  
-  public static Stats getInstance()
-  {
-    return Stats.instance;
-  }
-  
-  private Stats() {}
-  
-  private volatile int connectedClients = 0;
+	public static final byte CONNECTIONS = 1;
+	public static final byte POSTED_NEWS = 2;
+	public static final byte GATEWAYED_NEWS = 3;
+	public static final byte FEEDED_NEWS = 4;
+	public static final byte MLGW_RUNSTART = 5;
+	public static final byte MLGW_RUNEND = 6;
+	private static Stats instance = new Stats();
 
-  /**
-   * A generic method that writes event data to the storage backend.
-   * If event logging is disabled with sonews.eventlog=false this method
-   * simply does nothing.
-   * @param type
-   * @param groupname
-   */
-  private void addEvent(byte type, String groupname)
-  {
-    try
-    {
-      if (Config.inst().get(Config.EVENTLOG, true))
-      {
+	public static Stats getInstance()
+	{
+		return Stats.instance;
+	}
 
-        Channel group = Channel.getByName(groupname);
-        if (group != null)
-        {
-          StorageManager.current().addEvent(
-                  System.currentTimeMillis(), type, group.getInternalID());
-        }
-      } 
-      else
-      {
-        Log.get().info("Group " + groupname + " does not exist.");
-      }
-    } 
-    catch (StorageBackendException ex)
-    {
-      ex.printStackTrace();
-    }
-  }
-  
-  public void clientConnect()
-  {
-    this.connectedClients++;
-  }
-  
-  public void clientDisconnect()
-  {
-    this.connectedClients--;
-  }
-  
-  public int connectedClients()
-  {
-    return this.connectedClients;
-  }
-  
-  public int getNumberOfGroups()
-  {
-    try
-    {
-      return StorageManager.current().countGroups();
-    }
-    catch(StorageBackendException ex)
-    {
-      ex.printStackTrace();
-      return -1;
-    }
-  }
-  
-  public int getNumberOfNews()
-  {
-    try
-    {
-      return StorageManager.current().countArticles();
-    }
-    catch(StorageBackendException ex)
-    {
-      ex.printStackTrace();
-      return -1;
-    }
-  }
-  
-  public int getYesterdaysEvents(final byte eventType, final int hour,
-    final Channel group)
-  {
-    // Determine the timestamp values for yesterday and the given hour
-    Calendar cal = Calendar.getInstance();
-    int year  = cal.get(Calendar.YEAR);
-    int month = cal.get(Calendar.MONTH);
-    int dayom = cal.get(Calendar.DAY_OF_MONTH) - 1; // Yesterday
-    
-    cal.set(year, month, dayom, hour, 0, 0);
-    long startTimestamp = cal.getTimeInMillis();
-    
-    cal.set(year, month, dayom, hour + 1, 0, 0);
-    long endTimestamp = cal.getTimeInMillis();
-    
-    try
-    {
-      return StorageManager.current()
-        .getEventsCount(eventType, startTimestamp, endTimestamp, group);
-    }
-    catch(StorageBackendException ex)
-    {
-      ex.printStackTrace();
-      return -1;
-    }
-  }
-  
-  public void mailPosted(String groupname)
-  {
-    addEvent(POSTED_NEWS, groupname);
-  }
-  
-  public void mailGatewayed(String groupname)
-  {
-    addEvent(GATEWAYED_NEWS, groupname);
-  }
-  
-  public void mailFeeded(String groupname)
-  {
-    addEvent(FEEDED_NEWS, groupname);
-  }
-  
-  public void mlgwRunStart()
-  {
-    addEvent(MLGW_RUNSTART, "control");
-  }
-  
-  public void mlgwRunEnd()
-  {
-    addEvent(MLGW_RUNEND, "control");
-  }
-  
-  private double perHour(int key, long gid)
-  {
-    try
-    {
-      return StorageManager.current().getEventsPerHour(key, gid);
-    }
-    catch(StorageBackendException ex)
-    {
-      ex.printStackTrace();
-      return -1;
-    }
-  }
-  
-  public double postedPerHour(long gid)
-  {
-    return perHour(POSTED_NEWS, gid);
-  }
-  
-  public double gatewayedPerHour(long gid)
-  {
-    return perHour(GATEWAYED_NEWS, gid);
-  }
-  
-  public double feededPerHour(long gid)
-  {
-    return perHour(FEEDED_NEWS, gid);
-  }
-  
+	private Stats()
+	{
+	}
+	private volatile int connectedClients = 0;
+
+	/**
+	 * A generic method that writes event data to the storage backend.
+	 * If event logging is disabled with sonews.eventlog=false this method
+	 * simply does nothing.
+	 * @param type
+	 * @param groupname
+	 */
+	private void addEvent(byte type, String groupname)
+	{
+		try {
+			if (Config.inst().get(Config.EVENTLOG, true)) {
+
+				Channel group = Channel.getByName(groupname);
+				if (group != null) {
+					StorageManager.current().addEvent(
+						System.currentTimeMillis(), type, group.getInternalID());
+				}
+			} else {
+				Log.get().info("Group " + groupname + " does not exist.");
+			}
+		} catch (StorageBackendException ex) {
+			ex.printStackTrace();
+		}
+	}
+
+	public void clientConnect()
+	{
+		this.connectedClients++;
+	}
+
+	public void clientDisconnect()
+	{
+		this.connectedClients--;
+	}
+
+	public int connectedClients()
+	{
+		return this.connectedClients;
+	}
+
+	public int getNumberOfGroups()
+	{
+		try {
+			return StorageManager.current().countGroups();
+		} catch (StorageBackendException ex) {
+			ex.printStackTrace();
+			return -1;
+		}
+	}
+
+	public int getNumberOfNews()
+	{
+		try {
+			return StorageManager.current().countArticles();
+		} catch (StorageBackendException ex) {
+			ex.printStackTrace();
+			return -1;
+		}
+	}
+
+	public int getYesterdaysEvents(final byte eventType, final int hour,
+		final Channel group)
+	{
+		// Determine the timestamp values for yesterday and the given hour
+		Calendar cal = Calendar.getInstance();
+		int year = cal.get(Calendar.YEAR);
+		int month = cal.get(Calendar.MONTH);
+		int dayom = cal.get(Calendar.DAY_OF_MONTH) - 1; // Yesterday
+
+		cal.set(year, month, dayom, hour, 0, 0);
+		long startTimestamp = cal.getTimeInMillis();
+
+		cal.set(year, month, dayom, hour + 1, 0, 0);
+		long endTimestamp = cal.getTimeInMillis();
+
+		try {
+			return StorageManager.current().getEventsCount(eventType, startTimestamp, endTimestamp, group);
+		} catch (StorageBackendException ex) {
+			ex.printStackTrace();
+			return -1;
+		}
+	}
+
+	public void mailPosted(String groupname)
+	{
+		addEvent(POSTED_NEWS, groupname);
+	}
+
+	public void mailGatewayed(String groupname)
+	{
+		addEvent(GATEWAYED_NEWS, groupname);
+	}
+
+	public void mailFeeded(String groupname)
+	{
+		addEvent(FEEDED_NEWS, groupname);
+	}
+
+	public void mlgwRunStart()
+	{
+		addEvent(MLGW_RUNSTART, "control");
+	}
+
+	public void mlgwRunEnd()
+	{
+		addEvent(MLGW_RUNEND, "control");
+	}
+
+	private double perHour(int key, long gid)
+	{
+		try {
+			return StorageManager.current().getEventsPerHour(key, gid);
+		} catch (StorageBackendException ex) {
+			ex.printStackTrace();
+			return -1;
+		}
+	}
+
+	public double postedPerHour(long gid)
+	{
+		return perHour(POSTED_NEWS, gid);
+	}
+
+	public double gatewayedPerHour(long gid)
+	{
+		return perHour(GATEWAYED_NEWS, gid);
+	}
+
+	public double feededPerHour(long gid)
+	{
+		return perHour(FEEDED_NEWS, gid);
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/util/StringTemplate.java
--- a/src/org/sonews/util/StringTemplate.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/util/StringTemplate.java	Sun Aug 29 18:17:37 2010 +0200
@@ -26,72 +26,67 @@
  * @author Christian Lins
  * @since sonews/0.5.0
  */
-public class StringTemplate 
+public class StringTemplate
 {
 
-  private String              str               = null;
-  private String              templateDelimiter = "%";
-  private Map<String, String> templateValues    = new HashMap<String, String>();
-  
-  public StringTemplate(String str, final String templateDelimiter)
-  {
-    if(str == null || templateDelimiter == null)
-    {
-      throw new IllegalArgumentException("null arguments not allowed");
-    }
+	private String str = null;
+	private String templateDelimiter = "%";
+	private Map<String, String> templateValues = new HashMap<String, String>();
 
-    this.str               = str;
-    this.templateDelimiter = templateDelimiter;
-  }
-  
-  public StringTemplate(String str)
-  {
-    this(str, "%");
-  }
-  
-  public StringTemplate set(String template, String value)
-  {
-    if(template == null || value == null)
-    {
-      throw new IllegalArgumentException("null arguments not allowed");
-    }
-    
-    this.templateValues.put(template, value);
-    return this;
-  }
-  
-  public StringTemplate set(String template, long value)
-  {
-    return set(template, Long.toString(value));
-  }
-  
-  public StringTemplate set(String template, double value)
-  {
-    return set(template, Double.toString(value));
-  }
-  
-  public StringTemplate set(String template, Object obj)
-  {
-    if(template == null || obj == null)
-    {
-      throw new IllegalArgumentException("null arguments not allowed");
-    }
+	public StringTemplate(String str, final String templateDelimiter)
+	{
+		if (str == null || templateDelimiter == null) {
+			throw new IllegalArgumentException("null arguments not allowed");
+		}
 
-    return set(template, obj.toString());
-  }
-  
-  @Override
-  public String toString()
-  {
-    String ret = str;
+		this.str = str;
+		this.templateDelimiter = templateDelimiter;
+	}
 
-    for(String key : this.templateValues.keySet())
-    {
-      String value = this.templateValues.get(key);
-      ret = ret.replace(templateDelimiter + key, value);
-    }
-    
-    return ret;
-  }
+	public StringTemplate(String str)
+	{
+		this(str, "%");
+	}
 
+	public StringTemplate set(String template, String value)
+	{
+		if (template == null || value == null) {
+			throw new IllegalArgumentException("null arguments not allowed");
+		}
+
+		this.templateValues.put(template, value);
+		return this;
+	}
+
+	public StringTemplate set(String template, long value)
+	{
+		return set(template, Long.toString(value));
+	}
+
+	public StringTemplate set(String template, double value)
+	{
+		return set(template, Double.toString(value));
+	}
+
+	public StringTemplate set(String template, Object obj)
+	{
+		if (template == null || obj == null) {
+			throw new IllegalArgumentException("null arguments not allowed");
+		}
+
+		return set(template, obj.toString());
+	}
+
+	@Override
+	public String toString()
+	{
+		String ret = str;
+
+		for (String key : this.templateValues.keySet()) {
+			String value = this.templateValues.get(key);
+			ret = ret.replace(templateDelimiter + key, value);
+		}
+
+		return ret;
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/util/TimeoutMap.java
--- a/src/org/sonews/util/TimeoutMap.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/util/TimeoutMap.java	Sun Aug 29 18:17:37 2010 +0200
@@ -31,115 +31,99 @@
  * @author Christian Lins
  * @since sonews/0.5.0
  */
-public class TimeoutMap<K,V> extends ConcurrentHashMap<K, V>
+public class TimeoutMap<K, V> extends ConcurrentHashMap<K, V>
 {
-  
-  private static final long serialVersionUID = 453453467700345L;
 
-  private int                    timeout     = 60000; // 60 sec
-  private transient Map<K, Long> timeoutMap  = new HashMap<K, Long>();
-  
-  /**
-   * Constructor.
-   * @param timeout Timeout in milliseconds
-   */
-  public TimeoutMap(final int timeout)
-  {
-    this.timeout = timeout;
-  }
-  
-  /**
-   * Uses default timeout (60 sec).
-   */
-  public TimeoutMap()
-  {
-  }
-  
-  /**
-   * 
-   * @param key
-   * @return true if key is still valid.
-   */
-  protected boolean checkTimeOut(Object key)
-  {
-    synchronized(this.timeoutMap)
-    {
-      if(this.timeoutMap.containsKey(key))
-      {
-        long keytime = this.timeoutMap.get(key);
-        if((System.currentTimeMillis() - keytime) < this.timeout)
-        {
-          return true;
-        }
-        else
-        {
-          remove(key);
-          return false;
-        }
-      }
-      else
-      {
-        return false;
-      }
-    }
-  }
-  
-  @Override
-  public boolean containsKey(Object key)
-  {
-    return checkTimeOut(key);
-  }
+	private static final long serialVersionUID = 453453467700345L;
+	private int timeout = 60000; // 60 sec
+	private transient Map<K, Long> timeoutMap = new HashMap<K, Long>();
 
-  @Override
-  public synchronized V get(Object key)
-  {
-    if(checkTimeOut(key))
-    {
-      return super.get(key);
-    }
-    else
-    {
-      return null;
-    }
-  }
+	/**
+	 * Constructor.
+	 * @param timeout Timeout in milliseconds
+	 */
+	public TimeoutMap(final int timeout)
+	{
+		this.timeout = timeout;
+	}
 
-  @Override
-  public V put(K key, V value)
-  {
-    synchronized(this.timeoutMap)
-    {
-      removeStaleKeys();
-      this.timeoutMap.put(key, System.currentTimeMillis());
-      return super.put(key, value);
-    }
-  }
+	/**
+	 * Uses default timeout (60 sec).
+	 */
+	public TimeoutMap()
+	{
+	}
 
-  /**
-   * @param arg0
-   * @return
-   */
-  @Override
-  public V remove(Object arg0)
-  {
-    synchronized(this.timeoutMap)
-    {
-      this.timeoutMap.remove(arg0);
-      V val = super.remove(arg0);
-      return val;
-    }
-  }
+	/**
+	 *
+	 * @param key
+	 * @return true if key is still valid.
+	 */
+	protected boolean checkTimeOut(Object key)
+	{
+		synchronized (this.timeoutMap) {
+			if (this.timeoutMap.containsKey(key)) {
+				long keytime = this.timeoutMap.get(key);
+				if ((System.currentTimeMillis() - keytime) < this.timeout) {
+					return true;
+				} else {
+					remove(key);
+					return false;
+				}
+			} else {
+				return false;
+			}
+		}
+	}
 
-  protected void removeStaleKeys()
-  {
-    synchronized(this.timeoutMap)
-    {
-      Set<Object> keySet = new HashSet<Object>(this.timeoutMap.keySet());
-      for(Object key : keySet)
-      {
-        // The key/value is removed by the checkTimeOut() method if true
-        checkTimeOut(key);
-      }
-    }
-  }
-  
+	@Override
+	public boolean containsKey(Object key)
+	{
+		return checkTimeOut(key);
+	}
+
+	@Override
+	public synchronized V get(Object key)
+	{
+		if (checkTimeOut(key)) {
+			return super.get(key);
+		} else {
+			return null;
+		}
+	}
+
+	@Override
+	public V put(K key, V value)
+	{
+		synchronized (this.timeoutMap) {
+			removeStaleKeys();
+			this.timeoutMap.put(key, System.currentTimeMillis());
+			return super.put(key, value);
+		}
+	}
+
+	/**
+	 * @param arg0
+	 * @return
+	 */
+	@Override
+	public V remove(Object arg0)
+	{
+		synchronized (this.timeoutMap) {
+			this.timeoutMap.remove(arg0);
+			V val = super.remove(arg0);
+			return val;
+		}
+	}
+
+	protected void removeStaleKeys()
+	{
+		synchronized (this.timeoutMap) {
+			Set<Object> keySet = new HashSet<Object>(this.timeoutMap.keySet());
+			for (Object key : keySet) {
+				// The key/value is removed by the checkTimeOut() method if true
+				checkTimeOut(key);
+			}
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/util/io/ArticleInputStream.java
--- a/src/org/sonews/util/io/ArticleInputStream.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/util/io/ArticleInputStream.java	Sun Aug 29 18:17:37 2010 +0200
@@ -32,40 +32,36 @@
 public class ArticleInputStream extends InputStream
 {
 
-  private byte[] buf;
-  private int    pos = 0;
-  
-  public ArticleInputStream(final Article art)
-    throws IOException, UnsupportedEncodingException
-  {
-    final ByteArrayOutputStream out = new ByteArrayOutputStream();
-    out.write(art.getHeaderSource().getBytes("UTF-8"));
-    out.write("\r\n\r\n".getBytes());
-    out.write(art.getBody()); // Without CRLF
-    out.flush();
-    this.buf = out.toByteArray();
-  }
+	private byte[] buf;
+	private int pos = 0;
 
-  /**
-   * This method reads one byte from the stream.  The <code>pos</code>
-   * counter is advanced to the next byte to be read.  The byte read is
-   * returned as an int in the range of 0-255.  If the stream position
-   * is already at the end of the buffer, no byte is read and a -1 is
-   * returned in order to indicate the end of the stream.
-   *
-   * @return The byte read, or -1 if end of stream
-   */
-  @Override
-  public synchronized int read()
-  {
-    if(pos < buf.length)
-    {
-      return ((int)buf[pos++]) & 0xFF;
-    }
-    else
-    {
-      return -1;
-    }
-  }
-  
+	public ArticleInputStream(final Article art)
+		throws IOException, UnsupportedEncodingException
+	{
+		final ByteArrayOutputStream out = new ByteArrayOutputStream();
+		out.write(art.getHeaderSource().getBytes("UTF-8"));
+		out.write("\r\n\r\n".getBytes());
+		out.write(art.getBody()); // Without CRLF
+		out.flush();
+		this.buf = out.toByteArray();
+	}
+
+	/**
+	 * This method reads one byte from the stream.  The <code>pos</code>
+	 * counter is advanced to the next byte to be read.  The byte read is
+	 * returned as an int in the range of 0-255.  If the stream position
+	 * is already at the end of the buffer, no byte is read and a -1 is
+	 * returned in order to indicate the end of the stream.
+	 *
+	 * @return The byte read, or -1 if end of stream
+	 */
+	@Override
+	public synchronized int read()
+	{
+		if (pos < buf.length) {
+			return ((int) buf[pos++]) & 0xFF;
+		} else {
+			return -1;
+		}
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/util/io/ArticleReader.java
--- a/src/org/sonews/util/io/ArticleReader.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/util/io/ArticleReader.java	Sun Aug 29 18:17:37 2010 +0200
@@ -34,102 +34,87 @@
  * @author Christian Lins
  * @since sonews/0.5.0
  */
-public class ArticleReader 
+public class ArticleReader
 {
 
-  private BufferedOutputStream out;
-  private BufferedInputStream  in;
-  private String               messageID;
-  
-  public ArticleReader(String host, int port, String messageID)
-    throws IOException, UnknownHostException
-  {
-    this.messageID = messageID;
+	private BufferedOutputStream out;
+	private BufferedInputStream in;
+	private String messageID;
 
-    // Connect to NNTP server
-    Socket socket = new Socket(host, port);
-    this.out = new BufferedOutputStream(socket.getOutputStream());
-    this.in  = new BufferedInputStream(socket.getInputStream());
-    String line = readln(this.in);
-    if(!line.startsWith("200 "))
-    {
-      throw new IOException("Invalid hello from server: " + line);
-    }
-  }
-  
-  private boolean eofArticle(byte[] buf)
-  {
-    if(buf.length < 4)
-    {
-      return false;
-    }
-    
-    int l = buf.length - 1;
-    return buf[l-3] == 10 // '*\n'
-        && buf[l-2] == '.'                   // '.'
-        && buf[l-1] == 13 && buf[l] == 10;  // '\r\n'
-  }
-  
-  public byte[] getArticleData()
-    throws IOException, UnsupportedEncodingException
-  {
-    long maxSize = Config.inst().get(Config.ARTICLE_MAXSIZE, 1024) * 1024L;
+	public ArticleReader(String host, int port, String messageID)
+		throws IOException, UnknownHostException
+	{
+		this.messageID = messageID;
 
-    try
-    {
-      this.out.write(("ARTICLE " + this.messageID + "\r\n").getBytes("UTF-8"));
-      this.out.flush();
+		// Connect to NNTP server
+		Socket socket = new Socket(host, port);
+		this.out = new BufferedOutputStream(socket.getOutputStream());
+		this.in = new BufferedInputStream(socket.getInputStream());
+		String line = readln(this.in);
+		if (!line.startsWith("200 ")) {
+			throw new IOException("Invalid hello from server: " + line);
+		}
+	}
 
-      String line = readln(this.in);
-      if(line.startsWith("220 "))
-      {
-        ByteArrayOutputStream buf = new ByteArrayOutputStream();
-        
-        while(!eofArticle(buf.toByteArray()))
-        {
-          for(int b = in.read(); b != 10; b = in.read())
-          {
-            buf.write(b);
-          }
+	private boolean eofArticle(byte[] buf)
+	{
+		if (buf.length < 4) {
+			return false;
+		}
 
-          buf.write(10);
-          if(buf.size() > maxSize)
-          {
-            Log.get().warning("Skipping message that is too large: " + buf.size());
-            return null;
-          }
-        }
-        
-        return buf.toByteArray();
-      }
-      else
-      {
-        Log.get().warning("ArticleReader: " + line);
-        return null;
-      }
-    }
-    catch(IOException ex)
-    {
-      throw ex;
-    }
-    finally
-    {
-      this.out.write("QUIT\r\n".getBytes("UTF-8"));
-      this.out.flush();
-      this.out.close();
-    }
-  }
-  
-  private String readln(InputStream in)
-    throws IOException
-  {
-    ByteArrayOutputStream buf = new ByteArrayOutputStream();
-    for(int b = in.read(); b != 10 /* \n */; b = in.read())
-    {
-      buf.write(b);
-    }
-    
-    return new String(buf.toByteArray());
-  }
+		int l = buf.length - 1;
+		return buf[l - 3] == 10 // '*\n'
+			&& buf[l - 2] == '.' // '.'
+			&& buf[l - 1] == 13 && buf[l] == 10;  // '\r\n'
+	}
 
+	public byte[] getArticleData()
+		throws IOException, UnsupportedEncodingException
+	{
+		long maxSize = Config.inst().get(Config.ARTICLE_MAXSIZE, 1024) * 1024L;
+
+		try {
+			this.out.write(("ARTICLE " + this.messageID + "\r\n").getBytes("UTF-8"));
+			this.out.flush();
+
+			String line = readln(this.in);
+			if (line.startsWith("220 ")) {
+				ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+				while (!eofArticle(buf.toByteArray())) {
+					for (int b = in.read(); b != 10; b = in.read()) {
+						buf.write(b);
+					}
+
+					buf.write(10);
+					if (buf.size() > maxSize) {
+						Log.get().warning("Skipping message that is too large: " + buf.size());
+						return null;
+					}
+				}
+
+				return buf.toByteArray();
+			} else {
+				Log.get().warning("ArticleReader: " + line);
+				return null;
+			}
+		} catch (IOException ex) {
+			throw ex;
+		} finally {
+			this.out.write("QUIT\r\n".getBytes("UTF-8"));
+			this.out.flush();
+			this.out.close();
+		}
+	}
+
+	private String readln(InputStream in)
+		throws IOException
+	{
+		ByteArrayOutputStream buf = new ByteArrayOutputStream();
+		for (int b = in.read(); b != 10 /* \n */; b = in.read()) {
+			buf.write(b);
+		}
+
+		return new String(buf.toByteArray());
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/util/io/ArticleWriter.java
--- a/src/org/sonews/util/io/ArticleWriter.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/util/io/ArticleWriter.java	Sun Aug 29 18:17:37 2010 +0200
@@ -32,102 +32,97 @@
  * @author Christian Lins
  * @since sonews/0.5.0
  */
-public class ArticleWriter 
+public class ArticleWriter
 {
-  
-  private BufferedOutputStream out;
-  private BufferedReader       inr;
 
-  public ArticleWriter(String host, int port)
-    throws IOException, UnknownHostException
-  {
-    // Connect to NNTP server
-    Socket socket = new Socket(host, port);
-    this.out = new BufferedOutputStream(socket.getOutputStream());
-    this.inr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-    String line = inr.readLine();
-    if(line == null || !line.startsWith("200 "))
-    {
-      throw new IOException("Invalid hello from server: " + line);
-    }
-  }
-  
-  public void close()
-    throws IOException, UnsupportedEncodingException
-  {
-    this.out.write("QUIT\r\n".getBytes("UTF-8"));
-    this.out.flush();
-  }
+	private BufferedOutputStream out;
+	private BufferedReader inr;
 
-  protected void finishPOST()
-    throws IOException
-  {
-    this.out.write("\r\n.\r\n".getBytes());
-    this.out.flush();
-    String line = inr.readLine();
-    if(line == null || !line.startsWith("240 ") || !line.startsWith("441 "))
-    {
-      throw new IOException(line);
-    }
-  }
+	public ArticleWriter(String host, int port)
+		throws IOException, UnknownHostException
+	{
+		// Connect to NNTP server
+		Socket socket = new Socket(host, port);
+		this.out = new BufferedOutputStream(socket.getOutputStream());
+		this.inr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+		String line = inr.readLine();
+		if (line == null || !line.startsWith("200 ")) {
+			throw new IOException("Invalid hello from server: " + line);
+		}
+	}
 
-  protected void preparePOST()
-    throws IOException
-  {
-    this.out.write("POST\r\n".getBytes("UTF-8"));
-    this.out.flush();
+	public void close()
+		throws IOException, UnsupportedEncodingException
+	{
+		this.out.write("QUIT\r\n".getBytes("UTF-8"));
+		this.out.flush();
+	}
 
-    String line = this.inr.readLine();
-    if(line == null || !line.startsWith("340 "))
-    {
-      throw new IOException(line);
-    }
-  }
+	protected void finishPOST()
+		throws IOException
+	{
+		this.out.write("\r\n.\r\n".getBytes());
+		this.out.flush();
+		String line = inr.readLine();
+		if (line == null || !line.startsWith("240 ") || !line.startsWith("441 ")) {
+			throw new IOException(line);
+		}
+	}
 
-  public void writeArticle(Article article)
-    throws IOException, UnsupportedEncodingException
-  {
-    byte[] buf = new byte[512];
-    ArticleInputStream in = new ArticleInputStream(article);
+	protected void preparePOST()
+		throws IOException
+	{
+		this.out.write("POST\r\n".getBytes("UTF-8"));
+		this.out.flush();
 
-    preparePOST();
-    
-    int len = in.read(buf);
-    while(len != -1)
-    {
-      writeLine(buf, len);
-      len = in.read(buf);
-    }
+		String line = this.inr.readLine();
+		if (line == null || !line.startsWith("340 ")) {
+			throw new IOException(line);
+		}
+	}
 
-    finishPOST();
-  }
+	public void writeArticle(Article article)
+		throws IOException, UnsupportedEncodingException
+	{
+		byte[] buf = new byte[512];
+		ArticleInputStream in = new ArticleInputStream(article);
 
-  /**
-   * Writes the raw content of an article to the remote server. This method
-   * does no charset conversion/handling of any kind so its the preferred
-   * method for sending an article to remote peers.
-   * @param rawArticle
-   * @throws IOException
-   */
-  public void writeArticle(byte[] rawArticle)
-    throws IOException
-  {
-    preparePOST();
-    writeLine(rawArticle, rawArticle.length);
-    finishPOST();
-  }
+		preparePOST();
 
-  /**
-   * Writes the given buffer to the connect remote server.
-   * @param buffer
-   * @param len
-   * @throws IOException
-   */
-  protected void writeLine(byte[] buffer, int len)
-    throws IOException
-  {
-    this.out.write(buffer, 0, len);
-    this.out.flush();
-  }
+		int len = in.read(buf);
+		while (len != -1) {
+			writeLine(buf, len);
+			len = in.read(buf);
+		}
 
+		finishPOST();
+	}
+
+	/**
+	 * Writes the raw content of an article to the remote server. This method
+	 * does no charset conversion/handling of any kind so its the preferred
+	 * method for sending an article to remote peers.
+	 * @param rawArticle
+	 * @throws IOException
+	 */
+	public void writeArticle(byte[] rawArticle)
+		throws IOException
+	{
+		preparePOST();
+		writeLine(rawArticle, rawArticle.length);
+		finishPOST();
+	}
+
+	/**
+	 * Writes the given buffer to the connect remote server.
+	 * @param buffer
+	 * @param len
+	 * @throws IOException
+	 */
+	protected void writeLine(byte[] buffer, int len)
+		throws IOException
+	{
+		this.out.write(buffer, 0, len);
+		this.out.flush();
+	}
 }
diff -r c404a87db5b7 -r 74139325d305 src/org/sonews/util/io/Resource.java
--- a/src/org/sonews/util/io/Resource.java	Sun Aug 29 17:43:58 2010 +0200
+++ b/src/org/sonews/util/io/Resource.java	Sun Aug 29 18:17:37 2010 +0200
@@ -32,101 +32,89 @@
  */
 public final class Resource
 {
-  
-  /**
-   * Loads a resource and returns it as URL reference.
-   * The Resource's classloader is used to load the resource, not
-   * the System's ClassLoader so it may be safe to use this method
-   * in a sandboxed environment.
-   * @return
-   */
-  public static URL getAsURL(final String name)
-  {
-    if(name == null)
-    {
-      return null;
-    }
 
-    return Resource.class.getClassLoader().getResource(name);
-  }
-  
-  /**
-   * Loads a resource and returns an InputStream to it.
-   * @param name
-   * @return
-   */
-  public static InputStream getAsStream(String name)
-  {
-    try
-    {
-      URL url = getAsURL(name);
-      if(url == null)
-      {
-        return null;
-      }
-      else
-      {
-        return url.openStream();
-      }
-    }
-    catch(IOException e)
-    {
-      e.printStackTrace();
-      return null;
-    }
-  }
+	/**
+	 * Loads a resource and returns it as URL reference.
+	 * The Resource's classloader is used to load the resource, not
+	 * the System's ClassLoader so it may be safe to use this method
+	 * in a sandboxed environment.
+	 * @return
+	 */
+	public static URL getAsURL(final String name)
+	{
+		if (name == null) {
+			return null;
+		}
 
-  /**
-   * Loads a plain text resource.
-   * @param withNewline If false all newlines are removed from the 
-   * return String
-   */
-  public static String getAsString(String name, boolean withNewline)
-  {
-    if(name == null)
-      return null;
+		return Resource.class.getClassLoader().getResource(name);
+	}
 
-    BufferedReader in = null;
-    try
-    {
-      InputStream ins = getAsStream(name);
-      if(ins == null)
-        return null;
+	/**
+	 * Loads a resource and returns an InputStream to it.
+	 * @param name
+	 * @return
+	 */
+	public static InputStream getAsStream(String name)
+	{
+		try {
+			URL url = getAsURL(name);
+			if (url == null) {
+				return null;
+			} else {
+				return url.openStream();
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
 
-      in = new BufferedReader(
-        new InputStreamReader(ins, Charset.forName("UTF-8")));
-      StringBuffer buf = new StringBuffer();
+	/**
+	 * Loads a plain text resource.
+	 * @param withNewline If false all newlines are removed from the
+	 * return String
+	 */
+	public static String getAsString(String name, boolean withNewline)
+	{
+		if (name == null) {
+			return null;
+		}
 
-      for(;;)
-      {
-        String line = in.readLine();
-        if(line == null)
-          break;
+		BufferedReader in = null;
+		try {
+			InputStream ins = getAsStream(name);
+			if (ins == null) {
+				return null;
+			}
 
-        buf.append(line);
-        if(withNewline)
-          buf.append('\n');
-      }
+			in = new BufferedReader(
+				new InputStreamReader(ins, Charset.forName("UTF-8")));
+			StringBuffer buf = new StringBuffer();
 
-      return buf.toString();
-    }
-    catch(Exception e)
-    {
-      e.printStackTrace();
-      return null;
-    }
-    finally
-    {
-      try
-      {
-        if(in != null)
-          in.close();
-      }
-      catch(IOException ex)
-      {
-        ex.printStackTrace();
-      }
-    }
-  }
+			for (;;) {
+				String line = in.readLine();
+				if (line == null) {
+					break;
+				}
 
+				buf.append(line);
+				if (withNewline) {
+					buf.append('\n');
+				}
+			}
+
+			return buf.toString();
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		} finally {
+			try {
+				if (in != null) {
+					in.close();
+				}
+			} catch (IOException ex) {
+				ex.printStackTrace();
+			}
+		}
+	}
 }