Switch intent style to Original K&R / Linux / Kernel.
authorcli
Sun, 29 Aug 2010 18:17:37 +0200
changeset 3774139325d305
parent 36 c404a87db5b7
child 38 fdfc7225f799
Switch intent style to Original K&R / Linux / Kernel.
src/org/sonews/Main.java
src/org/sonews/ShutdownHook.java
src/org/sonews/acl/AccessControl.java
src/org/sonews/acl/AuthInfoCommand.java
src/org/sonews/config/AbstractConfig.java
src/org/sonews/config/BackendConfig.java
src/org/sonews/config/CommandLineConfig.java
src/org/sonews/config/Config.java
src/org/sonews/config/FileConfig.java
src/org/sonews/daemon/AbstractDaemon.java
src/org/sonews/daemon/ChannelLineBuffers.java
src/org/sonews/daemon/ChannelReader.java
src/org/sonews/daemon/ChannelWriter.java
src/org/sonews/daemon/CommandSelector.java
src/org/sonews/daemon/ConnectionWorker.java
src/org/sonews/daemon/Connections.java
src/org/sonews/daemon/LineEncoder.java
src/org/sonews/daemon/NNTPConnection.java
src/org/sonews/daemon/NNTPDaemon.java
src/org/sonews/daemon/command/ArticleCommand.java
src/org/sonews/daemon/command/CapabilitiesCommand.java
src/org/sonews/daemon/command/Command.java
src/org/sonews/daemon/command/GroupCommand.java
src/org/sonews/daemon/command/HelpCommand.java
src/org/sonews/daemon/command/HelpfulCommand.java
src/org/sonews/daemon/command/ListCommand.java
src/org/sonews/daemon/command/ListGroupCommand.java
src/org/sonews/daemon/command/ModeReaderCommand.java
src/org/sonews/daemon/command/NewGroupsCommand.java
src/org/sonews/daemon/command/NextPrevCommand.java
src/org/sonews/daemon/command/OverCommand.java
src/org/sonews/daemon/command/PostCommand.java
src/org/sonews/daemon/command/PostState.java
src/org/sonews/daemon/command/QuitCommand.java
src/org/sonews/daemon/command/StatCommand.java
src/org/sonews/daemon/command/UnsupportedCommand.java
src/org/sonews/daemon/command/XDaemonCommand.java
src/org/sonews/daemon/command/XPatCommand.java
src/org/sonews/feed/FeedManager.java
src/org/sonews/feed/PullFeeder.java
src/org/sonews/feed/PushFeeder.java
src/org/sonews/feed/Subscription.java
src/org/sonews/mlgw/Dispatcher.java
src/org/sonews/mlgw/MailPoller.java
src/org/sonews/mlgw/SMTPTransport.java
src/org/sonews/plugin/Plugin.java
src/org/sonews/storage/Article.java
src/org/sonews/storage/ArticleHead.java
src/org/sonews/storage/Channel.java
src/org/sonews/storage/Group.java
src/org/sonews/storage/Headers.java
src/org/sonews/storage/Storage.java
src/org/sonews/storage/StorageBackendException.java
src/org/sonews/storage/StorageManager.java
src/org/sonews/storage/StorageProvider.java
src/org/sonews/storage/impl/JDBCDatabase.java
src/org/sonews/storage/impl/JDBCDatabaseProvider.java
src/org/sonews/util/DatabaseSetup.java
src/org/sonews/util/Log.java
src/org/sonews/util/Pair.java
src/org/sonews/util/Purger.java
src/org/sonews/util/Stats.java
src/org/sonews/util/StringTemplate.java
src/org/sonews/util/TimeoutMap.java
src/org/sonews/util/io/ArticleInputStream.java
src/org/sonews/util/io/ArticleReader.java
src/org/sonews/util/io/ArticleWriter.java
src/org/sonews/util/io/Resource.java
     1.1 --- a/src/org/sonews/Main.java	Sun Aug 29 17:43:58 2010 +0200
     1.2 +++ b/src/org/sonews/Main.java	Sun Aug 29 18:17:37 2010 +0200
     1.3 @@ -44,155 +44,123 @@
     1.4   */
     1.5  public final class Main
     1.6  {
     1.7 -  
     1.8 -  private Main()
     1.9 -  {
    1.10 -  }
    1.11  
    1.12 -  /** Version information of the sonews daemon */
    1.13 -  public static final String VERSION = "sonews/1.1.0";
    1.14 -  public static final Date   STARTDATE = new Date();
    1.15 -  
    1.16 -  /**
    1.17 -   * The main entrypoint.
    1.18 -   * @param args
    1.19 -   * @throws Exception
    1.20 -   */
    1.21 -  public static void main(String[] args) throws Exception
    1.22 -  {
    1.23 -    System.out.println(VERSION);
    1.24 -    Thread.currentThread().setName("Mainthread");
    1.25 +	/** Version information of the sonews daemon */
    1.26 +	public static final String VERSION = "sonews/1.1.0";
    1.27 +	public static final Date STARTDATE = new Date();
    1.28  
    1.29 -    // Command line arguments
    1.30 -    boolean feed    = false;  // Enable feeding?
    1.31 -    boolean mlgw    = false;  // Enable Mailinglist gateway?
    1.32 -    int     port    = -1;
    1.33 -    
    1.34 -    for(int n = 0; n < args.length; n++)
    1.35 -    {
    1.36 -      if(args[n].equals("-c") || args[n].equals("-config"))
    1.37 -      {
    1.38 -        Config.inst().set(Config.LEVEL_CLI, Config.CONFIGFILE, args[++n]);
    1.39 -        System.out.println("Using config file " + args[n]);
    1.40 -      }
    1.41 -      else if(args[n].equals("-dumpjdbcdriver"))
    1.42 -      {
    1.43 -        System.out.println("Available JDBC drivers:");
    1.44 -        Enumeration<Driver> drvs =  DriverManager.getDrivers();
    1.45 -        while(drvs.hasMoreElements())
    1.46 -        {
    1.47 -          System.out.println(drvs.nextElement());
    1.48 -        }
    1.49 -        return;
    1.50 -      }
    1.51 -      else if(args[n].equals("-feed"))
    1.52 -      {
    1.53 -        feed = true;
    1.54 -      }
    1.55 -      else if(args[n].equals("-h") || args[n].equals("-help"))
    1.56 -      {
    1.57 -        printArguments();
    1.58 -        return;
    1.59 -      }
    1.60 -      else if(args[n].equals("-mlgw"))
    1.61 -      {
    1.62 -        mlgw = true;
    1.63 -      }
    1.64 -      else if(args[n].equals("-p"))
    1.65 -      {
    1.66 -        port = Integer.parseInt(args[++n]);
    1.67 -      }
    1.68 -      else if(args[n].equals("-plugin"))
    1.69 -      {
    1.70 -        System.out.println("Warning: -plugin-storage is not implemented!");
    1.71 -      }
    1.72 -      else if(args[n].equals("-plugin-command"))
    1.73 -      {
    1.74 -        try
    1.75 -        {
    1.76 -          CommandSelector.addCommandHandler(args[++n]);
    1.77 -        }
    1.78 -        catch(Exception ex)
    1.79 -        {
    1.80 -          Log.get().warning("Could not load command plugin: " + args[n]);
    1.81 -          Log.get().log(Level.INFO, "Main.java", ex);
    1.82 -        }
    1.83 -      }
    1.84 -      else if(args[n].equals("-plugin-storage"))
    1.85 -      {
    1.86 -        System.out.println("Warning: -plugin-storage is not implemented!");
    1.87 -      }
    1.88 -      else if(args[n].equals("-v") || args[n].equals("-version"))
    1.89 -      {
    1.90 -        // Simply return as the version info is already printed above
    1.91 -        return;
    1.92 -      }
    1.93 -    }
    1.94 -    
    1.95 -    // Try to load the JDBCDatabase;
    1.96 -    // Do NOT USE BackendConfig or Log classes before this point because they require
    1.97 -    // a working JDBCDatabase connection.
    1.98 -    try
    1.99 -    {
   1.100 -      StorageProvider sprov =
   1.101 -        StorageManager.loadProvider("org.sonews.storage.impl.JDBCDatabaseProvider");
   1.102 -      StorageManager.enableProvider(sprov);
   1.103 -      
   1.104 -      // Make sure some elementary groups are existing
   1.105 -      if(!StorageManager.current().isGroupExisting("control"))
   1.106 -      {
   1.107 -        StorageManager.current().addGroup("control", 0);
   1.108 -        Log.get().info("Group 'control' created.");
   1.109 -      }
   1.110 -    }
   1.111 -    catch(StorageBackendException ex)
   1.112 -    {
   1.113 -      ex.printStackTrace();
   1.114 -      System.err.println("Database initialization failed with " + ex.toString());
   1.115 -      System.err.println("Make sure you have specified the correct database" +
   1.116 -        " settings in sonews.conf!");
   1.117 -      return;
   1.118 -    }
   1.119 -    
   1.120 -    ChannelLineBuffers.allocateDirect();
   1.121 -    
   1.122 -    // Add shutdown hook
   1.123 -    Runtime.getRuntime().addShutdownHook(new ShutdownHook());
   1.124 -    
   1.125 -    // Start the listening daemon
   1.126 -    if(port <= 0)
   1.127 -    {
   1.128 -      port = Config.inst().get(Config.PORT, 119);
   1.129 -    }
   1.130 -    final NNTPDaemon daemon = NNTPDaemon.createInstance(port);
   1.131 -    daemon.start();
   1.132 -    
   1.133 -    // Start Connections purger thread...
   1.134 -    Connections.getInstance().start();
   1.135 -    
   1.136 -    // Start mailinglist gateway...
   1.137 -    if(mlgw)
   1.138 -    {
   1.139 -      new MailPoller().start();
   1.140 -    }
   1.141 -    
   1.142 -    // Start feeds
   1.143 -    if(feed)
   1.144 -    {
   1.145 -      FeedManager.startFeeding();
   1.146 -    }
   1.147 +	/**
   1.148 +	 * The main entrypoint.
   1.149 +	 * @param args
   1.150 +	 * @throws Exception
   1.151 +	 */
   1.152 +	public static void main(String[] args) throws Exception
   1.153 +	{
   1.154 +		System.out.println(VERSION);
   1.155 +		Thread.currentThread().setName("Mainthread");
   1.156  
   1.157 -    Purger purger = new Purger();
   1.158 -    purger.start();
   1.159 -    
   1.160 -    // Wait for main thread to exit (setDaemon(false))
   1.161 -    daemon.join();
   1.162 -  }
   1.163 -  
   1.164 -  private static void printArguments()
   1.165 -  {
   1.166 -    String usage = Resource.getAsString("helpers/usage", true);
   1.167 -    System.out.println(usage);
   1.168 -  }
   1.169 +		// Command line arguments
   1.170 +		boolean feed = false;  // Enable feeding?
   1.171 +		boolean mlgw = false;  // Enable Mailinglist gateway?
   1.172 +		int port = -1;
   1.173  
   1.174 +		for (int n = 0; n < args.length; n++) {
   1.175 +			if (args[n].equals("-c") || args[n].equals("-config")) {
   1.176 +				Config.inst().set(Config.LEVEL_CLI, Config.CONFIGFILE, args[++n]);
   1.177 +				System.out.println("Using config file " + args[n]);
   1.178 +			} else if (args[n].equals("-dumpjdbcdriver")) {
   1.179 +				System.out.println("Available JDBC drivers:");
   1.180 +				Enumeration<Driver> drvs = DriverManager.getDrivers();
   1.181 +				while (drvs.hasMoreElements()) {
   1.182 +					System.out.println(drvs.nextElement());
   1.183 +				}
   1.184 +				return;
   1.185 +			} else if (args[n].equals("-feed")) {
   1.186 +				feed = true;
   1.187 +			} else if (args[n].equals("-h") || args[n].equals("-help")) {
   1.188 +				printArguments();
   1.189 +				return;
   1.190 +			} else if (args[n].equals("-mlgw")) {
   1.191 +				mlgw = true;
   1.192 +			} else if (args[n].equals("-p")) {
   1.193 +				port = Integer.parseInt(args[++n]);
   1.194 +			} else if (args[n].equals("-plugin")) {
   1.195 +				System.out.println("Warning: -plugin-storage is not implemented!");
   1.196 +			} else if (args[n].equals("-plugin-command")) {
   1.197 +				try {
   1.198 +					CommandSelector.addCommandHandler(args[++n]);
   1.199 +				} catch (Exception ex) {
   1.200 +					Log.get().warning("Could not load command plugin: " + args[n]);
   1.201 +					Log.get().log(Level.INFO, "Main.java", ex);
   1.202 +				}
   1.203 +			} else if (args[n].equals("-plugin-storage")) {
   1.204 +				System.out.println("Warning: -plugin-storage is not implemented!");
   1.205 +			} else if (args[n].equals("-v") || args[n].equals("-version")) {
   1.206 +				// Simply return as the version info is already printed above
   1.207 +				return;
   1.208 +			}
   1.209 +		}
   1.210 +
   1.211 +		// Try to load the JDBCDatabase;
   1.212 +		// Do NOT USE BackendConfig or Log classes before this point because they require
   1.213 +		// a working JDBCDatabase connection.
   1.214 +		try {
   1.215 +			StorageProvider sprov =
   1.216 +				StorageManager.loadProvider("org.sonews.storage.impl.JDBCDatabaseProvider");
   1.217 +			StorageManager.enableProvider(sprov);
   1.218 +
   1.219 +			// Make sure some elementary groups are existing
   1.220 +			if (!StorageManager.current().isGroupExisting("control")) {
   1.221 +				StorageManager.current().addGroup("control", 0);
   1.222 +				Log.get().info("Group 'control' created.");
   1.223 +			}
   1.224 +		} catch (StorageBackendException ex) {
   1.225 +			ex.printStackTrace();
   1.226 +			System.err.println("Database initialization failed with " + ex.toString());
   1.227 +			System.err.println("Make sure you have specified the correct database"
   1.228 +				+ " settings in sonews.conf!");
   1.229 +			return;
   1.230 +		}
   1.231 +
   1.232 +		ChannelLineBuffers.allocateDirect();
   1.233 +
   1.234 +		// Add shutdown hook
   1.235 +		Runtime.getRuntime().addShutdownHook(new ShutdownHook());
   1.236 +
   1.237 +		// Start the listening daemon
   1.238 +		if (port <= 0) {
   1.239 +			port = Config.inst().get(Config.PORT, 119);
   1.240 +		}
   1.241 +		final NNTPDaemon daemon = NNTPDaemon.createInstance(port);
   1.242 +		daemon.start();
   1.243 +
   1.244 +		// Start Connections purger thread...
   1.245 +		Connections.getInstance().start();
   1.246 +
   1.247 +		// Start mailinglist gateway...
   1.248 +		if (mlgw) {
   1.249 +			new MailPoller().start();
   1.250 +		}
   1.251 +
   1.252 +		// Start feeds
   1.253 +		if (feed) {
   1.254 +			FeedManager.startFeeding();
   1.255 +		}
   1.256 +
   1.257 +		Purger purger = new Purger();
   1.258 +		purger.start();
   1.259 +
   1.260 +		// Wait for main thread to exit (setDaemon(false))
   1.261 +		daemon.join();
   1.262 +	}
   1.263 +
   1.264 +	private static void printArguments()
   1.265 +	{
   1.266 +		String usage = Resource.getAsString("helpers/usage", true);
   1.267 +		System.out.println(usage);
   1.268 +	}
   1.269 +
   1.270 +	private Main()
   1.271 +	{
   1.272 +	}
   1.273  }
     2.1 --- a/src/org/sonews/ShutdownHook.java	Sun Aug 29 17:43:58 2010 +0200
     2.2 +++ b/src/org/sonews/ShutdownHook.java	Sun Aug 29 18:17:37 2010 +0200
     2.3 @@ -30,55 +30,44 @@
     2.4  class ShutdownHook extends Thread
     2.5  {
     2.6  
     2.7 -  /**
     2.8 -   * Called when the JVM exits.
     2.9 -   */
    2.10 -  @Override
    2.11 -  public void run()
    2.12 -  {
    2.13 -    System.out.println("sonews: Trying to shutdown all threads...");
    2.14 +	/**
    2.15 +	 * Called when the JVM exits.
    2.16 +	 */
    2.17 +	@Override
    2.18 +	public void run()
    2.19 +	{
    2.20 +		System.out.println("sonews: Trying to shutdown all threads...");
    2.21  
    2.22 -    Map<Thread, StackTraceElement[]> threadsMap = Thread.getAllStackTraces();
    2.23 -    for(Thread thread : threadsMap.keySet())
    2.24 -    {
    2.25 -      // Interrupt the thread if it's a AbstractDaemon
    2.26 -      AbstractDaemon daemon;
    2.27 -      if(thread instanceof AbstractDaemon && thread.isAlive())
    2.28 -      {
    2.29 -        try
    2.30 -        {
    2.31 -          daemon = (AbstractDaemon)thread;
    2.32 -          daemon.shutdownNow();
    2.33 -        }
    2.34 -        catch(SQLException ex)
    2.35 -        {
    2.36 -          System.out.println("sonews: " + ex);
    2.37 -        }
    2.38 -      }
    2.39 -    }
    2.40 -    
    2.41 -    for(Thread thread : threadsMap.keySet())
    2.42 -    {
    2.43 -      AbstractDaemon daemon;
    2.44 -      if(thread instanceof AbstractDaemon && thread.isAlive())
    2.45 -      {
    2.46 -        daemon = (AbstractDaemon)thread;
    2.47 -        System.out.println("sonews: Waiting for " + daemon + " to exit...");
    2.48 -        try
    2.49 -        {
    2.50 -          daemon.join(500);
    2.51 -        }
    2.52 -        catch(InterruptedException ex)
    2.53 -        {
    2.54 -          System.out.println(ex.getLocalizedMessage());
    2.55 -        }
    2.56 -      }
    2.57 -    }
    2.58 -    
    2.59 -    // We have notified all not-sleeping AbstractDaemons of the shutdown;
    2.60 -    // all other threads can be simply purged on VM shutdown
    2.61 -    
    2.62 -    System.out.println("sonews: Clean shutdown.");
    2.63 -  }
    2.64 -  
    2.65 +		Map<Thread, StackTraceElement[]> threadsMap = Thread.getAllStackTraces();
    2.66 +		for (Thread thread : threadsMap.keySet()) {
    2.67 +			// Interrupt the thread if it's a AbstractDaemon
    2.68 +			AbstractDaemon daemon;
    2.69 +			if (thread instanceof AbstractDaemon && thread.isAlive()) {
    2.70 +				try {
    2.71 +					daemon = (AbstractDaemon) thread;
    2.72 +					daemon.shutdownNow();
    2.73 +				} catch (SQLException ex) {
    2.74 +					System.out.println("sonews: " + ex);
    2.75 +				}
    2.76 +			}
    2.77 +		}
    2.78 +
    2.79 +		for (Thread thread : threadsMap.keySet()) {
    2.80 +			AbstractDaemon daemon;
    2.81 +			if (thread instanceof AbstractDaemon && thread.isAlive()) {
    2.82 +				daemon = (AbstractDaemon) thread;
    2.83 +				System.out.println("sonews: Waiting for " + daemon + " to exit...");
    2.84 +				try {
    2.85 +					daemon.join(500);
    2.86 +				} catch (InterruptedException ex) {
    2.87 +					System.out.println(ex.getLocalizedMessage());
    2.88 +				}
    2.89 +			}
    2.90 +		}
    2.91 +
    2.92 +		// We have notified all not-sleeping AbstractDaemons of the shutdown;
    2.93 +		// all other threads can be simply purged on VM shutdown
    2.94 +
    2.95 +		System.out.println("sonews: Clean shutdown.");
    2.96 +	}
    2.97  }
     3.1 --- a/src/org/sonews/acl/AccessControl.java	Sun Aug 29 17:43:58 2010 +0200
     3.2 +++ b/src/org/sonews/acl/AccessControl.java	Sun Aug 29 18:17:37 2010 +0200
     3.3 @@ -26,6 +26,5 @@
     3.4  public interface AccessControl
     3.5  {
     3.6  
     3.7 -  boolean hasPermission(String user, char[] secret, String permission);
     3.8 -
     3.9 +	boolean hasPermission(String user, char[] secret, String permission);
    3.10  }
     4.1 --- a/src/org/sonews/acl/AuthInfoCommand.java	Sun Aug 29 17:43:58 2010 +0200
     4.2 +++ b/src/org/sonews/acl/AuthInfoCommand.java	Sun Aug 29 18:17:37 2010 +0200
     4.3 @@ -31,34 +31,34 @@
     4.4  public class AuthInfoCommand implements Command
     4.5  {
     4.6  
     4.7 -  @Override
     4.8 -  public String[] getSupportedCommandStrings()
     4.9 -  {
    4.10 -    throw new UnsupportedOperationException("Not supported yet.");
    4.11 -  }
    4.12 +	@Override
    4.13 +	public String[] getSupportedCommandStrings()
    4.14 +	{
    4.15 +		throw new UnsupportedOperationException("Not supported yet.");
    4.16 +	}
    4.17  
    4.18 -  @Override
    4.19 -  public boolean hasFinished()
    4.20 -  {
    4.21 -    throw new UnsupportedOperationException("Not supported yet.");
    4.22 -  }
    4.23 +	@Override
    4.24 +	public boolean hasFinished()
    4.25 +	{
    4.26 +		throw new UnsupportedOperationException("Not supported yet.");
    4.27 +	}
    4.28  
    4.29 -  @Override
    4.30 -  public String impliedCapability()
    4.31 -  {
    4.32 -    throw new UnsupportedOperationException("Not supported yet.");
    4.33 -  }
    4.34 +	@Override
    4.35 +	public String impliedCapability()
    4.36 +	{
    4.37 +		throw new UnsupportedOperationException("Not supported yet.");
    4.38 +	}
    4.39  
    4.40 -  @Override
    4.41 -  public boolean isStateful()
    4.42 -  {
    4.43 -    throw new UnsupportedOperationException("Not supported yet.");
    4.44 -  }
    4.45 +	@Override
    4.46 +	public boolean isStateful()
    4.47 +	{
    4.48 +		throw new UnsupportedOperationException("Not supported yet.");
    4.49 +	}
    4.50  
    4.51 -  @Override
    4.52 -  public void processLine(NNTPConnection conn, String line, byte[] rawLine) throws IOException, StorageBackendException
    4.53 -  {
    4.54 -    throw new UnsupportedOperationException("Not supported yet.");
    4.55 -  }
    4.56 -
    4.57 +	@Override
    4.58 +	public void processLine(NNTPConnection conn, String line, byte[] rawLine)
    4.59 +		throws IOException, StorageBackendException
    4.60 +	{
    4.61 +		throw new UnsupportedOperationException("Not supported yet.");
    4.62 +	}
    4.63  }
     5.1 --- a/src/org/sonews/config/AbstractConfig.java	Sun Aug 29 17:43:58 2010 +0200
     5.2 +++ b/src/org/sonews/config/AbstractConfig.java	Sun Aug 29 18:17:37 2010 +0200
     5.3 @@ -23,35 +23,34 @@
     5.4   * @author Christian Lins
     5.5   * @since sonews/0.5.0
     5.6   */
     5.7 -public abstract class AbstractConfig 
     5.8 +public abstract class AbstractConfig
     5.9  {
    5.10 -  
    5.11 -  public abstract String get(String key, String defVal);
    5.12 -  
    5.13 -  public int get(final String key, final int defVal)
    5.14 -  {
    5.15 -    return Integer.parseInt(
    5.16 -      get(key, Integer.toString(defVal)));
    5.17 -  }
    5.18 -  
    5.19 -  public boolean get(String key, boolean defVal)
    5.20 -  {
    5.21 -    String val = get(key, Boolean.toString(defVal));
    5.22 -    return Boolean.parseBoolean(val);
    5.23 -  }
    5.24  
    5.25 -  /**
    5.26 -   * Returns a long config value specified via the given key.
    5.27 -   * @param key
    5.28 -   * @param defVal
    5.29 -   * @return
    5.30 -   */
    5.31 -  public long get(String key, long defVal)
    5.32 -  {
    5.33 -    String val = get(key, Long.toString(defVal));
    5.34 -    return Long.parseLong(val);
    5.35 -  }
    5.36 +	public abstract String get(String key, String defVal);
    5.37  
    5.38 -  protected abstract void set(String key, String val);
    5.39 -  
    5.40 +	public int get(final String key, final int defVal)
    5.41 +	{
    5.42 +		return Integer.parseInt(
    5.43 +			get(key, Integer.toString(defVal)));
    5.44 +	}
    5.45 +
    5.46 +	public boolean get(String key, boolean defVal)
    5.47 +	{
    5.48 +		String val = get(key, Boolean.toString(defVal));
    5.49 +		return Boolean.parseBoolean(val);
    5.50 +	}
    5.51 +
    5.52 +	/**
    5.53 +	 * Returns a long config value specified via the given key.
    5.54 +	 * @param key
    5.55 +	 * @param defVal
    5.56 +	 * @return
    5.57 +	 */
    5.58 +	public long get(String key, long defVal)
    5.59 +	{
    5.60 +		String val = get(key, Long.toString(defVal));
    5.61 +		return Long.parseLong(val);
    5.62 +	}
    5.63 +
    5.64 +	protected abstract void set(String key, String val);
    5.65  }
     6.1 --- a/src/org/sonews/config/BackendConfig.java	Sun Aug 29 17:43:58 2010 +0200
     6.2 +++ b/src/org/sonews/config/BackendConfig.java	Sun Aug 29 18:17:37 2010 +0200
     6.3 @@ -33,83 +33,67 @@
     6.4  class BackendConfig extends AbstractConfig
     6.5  {
     6.6  
     6.7 -  private static BackendConfig instance = new BackendConfig();
     6.8 -  
     6.9 -  public static BackendConfig getInstance()
    6.10 -  {
    6.11 -    return instance;
    6.12 -  }
    6.13 -  
    6.14 -  private final TimeoutMap<String, String> values 
    6.15 -    = new TimeoutMap<String, String>();
    6.16 -  
    6.17 -  private BackendConfig()
    6.18 -  {
    6.19 -    super();
    6.20 -  }
    6.21 -  
    6.22 -  /**
    6.23 -   * Returns the config value for the given key or the defaultValue if the
    6.24 -   * key is not found in config.
    6.25 -   * @param key
    6.26 -   * @param defaultValue
    6.27 -   * @return
    6.28 -   */
    6.29 -  @Override
    6.30 -  public String get(String key, String defaultValue)
    6.31 -  {
    6.32 -    try
    6.33 -    {
    6.34 -      String configValue = values.get(key);
    6.35 -      if(configValue == null)
    6.36 -      {
    6.37 -        if(StorageManager.current() == null)
    6.38 -        {
    6.39 -          Log.get().warning("BackendConfig not available, using default.");
    6.40 -          return defaultValue;
    6.41 -        }
    6.42 +	private static BackendConfig instance = new BackendConfig();
    6.43  
    6.44 -        configValue = StorageManager.current().getConfigValue(key);
    6.45 -        if(configValue == null)
    6.46 -        {
    6.47 -          return defaultValue;
    6.48 -        }
    6.49 -        else
    6.50 -        {
    6.51 -          values.put(key, configValue);
    6.52 -          return configValue;
    6.53 -        }
    6.54 -      }
    6.55 -      else
    6.56 -      {
    6.57 -        return configValue;
    6.58 -      }
    6.59 -    }
    6.60 -    catch(StorageBackendException ex)
    6.61 -    {
    6.62 -      Log.get().log(Level.SEVERE, "Storage backend problem", ex);
    6.63 -      return defaultValue;
    6.64 -    }
    6.65 -  }
    6.66 -  
    6.67 -  /**
    6.68 -   * Sets the config value which is identified by the given key.
    6.69 -   * @param key
    6.70 -   * @param value
    6.71 -   */
    6.72 -  public void set(String key, String value)
    6.73 -  {
    6.74 -    values.put(key, value);
    6.75 -    
    6.76 -    try
    6.77 -    {
    6.78 -      // Write values to database
    6.79 -      StorageManager.current().setConfigValue(key, value);
    6.80 -    }
    6.81 -    catch(StorageBackendException ex)
    6.82 -    {
    6.83 -      ex.printStackTrace();
    6.84 -    }
    6.85 -  }
    6.86 -  
    6.87 +	public static BackendConfig getInstance()
    6.88 +	{
    6.89 +		return instance;
    6.90 +	}
    6.91 +	private final TimeoutMap<String, String> values = new TimeoutMap<String, String>();
    6.92 +
    6.93 +	private BackendConfig()
    6.94 +	{
    6.95 +		super();
    6.96 +	}
    6.97 +
    6.98 +	/**
    6.99 +	 * Returns the config value for the given key or the defaultValue if the
   6.100 +	 * key is not found in config.
   6.101 +	 * @param key
   6.102 +	 * @param defaultValue
   6.103 +	 * @return
   6.104 +	 */
   6.105 +	@Override
   6.106 +	public String get(String key, String defaultValue)
   6.107 +	{
   6.108 +		try {
   6.109 +			String configValue = values.get(key);
   6.110 +			if (configValue == null) {
   6.111 +				if (StorageManager.current() == null) {
   6.112 +					Log.get().warning("BackendConfig not available, using default.");
   6.113 +					return defaultValue;
   6.114 +				}
   6.115 +
   6.116 +				configValue = StorageManager.current().getConfigValue(key);
   6.117 +				if (configValue == null) {
   6.118 +					return defaultValue;
   6.119 +				} else {
   6.120 +					values.put(key, configValue);
   6.121 +					return configValue;
   6.122 +				}
   6.123 +			} else {
   6.124 +				return configValue;
   6.125 +			}
   6.126 +		} catch (StorageBackendException ex) {
   6.127 +			Log.get().log(Level.SEVERE, "Storage backend problem", ex);
   6.128 +			return defaultValue;
   6.129 +		}
   6.130 +	}
   6.131 +
   6.132 +	/**
   6.133 +	 * Sets the config value which is identified by the given key.
   6.134 +	 * @param key
   6.135 +	 * @param value
   6.136 +	 */
   6.137 +	public void set(String key, String value)
   6.138 +	{
   6.139 +		values.put(key, value);
   6.140 +
   6.141 +		try {
   6.142 +			// Write values to database
   6.143 +			StorageManager.current().setConfigValue(key, value);
   6.144 +		} catch (StorageBackendException ex) {
   6.145 +			ex.printStackTrace();
   6.146 +		}
   6.147 +	}
   6.148  }
     7.1 --- a/src/org/sonews/config/CommandLineConfig.java	Sun Aug 29 17:43:58 2010 +0200
     7.2 +++ b/src/org/sonews/config/CommandLineConfig.java	Sun Aug 29 18:17:37 2010 +0200
     7.3 @@ -28,37 +28,34 @@
     7.4  class CommandLineConfig extends AbstractConfig
     7.5  {
     7.6  
     7.7 -  private static final CommandLineConfig instance = new CommandLineConfig();
     7.8 +	private static final CommandLineConfig instance = new CommandLineConfig();
     7.9  
    7.10 -  public static CommandLineConfig getInstance()
    7.11 -  {
    7.12 -    return instance;
    7.13 -  }
    7.14 +	public static CommandLineConfig getInstance()
    7.15 +	{
    7.16 +		return instance;
    7.17 +	}
    7.18 +	private final Map<String, String> values = new HashMap<String, String>();
    7.19  
    7.20 -  private final Map<String, String> values = new HashMap<String, String>();
    7.21 -  
    7.22 -  private CommandLineConfig() {}
    7.23 +	private CommandLineConfig()
    7.24 +	{
    7.25 +	}
    7.26  
    7.27 -  @Override
    7.28 -  public String get(String key, String def)
    7.29 -  {
    7.30 -    synchronized(this.values)
    7.31 -    {
    7.32 -      if(this.values.containsKey(key))
    7.33 -      {
    7.34 -        def = this.values.get(key);
    7.35 -      }
    7.36 -    }
    7.37 -    return def;
    7.38 -  }
    7.39 +	@Override
    7.40 +	public String get(String key, String def)
    7.41 +	{
    7.42 +		synchronized (this.values) {
    7.43 +			if (this.values.containsKey(key)) {
    7.44 +				def = this.values.get(key);
    7.45 +			}
    7.46 +		}
    7.47 +		return def;
    7.48 +	}
    7.49  
    7.50 -  @Override
    7.51 -  public void set(String key, String val)
    7.52 -  {
    7.53 -    synchronized(this.values)
    7.54 -    {
    7.55 -      this.values.put(key, val);
    7.56 -    }
    7.57 -  }
    7.58 -
    7.59 +	@Override
    7.60 +	public void set(String key, String val)
    7.61 +	{
    7.62 +		synchronized (this.values) {
    7.63 +			this.values.put(key, val);
    7.64 +		}
    7.65 +	}
    7.66  }
     8.1 --- a/src/org/sonews/config/Config.java	Sun Aug 29 17:43:58 2010 +0200
     8.2 +++ b/src/org/sonews/config/Config.java	Sun Aug 29 18:17:37 2010 +0200
     8.3 @@ -25,151 +25,132 @@
     8.4   */
     8.5  public class Config extends AbstractConfig
     8.6  {
     8.7 -  
     8.8 -  public static final int LEVEL_CLI     = 1;
     8.9 -  public static final int LEVEL_FILE    = 2;
    8.10 -  public static final int LEVEL_BACKEND = 3;
    8.11  
    8.12 -  public static final String CONFIGFILE = "sonews.configfile";
    8.13 -  
    8.14 -    /** BackendConfig key constant. Value is the maximum article size in kilobytes. */
    8.15 -  public static final String ARTICLE_MAXSIZE   = "sonews.article.maxsize";
    8.16 +	public static final int LEVEL_CLI = 1;
    8.17 +	public static final int LEVEL_FILE = 2;
    8.18 +	public static final int LEVEL_BACKEND = 3;
    8.19 +	public static final String CONFIGFILE = "sonews.configfile";
    8.20 +	/** BackendConfig key constant. Value is the maximum article size in kilobytes. */
    8.21 +	public static final String ARTICLE_MAXSIZE = "sonews.article.maxsize";
    8.22 +	/** BackendConfig key constant. Value: Amount of news that are feeded per run. */
    8.23 +	public static final String EVENTLOG = "sonews.eventlog";
    8.24 +	public static final String FEED_NEWSPERRUN = "sonews.feed.newsperrun";
    8.25 +	public static final String FEED_PULLINTERVAL = "sonews.feed.pullinterval";
    8.26 +	public static final String HOSTNAME = "sonews.hostname";
    8.27 +	public static final String PORT = "sonews.port";
    8.28 +	public static final String TIMEOUT = "sonews.timeout";
    8.29 +	public static final String LOGLEVEL = "sonews.loglevel";
    8.30 +	public static final String MLPOLL_DELETEUNKNOWN = "sonews.mlpoll.deleteunknown";
    8.31 +	public static final String MLPOLL_HOST = "sonews.mlpoll.host";
    8.32 +	public static final String MLPOLL_PASSWORD = "sonews.mlpoll.password";
    8.33 +	public static final String MLPOLL_USER = "sonews.mlpoll.user";
    8.34 +	public static final String MLSEND_ADDRESS = "sonews.mlsend.address";
    8.35 +	public static final String MLSEND_RW_FROM = "sonews.mlsend.rewrite.from";
    8.36 +	public static final String MLSEND_RW_SENDER = "sonews.mlsend.rewrite.sender";
    8.37 +	public static final String MLSEND_HOST = "sonews.mlsend.host";
    8.38 +	public static final String MLSEND_PASSWORD = "sonews.mlsend.password";
    8.39 +	public static final String MLSEND_PORT = "sonews.mlsend.port";
    8.40 +	public static final String MLSEND_USER = "sonews.mlsend.user";
    8.41 +	/** Key constant. If value is "true" every I/O is written to logfile
    8.42 +	 * (which is a lot!)
    8.43 +	 */
    8.44 +	public static final String DEBUG = "sonews.debug";
    8.45 +	/** Key constant. Value is classname of the JDBC driver */
    8.46 +	public static final String STORAGE_DBMSDRIVER = "sonews.storage.dbmsdriver";
    8.47 +	/** Key constant. Value is JDBC connect String to the database. */
    8.48 +	public static final String STORAGE_DATABASE = "sonews.storage.database";
    8.49 +	/** Key constant. Value is the username for the DBMS. */
    8.50 +	public static final String STORAGE_USER = "sonews.storage.user";
    8.51 +	/** Key constant. Value is the password for the DBMS. */
    8.52 +	public static final String STORAGE_PASSWORD = "sonews.storage.password";
    8.53 +	/** Key constant. Value is the name of the host which is allowed to use the
    8.54 +	 *  XDAEMON command; default: "localhost" */
    8.55 +	public static final String XDAEMON_HOST = "sonews.xdaemon.host";
    8.56 +	/** The config key for the filename of the logfile */
    8.57 +	public static final String LOGFILE = "sonews.log";
    8.58 +	public static final String[] AVAILABLE_KEYS = {
    8.59 +		ARTICLE_MAXSIZE,
    8.60 +		EVENTLOG,
    8.61 +		FEED_NEWSPERRUN,
    8.62 +		FEED_PULLINTERVAL,
    8.63 +		HOSTNAME,
    8.64 +		MLPOLL_DELETEUNKNOWN,
    8.65 +		MLPOLL_HOST,
    8.66 +		MLPOLL_PASSWORD,
    8.67 +		MLPOLL_USER,
    8.68 +		MLSEND_ADDRESS,
    8.69 +		MLSEND_HOST,
    8.70 +		MLSEND_PASSWORD,
    8.71 +		MLSEND_PORT,
    8.72 +		MLSEND_RW_FROM,
    8.73 +		MLSEND_RW_SENDER,
    8.74 +		MLSEND_USER,
    8.75 +		PORT,
    8.76 +		TIMEOUT,
    8.77 +		XDAEMON_HOST
    8.78 +	};
    8.79 +	private static Config instance = new Config();
    8.80  
    8.81 -  /** BackendConfig key constant. Value: Amount of news that are feeded per run. */
    8.82 -  public static final String EVENTLOG          = "sonews.eventlog";
    8.83 -  public static final String FEED_NEWSPERRUN   = "sonews.feed.newsperrun";
    8.84 -  public static final String FEED_PULLINTERVAL = "sonews.feed.pullinterval";
    8.85 -  public static final String HOSTNAME          = "sonews.hostname";
    8.86 -  public static final String PORT              = "sonews.port";
    8.87 -  public static final String TIMEOUT           = "sonews.timeout";
    8.88 -  public static final String LOGLEVEL          = "sonews.loglevel";
    8.89 -  public static final String MLPOLL_DELETEUNKNOWN = "sonews.mlpoll.deleteunknown";
    8.90 -  public static final String MLPOLL_HOST       = "sonews.mlpoll.host";
    8.91 -  public static final String MLPOLL_PASSWORD   = "sonews.mlpoll.password";
    8.92 -  public static final String MLPOLL_USER       = "sonews.mlpoll.user";
    8.93 -  public static final String MLSEND_ADDRESS    = "sonews.mlsend.address";
    8.94 -  public static final String MLSEND_RW_FROM    = "sonews.mlsend.rewrite.from";
    8.95 -  public static final String MLSEND_RW_SENDER  = "sonews.mlsend.rewrite.sender";
    8.96 -  public static final String MLSEND_HOST       = "sonews.mlsend.host";
    8.97 -  public static final String MLSEND_PASSWORD   = "sonews.mlsend.password";
    8.98 -  public static final String MLSEND_PORT       = "sonews.mlsend.port";
    8.99 -  public static final String MLSEND_USER       = "sonews.mlsend.user";
   8.100 -  
   8.101 -  /** Key constant. If value is "true" every I/O is written to logfile
   8.102 -   * (which is a lot!)
   8.103 -   */
   8.104 -  public static final String DEBUG              = "sonews.debug";
   8.105 +	public static Config inst()
   8.106 +	{
   8.107 +		return instance;
   8.108 +	}
   8.109  
   8.110 -  /** Key constant. Value is classname of the JDBC driver */
   8.111 -  public static final String STORAGE_DBMSDRIVER = "sonews.storage.dbmsdriver";
   8.112 +	private Config()
   8.113 +	{
   8.114 +	}
   8.115  
   8.116 -  /** Key constant. Value is JDBC connect String to the database. */
   8.117 -  public static final String STORAGE_DATABASE   = "sonews.storage.database";
   8.118 +	@Override
   8.119 +	public String get(String key, String def)
   8.120 +	{
   8.121 +		String val = CommandLineConfig.getInstance().get(key, null);
   8.122  
   8.123 -  /** Key constant. Value is the username for the DBMS. */
   8.124 -  public static final String STORAGE_USER       = "sonews.storage.user";
   8.125 +		if (val == null) {
   8.126 +			val = FileConfig.getInstance().get(key, null);
   8.127 +		}
   8.128  
   8.129 -  /** Key constant. Value is the password for the DBMS. */
   8.130 -  public static final String STORAGE_PASSWORD   = "sonews.storage.password";
   8.131 +		if (val == null) {
   8.132 +			val = BackendConfig.getInstance().get(key, def);
   8.133 +		}
   8.134  
   8.135 -  /** Key constant. Value is the name of the host which is allowed to use the
   8.136 -   *  XDAEMON command; default: "localhost" */
   8.137 -  public static final String XDAEMON_HOST       = "sonews.xdaemon.host";
   8.138 +		return val;
   8.139 +	}
   8.140  
   8.141 -  /** The config key for the filename of the logfile */
   8.142 -  public static final String LOGFILE = "sonews.log";
   8.143 +	public String get(int maxLevel, String key, String def)
   8.144 +	{
   8.145 +		String val = CommandLineConfig.getInstance().get(key, null);
   8.146  
   8.147 -  public static final String[] AVAILABLE_KEYS = {
   8.148 -      ARTICLE_MAXSIZE,
   8.149 -      EVENTLOG,
   8.150 -      FEED_NEWSPERRUN,
   8.151 -      FEED_PULLINTERVAL,
   8.152 -      HOSTNAME,
   8.153 -      MLPOLL_DELETEUNKNOWN,
   8.154 -      MLPOLL_HOST,
   8.155 -      MLPOLL_PASSWORD,
   8.156 -      MLPOLL_USER,
   8.157 -      MLSEND_ADDRESS,
   8.158 -      MLSEND_HOST,
   8.159 -      MLSEND_PASSWORD,
   8.160 -      MLSEND_PORT,
   8.161 -      MLSEND_RW_FROM,
   8.162 -      MLSEND_RW_SENDER,
   8.163 -      MLSEND_USER,
   8.164 -      PORT,
   8.165 -      TIMEOUT,
   8.166 -      XDAEMON_HOST
   8.167 -  };
   8.168 +		if (val == null && maxLevel >= LEVEL_FILE) {
   8.169 +			val = FileConfig.getInstance().get(key, null);
   8.170 +			if (val == null && maxLevel >= LEVEL_BACKEND) {
   8.171 +				val = BackendConfig.getInstance().get(key, def);
   8.172 +			}
   8.173 +		}
   8.174  
   8.175 -  private static Config instance = new Config();
   8.176 -  
   8.177 -  public static Config inst()
   8.178 -  {
   8.179 -    return instance;
   8.180 -  }
   8.181 -  
   8.182 -  private Config(){}
   8.183 +		return val != null ? val : def;
   8.184 +	}
   8.185  
   8.186 -  @Override
   8.187 -  public String get(String key, String def)
   8.188 -  {
   8.189 -    String val = CommandLineConfig.getInstance().get(key, null);
   8.190 -    
   8.191 -    if(val == null)
   8.192 -    {
   8.193 -      val = FileConfig.getInstance().get(key, null);
   8.194 -    }
   8.195 +	@Override
   8.196 +	public void set(String key, String val)
   8.197 +	{
   8.198 +		set(LEVEL_BACKEND, key, val);
   8.199 +	}
   8.200  
   8.201 -    if(val == null)
   8.202 -    {
   8.203 -      val = BackendConfig.getInstance().get(key, def);
   8.204 -    }
   8.205 -
   8.206 -    return val;
   8.207 -  }
   8.208 -
   8.209 -  public String get(int maxLevel, String key, String def)
   8.210 -  {
   8.211 -    String val = CommandLineConfig.getInstance().get(key, null);
   8.212 -
   8.213 -    if(val == null && maxLevel >= LEVEL_FILE)
   8.214 -    {
   8.215 -      val = FileConfig.getInstance().get(key, null);
   8.216 -      if(val == null && maxLevel >= LEVEL_BACKEND)
   8.217 -      {
   8.218 -        val = BackendConfig.getInstance().get(key, def);
   8.219 -      }
   8.220 -    }
   8.221 -
   8.222 -    return val != null ? val : def;
   8.223 -  }
   8.224 -
   8.225 -  @Override
   8.226 -  public void set(String key, String val)
   8.227 -  {
   8.228 -    set(LEVEL_BACKEND, key, val);
   8.229 -  }
   8.230 -
   8.231 -  public void set(int level, String key, String val)
   8.232 -  {
   8.233 -    switch(level)
   8.234 -    {
   8.235 -      case LEVEL_CLI:
   8.236 -      {
   8.237 -        CommandLineConfig.getInstance().set(key, val);
   8.238 -        break;
   8.239 -      }
   8.240 -      case LEVEL_FILE:
   8.241 -      {
   8.242 -        FileConfig.getInstance().set(key, val);
   8.243 -        break;
   8.244 -      }
   8.245 -      case LEVEL_BACKEND:
   8.246 -      {
   8.247 -        BackendConfig.getInstance().set(key, val);
   8.248 -        break;
   8.249 -      }
   8.250 -    }
   8.251 -  }
   8.252 -
   8.253 +	public void set(int level, String key, String val)
   8.254 +	{
   8.255 +		switch (level) {
   8.256 +			case LEVEL_CLI: {
   8.257 +				CommandLineConfig.getInstance().set(key, val);
   8.258 +				break;
   8.259 +			}
   8.260 +			case LEVEL_FILE: {
   8.261 +				FileConfig.getInstance().set(key, val);
   8.262 +				break;
   8.263 +			}
   8.264 +			case LEVEL_BACKEND: {
   8.265 +				BackendConfig.getInstance().set(key, val);
   8.266 +				break;
   8.267 +			}
   8.268 +		}
   8.269 +	}
   8.270  }
     9.1 --- a/src/org/sonews/config/FileConfig.java	Sun Aug 29 17:43:58 2010 +0200
     9.2 +++ b/src/org/sonews/config/FileConfig.java	Sun Aug 29 18:17:37 2010 +0200
     9.3 @@ -35,136 +35,120 @@
     9.4  class FileConfig extends AbstractConfig
     9.5  {
     9.6  
     9.7 -  private static final Properties defaultConfig = new Properties();
     9.8 -  
     9.9 -  private static FileConfig instance = null;
    9.10 -  
    9.11 -  static
    9.12 -  {
    9.13 -    // Set some default values
    9.14 -    defaultConfig.setProperty(Config.STORAGE_DATABASE, "jdbc:mysql://localhost/sonews");
    9.15 -    defaultConfig.setProperty(Config.STORAGE_DBMSDRIVER, "com.mysql.jdbc.Driver");
    9.16 -    defaultConfig.setProperty(Config.STORAGE_USER, "sonews_user");
    9.17 -    defaultConfig.setProperty(Config.STORAGE_PASSWORD, "mysecret");
    9.18 -    defaultConfig.setProperty(Config.DEBUG, "false");
    9.19 -  }
    9.20 -  
    9.21 -  /**
    9.22 -   * Note: this method is not thread-safe
    9.23 -   * @return A Config instance
    9.24 -   */
    9.25 -  public static synchronized FileConfig getInstance()
    9.26 -  {
    9.27 -    if(instance == null)
    9.28 -    {
    9.29 -      instance = new FileConfig();
    9.30 -    }
    9.31 -    return instance;
    9.32 -  }
    9.33 +	private static final Properties defaultConfig = new Properties();
    9.34 +	private static FileConfig instance = null;
    9.35  
    9.36 -  // Every config instance is initialized with the default values.
    9.37 -  private final Properties settings = (Properties)defaultConfig.clone();
    9.38 +	static {
    9.39 +		// Set some default values
    9.40 +		defaultConfig.setProperty(Config.STORAGE_DATABASE, "jdbc:mysql://localhost/sonews");
    9.41 +		defaultConfig.setProperty(Config.STORAGE_DBMSDRIVER, "com.mysql.jdbc.Driver");
    9.42 +		defaultConfig.setProperty(Config.STORAGE_USER, "sonews_user");
    9.43 +		defaultConfig.setProperty(Config.STORAGE_PASSWORD, "mysecret");
    9.44 +		defaultConfig.setProperty(Config.DEBUG, "false");
    9.45 +	}
    9.46  
    9.47 -  /**
    9.48 -   * Config is a singelton class with only one instance at time.
    9.49 -   * So the constructor is private to prevent the creation of more
    9.50 -   * then one Config instance.
    9.51 -   * @see Config.getInstance() to retrieve an instance of Config
    9.52 -   */
    9.53 -  private FileConfig()
    9.54 -  {
    9.55 -    try
    9.56 -    {
    9.57 -      // Load settings from file
    9.58 -      load();
    9.59 -    }
    9.60 -    catch(IOException ex)
    9.61 -    {
    9.62 -      ex.printStackTrace();
    9.63 -    }
    9.64 -  }
    9.65 +	/**
    9.66 +	 * Note: this method is not thread-safe
    9.67 +	 * @return A Config instance
    9.68 +	 */
    9.69 +	public static synchronized FileConfig getInstance()
    9.70 +	{
    9.71 +		if (instance == null) {
    9.72 +			instance = new FileConfig();
    9.73 +		}
    9.74 +		return instance;
    9.75 +	}
    9.76 +	// Every config instance is initialized with the default values.
    9.77 +	private final Properties settings = (Properties) defaultConfig.clone();
    9.78  
    9.79 -  /**
    9.80 -   * Loads the configuration from the config file. By default this is done
    9.81 -   * by the (private) constructor but it can be useful to reload the config
    9.82 -   * by invoking this method.
    9.83 -   * @throws IOException
    9.84 -   */
    9.85 -  public void load() 
    9.86 -    throws IOException
    9.87 -  {
    9.88 -    FileInputStream in = null;
    9.89 -    
    9.90 -    try
    9.91 -    {
    9.92 -      in = new FileInputStream(
    9.93 -        Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
    9.94 -      settings.load(in);
    9.95 -    }
    9.96 -    catch (FileNotFoundException e)
    9.97 -    {
    9.98 -      // MUST NOT use Log otherwise endless loop
    9.99 -      System.err.println(e.getMessage());
   9.100 -      save();
   9.101 -    }
   9.102 -    finally
   9.103 -    {
   9.104 -      if(in != null)
   9.105 -        in.close();
   9.106 -    }
   9.107 -  }
   9.108 +	/**
   9.109 +	 * Config is a singelton class with only one instance at time.
   9.110 +	 * So the constructor is private to prevent the creation of more
   9.111 +	 * then one Config instance.
   9.112 +	 * @see Config.getInstance() to retrieve an instance of Config
   9.113 +	 */
   9.114 +	private FileConfig()
   9.115 +	{
   9.116 +		try {
   9.117 +			// Load settings from file
   9.118 +			load();
   9.119 +		} catch (IOException ex) {
   9.120 +			ex.printStackTrace();
   9.121 +		}
   9.122 +	}
   9.123  
   9.124 -  /**
   9.125 -   * Saves this Config to the config file. By default this is done
   9.126 -   * at program end.
   9.127 -   * @throws FileNotFoundException
   9.128 -   * @throws IOException
   9.129 -   */
   9.130 -  public void save() throws FileNotFoundException, IOException
   9.131 -  {
   9.132 -    FileOutputStream out = null;
   9.133 -    try
   9.134 -    {
   9.135 -      out = new FileOutputStream(
   9.136 -        Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
   9.137 -      settings.store(out, "SONEWS Config File");
   9.138 -      out.flush();
   9.139 -    }
   9.140 -    catch(IOException ex)
   9.141 -    {
   9.142 -      throw ex;
   9.143 -    }
   9.144 -    finally
   9.145 -    {
   9.146 -      if(out != null)
   9.147 -        out.close();
   9.148 -    }
   9.149 -  }
   9.150 -  
   9.151 -  /**
   9.152 -   * Returns the value that is stored within this config
   9.153 -   * identified by the given key. If the key cannot be found
   9.154 -   * the default value is returned.
   9.155 -   * @param key Key to identify the value.
   9.156 -   * @param def The default value that is returned if the key
   9.157 -   * is not found in this Config.
   9.158 -   * @return
   9.159 -   */
   9.160 -  @Override
   9.161 -  public String get(String key, String def)
   9.162 -  {
   9.163 -    return settings.getProperty(key, def);
   9.164 -  }
   9.165 +	/**
   9.166 +	 * Loads the configuration from the config file. By default this is done
   9.167 +	 * by the (private) constructor but it can be useful to reload the config
   9.168 +	 * by invoking this method.
   9.169 +	 * @throws IOException
   9.170 +	 */
   9.171 +	public void load()
   9.172 +		throws IOException
   9.173 +	{
   9.174 +		FileInputStream in = null;
   9.175  
   9.176 -  /**
   9.177 -   * Sets the value for a given key.
   9.178 -   * @param key
   9.179 -   * @param value
   9.180 -   */
   9.181 -  @Override
   9.182 -  public void set(final String key, final String value)
   9.183 -  {
   9.184 -    settings.setProperty(key, value);
   9.185 -  }
   9.186 +		try {
   9.187 +			in = new FileInputStream(
   9.188 +				Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
   9.189 +			settings.load(in);
   9.190 +		} catch (FileNotFoundException e) {
   9.191 +			// MUST NOT use Log otherwise endless loop
   9.192 +			System.err.println(e.getMessage());
   9.193 +			save();
   9.194 +		} finally {
   9.195 +			if (in != null) {
   9.196 +				in.close();
   9.197 +			}
   9.198 +		}
   9.199 +	}
   9.200  
   9.201 +	/**
   9.202 +	 * Saves this Config to the config file. By default this is done
   9.203 +	 * at program end.
   9.204 +	 * @throws FileNotFoundException
   9.205 +	 * @throws IOException
   9.206 +	 */
   9.207 +	public void save() throws FileNotFoundException, IOException
   9.208 +	{
   9.209 +		FileOutputStream out = null;
   9.210 +		try {
   9.211 +			out = new FileOutputStream(
   9.212 +				Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
   9.213 +			settings.store(out, "SONEWS Config File");
   9.214 +			out.flush();
   9.215 +		} catch (IOException ex) {
   9.216 +			throw ex;
   9.217 +		} finally {
   9.218 +			if (out != null) {
   9.219 +				out.close();
   9.220 +			}
   9.221 +		}
   9.222 +	}
   9.223 +
   9.224 +	/**
   9.225 +	 * Returns the value that is stored within this config
   9.226 +	 * identified by the given key. If the key cannot be found
   9.227 +	 * the default value is returned.
   9.228 +	 * @param key Key to identify the value.
   9.229 +	 * @param def The default value that is returned if the key
   9.230 +	 * is not found in this Config.
   9.231 +	 * @return
   9.232 +	 */
   9.233 +	@Override
   9.234 +	public String get(String key, String def)
   9.235 +	{
   9.236 +		return settings.getProperty(key, def);
   9.237 +	}
   9.238 +
   9.239 +	/**
   9.240 +	 * Sets the value for a given key.
   9.241 +	 * @param key
   9.242 +	 * @param value
   9.243 +	 */
   9.244 +	@Override
   9.245 +	public void set(final String key, final String value)
   9.246 +	{
   9.247 +		settings.setProperty(key, value);
   9.248 +	}
   9.249  }
    10.1 --- a/src/org/sonews/daemon/AbstractDaemon.java	Sun Aug 29 17:43:58 2010 +0200
    10.2 +++ b/src/org/sonews/daemon/AbstractDaemon.java	Sun Aug 29 18:17:37 2010 +0200
    10.3 @@ -32,70 +32,63 @@
    10.4  public abstract class AbstractDaemon extends Thread
    10.5  {
    10.6  
    10.7 -  /** This variable is write synchronized through setRunning */
    10.8 -  private boolean isRunning = false;
    10.9 +	/** This variable is write synchronized through setRunning */
   10.10 +	private boolean isRunning = false;
   10.11  
   10.12 -  /**
   10.13 -   * Protected constructor. Will be called by derived classes.
   10.14 -   */
   10.15 -  protected AbstractDaemon()
   10.16 -  {
   10.17 -    setDaemon(true); // VM will exit when all threads are daemons
   10.18 -    setName(getClass().getSimpleName());
   10.19 -  }
   10.20 -  
   10.21 -  /**
   10.22 -   * @return true if shutdown() was not yet called.
   10.23 -   */
   10.24 -  public boolean isRunning()
   10.25 -  {
   10.26 -    synchronized(this)
   10.27 -    {
   10.28 -      return this.isRunning;
   10.29 -    }
   10.30 -  }
   10.31 -  
   10.32 -  /**
   10.33 -   * Marks this thread to exit soon. Closes the associated JDBCDatabase connection
   10.34 -   * if available.
   10.35 -   * @throws java.sql.SQLException
   10.36 -   */
   10.37 -  public void shutdownNow()
   10.38 -    throws SQLException
   10.39 -  {
   10.40 -    synchronized(this)
   10.41 -    {
   10.42 -      this.isRunning = false;
   10.43 -      StorageManager.disableProvider();
   10.44 -    }
   10.45 -  }
   10.46 -  
   10.47 -  /**
   10.48 -   * Calls shutdownNow() but catches SQLExceptions if occurring.
   10.49 -   */
   10.50 -  public void shutdown()
   10.51 -  {
   10.52 -    try
   10.53 -    {
   10.54 -      shutdownNow();
   10.55 -    }
   10.56 -    catch(SQLException ex)
   10.57 -    {
   10.58 -      Log.get().warning(ex.toString());
   10.59 -    }
   10.60 -  }
   10.61 -  
   10.62 -  /**
   10.63 -   * Starts this daemon.
   10.64 -   */
   10.65 -  @Override
   10.66 -  public void start()
   10.67 -  {
   10.68 -    synchronized(this)
   10.69 -    {
   10.70 -      this.isRunning = true;
   10.71 -    }
   10.72 -    super.start();
   10.73 -  }
   10.74 -  
   10.75 +	/**
   10.76 +	 * Protected constructor. Will be called by derived classes.
   10.77 +	 */
   10.78 +	protected AbstractDaemon()
   10.79 +	{
   10.80 +		setDaemon(true); // VM will exit when all threads are daemons
   10.81 +		setName(getClass().getSimpleName());
   10.82 +	}
   10.83 +
   10.84 +	/**
   10.85 +	 * @return true if shutdown() was not yet called.
   10.86 +	 */
   10.87 +	public boolean isRunning()
   10.88 +	{
   10.89 +		synchronized (this) {
   10.90 +			return this.isRunning;
   10.91 +		}
   10.92 +	}
   10.93 +
   10.94 +	/**
   10.95 +	 * Marks this thread to exit soon. Closes the associated JDBCDatabase connection
   10.96 +	 * if available.
   10.97 +	 * @throws java.sql.SQLException
   10.98 +	 */
   10.99 +	public void shutdownNow()
  10.100 +		throws SQLException
  10.101 +	{
  10.102 +		synchronized (this) {
  10.103 +			this.isRunning = false;
  10.104 +			StorageManager.disableProvider();
  10.105 +		}
  10.106 +	}
  10.107 +
  10.108 +	/**
  10.109 +	 * Calls shutdownNow() but catches SQLExceptions if occurring.
  10.110 +	 */
  10.111 +	public void shutdown()
  10.112 +	{
  10.113 +		try {
  10.114 +			shutdownNow();
  10.115 +		} catch (SQLException ex) {
  10.116 +			Log.get().warning(ex.toString());
  10.117 +		}
  10.118 +	}
  10.119 +
  10.120 +	/**
  10.121 +	 * Starts this daemon.
  10.122 +	 */
  10.123 +	@Override
  10.124 +	public void start()
  10.125 +	{
  10.126 +		synchronized (this) {
  10.127 +			this.isRunning = true;
  10.128 +		}
  10.129 +		super.start();
  10.130 +	}
  10.131  }
    11.1 --- a/src/org/sonews/daemon/ChannelLineBuffers.java	Sun Aug 29 17:43:58 2010 +0200
    11.2 +++ b/src/org/sonews/daemon/ChannelLineBuffers.java	Sun Aug 29 18:17:37 2010 +0200
    11.3 @@ -30,254 +30,223 @@
    11.4   * @author Christian Lins
    11.5   * @since sonews/0.5.0
    11.6   */
    11.7 -public class ChannelLineBuffers 
    11.8 +public class ChannelLineBuffers
    11.9  {
   11.10 -  
   11.11 -  /**
   11.12 -   * Size of one small buffer; 
   11.13 -   * per default this is 512 bytes to fit one standard line.
   11.14 -   */
   11.15 -  public static final int BUFFER_SIZE = 512;
   11.16 -  
   11.17 -  private static int maxCachedBuffers = 2048; // Cached buffers maximum
   11.18 -  
   11.19 -  private static final List<ByteBuffer> freeSmallBuffers
   11.20 -    = new ArrayList<ByteBuffer>(maxCachedBuffers);
   11.21 -  
   11.22 -  /**
   11.23 -   * Allocates a predefined number of direct ByteBuffers (allocated via
   11.24 -   * ByteBuffer.allocateDirect()). This method is Thread-safe, but should only
   11.25 -   * called at startup.
   11.26 -   */
   11.27 -  public static void allocateDirect()
   11.28 -  {
   11.29 -    synchronized(freeSmallBuffers)
   11.30 -    {
   11.31 -      for(int n = 0; n < maxCachedBuffers; n++)
   11.32 -      {
   11.33 -        ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
   11.34 -        freeSmallBuffers.add(buffer);
   11.35 -      }
   11.36 -    }
   11.37 -  }
   11.38 -  
   11.39 -  private ByteBuffer       inputBuffer   = newLineBuffer();
   11.40 -  private List<ByteBuffer> outputBuffers = new ArrayList<ByteBuffer>();
   11.41 -  
   11.42 -  /**
   11.43 -   * Add the given ByteBuffer to the list of buffers to be send to the client.
   11.44 -   * This method is Thread-safe.
   11.45 -   * @param buffer
   11.46 -   * @throws java.nio.channels.ClosedChannelException If the client channel was
   11.47 -   * already closed.
   11.48 -   */
   11.49 -  public void addOutputBuffer(ByteBuffer buffer)
   11.50 -    throws ClosedChannelException
   11.51 -  {
   11.52 -    if(outputBuffers == null)
   11.53 -    {
   11.54 -      throw new ClosedChannelException();
   11.55 -    }
   11.56 -    
   11.57 -    synchronized(outputBuffers)
   11.58 -    {
   11.59 -      outputBuffers.add(buffer);
   11.60 -    }
   11.61 -  }
   11.62 -  
   11.63 -  /**
   11.64 -   * Currently a channel has only one input buffer. This *may* be a bottleneck
   11.65 -   * and should investigated in the future.
   11.66 -   * @param channel
   11.67 -   * @return The input buffer associated with given channel.
   11.68 -   */
   11.69 -  public ByteBuffer getInputBuffer()
   11.70 -  {
   11.71 -    return inputBuffer;
   11.72 -  }
   11.73 -  
   11.74 -  /**
   11.75 -   * Returns the current output buffer for writing(!) to SocketChannel.
   11.76 -   * @param channel
   11.77 -   * @return The next input buffer that contains unprocessed data or null
   11.78 -   * if the connection was closed or there are no more unprocessed buffers.
   11.79 -   */
   11.80 -  public ByteBuffer getOutputBuffer()
   11.81 -  {
   11.82 -    synchronized(outputBuffers)
   11.83 -    {
   11.84 -      if(outputBuffers == null || outputBuffers.isEmpty())
   11.85 -      {
   11.86 -        return null;
   11.87 -      }
   11.88 -      else
   11.89 -      {
   11.90 -        ByteBuffer buffer = outputBuffers.get(0);
   11.91 -        if(buffer.remaining() == 0)
   11.92 -        {
   11.93 -          outputBuffers.remove(0);
   11.94 -          // Add old buffers to the list of free buffers
   11.95 -          recycleBuffer(buffer);
   11.96 -          buffer = getOutputBuffer();
   11.97 -        }
   11.98 -        return buffer;
   11.99 -      }
  11.100 -    }
  11.101 -  }
  11.102  
  11.103 -  /**
  11.104 -   * @return false if there are output buffers pending to be written to the
  11.105 -   * client.
  11.106 -   */
  11.107 -  boolean isOutputBufferEmpty()
  11.108 -  {
  11.109 -    synchronized(outputBuffers)
  11.110 -    {
  11.111 -      return outputBuffers.isEmpty();
  11.112 -    }
  11.113 -  }
  11.114 -  
  11.115 -  /**
  11.116 -   * Goes through the input buffer of the given channel and searches
  11.117 -   * for next line terminator. If a '\n' is found, the bytes up to the
  11.118 -   * line terminator are returned as array of bytes (the line terminator
  11.119 -   * is omitted). If none is found the method returns null.
  11.120 -   * @param channel
  11.121 -   * @return A ByteBuffer wrapping the line.
  11.122 -   */
  11.123 -  ByteBuffer nextInputLine()
  11.124 -  {
  11.125 -    if(inputBuffer == null)
  11.126 -    {
  11.127 -      return null;
  11.128 -    }
  11.129 -    
  11.130 -    synchronized(inputBuffer)
  11.131 -    {
  11.132 -      ByteBuffer buffer = inputBuffer;
  11.133 +	/**
  11.134 +	 * Size of one small buffer;
  11.135 +	 * per default this is 512 bytes to fit one standard line.
  11.136 +	 */
  11.137 +	public static final int BUFFER_SIZE = 512;
  11.138 +	private static int maxCachedBuffers = 2048; // Cached buffers maximum
  11.139 +	private static final List<ByteBuffer> freeSmallBuffers = new ArrayList<ByteBuffer>(maxCachedBuffers);
  11.140  
  11.141 -      // Mark the current write position
  11.142 -      int mark = buffer.position();
  11.143 +	/**
  11.144 +	 * Allocates a predefined number of direct ByteBuffers (allocated via
  11.145 +	 * ByteBuffer.allocateDirect()). This method is Thread-safe, but should only
  11.146 +	 * called at startup.
  11.147 +	 */
  11.148 +	public static void allocateDirect()
  11.149 +	{
  11.150 +		synchronized (freeSmallBuffers) {
  11.151 +			for (int n = 0; n < maxCachedBuffers; n++) {
  11.152 +				ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
  11.153 +				freeSmallBuffers.add(buffer);
  11.154 +			}
  11.155 +		}
  11.156 +	}
  11.157 +	private ByteBuffer inputBuffer = newLineBuffer();
  11.158 +	private List<ByteBuffer> outputBuffers = new ArrayList<ByteBuffer>();
  11.159  
  11.160 -      // Set position to 0 and limit to current position
  11.161 -      buffer.flip();
  11.162 +	/**
  11.163 +	 * Add the given ByteBuffer to the list of buffers to be send to the client.
  11.164 +	 * This method is Thread-safe.
  11.165 +	 * @param buffer
  11.166 +	 * @throws java.nio.channels.ClosedChannelException If the client channel was
  11.167 +	 * already closed.
  11.168 +	 */
  11.169 +	public void addOutputBuffer(ByteBuffer buffer)
  11.170 +		throws ClosedChannelException
  11.171 +	{
  11.172 +		if (outputBuffers == null) {
  11.173 +			throw new ClosedChannelException();
  11.174 +		}
  11.175  
  11.176 -      ByteBuffer lineBuffer = newLineBuffer();
  11.177 +		synchronized (outputBuffers) {
  11.178 +			outputBuffers.add(buffer);
  11.179 +		}
  11.180 +	}
  11.181  
  11.182 -      while (buffer.position() < buffer.limit())
  11.183 -      {
  11.184 -        byte b = buffer.get();
  11.185 -        if (b == 10) // '\n'
  11.186 -        {
  11.187 -          // The bytes between the buffer's current position and its limit, 
  11.188 -          // if any, are copied to the beginning of the buffer. That is, the 
  11.189 -          // byte at index p = position() is copied to index zero, the byte at 
  11.190 -          // index p + 1 is copied to index one, and so forth until the byte 
  11.191 -          // at index limit() - 1 is copied to index n = limit() - 1 - p. 
  11.192 -          // The buffer's position is then set to n+1 and its limit is set to 
  11.193 -          // its capacity.
  11.194 -          buffer.compact();
  11.195 +	/**
  11.196 +	 * Currently a channel has only one input buffer. This *may* be a bottleneck
  11.197 +	 * and should investigated in the future.
  11.198 +	 * @param channel
  11.199 +	 * @return The input buffer associated with given channel.
  11.200 +	 */
  11.201 +	public ByteBuffer getInputBuffer()
  11.202 +	{
  11.203 +		return inputBuffer;
  11.204 +	}
  11.205  
  11.206 -          lineBuffer.flip(); // limit to position, position to 0
  11.207 -          return lineBuffer;
  11.208 -        }
  11.209 -        else
  11.210 -        {
  11.211 -          lineBuffer.put(b);
  11.212 -        }
  11.213 -      }
  11.214 +	/**
  11.215 +	 * Returns the current output buffer for writing(!) to SocketChannel.
  11.216 +	 * @param channel
  11.217 +	 * @return The next input buffer that contains unprocessed data or null
  11.218 +	 * if the connection was closed or there are no more unprocessed buffers.
  11.219 +	 */
  11.220 +	public ByteBuffer getOutputBuffer()
  11.221 +	{
  11.222 +		synchronized (outputBuffers) {
  11.223 +			if (outputBuffers == null || outputBuffers.isEmpty()) {
  11.224 +				return null;
  11.225 +			} else {
  11.226 +				ByteBuffer buffer = outputBuffers.get(0);
  11.227 +				if (buffer.remaining() == 0) {
  11.228 +					outputBuffers.remove(0);
  11.229 +					// Add old buffers to the list of free buffers
  11.230 +					recycleBuffer(buffer);
  11.231 +					buffer = getOutputBuffer();
  11.232 +				}
  11.233 +				return buffer;
  11.234 +			}
  11.235 +		}
  11.236 +	}
  11.237  
  11.238 -      buffer.limit(BUFFER_SIZE);
  11.239 -      buffer.position(mark);
  11.240 +	/**
  11.241 +	 * @return false if there are output buffers pending to be written to the
  11.242 +	 * client.
  11.243 +	 */
  11.244 +	boolean isOutputBufferEmpty()
  11.245 +	{
  11.246 +		synchronized (outputBuffers) {
  11.247 +			return outputBuffers.isEmpty();
  11.248 +		}
  11.249 +	}
  11.250  
  11.251 -      if(buffer.hasRemaining())
  11.252 -      {
  11.253 -        return null;
  11.254 -      }
  11.255 -      else
  11.256 -      {
  11.257 -        // In the first 512 was no newline found, so the input is not standard
  11.258 -        // compliant. We return the current buffer as new line and add a space
  11.259 -        // to the beginning of the next line which corrects some overlong header
  11.260 -        // lines.
  11.261 -        inputBuffer = newLineBuffer();
  11.262 -        inputBuffer.put((byte)' ');
  11.263 -        buffer.flip();
  11.264 -        return buffer;
  11.265 -      }
  11.266 -    }
  11.267 -  }
  11.268 -  
  11.269 -  /**
  11.270 -   * Returns a at least 512 bytes long ByteBuffer ready for usage.
  11.271 -   * The method first try to reuse an already allocated (cached) buffer but
  11.272 -   * if that fails returns a newly allocated direct buffer.
  11.273 -   * Use recycleBuffer() method when you do not longer use the allocated buffer.
  11.274 -   */
  11.275 -  static ByteBuffer newLineBuffer()
  11.276 -  {
  11.277 -    ByteBuffer buf = null;
  11.278 -    synchronized(freeSmallBuffers)
  11.279 -    {
  11.280 -      if(!freeSmallBuffers.isEmpty())
  11.281 -      {
  11.282 -        buf = freeSmallBuffers.remove(0);
  11.283 -      }
  11.284 -    }
  11.285 -      
  11.286 -    if(buf == null)
  11.287 -    {
  11.288 -      // Allocate a non-direct buffer
  11.289 -      buf = ByteBuffer.allocate(BUFFER_SIZE);
  11.290 -    }
  11.291 -    
  11.292 -    assert buf.position() == 0;
  11.293 -    assert buf.limit() >= BUFFER_SIZE;
  11.294 -    
  11.295 -    return buf;
  11.296 -  }
  11.297 -  
  11.298 -  /**
  11.299 -   * Adds the given buffer to the list of free buffers if it is a valuable
  11.300 -   * direct allocated buffer.
  11.301 -   * @param buffer
  11.302 -   */
  11.303 -  public static void recycleBuffer(ByteBuffer buffer)
  11.304 -  {
  11.305 -    assert buffer != null;
  11.306 +	/**
  11.307 +	 * Goes through the input buffer of the given channel and searches
  11.308 +	 * for next line terminator. If a '\n' is found, the bytes up to the
  11.309 +	 * line terminator are returned as array of bytes (the line terminator
  11.310 +	 * is omitted). If none is found the method returns null.
  11.311 +	 * @param channel
  11.312 +	 * @return A ByteBuffer wrapping the line.
  11.313 +	 */
  11.314 +	ByteBuffer nextInputLine()
  11.315 +	{
  11.316 +		if (inputBuffer == null) {
  11.317 +			return null;
  11.318 +		}
  11.319  
  11.320 -    if(buffer.isDirect())
  11.321 -    {
  11.322 -      assert buffer.capacity() >= BUFFER_SIZE;
  11.323 -      
  11.324 -      // Add old buffers to the list of free buffers
  11.325 -      synchronized(freeSmallBuffers)
  11.326 -      {
  11.327 -        buffer.clear(); // Set position to 0 and limit to capacity
  11.328 -        freeSmallBuffers.add(buffer);
  11.329 -      }
  11.330 -    } // if(buffer.isDirect())
  11.331 -  }
  11.332 -  
  11.333 -  /**
  11.334 -   * Recycles all buffers of this ChannelLineBuffers object.
  11.335 -   */
  11.336 -  public void recycleBuffers()
  11.337 -  {
  11.338 -    synchronized(inputBuffer)
  11.339 -    {
  11.340 -      recycleBuffer(inputBuffer);
  11.341 -      this.inputBuffer = null;
  11.342 -    }
  11.343 -    
  11.344 -    synchronized(outputBuffers)
  11.345 -    {
  11.346 -      for(ByteBuffer buf : outputBuffers)
  11.347 -      {
  11.348 -        recycleBuffer(buf);
  11.349 -      }
  11.350 -      outputBuffers = null;
  11.351 -    }
  11.352 -  }
  11.353 -  
  11.354 +		synchronized (inputBuffer) {
  11.355 +			ByteBuffer buffer = inputBuffer;
  11.356 +
  11.357 +			// Mark the current write position
  11.358 +			int mark = buffer.position();
  11.359 +
  11.360 +			// Set position to 0 and limit to current position
  11.361 +			buffer.flip();
  11.362 +
  11.363 +			ByteBuffer lineBuffer = newLineBuffer();
  11.364 +
  11.365 +			while (buffer.position() < buffer.limit()) {
  11.366 +				byte b = buffer.get();
  11.367 +				if (b == 10) // '\n'
  11.368 +				{
  11.369 +					// The bytes between the buffer's current position and its limit,
  11.370 +					// if any, are copied to the beginning of the buffer. That is, the
  11.371 +					// byte at index p = position() is copied to index zero, the byte at
  11.372 +					// index p + 1 is copied to index one, and so forth until the byte
  11.373 +					// at index limit() - 1 is copied to index n = limit() - 1 - p.
  11.374 +					// The buffer's position is then set to n+1 and its limit is set to
  11.375 +					// its capacity.
  11.376 +					buffer.compact();
  11.377 +
  11.378 +					lineBuffer.flip(); // limit to position, position to 0
  11.379 +					return lineBuffer;
  11.380 +				} else {
  11.381 +					lineBuffer.put(b);
  11.382 +				}
  11.383 +			}
  11.384 +
  11.385 +			buffer.limit(BUFFER_SIZE);
  11.386 +			buffer.position(mark);
  11.387 +
  11.388 +			if (buffer.hasRemaining()) {
  11.389 +				return null;
  11.390 +			} else {
  11.391 +				// In the first 512 was no newline found, so the input is not standard
  11.392 +				// compliant. We return the current buffer as new line and add a space
  11.393 +				// to the beginning of the next line which corrects some overlong header
  11.394 +				// lines.
  11.395 +				inputBuffer = newLineBuffer();
  11.396 +				inputBuffer.put((byte) ' ');
  11.397 +				buffer.flip();
  11.398 +				return buffer;
  11.399 +			}
  11.400 +		}
  11.401 +	}
  11.402 +
  11.403 +	/**
  11.404 +	 * Returns a at least 512 bytes long ByteBuffer ready for usage.
  11.405 +	 * The method first try to reuse an already allocated (cached) buffer but
  11.406 +	 * if that fails returns a newly allocated direct buffer.
  11.407 +	 * Use recycleBuffer() method when you do not longer use the allocated buffer.
  11.408 +	 */
  11.409 +	static ByteBuffer newLineBuffer()
  11.410 +	{
  11.411 +		ByteBuffer buf = null;
  11.412 +		synchronized (freeSmallBuffers) {
  11.413 +			if (!freeSmallBuffers.isEmpty()) {
  11.414 +				buf = freeSmallBuffers.remove(0);
  11.415 +			}
  11.416 +		}
  11.417 +
  11.418 +		if (buf == null) {
  11.419 +			// Allocate a non-direct buffer
  11.420 +			buf = ByteBuffer.allocate(BUFFER_SIZE);
  11.421 +		}
  11.422 +
  11.423 +		assert buf.position() == 0;
  11.424 +		assert buf.limit() >= BUFFER_SIZE;
  11.425 +
  11.426 +		return buf;
  11.427 +	}
  11.428 +
  11.429 +	/**
  11.430 +	 * Adds the given buffer to the list of free buffers if it is a valuable
  11.431 +	 * direct allocated buffer.
  11.432 +	 * @param buffer
  11.433 +	 */
  11.434 +	public static void recycleBuffer(ByteBuffer buffer)
  11.435 +	{
  11.436 +		assert buffer != null;
  11.437 +
  11.438 +		if (buffer.isDirect()) {
  11.439 +			assert buffer.capacity() >= BUFFER_SIZE;
  11.440 +
  11.441 +			// Add old buffers to the list of free buffers
  11.442 +			synchronized (freeSmallBuffers) {
  11.443 +				buffer.clear(); // Set position to 0 and limit to capacity
  11.444 +				freeSmallBuffers.add(buffer);
  11.445 +			}
  11.446 +		} // if(buffer.isDirect())
  11.447 +	}
  11.448 +
  11.449 +	/**
  11.450 +	 * Recycles all buffers of this ChannelLineBuffers object.
  11.451 +	 */
  11.452 +	public void recycleBuffers()
  11.453 +	{
  11.454 +		synchronized (inputBuffer) {
  11.455 +			recycleBuffer(inputBuffer);
  11.456 +			this.inputBuffer = null;
  11.457 +		}
  11.458 +
  11.459 +		synchronized (outputBuffers) {
  11.460 +			for (ByteBuffer buf : outputBuffers) {
  11.461 +				recycleBuffer(buf);
  11.462 +			}
  11.463 +			outputBuffers = null;
  11.464 +		}
  11.465 +	}
  11.466  }
    12.1 --- a/src/org/sonews/daemon/ChannelReader.java	Sun Aug 29 17:43:58 2010 +0200
    12.2 +++ b/src/org/sonews/daemon/ChannelReader.java	Sun Aug 29 18:17:37 2010 +0200
    12.3 @@ -37,166 +37,141 @@
    12.4  class ChannelReader extends AbstractDaemon
    12.5  {
    12.6  
    12.7 -  private static ChannelReader instance = new ChannelReader();
    12.8 +	private static ChannelReader instance = new ChannelReader();
    12.9  
   12.10 -  /**
   12.11 -   * @return Active ChannelReader instance.
   12.12 -   */
   12.13 -  public static ChannelReader getInstance()
   12.14 -  {
   12.15 -    return instance;
   12.16 -  }
   12.17 -  
   12.18 -  private Selector selector = null;
   12.19 -  
   12.20 -  protected ChannelReader()
   12.21 -  {
   12.22 -  }
   12.23 -  
   12.24 -  /**
   12.25 -   * Sets the selector which is used by this reader to determine the channel
   12.26 -   * to read from.
   12.27 -   * @param selector
   12.28 -   */
   12.29 -  public void setSelector(final Selector selector)
   12.30 -  {
   12.31 -    this.selector = selector;
   12.32 -  }
   12.33 -  
   12.34 -  /**
   12.35 -   * Run loop. Blocks until some data is available in a channel.
   12.36 -   */
   12.37 -  @Override
   12.38 -  public void run()
   12.39 -  {
   12.40 -    assert selector != null;
   12.41 +	/**
   12.42 +	 * @return Active ChannelReader instance.
   12.43 +	 */
   12.44 +	public static ChannelReader getInstance()
   12.45 +	{
   12.46 +		return instance;
   12.47 +	}
   12.48 +	private Selector selector = null;
   12.49  
   12.50 -    while(isRunning())
   12.51 -    {
   12.52 -      try
   12.53 -      {
   12.54 -        // select() blocks until some SelectableChannels are ready for
   12.55 -        // processing. There is no need to lock the selector as we have only
   12.56 -        // one thread per selector.
   12.57 -        selector.select();
   12.58 +	protected ChannelReader()
   12.59 +	{
   12.60 +	}
   12.61  
   12.62 -        // Get list of selection keys with pending events.
   12.63 -        // Note: the selected key set is not thread-safe
   12.64 -        SocketChannel channel = null;
   12.65 -        NNTPConnection conn = null;
   12.66 -        final Set<SelectionKey> selKeys = selector.selectedKeys();
   12.67 -        SelectionKey selKey = null;
   12.68 +	/**
   12.69 +	 * Sets the selector which is used by this reader to determine the channel
   12.70 +	 * to read from.
   12.71 +	 * @param selector
   12.72 +	 */
   12.73 +	public void setSelector(final Selector selector)
   12.74 +	{
   12.75 +		this.selector = selector;
   12.76 +	}
   12.77  
   12.78 -        synchronized (selKeys)
   12.79 -        {
   12.80 -          Iterator it = selKeys.iterator();
   12.81 +	/**
   12.82 +	 * Run loop. Blocks until some data is available in a channel.
   12.83 +	 */
   12.84 +	@Override
   12.85 +	public void run()
   12.86 +	{
   12.87 +		assert selector != null;
   12.88  
   12.89 -          // Process the first pending event
   12.90 -          while (it.hasNext())
   12.91 -          {
   12.92 -            selKey = (SelectionKey) it.next();
   12.93 -            channel = (SocketChannel) selKey.channel();
   12.94 -            conn = Connections.getInstance().get(channel);
   12.95 +		while (isRunning()) {
   12.96 +			try {
   12.97 +				// select() blocks until some SelectableChannels are ready for
   12.98 +				// processing. There is no need to lock the selector as we have only
   12.99 +				// one thread per selector.
  12.100 +				selector.select();
  12.101  
  12.102 -            // Because we cannot lock the selKey as that would cause a deadlock
  12.103 -            // we lock the connection. To preserve the order of the received
  12.104 -            // byte blocks a selection key for a connection that has pending
  12.105 -            // read events is skipped.
  12.106 -            if (conn == null || conn.tryReadLock())
  12.107 -            {
  12.108 -              // Remove from set to indicate that it's being processed
  12.109 -              it.remove();
  12.110 -              if (conn != null)
  12.111 -              {
  12.112 -                break; // End while loop
  12.113 -              }
  12.114 -            }
  12.115 -            else
  12.116 -            {
  12.117 -              selKey = null;
  12.118 -              channel = null;
  12.119 -              conn = null;
  12.120 -            }
  12.121 -          }
  12.122 -        }
  12.123 +				// Get list of selection keys with pending events.
  12.124 +				// Note: the selected key set is not thread-safe
  12.125 +				SocketChannel channel = null;
  12.126 +				NNTPConnection conn = null;
  12.127 +				final Set<SelectionKey> selKeys = selector.selectedKeys();
  12.128 +				SelectionKey selKey = null;
  12.129  
  12.130 -        // Do not lock the selKeys while processing because this causes
  12.131 -        // a deadlock in sun.nio.ch.SelectorImpl.lockAndDoSelect()
  12.132 -        if (selKey != null && channel != null && conn != null)
  12.133 -        {
  12.134 -          processSelectionKey(conn, channel, selKey);
  12.135 -          conn.unlockReadLock();
  12.136 -        }
  12.137 +				synchronized (selKeys) {
  12.138 +					Iterator it = selKeys.iterator();
  12.139  
  12.140 -      }
  12.141 -      catch(CancelledKeyException ex)
  12.142 -      {
  12.143 -        Log.get().warning("ChannelReader.run(): " + ex);
  12.144 -        Log.get().log(Level.INFO, "", ex);
  12.145 -      }
  12.146 -      catch(Exception ex)
  12.147 -      {
  12.148 -        ex.printStackTrace();
  12.149 -      }
  12.150 -      
  12.151 -      // Eventually wait for a register operation
  12.152 -      synchronized (NNTPDaemon.RegisterGate)
  12.153 -      {
  12.154 -      // Do nothing; FindBugs may warn about an empty synchronized 
  12.155 -      // statement, but we cannot use a wait()/notify() mechanism here.
  12.156 -      // If we used something like RegisterGate.wait() we block here
  12.157 -      // until the NNTPDaemon calls notify(). But the daemon only
  12.158 -      // calls notify() if itself is NOT blocked in the listening socket.
  12.159 -      }
  12.160 -    } // while(isRunning())
  12.161 -  }
  12.162 -  
  12.163 -  private void processSelectionKey(final NNTPConnection connection,
  12.164 -    final SocketChannel socketChannel, final SelectionKey selKey)
  12.165 -    throws InterruptedException, IOException
  12.166 -  {
  12.167 -    assert selKey != null;
  12.168 -    assert selKey.isReadable();
  12.169 -    
  12.170 -    // Some bytes are available for reading
  12.171 -    if(selKey.isValid())
  12.172 -    {   
  12.173 -      // Lock the channel
  12.174 -      //synchronized(socketChannel)
  12.175 -      {
  12.176 -        // Read the data into the appropriate buffer
  12.177 -        ByteBuffer buf = connection.getInputBuffer();
  12.178 -        int read = -1;
  12.179 -        try 
  12.180 -        {
  12.181 -          read = socketChannel.read(buf);
  12.182 -        }
  12.183 -        catch(IOException ex)
  12.184 -        {
  12.185 -          // The connection was probably closed by the remote host
  12.186 -          // in a non-clean fashion
  12.187 -          Log.get().info("ChannelReader.processSelectionKey(): " + ex);
  12.188 -        }
  12.189 -        catch(Exception ex) 
  12.190 -        {
  12.191 -          Log.get().warning("ChannelReader.processSelectionKey(): " + ex);
  12.192 -        }
  12.193 -        
  12.194 -        if(read == -1) // End of stream
  12.195 -        {
  12.196 -          selKey.cancel();
  12.197 -        }
  12.198 -        else if(read > 0) // If some data was read
  12.199 -        {
  12.200 -          ConnectionWorker.addChannel(socketChannel);
  12.201 -        }
  12.202 -      }
  12.203 -    }
  12.204 -    else
  12.205 -    {
  12.206 -      // Should not happen
  12.207 -      Log.get().severe("Should not happen: " + selKey.toString());
  12.208 -    }
  12.209 -  }
  12.210 -  
  12.211 +					// Process the first pending event
  12.212 +					while (it.hasNext()) {
  12.213 +						selKey = (SelectionKey) it.next();
  12.214 +						channel = (SocketChannel) selKey.channel();
  12.215 +						conn = Connections.getInstance().get(channel);
  12.216 +
  12.217 +						// Because we cannot lock the selKey as that would cause a deadlock
  12.218 +						// we lock the connection. To preserve the order of the received
  12.219 +						// byte blocks a selection key for a connection that has pending
  12.220 +						// read events is skipped.
  12.221 +						if (conn == null || conn.tryReadLock()) {
  12.222 +							// Remove from set to indicate that it's being processed
  12.223 +							it.remove();
  12.224 +							if (conn != null) {
  12.225 +								break; // End while loop
  12.226 +							}
  12.227 +						} else {
  12.228 +							selKey = null;
  12.229 +							channel = null;
  12.230 +							conn = null;
  12.231 +						}
  12.232 +					}
  12.233 +				}
  12.234 +
  12.235 +				// Do not lock the selKeys while processing because this causes
  12.236 +				// a deadlock in sun.nio.ch.SelectorImpl.lockAndDoSelect()
  12.237 +				if (selKey != null && channel != null && conn != null) {
  12.238 +					processSelectionKey(conn, channel, selKey);
  12.239 +					conn.unlockReadLock();
  12.240 +				}
  12.241 +
  12.242 +			} catch (CancelledKeyException ex) {
  12.243 +				Log.get().warning("ChannelReader.run(): " + ex);
  12.244 +				Log.get().log(Level.INFO, "", ex);
  12.245 +			} catch (Exception ex) {
  12.246 +				ex.printStackTrace();
  12.247 +			}
  12.248 +
  12.249 +			// Eventually wait for a register operation
  12.250 +			synchronized (NNTPDaemon.RegisterGate) {
  12.251 +				// Do nothing; FindBugs may warn about an empty synchronized
  12.252 +				// statement, but we cannot use a wait()/notify() mechanism here.
  12.253 +				// If we used something like RegisterGate.wait() we block here
  12.254 +				// until the NNTPDaemon calls notify(). But the daemon only
  12.255 +				// calls notify() if itself is NOT blocked in the listening socket.
  12.256 +			}
  12.257 +		} // while(isRunning())
  12.258 +	}
  12.259 +
  12.260 +	private void processSelectionKey(final NNTPConnection connection,
  12.261 +		final SocketChannel socketChannel, final SelectionKey selKey)
  12.262 +		throws InterruptedException, IOException
  12.263 +	{
  12.264 +		assert selKey != null;
  12.265 +		assert selKey.isReadable();
  12.266 +
  12.267 +		// Some bytes are available for reading
  12.268 +		if (selKey.isValid()) {
  12.269 +			// Lock the channel
  12.270 +			//synchronized(socketChannel)
  12.271 +			{
  12.272 +				// Read the data into the appropriate buffer
  12.273 +				ByteBuffer buf = connection.getInputBuffer();
  12.274 +				int read = -1;
  12.275 +				try {
  12.276 +					read = socketChannel.read(buf);
  12.277 +				} catch (IOException ex) {
  12.278 +					// The connection was probably closed by the remote host
  12.279 +					// in a non-clean fashion
  12.280 +					Log.get().info("ChannelReader.processSelectionKey(): " + ex);
  12.281 +				} catch (Exception ex) {
  12.282 +					Log.get().warning("ChannelReader.processSelectionKey(): " + ex);
  12.283 +				}
  12.284 +
  12.285 +				if (read == -1) // End of stream
  12.286 +				{
  12.287 +					selKey.cancel();
  12.288 +				} else if (read > 0) // If some data was read
  12.289 +				{
  12.290 +					ConnectionWorker.addChannel(socketChannel);
  12.291 +				}
  12.292 +			}
  12.293 +		} else {
  12.294 +			// Should not happen
  12.295 +			Log.get().severe("Should not happen: " + selKey.toString());
  12.296 +		}
  12.297 +	}
  12.298  }
    13.1 --- a/src/org/sonews/daemon/ChannelWriter.java	Sun Aug 29 17:43:58 2010 +0200
    13.2 +++ b/src/org/sonews/daemon/ChannelWriter.java	Sun Aug 29 18:17:37 2010 +0200
    13.3 @@ -35,176 +35,151 @@
    13.4  class ChannelWriter extends AbstractDaemon
    13.5  {
    13.6  
    13.7 -  private static ChannelWriter instance = new ChannelWriter();
    13.8 +	private static ChannelWriter instance = new ChannelWriter();
    13.9  
   13.10 -  /**
   13.11 -   * @return Returns the active ChannelWriter instance.
   13.12 -   */
   13.13 -  public static ChannelWriter getInstance()
   13.14 -  {
   13.15 -    return instance;
   13.16 -  }
   13.17 -  
   13.18 -  private Selector selector = null;
   13.19 -  
   13.20 -  protected ChannelWriter()
   13.21 -  {
   13.22 -  }
   13.23 -  
   13.24 -  /**
   13.25 -   * @return Selector associated with this instance.
   13.26 -   */
   13.27 -  public Selector getSelector()
   13.28 -  {
   13.29 -    return this.selector;
   13.30 -  }
   13.31 -  
   13.32 -  /**
   13.33 -   * Sets the selector that is used by this ChannelWriter.
   13.34 -   * @param selector
   13.35 -   */
   13.36 -  public void setSelector(final Selector selector)
   13.37 -  {
   13.38 -    this.selector = selector;
   13.39 -  }
   13.40 -  
   13.41 -  /**
   13.42 -   * Run loop.
   13.43 -   */
   13.44 -  @Override
   13.45 -  public void run()
   13.46 -  {
   13.47 -    assert selector != null;
   13.48 +	/**
   13.49 +	 * @return Returns the active ChannelWriter instance.
   13.50 +	 */
   13.51 +	public static ChannelWriter getInstance()
   13.52 +	{
   13.53 +		return instance;
   13.54 +	}
   13.55 +	private Selector selector = null;
   13.56  
   13.57 -    while(isRunning())
   13.58 -    {
   13.59 -      try
   13.60 -      {
   13.61 -        SelectionKey   selKey        = null;
   13.62 -        SocketChannel  socketChannel = null;
   13.63 -        NNTPConnection connection    = null;
   13.64 +	protected ChannelWriter()
   13.65 +	{
   13.66 +	}
   13.67  
   13.68 -        // select() blocks until some SelectableChannels are ready for
   13.69 -        // processing. There is no need to synchronize the selector as we
   13.70 -        // have only one thread per selector.
   13.71 -        selector.select(); // The return value of select can be ignored
   13.72 +	/**
   13.73 +	 * @return Selector associated with this instance.
   13.74 +	 */
   13.75 +	public Selector getSelector()
   13.76 +	{
   13.77 +		return this.selector;
   13.78 +	}
   13.79  
   13.80 -        // Get list of selection keys with pending OP_WRITE events.
   13.81 -        // The keySET is not thread-safe whereas the keys itself are.
   13.82 -        Iterator it = selector.selectedKeys().iterator();
   13.83 +	/**
   13.84 +	 * Sets the selector that is used by this ChannelWriter.
   13.85 +	 * @param selector
   13.86 +	 */
   13.87 +	public void setSelector(final Selector selector)
   13.88 +	{
   13.89 +		this.selector = selector;
   13.90 +	}
   13.91  
   13.92 -        while (it.hasNext())
   13.93 -        {
   13.94 -          // We remove the first event from the set and store it for
   13.95 -          // later processing.
   13.96 -          selKey = (SelectionKey) it.next();
   13.97 -          socketChannel = (SocketChannel) selKey.channel();
   13.98 -          connection = Connections.getInstance().get(socketChannel);
   13.99 +	/**
  13.100 +	 * Run loop.
  13.101 +	 */
  13.102 +	@Override
  13.103 +	public void run()
  13.104 +	{
  13.105 +		assert selector != null;
  13.106  
  13.107 -          it.remove();
  13.108 -          if (connection != null)
  13.109 -          {
  13.110 -            break;
  13.111 -          }
  13.112 -          else
  13.113 -          {
  13.114 -            selKey = null;
  13.115 -          }
  13.116 -        }
  13.117 -        
  13.118 -        if (selKey != null)
  13.119 -        {
  13.120 -          try
  13.121 -          {
  13.122 -            // Process the selected key.
  13.123 -            // As there is only one OP_WRITE key for a given channel, we need
  13.124 -            // not to synchronize this processing to retain the order.
  13.125 -            processSelectionKey(connection, socketChannel, selKey);
  13.126 -          }
  13.127 -          catch (IOException ex)
  13.128 -          {
  13.129 -            Log.get().warning("Error writing to channel: " + ex);
  13.130 +		while (isRunning()) {
  13.131 +			try {
  13.132 +				SelectionKey selKey = null;
  13.133 +				SocketChannel socketChannel = null;
  13.134 +				NNTPConnection connection = null;
  13.135  
  13.136 -            // Cancel write events for this channel
  13.137 -            selKey.cancel();
  13.138 -            connection.shutdownInput();
  13.139 -            connection.shutdownOutput();
  13.140 -          }
  13.141 -        }
  13.142 -        
  13.143 -        // Eventually wait for a register operation
  13.144 -        synchronized(NNTPDaemon.RegisterGate) { /* do nothing */ }
  13.145 -      }
  13.146 -      catch(CancelledKeyException ex)
  13.147 -      {
  13.148 -        Log.get().info("ChannelWriter.run(): " + ex);
  13.149 -      }
  13.150 -      catch(Exception ex)
  13.151 -      {
  13.152 -        ex.printStackTrace();
  13.153 -      }
  13.154 -    } // while(isRunning())
  13.155 -  }
  13.156 -  
  13.157 -  private void processSelectionKey(final NNTPConnection connection,
  13.158 -    final SocketChannel socketChannel, final SelectionKey selKey)
  13.159 -    throws InterruptedException, IOException
  13.160 -  {
  13.161 -    assert connection != null;
  13.162 -    assert socketChannel != null;
  13.163 -    assert selKey != null;
  13.164 -    assert selKey.isWritable();
  13.165 +				// select() blocks until some SelectableChannels are ready for
  13.166 +				// processing. There is no need to synchronize the selector as we
  13.167 +				// have only one thread per selector.
  13.168 +				selector.select(); // The return value of select can be ignored
  13.169  
  13.170 -    // SocketChannel is ready for writing
  13.171 -    if(selKey.isValid())
  13.172 -    {
  13.173 -      // Lock the socket channel
  13.174 -      synchronized(socketChannel)
  13.175 -      {
  13.176 -        // Get next output buffer
  13.177 -        ByteBuffer buf = connection.getOutputBuffer();
  13.178 -        if(buf == null)
  13.179 -        {
  13.180 -          // Currently we have nothing to write, so we stop the writeable
  13.181 -          // events until we have something to write to the socket channel
  13.182 -          //selKey.cancel();
  13.183 -          selKey.interestOps(0);
  13.184 -          // Update activity timestamp to prevent too early disconnects
  13.185 -          // on slow client connections
  13.186 -          connection.setLastActivity(System.currentTimeMillis());
  13.187 -          return;
  13.188 -        }
  13.189 - 
  13.190 -        while(buf != null) // There is data to be send
  13.191 -        {
  13.192 -          // Write buffer to socket channel; this method does not block
  13.193 -          if(socketChannel.write(buf) <= 0)
  13.194 -          {
  13.195 -            // Perhaps there is data to be written, but the SocketChannel's
  13.196 -            // buffer is full, so we stop writing to until the next event.
  13.197 -            break;
  13.198 -          }
  13.199 -          else
  13.200 -          {
  13.201 -            // Retrieve next buffer if available; method may return the same
  13.202 -            // buffer instance if it still have some bytes remaining
  13.203 -            buf = connection.getOutputBuffer();
  13.204 -          }
  13.205 -        }
  13.206 -      }
  13.207 -    }
  13.208 -    else
  13.209 -    {
  13.210 -      Log.get().warning("Invalid OP_WRITE key: " + selKey);
  13.211 +				// Get list of selection keys with pending OP_WRITE events.
  13.212 +				// The keySET is not thread-safe whereas the keys itself are.
  13.213 +				Iterator it = selector.selectedKeys().iterator();
  13.214  
  13.215 -      if(socketChannel.socket().isClosed())
  13.216 -      {
  13.217 -        connection.shutdownInput();
  13.218 -        connection.shutdownOutput();
  13.219 -        socketChannel.close();
  13.220 -        Log.get().info("Connection closed.");
  13.221 -      }
  13.222 -    }
  13.223 -  }
  13.224 -  
  13.225 +				while (it.hasNext()) {
  13.226 +					// We remove the first event from the set and store it for
  13.227 +					// later processing.
  13.228 +					selKey = (SelectionKey) it.next();
  13.229 +					socketChannel = (SocketChannel) selKey.channel();
  13.230 +					connection = Connections.getInstance().get(socketChannel);
  13.231 +
  13.232 +					it.remove();
  13.233 +					if (connection != null) {
  13.234 +						break;
  13.235 +					} else {
  13.236 +						selKey = null;
  13.237 +					}
  13.238 +				}
  13.239 +
  13.240 +				if (selKey != null) {
  13.241 +					try {
  13.242 +						// Process the selected key.
  13.243 +						// As there is only one OP_WRITE key for a given channel, we need
  13.244 +						// not to synchronize this processing to retain the order.
  13.245 +						processSelectionKey(connection, socketChannel, selKey);
  13.246 +					} catch (IOException ex) {
  13.247 +						Log.get().warning("Error writing to channel: " + ex);
  13.248 +
  13.249 +						// Cancel write events for this channel
  13.250 +						selKey.cancel();
  13.251 +						connection.shutdownInput();
  13.252 +						connection.shutdownOutput();
  13.253 +					}
  13.254 +				}
  13.255 +
  13.256 +				// Eventually wait for a register operation
  13.257 +				synchronized (NNTPDaemon.RegisterGate) { /* do nothing */ }
  13.258 +			} catch (CancelledKeyException ex) {
  13.259 +				Log.get().info("ChannelWriter.run(): " + ex);
  13.260 +			} catch (Exception ex) {
  13.261 +				ex.printStackTrace();
  13.262 +			}
  13.263 +		} // while(isRunning())
  13.264 +	}
  13.265 +
  13.266 +	private void processSelectionKey(final NNTPConnection connection,
  13.267 +		final SocketChannel socketChannel, final SelectionKey selKey)
  13.268 +		throws InterruptedException, IOException
  13.269 +	{
  13.270 +		assert connection != null;
  13.271 +		assert socketChannel != null;
  13.272 +		assert selKey != null;
  13.273 +		assert selKey.isWritable();
  13.274 +
  13.275 +		// SocketChannel is ready for writing
  13.276 +		if (selKey.isValid()) {
  13.277 +			// Lock the socket channel
  13.278 +			synchronized (socketChannel) {
  13.279 +				// Get next output buffer
  13.280 +				ByteBuffer buf = connection.getOutputBuffer();
  13.281 +				if (buf == null) {
  13.282 +					// Currently we have nothing to write, so we stop the writeable
  13.283 +					// events until we have something to write to the socket channel
  13.284 +					//selKey.cancel();
  13.285 +					selKey.interestOps(0);
  13.286 +					// Update activity timestamp to prevent too early disconnects
  13.287 +					// on slow client connections
  13.288 +					connection.setLastActivity(System.currentTimeMillis());
  13.289 +					return;
  13.290 +				}
  13.291 +
  13.292 +				while (buf != null) // There is data to be send
  13.293 +				{
  13.294 +					// Write buffer to socket channel; this method does not block
  13.295 +					if (socketChannel.write(buf) <= 0) {
  13.296 +						// Perhaps there is data to be written, but the SocketChannel's
  13.297 +						// buffer is full, so we stop writing to until the next event.
  13.298 +						break;
  13.299 +					} else {
  13.300 +						// Retrieve next buffer if available; method may return the same
  13.301 +						// buffer instance if it still have some bytes remaining
  13.302 +						buf = connection.getOutputBuffer();
  13.303 +					}
  13.304 +				}
  13.305 +			}
  13.306 +		} else {
  13.307 +			Log.get().warning("Invalid OP_WRITE key: " + selKey);
  13.308 +
  13.309 +			if (socketChannel.socket().isClosed()) {
  13.310 +				connection.shutdownInput();
  13.311 +				connection.shutdownOutput();
  13.312 +				socketChannel.close();
  13.313 +				Log.get().info("Connection closed.");
  13.314 +			}
  13.315 +		}
  13.316 +	}
  13.317  }
    14.1 --- a/src/org/sonews/daemon/CommandSelector.java	Sun Aug 29 17:43:58 2010 +0200
    14.2 +++ b/src/org/sonews/daemon/CommandSelector.java	Sun Aug 29 18:17:37 2010 +0200
    14.3 @@ -35,107 +35,84 @@
    14.4  public class CommandSelector
    14.5  {
    14.6  
    14.7 -  private static Map<Thread, CommandSelector> instances
    14.8 -    = new ConcurrentHashMap<Thread, CommandSelector>();
    14.9 -  private static Map<String, Class<?>> commandClassesMapping
   14.10 -    = new ConcurrentHashMap<String, Class<?>>();
   14.11 +	private static Map<Thread, CommandSelector> instances = new ConcurrentHashMap<Thread, CommandSelector>();
   14.12 +	private static Map<String, Class<?>> commandClassesMapping = new ConcurrentHashMap<String, Class<?>>();
   14.13  
   14.14 -  static
   14.15 -  {
   14.16 -    String[] classes = Resource.getAsString("helpers/commands.list", true).split("\n");
   14.17 -    for(String className : classes)
   14.18 -    {
   14.19 -      if(className.charAt(0) == '#')
   14.20 -      {
   14.21 -        // Skip comments
   14.22 -        continue;
   14.23 -      }
   14.24 +	static {
   14.25 +		String[] classes = Resource.getAsString("helpers/commands.list", true).split("\n");
   14.26 +		for (String className : classes) {
   14.27 +			if (className.charAt(0) == '#') {
   14.28 +				// Skip comments
   14.29 +				continue;
   14.30 +			}
   14.31  
   14.32 -      try
   14.33 -      {
   14.34 -        addCommandHandler(className);
   14.35 -      }
   14.36 -      catch(ClassNotFoundException ex)
   14.37 -      {
   14.38 -        Log.get().warning("Could not load command class: " + ex);
   14.39 -      }
   14.40 -      catch(InstantiationException ex)
   14.41 -      {
   14.42 -        Log.get().severe("Could not instantiate command class: " + ex);
   14.43 -      }
   14.44 -      catch(IllegalAccessException ex)
   14.45 -      {
   14.46 -        Log.get().severe("Could not access command class: " + ex);
   14.47 -      }
   14.48 -    }
   14.49 -  }
   14.50 +			try {
   14.51 +				addCommandHandler(className);
   14.52 +			} catch (ClassNotFoundException ex) {
   14.53 +				Log.get().warning("Could not load command class: " + ex);
   14.54 +			} catch (InstantiationException ex) {
   14.55 +				Log.get().severe("Could not instantiate command class: " + ex);
   14.56 +			} catch (IllegalAccessException ex) {
   14.57 +				Log.get().severe("Could not access command class: " + ex);
   14.58 +			}
   14.59 +		}
   14.60 +	}
   14.61  
   14.62 -  public static void addCommandHandler(String className)
   14.63 -    throws ClassNotFoundException, InstantiationException, IllegalAccessException
   14.64 -  {
   14.65 -    Class<?> clazz = Class.forName(className);
   14.66 -    Command cmd = (Command)clazz.newInstance();
   14.67 -    String[] cmdStrs = cmd.getSupportedCommandStrings();
   14.68 -    for (String cmdStr : cmdStrs)
   14.69 -    {
   14.70 -      commandClassesMapping.put(cmdStr, clazz);
   14.71 -    }
   14.72 -  }
   14.73 +	public static void addCommandHandler(String className)
   14.74 +		throws ClassNotFoundException, InstantiationException,
   14.75 +		IllegalAccessException
   14.76 +	{
   14.77 +		Class<?> clazz = Class.forName(className);
   14.78 +		Command cmd = (Command) clazz.newInstance();
   14.79 +		String[] cmdStrs = cmd.getSupportedCommandStrings();
   14.80 +		for (String cmdStr : cmdStrs) {
   14.81 +			commandClassesMapping.put(cmdStr, clazz);
   14.82 +		}
   14.83 +	}
   14.84  
   14.85 -  public static Set<String> getCommandNames()
   14.86 -  {
   14.87 -    return commandClassesMapping.keySet();
   14.88 -  }
   14.89 +	public static Set<String> getCommandNames()
   14.90 +	{
   14.91 +		return commandClassesMapping.keySet();
   14.92 +	}
   14.93  
   14.94 -  public static CommandSelector getInstance()
   14.95 -  {
   14.96 -    CommandSelector csel = instances.get(Thread.currentThread());
   14.97 -    if(csel == null)
   14.98 -    {
   14.99 -      csel = new CommandSelector();
  14.100 -      instances.put(Thread.currentThread(), csel);
  14.101 -    }
  14.102 -    return csel;
  14.103 -  }
  14.104 +	public static CommandSelector getInstance()
  14.105 +	{
  14.106 +		CommandSelector csel = instances.get(Thread.currentThread());
  14.107 +		if (csel == null) {
  14.108 +			csel = new CommandSelector();
  14.109 +			instances.put(Thread.currentThread(), csel);
  14.110 +		}
  14.111 +		return csel;
  14.112 +	}
  14.113 +	private Map<String, Command> commandMapping = new HashMap<String, Command>();
  14.114 +	private Command unsupportedCmd = new UnsupportedCommand();
  14.115  
  14.116 -  private Map<String, Command> commandMapping = new HashMap<String, Command>();
  14.117 -  private Command              unsupportedCmd = new UnsupportedCommand();
  14.118 +	private CommandSelector()
  14.119 +	{
  14.120 +	}
  14.121  
  14.122 -  private CommandSelector()
  14.123 -  {}
  14.124 +	public Command get(String commandName)
  14.125 +	{
  14.126 +		try {
  14.127 +			commandName = commandName.toUpperCase();
  14.128 +			Command cmd = this.commandMapping.get(commandName);
  14.129  
  14.130 -  public Command get(String commandName)
  14.131 -  {
  14.132 -    try
  14.133 -    {
  14.134 -      commandName = commandName.toUpperCase();
  14.135 -      Command cmd = this.commandMapping.get(commandName);
  14.136 +			if (cmd == null) {
  14.137 +				Class<?> clazz = commandClassesMapping.get(commandName);
  14.138 +				if (clazz == null) {
  14.139 +					cmd = this.unsupportedCmd;
  14.140 +				} else {
  14.141 +					cmd = (Command) clazz.newInstance();
  14.142 +					this.commandMapping.put(commandName, cmd);
  14.143 +				}
  14.144 +			} else if (cmd.isStateful()) {
  14.145 +				cmd = cmd.getClass().newInstance();
  14.146 +			}
  14.147  
  14.148 -      if(cmd == null)
  14.149 -      {
  14.150 -        Class<?> clazz = commandClassesMapping.get(commandName);
  14.151 -        if(clazz == null)
  14.152 -        {
  14.153 -          cmd = this.unsupportedCmd;
  14.154 -        }
  14.155 -        else
  14.156 -        {
  14.157 -          cmd = (Command)clazz.newInstance();
  14.158 -          this.commandMapping.put(commandName, cmd);
  14.159 -        }
  14.160 -      }
  14.161 -      else if(cmd.isStateful())
  14.162 -      {
  14.163 -        cmd = cmd.getClass().newInstance();
  14.164 -      }
  14.165 -
  14.166 -      return cmd;
  14.167 -    }
  14.168 -    catch(Exception ex)
  14.169 -    {
  14.170 -      ex.printStackTrace();
  14.171 -      return this.unsupportedCmd;
  14.172 -    }
  14.173 -  }
  14.174 -
  14.175 +			return cmd;
  14.176 +		} catch (Exception ex) {
  14.177 +			ex.printStackTrace();
  14.178 +			return this.unsupportedCmd;
  14.179 +		}
  14.180 +	}
  14.181  }
    15.1 --- a/src/org/sonews/daemon/ConnectionWorker.java	Sun Aug 29 17:43:58 2010 +0200
    15.2 +++ b/src/org/sonews/daemon/ConnectionWorker.java	Sun Aug 29 18:17:37 2010 +0200
    15.3 @@ -31,72 +31,60 @@
    15.4  class ConnectionWorker extends AbstractDaemon
    15.5  {
    15.6  
    15.7 -  // 256 pending events should be enough
    15.8 -  private static ArrayBlockingQueue<SocketChannel> pendingChannels
    15.9 -    = new ArrayBlockingQueue<SocketChannel>(256, true);
   15.10 -  
   15.11 -  /**
   15.12 -   * Registers the given channel for further event processing.
   15.13 -   * @param channel
   15.14 -   */
   15.15 -  public static void addChannel(SocketChannel channel)
   15.16 -    throws InterruptedException
   15.17 -  {
   15.18 -    pendingChannels.put(channel);
   15.19 -  }
   15.20 -  
   15.21 -  /**
   15.22 -   * Processing loop.
   15.23 -   */
   15.24 -  @Override
   15.25 -  public void run()
   15.26 -  {
   15.27 -    while(isRunning())
   15.28 -    {
   15.29 -      try
   15.30 -      {
   15.31 -        // Retrieve and remove if available, otherwise wait.
   15.32 -        SocketChannel channel = pendingChannels.take();
   15.33 +	// 256 pending events should be enough
   15.34 +	private static ArrayBlockingQueue<SocketChannel> pendingChannels = new ArrayBlockingQueue<SocketChannel>(256, true);
   15.35  
   15.36 -        if(channel != null)
   15.37 -        {
   15.38 -          // Connections.getInstance().get() MAY return null
   15.39 -          NNTPConnection conn = Connections.getInstance().get(channel);
   15.40 -          
   15.41 -          // Try to lock the connection object
   15.42 -          if(conn != null && conn.tryReadLock())
   15.43 -          {
   15.44 -            ByteBuffer buf = conn.getBuffers().nextInputLine();
   15.45 -            while(buf != null) // Complete line was received
   15.46 -            {
   15.47 -              final byte[] line = new byte[buf.limit()];
   15.48 -              buf.get(line);
   15.49 -              ChannelLineBuffers.recycleBuffer(buf);
   15.50 -              
   15.51 -              // Here is the actual work done
   15.52 -              conn.lineReceived(line);
   15.53 +	/**
   15.54 +	 * Registers the given channel for further event processing.
   15.55 +	 * @param channel
   15.56 +	 */
   15.57 +	public static void addChannel(SocketChannel channel)
   15.58 +		throws InterruptedException
   15.59 +	{
   15.60 +		pendingChannels.put(channel);
   15.61 +	}
   15.62  
   15.63 -              // Read next line as we could have already received the next line
   15.64 -              buf = conn.getBuffers().nextInputLine();
   15.65 -            }
   15.66 -            conn.unlockReadLock();
   15.67 -          }
   15.68 -          else
   15.69 -          {
   15.70 -            addChannel(channel);
   15.71 -          }
   15.72 -        }
   15.73 -      }
   15.74 -      catch(InterruptedException ex)
   15.75 -      {
   15.76 -        Log.get().info("ConnectionWorker interrupted: " + ex);
   15.77 -      }
   15.78 -      catch(Exception ex)
   15.79 -      {
   15.80 -        Log.get().severe("Exception in ConnectionWorker: " + ex);
   15.81 -        ex.printStackTrace();
   15.82 -      }
   15.83 -    } // end while(isRunning())
   15.84 -  }
   15.85 -  
   15.86 +	/**
   15.87 +	 * Processing loop.
   15.88 +	 */
   15.89 +	@Override
   15.90 +	public void run()
   15.91 +	{
   15.92 +		while (isRunning()) {
   15.93 +			try {
   15.94 +				// Retrieve and remove if available, otherwise wait.
   15.95 +				SocketChannel channel = pendingChannels.take();
   15.96 +
   15.97 +				if (channel != null) {
   15.98 +					// Connections.getInstance().get() MAY return null
   15.99 +					NNTPConnection conn = Connections.getInstance().get(channel);
  15.100 +
  15.101 +					// Try to lock the connection object
  15.102 +					if (conn != null && conn.tryReadLock()) {
  15.103 +						ByteBuffer buf = conn.getBuffers().nextInputLine();
  15.104 +						while (buf != null) // Complete line was received
  15.105 +						{
  15.106 +							final byte[] line = new byte[buf.limit()];
  15.107 +							buf.get(line);
  15.108 +							ChannelLineBuffers.recycleBuffer(buf);
  15.109 +
  15.110 +							// Here is the actual work done
  15.111 +							conn.lineReceived(line);
  15.112 +
  15.113 +							// Read next line as we could have already received the next line
  15.114 +							buf = conn.getBuffers().nextInputLine();
  15.115 +						}
  15.116 +						conn.unlockReadLock();
  15.117 +					} else {
  15.118 +						addChannel(channel);
  15.119 +					}
  15.120 +				}
  15.121 +			} catch (InterruptedException ex) {
  15.122 +				Log.get().info("ConnectionWorker interrupted: " + ex);
  15.123 +			} catch (Exception ex) {
  15.124 +				Log.get().severe("Exception in ConnectionWorker: " + ex);
  15.125 +				ex.printStackTrace();
  15.126 +			}
  15.127 +		} // end while(isRunning())
  15.128 +	}
  15.129  }
    16.1 --- a/src/org/sonews/daemon/Connections.java	Sun Aug 29 17:43:58 2010 +0200
    16.2 +++ b/src/org/sonews/daemon/Connections.java	Sun Aug 29 18:17:37 2010 +0200
    16.3 @@ -41,141 +41,120 @@
    16.4  public final class Connections extends AbstractDaemon
    16.5  {
    16.6  
    16.7 -  private static final Connections instance = new Connections();
    16.8 -  
    16.9 -  /**
   16.10 -   * @return Active Connections instance.
   16.11 -   */
   16.12 -  public static Connections getInstance()
   16.13 -  {
   16.14 -    return Connections.instance;
   16.15 -  }
   16.16 -  
   16.17 -  private final List<NNTPConnection> connections 
   16.18 -    = new ArrayList<NNTPConnection>();
   16.19 -  private final Map<SocketChannel, NNTPConnection> connByChannel 
   16.20 -    = new HashMap<SocketChannel, NNTPConnection>();
   16.21 -  
   16.22 -  private Connections()
   16.23 -  {
   16.24 -    setName("Connections");
   16.25 -  }
   16.26 -  
   16.27 -  /**
   16.28 -   * Adds the given NNTPConnection to the Connections management.
   16.29 -   * @param conn
   16.30 -   * @see org.sonews.daemon.NNTPConnection
   16.31 -   */
   16.32 -  public void add(final NNTPConnection conn)
   16.33 -  {
   16.34 -    synchronized(this.connections)
   16.35 -    {
   16.36 -      this.connections.add(conn);
   16.37 -      this.connByChannel.put(conn.getSocketChannel(), conn);
   16.38 -    }
   16.39 -  }
   16.40 -  
   16.41 -  /**
   16.42 -   * @param channel
   16.43 -   * @return NNTPConnection instance that is associated with the given
   16.44 -   * SocketChannel.
   16.45 -   */
   16.46 -  public NNTPConnection get(final SocketChannel channel)
   16.47 -  {
   16.48 -    synchronized(this.connections)
   16.49 -    {
   16.50 -      return this.connByChannel.get(channel);
   16.51 -    }
   16.52 -  }
   16.53 +	private static final Connections instance = new Connections();
   16.54  
   16.55 -  int getConnectionCount(String remote)
   16.56 -  {
   16.57 -    int cnt = 0;
   16.58 -    synchronized(this.connections)
   16.59 -    {
   16.60 -      for(NNTPConnection conn : this.connections)
   16.61 -      {
   16.62 -        assert conn != null;
   16.63 -        assert conn.getSocketChannel() != null;
   16.64 +	/**
   16.65 +	 * @return Active Connections instance.
   16.66 +	 */
   16.67 +	public static Connections getInstance()
   16.68 +	{
   16.69 +		return Connections.instance;
   16.70 +	}
   16.71 +	private final List<NNTPConnection> connections = new ArrayList<NNTPConnection>();
   16.72 +	private final Map<SocketChannel, NNTPConnection> connByChannel = new HashMap<SocketChannel, NNTPConnection>();
   16.73  
   16.74 -        Socket socket = conn.getSocketChannel().socket();
   16.75 -        if(socket != null)
   16.76 -        {
   16.77 -          InetSocketAddress sockAddr = (InetSocketAddress)socket.getRemoteSocketAddress();
   16.78 -          if(sockAddr != null)
   16.79 -          {
   16.80 -            if(sockAddr.getHostName().equals(remote))
   16.81 -            {
   16.82 -              cnt++;
   16.83 -            }
   16.84 -          }
   16.85 -        } // if(socket != null)
   16.86 -      }
   16.87 -    }
   16.88 -    return cnt;
   16.89 -  }
   16.90 -  
   16.91 -  /**
   16.92 -   * Run loops. Checks periodically for timed out connections and purged them
   16.93 -   * from the lists.
   16.94 -   */
   16.95 -  @Override
   16.96 -  public void run()
   16.97 -  {
   16.98 -    while(isRunning())
   16.99 -    {
  16.100 -      int timeoutMillis = 1000 * Config.inst().get(Config.TIMEOUT, 180);
  16.101 -      
  16.102 -      synchronized (this.connections)
  16.103 -      {
  16.104 -        final ListIterator<NNTPConnection> iter = this.connections.listIterator();
  16.105 -        NNTPConnection conn;
  16.106 +	private Connections()
  16.107 +	{
  16.108 +		setName("Connections");
  16.109 +	}
  16.110  
  16.111 -        while (iter.hasNext())
  16.112 -        {
  16.113 -          conn = iter.next();
  16.114 -          if((System.currentTimeMillis() - conn.getLastActivity()) > timeoutMillis
  16.115 -              && conn.getBuffers().isOutputBufferEmpty())
  16.116 -          {
  16.117 -            // A connection timeout has occurred so purge the connection
  16.118 -            iter.remove();
  16.119 +	/**
  16.120 +	 * Adds the given NNTPConnection to the Connections management.
  16.121 +	 * @param conn
  16.122 +	 * @see org.sonews.daemon.NNTPConnection
  16.123 +	 */
  16.124 +	public void add(final NNTPConnection conn)
  16.125 +	{
  16.126 +		synchronized (this.connections) {
  16.127 +			this.connections.add(conn);
  16.128 +			this.connByChannel.put(conn.getSocketChannel(), conn);
  16.129 +		}
  16.130 +	}
  16.131  
  16.132 -            // Close and remove the channel
  16.133 -            SocketChannel channel = conn.getSocketChannel();
  16.134 -            connByChannel.remove(channel);
  16.135 -            
  16.136 -            try
  16.137 -            {
  16.138 -              assert channel != null;
  16.139 -              assert channel.socket() != null;
  16.140 -      
  16.141 -              // Close the channel; implicitely cancels all selectionkeys
  16.142 -              channel.close();
  16.143 -              Log.get().info("Disconnected: " + channel.socket().getRemoteSocketAddress() +
  16.144 -                " (timeout)");
  16.145 -            }
  16.146 -            catch(IOException ex)
  16.147 -            {
  16.148 -              Log.get().warning("Connections.run(): " + ex);
  16.149 -            }
  16.150 +	/**
  16.151 +	 * @param channel
  16.152 +	 * @return NNTPConnection instance that is associated with the given
  16.153 +	 * SocketChannel.
  16.154 +	 */
  16.155 +	public NNTPConnection get(final SocketChannel channel)
  16.156 +	{
  16.157 +		synchronized (this.connections) {
  16.158 +			return this.connByChannel.get(channel);
  16.159 +		}
  16.160 +	}
  16.161  
  16.162 -            // Recycle the used buffers
  16.163 -            conn.getBuffers().recycleBuffers();
  16.164 -            
  16.165 -            Stats.getInstance().clientDisconnect();
  16.166 -          }
  16.167 -        }
  16.168 -      }
  16.169 +	int getConnectionCount(String remote)
  16.170 +	{
  16.171 +		int cnt = 0;
  16.172 +		synchronized (this.connections) {
  16.173 +			for (NNTPConnection conn : this.connections) {
  16.174 +				assert conn != null;
  16.175 +				assert conn.getSocketChannel() != null;
  16.176  
  16.177 -      try
  16.178 -      {
  16.179 -        Thread.sleep(10000); // Sleep ten seconds
  16.180 -      }
  16.181 -      catch(InterruptedException ex)
  16.182 -      {
  16.183 -        Log.get().warning("Connections Thread was interrupted: " + ex.getMessage());
  16.184 -      }
  16.185 -    }
  16.186 -  }
  16.187 -  
  16.188 +				Socket socket = conn.getSocketChannel().socket();
  16.189 +				if (socket != null) {
  16.190 +					InetSocketAddress sockAddr = (InetSocketAddress) socket.getRemoteSocketAddress();
  16.191 +					if (sockAddr != null) {
  16.192 +						if (sockAddr.getHostName().equals(remote)) {
  16.193 +							cnt++;
  16.194 +						}
  16.195 +					}
  16.196 +				} // if(socket != null)
  16.197 +			}
  16.198 +		}
  16.199 +		return cnt;
  16.200 +	}
  16.201 +
  16.202 +	/**
  16.203 +	 * Run loops. Checks periodically for timed out connections and purged them
  16.204 +	 * from the lists.
  16.205 +	 */
  16.206 +	@Override
  16.207 +	public void run()
  16.208 +	{
  16.209 +		while (isRunning()) {
  16.210 +			int timeoutMillis = 1000 * Config.inst().get(Config.TIMEOUT, 180);
  16.211 +
  16.212 +			synchronized (this.connections) {
  16.213 +				final ListIterator<NNTPConnection> iter = this.connections.listIterator();
  16.214 +				NNTPConnection conn;
  16.215 +
  16.216 +				while (iter.hasNext()) {
  16.217 +					conn = iter.next();
  16.218 +					if ((System.currentTimeMillis() - conn.getLastActivity()) > timeoutMillis
  16.219 +						&& conn.getBuffers().isOutputBufferEmpty()) {
  16.220 +						// A connection timeout has occurred so purge the connection
  16.221 +						iter.remove();
  16.222 +
  16.223 +						// Close and remove the channel
  16.224 +						SocketChannel channel = conn.getSocketChannel();
  16.225 +						connByChannel.remove(channel);
  16.226 +
  16.227 +						try {
  16.228 +							assert channel != null;
  16.229 +							assert channel.socket() != null;
  16.230 +
  16.231 +							// Close the channel; implicitely cancels all selectionkeys
  16.232 +							channel.close();
  16.233 +							Log.get().info("Disconnected: " + channel.socket().getRemoteSocketAddress()
  16.234 +								+ " (timeout)");
  16.235 +						} catch (IOException ex) {
  16.236 +							Log.get().warning("Connections.run(): " + ex);
  16.237 +						}
  16.238 +
  16.239 +						// Recycle the used buffers
  16.240 +						conn.getBuffers().recycleBuffers();
  16.241 +
  16.242 +						Stats.getInstance().clientDisconnect();
  16.243 +					}
  16.244 +				}
  16.245 +			}
  16.246 +
  16.247 +			try {
  16.248 +				Thread.sleep(10000); // Sleep ten seconds
  16.249 +			} catch (InterruptedException ex) {
  16.250 +				Log.get().warning("Connections Thread was interrupted: " + ex.getMessage());
  16.251 +			}
  16.252 +		}
  16.253 +	}
  16.254  }
    17.1 --- a/src/org/sonews/daemon/LineEncoder.java	Sun Aug 29 17:43:58 2010 +0200
    17.2 +++ b/src/org/sonews/daemon/LineEncoder.java	Sun Aug 29 18:17:37 2010 +0200
    17.3 @@ -33,48 +33,46 @@
    17.4  class LineEncoder
    17.5  {
    17.6  
    17.7 -  private CharBuffer    characters;
    17.8 -  private Charset       charset;
    17.9 -  
   17.10 -  /**
   17.11 -   * Constructs new LineEncoder.
   17.12 -   * @param characters
   17.13 -   * @param charset
   17.14 -   */
   17.15 -  public LineEncoder(CharBuffer characters, Charset charset)
   17.16 -  {
   17.17 -    this.characters = characters;
   17.18 -    this.charset    = charset;
   17.19 -  }
   17.20 -  
   17.21 -  /**
   17.22 -   * Encodes the characters of this instance to the given ChannelLineBuffers
   17.23 -   * using the Charset of this instance.
   17.24 -   * @param buffer
   17.25 -   * @throws java.nio.channels.ClosedChannelException
   17.26 -   */
   17.27 -  public void encode(ChannelLineBuffers buffer)
   17.28 -    throws ClosedChannelException
   17.29 -  {
   17.30 -    CharsetEncoder encoder = charset.newEncoder();
   17.31 -    while (characters.hasRemaining())
   17.32 -    {
   17.33 -      ByteBuffer buf = ChannelLineBuffers.newLineBuffer();
   17.34 -      assert buf.position() == 0;
   17.35 -      assert buf.capacity() >= 512;
   17.36 +	private CharBuffer characters;
   17.37 +	private Charset charset;
   17.38  
   17.39 -      CoderResult res = encoder.encode(characters, buf, true);
   17.40 +	/**
   17.41 +	 * Constructs new LineEncoder.
   17.42 +	 * @param characters
   17.43 +	 * @param charset
   17.44 +	 */
   17.45 +	public LineEncoder(CharBuffer characters, Charset charset)
   17.46 +	{
   17.47 +		this.characters = characters;
   17.48 +		this.charset = charset;
   17.49 +	}
   17.50  
   17.51 -      // Set limit to current position and current position to 0;
   17.52 -      // means make ready for read from buffer
   17.53 -      buf.flip();
   17.54 -      buffer.addOutputBuffer(buf);
   17.55 +	/**
   17.56 +	 * Encodes the characters of this instance to the given ChannelLineBuffers
   17.57 +	 * using the Charset of this instance.
   17.58 +	 * @param buffer
   17.59 +	 * @throws java.nio.channels.ClosedChannelException
   17.60 +	 */
   17.61 +	public void encode(ChannelLineBuffers buffer)
   17.62 +		throws ClosedChannelException
   17.63 +	{
   17.64 +		CharsetEncoder encoder = charset.newEncoder();
   17.65 +		while (characters.hasRemaining()) {
   17.66 +			ByteBuffer buf = ChannelLineBuffers.newLineBuffer();
   17.67 +			assert buf.position() == 0;
   17.68 +			assert buf.capacity() >= 512;
   17.69  
   17.70 -      if (res.isUnderflow()) // All input processed
   17.71 -      {
   17.72 -        break;
   17.73 -      }
   17.74 -    }
   17.75 -  }
   17.76 -  
   17.77 +			CoderResult res = encoder.encode(characters, buf, true);
   17.78 +
   17.79 +			// Set limit to current position and current position to 0;
   17.80 +			// means make ready for read from buffer
   17.81 +			buf.flip();
   17.82 +			buffer.addOutputBuffer(buf);
   17.83 +
   17.84 +			if (res.isUnderflow()) // All input processed
   17.85 +			{
   17.86 +				break;
   17.87 +			}
   17.88 +		}
   17.89 +	}
   17.90  }
    18.1 --- a/src/org/sonews/daemon/NNTPConnection.java	Sun Aug 29 17:43:58 2010 +0200
    18.2 +++ b/src/org/sonews/daemon/NNTPConnection.java	Sun Aug 29 18:17:37 2010 +0200
    18.3 @@ -46,383 +46,341 @@
    18.4  public final class NNTPConnection
    18.5  {
    18.6  
    18.7 -  public static final String NEWLINE            = "\r\n";    // RFC defines this as newline
    18.8 -  public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
    18.9 -  
   18.10 -  private static final Timer cancelTimer = new Timer(true); // Thread-safe? True for run as daemon
   18.11 -  
   18.12 -  /** SocketChannel is generally thread-safe */
   18.13 -  private SocketChannel   channel        = null;
   18.14 -  private Charset         charset        = Charset.forName("UTF-8");
   18.15 -  private Command         command        = null;
   18.16 -  private Article         currentArticle = null;
   18.17 -  private Channel         currentGroup   = null;
   18.18 -  private volatile long   lastActivity   = System.currentTimeMillis();
   18.19 -  private ChannelLineBuffers lineBuffers = new ChannelLineBuffers();
   18.20 -  private int             readLock       = 0;
   18.21 -  private final Object    readLockGate   = new Object();
   18.22 -  private SelectionKey    writeSelKey    = null;
   18.23 -  
   18.24 -  public NNTPConnection(final SocketChannel channel)
   18.25 -    throws IOException
   18.26 -  {
   18.27 -    if(channel == null)
   18.28 -    {
   18.29 -      throw new IllegalArgumentException("channel is null");
   18.30 -    }
   18.31 +	public static final String NEWLINE = "\r\n";    // RFC defines this as newline
   18.32 +	public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
   18.33 +	private static final Timer cancelTimer = new Timer(true); // Thread-safe? True for run as daemon
   18.34 +	/** SocketChannel is generally thread-safe */
   18.35 +	private SocketChannel channel = null;
   18.36 +	private Charset charset = Charset.forName("UTF-8");
   18.37 +	private Command command = null;
   18.38 +	private Article currentArticle = null;
   18.39 +	private Channel currentGroup = null;
   18.40 +	private volatile long lastActivity = System.currentTimeMillis();
   18.41 +	private ChannelLineBuffers lineBuffers = new ChannelLineBuffers();
   18.42 +	private int readLock = 0;
   18.43 +	private final Object readLockGate = new Object();
   18.44 +	private SelectionKey writeSelKey = null;
   18.45  
   18.46 -    this.channel = channel;
   18.47 -    Stats.getInstance().clientConnect();
   18.48 -  }
   18.49 -  
   18.50 -  /**
   18.51 -   * Tries to get the read lock for this NNTPConnection. This method is Thread-
   18.52 -   * safe and returns true of the read lock was successfully set. If the lock
   18.53 -   * is still hold by another Thread the method returns false.
   18.54 -   */
   18.55 -  boolean tryReadLock()
   18.56 -  {
   18.57 -    // As synchronizing simple types may cause deadlocks,
   18.58 -    // we use a gate object.
   18.59 -    synchronized(readLockGate)
   18.60 -    {
   18.61 -      if(readLock != 0)
   18.62 -      {
   18.63 -        return false;
   18.64 -      }
   18.65 -      else
   18.66 -      {
   18.67 -        readLock = Thread.currentThread().hashCode();
   18.68 -        return true;
   18.69 -      }
   18.70 -    }
   18.71 -  }
   18.72 -  
   18.73 -  /**
   18.74 -   * Releases the read lock in a Thread-safe way.
   18.75 -   * @throws IllegalMonitorStateException if a Thread not holding the lock
   18.76 -   * tries to release it.
   18.77 -   */
   18.78 -  void unlockReadLock()
   18.79 -  {
   18.80 -    synchronized(readLockGate)
   18.81 -    {
   18.82 -      if(readLock == Thread.currentThread().hashCode())
   18.83 -      {
   18.84 -        readLock = 0;
   18.85 -      }
   18.86 -      else
   18.87 -      {
   18.88 -        throw new IllegalMonitorStateException();
   18.89 -      }
   18.90 -    }
   18.91 -  }
   18.92 -  
   18.93 -  /**
   18.94 -   * @return Current input buffer of this NNTPConnection instance.
   18.95 -   */
   18.96 -  public ByteBuffer getInputBuffer()
   18.97 -  {
   18.98 -    return this.lineBuffers.getInputBuffer();
   18.99 -  }
  18.100 -  
  18.101 -  /**
  18.102 -   * @return Output buffer of this NNTPConnection which has at least one byte
  18.103 -   * free storage.
  18.104 -   */
  18.105 -  public ByteBuffer getOutputBuffer()
  18.106 -  {
  18.107 -    return this.lineBuffers.getOutputBuffer();
  18.108 -  }
  18.109 -  
  18.110 -  /**
  18.111 -   * @return ChannelLineBuffers instance associated with this NNTPConnection.
  18.112 -   */
  18.113 -  public ChannelLineBuffers getBuffers()
  18.114 -  {
  18.115 -    return this.lineBuffers;
  18.116 -  }
  18.117 -  
  18.118 -  /**
  18.119 -   * @return true if this connection comes from a local remote address.
  18.120 -   */
  18.121 -  public boolean isLocalConnection()
  18.122 -  {
  18.123 -    return ((InetSocketAddress)this.channel.socket().getRemoteSocketAddress())
  18.124 -      .getHostName().equalsIgnoreCase("localhost");
  18.125 -  }
  18.126 +	public NNTPConnection(final SocketChannel channel)
  18.127 +		throws IOException
  18.128 +	{
  18.129 +		if (channel == null) {
  18.130 +			throw new IllegalArgumentException("channel is null");
  18.131 +		}
  18.132  
  18.133 -  void setWriteSelectionKey(SelectionKey selKey)
  18.134 -  {
  18.135 -    this.writeSelKey = selKey;
  18.136 -  }
  18.137 +		this.channel = channel;
  18.138 +		Stats.getInstance().clientConnect();
  18.139 +	}
  18.140  
  18.141 -  public void shutdownInput()
  18.142 -  {
  18.143 -    try
  18.144 -    {
  18.145 -      // Closes the input line of the channel's socket, so no new data
  18.146 -      // will be received and a timeout can be triggered.
  18.147 -      this.channel.socket().shutdownInput();
  18.148 -    }
  18.149 -    catch(IOException ex)
  18.150 -    {
  18.151 -      Log.get().warning("Exception in NNTPConnection.shutdownInput(): " + ex);
  18.152 -    }
  18.153 -  }
  18.154 -  
  18.155 -  public void shutdownOutput()
  18.156 -  {
  18.157 -    cancelTimer.schedule(new TimerTask() 
  18.158 -    {
  18.159 -      @Override
  18.160 -      public void run()
  18.161 -      {
  18.162 -        try
  18.163 -        {
  18.164 -          // Closes the output line of the channel's socket.
  18.165 -          channel.socket().shutdownOutput();
  18.166 -          channel.close();
  18.167 -        }
  18.168 -        catch(SocketException ex)
  18.169 -        {
  18.170 -          // Socket was already disconnected
  18.171 -          Log.get().info("NNTPConnection.shutdownOutput(): " + ex);
  18.172 -        }
  18.173 -        catch(Exception ex)
  18.174 -        {
  18.175 -          Log.get().warning("NNTPConnection.shutdownOutput(): " + ex);
  18.176 -        }
  18.177 -      }
  18.178 -    }, 3000);
  18.179 -  }
  18.180 -  
  18.181 -  public SocketChannel getSocketChannel()
  18.182 -  {
  18.183 -    return this.channel;
  18.184 -  }
  18.185 -  
  18.186 -  public Article getCurrentArticle()
  18.187 -  {
  18.188 -    return this.currentArticle;
  18.189 -  }
  18.190 -  
  18.191 -  public Charset getCurrentCharset()
  18.192 -  {
  18.193 -    return this.charset;
  18.194 -  }
  18.195 +	/**
  18.196 +	 * Tries to get the read lock for this NNTPConnection. This method is Thread-
  18.197 +	 * safe and returns true of the read lock was successfully set. If the lock
  18.198 +	 * is still hold by another Thread the method returns false.
  18.199 +	 */
  18.200 +	boolean tryReadLock()
  18.201 +	{
  18.202 +		// As synchronizing simple types may cause deadlocks,
  18.203 +		// we use a gate object.
  18.204 +		synchronized (readLockGate) {
  18.205 +			if (readLock != 0) {
  18.206 +				return false;
  18.207 +			} else {
  18.208 +				readLock = Thread.currentThread().hashCode();
  18.209 +				return true;
  18.210 +			}
  18.211 +		}
  18.212 +	}
  18.213  
  18.214 -  /**
  18.215 -   * @return The currently selected communication channel (not SocketChannel)
  18.216 -   */
  18.217 -  public Channel getCurrentChannel()
  18.218 -  {
  18.219 -    return this.currentGroup;
  18.220 -  }
  18.221 -  
  18.222 -  public void setCurrentArticle(final Article article)
  18.223 -  {
  18.224 -    this.currentArticle = article;
  18.225 -  }
  18.226 -  
  18.227 -  public void setCurrentGroup(final Channel group)
  18.228 -  {
  18.229 -    this.currentGroup = group;
  18.230 -  }
  18.231 -  
  18.232 -  public long getLastActivity()
  18.233 -  {
  18.234 -    return this.lastActivity;
  18.235 -  }
  18.236 -  
  18.237 -  /**
  18.238 -   * Due to the readLockGate there is no need to synchronize this method.
  18.239 -   * @param raw
  18.240 -   * @throws IllegalArgumentException if raw is null.
  18.241 -   * @throws IllegalStateException if calling thread does not own the readLock.
  18.242 -   */
  18.243 -  void lineReceived(byte[] raw)
  18.244 -  {
  18.245 -    if(raw == null)
  18.246 -    {
  18.247 -      throw new IllegalArgumentException("raw is null");
  18.248 -    }
  18.249 -    
  18.250 -    if(readLock == 0 || readLock != Thread.currentThread().hashCode())
  18.251 -    {
  18.252 -      throw new IllegalStateException("readLock not properly set");
  18.253 -    }
  18.254 +	/**
  18.255 +	 * Releases the read lock in a Thread-safe way.
  18.256 +	 * @throws IllegalMonitorStateException if a Thread not holding the lock
  18.257 +	 * tries to release it.
  18.258 +	 */
  18.259 +	void unlockReadLock()
  18.260 +	{
  18.261 +		synchronized (readLockGate) {
  18.262 +			if (readLock == Thread.currentThread().hashCode()) {
  18.263 +				readLock = 0;
  18.264 +			} else {
  18.265 +				throw new IllegalMonitorStateException();
  18.266 +			}
  18.267 +		}
  18.268 +	}
  18.269  
  18.270 -    this.lastActivity = System.currentTimeMillis();
  18.271 -    
  18.272 -    String line = new String(raw, this.charset);
  18.273 -    
  18.274 -    // There might be a trailing \r, but trim() is a bad idea
  18.275 -    // as it removes also leading spaces from long header lines.
  18.276 -    if(line.endsWith("\r"))
  18.277 -    {
  18.278 -      line = line.substring(0, line.length() - 1);
  18.279 -      raw  = Arrays.copyOf(raw, raw.length - 1);
  18.280 -    }
  18.281 -    
  18.282 -    Log.get().fine("<< " + line);
  18.283 -    
  18.284 -    if(command == null)
  18.285 -    {
  18.286 -      command = parseCommandLine(line);
  18.287 -      assert command != null;
  18.288 -    }
  18.289 +	/**
  18.290 +	 * @return Current input buffer of this NNTPConnection instance.
  18.291 +	 */
  18.292 +	public ByteBuffer getInputBuffer()
  18.293 +	{
  18.294 +		return this.lineBuffers.getInputBuffer();
  18.295 +	}
  18.296  
  18.297 -    try
  18.298 -    {
  18.299 -      // The command object will process the line we just received
  18.300 -      try
  18.301 -      {
  18.302 -        command.processLine(this, line, raw);
  18.303 -      }
  18.304 -      catch(StorageBackendException ex)
  18.305 -      {
  18.306 -        Log.get().info("Retry command processing after StorageBackendException");
  18.307 +	/**
  18.308 +	 * @return Output buffer of this NNTPConnection which has at least one byte
  18.309 +	 * free storage.
  18.310 +	 */
  18.311 +	public ByteBuffer getOutputBuffer()
  18.312 +	{
  18.313 +		return this.lineBuffers.getOutputBuffer();
  18.314 +	}
  18.315  
  18.316 -        // Try it a second time, so that the backend has time to recover
  18.317 -        command.processLine(this, line, raw);
  18.318 -      }
  18.319 -    }
  18.320 -    catch(ClosedChannelException ex0)
  18.321 -    {
  18.322 -      try
  18.323 -      {
  18.324 -        Log.get().info("Connection to " + channel.socket().getRemoteSocketAddress()
  18.325 -            + " closed: " + ex0);
  18.326 -      }
  18.327 -      catch(Exception ex0a)
  18.328 -      {
  18.329 -        ex0a.printStackTrace();
  18.330 -      }
  18.331 -    }
  18.332 -    catch(Exception ex1) // This will catch a second StorageBackendException
  18.333 -    {
  18.334 -      try
  18.335 -      {
  18.336 -        command = null;
  18.337 -        ex1.printStackTrace();
  18.338 -        println("500 Internal server error");
  18.339 -      }
  18.340 -      catch(Exception ex2)
  18.341 -      {
  18.342 -        ex2.printStackTrace();
  18.343 -      }
  18.344 -    }
  18.345 +	/**
  18.346 +	 * @return ChannelLineBuffers instance associated with this NNTPConnection.
  18.347 +	 */
  18.348 +	public ChannelLineBuffers getBuffers()
  18.349 +	{
  18.350 +		return this.lineBuffers;
  18.351 +	}
  18.352  
  18.353 -    if(command == null || command.hasFinished())
  18.354 -    {
  18.355 -      command = null;
  18.356 -      charset = Charset.forName("UTF-8"); // Reset to default
  18.357 -    }
  18.358 -  }
  18.359 -  
  18.360 -  /**
  18.361 -   * This method determines the fitting command processing class.
  18.362 -   * @param line
  18.363 -   * @return
  18.364 -   */
  18.365 -  private Command parseCommandLine(String line)
  18.366 -  {
  18.367 -    String cmdStr = line.split(" ")[0];
  18.368 -    return CommandSelector.getInstance().get(cmdStr);
  18.369 -  }
  18.370 -  
  18.371 -  /**
  18.372 -   * Puts the given line into the output buffer, adds a newline character
  18.373 -   * and returns. The method returns immediately and does not block until
  18.374 -   * the line was sent. If line is longer than 510 octets it is split up in
  18.375 -   * several lines. Each line is terminated by \r\n (NNTPConnection.NEWLINE).
  18.376 -   * @param line
  18.377 -   */
  18.378 -  public void println(final CharSequence line, final Charset charset)
  18.379 -    throws IOException
  18.380 -  {    
  18.381 -    writeToChannel(CharBuffer.wrap(line), charset, line);
  18.382 -    writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
  18.383 -  }
  18.384 +	/**
  18.385 +	 * @return true if this connection comes from a local remote address.
  18.386 +	 */
  18.387 +	public boolean isLocalConnection()
  18.388 +	{
  18.389 +		return ((InetSocketAddress) this.channel.socket().getRemoteSocketAddress()).getHostName().equalsIgnoreCase("localhost");
  18.390 +	}
  18.391  
  18.392 -  /**
  18.393 -   * Writes the given raw lines to the output buffers and finishes with
  18.394 -   * a newline character (\r\n).
  18.395 -   * @param rawLines
  18.396 -   */
  18.397 -  public void println(final byte[] rawLines)
  18.398 -    throws IOException
  18.399 -  {
  18.400 -    this.lineBuffers.addOutputBuffer(ByteBuffer.wrap(rawLines));
  18.401 -    writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
  18.402 -  }
  18.403 -  
  18.404 -  /**
  18.405 -   * Encodes the given CharBuffer using the given Charset to a bunch of
  18.406 -   * ByteBuffers (each 512 bytes large) and enqueues them for writing at the
  18.407 -   * connected SocketChannel.
  18.408 -   * @throws java.io.IOException
  18.409 -   */
  18.410 -  private void writeToChannel(CharBuffer characters, final Charset charset,
  18.411 -    CharSequence debugLine)
  18.412 -    throws IOException
  18.413 -  {
  18.414 -    if(!charset.canEncode())
  18.415 -    {
  18.416 -      Log.get().severe("FATAL: Charset " + charset + " cannot encode!");
  18.417 -      return;
  18.418 -    }
  18.419 -    
  18.420 -    // Write characters to output buffers
  18.421 -    LineEncoder lenc = new LineEncoder(characters, charset);
  18.422 -    lenc.encode(lineBuffers);
  18.423 -    
  18.424 -    enableWriteEvents(debugLine);
  18.425 -  }
  18.426 +	void setWriteSelectionKey(SelectionKey selKey)
  18.427 +	{
  18.428 +		this.writeSelKey = selKey;
  18.429 +	}
  18.430  
  18.431 -  private void enableWriteEvents(CharSequence debugLine)
  18.432 -  {
  18.433 -    // Enable OP_WRITE events so that the buffers are processed
  18.434 -    try
  18.435 -    {
  18.436 -      this.writeSelKey.interestOps(SelectionKey.OP_WRITE);
  18.437 -      ChannelWriter.getInstance().getSelector().wakeup();
  18.438 -    }
  18.439 -    catch(Exception ex) // CancelledKeyException and ChannelCloseException
  18.440 -    {
  18.441 -      Log.get().warning("NNTPConnection.writeToChannel(): " + ex);
  18.442 -      return;
  18.443 -    }
  18.444 +	public void shutdownInput()
  18.445 +	{
  18.446 +		try {
  18.447 +			// Closes the input line of the channel's socket, so no new data
  18.448 +			// will be received and a timeout can be triggered.
  18.449 +			this.channel.socket().shutdownInput();
  18.450 +		} catch (IOException ex) {
  18.451 +			Log.get().warning("Exception in NNTPConnection.shutdownInput(): " + ex);
  18.452 +		}
  18.453 +	}
  18.454  
  18.455 -    // Update last activity timestamp
  18.456 -    this.lastActivity = System.currentTimeMillis();
  18.457 -    if(debugLine != null)
  18.458 -    {
  18.459 -      Log.get().fine(">> " + debugLine);
  18.460 -    }
  18.461 -  }
  18.462 -  
  18.463 -  public void println(final CharSequence line)
  18.464 -    throws IOException
  18.465 -  {
  18.466 -    println(line, charset);
  18.467 -  }
  18.468 -  
  18.469 -  public void print(final String line)
  18.470 -    throws IOException
  18.471 -  {
  18.472 -    writeToChannel(CharBuffer.wrap(line), charset, line);
  18.473 -  }
  18.474 -  
  18.475 -  public void setCurrentCharset(final Charset charset)
  18.476 -  {
  18.477 -    this.charset = charset;
  18.478 -  }
  18.479 +	public void shutdownOutput()
  18.480 +	{
  18.481 +		cancelTimer.schedule(new TimerTask()
  18.482 +		{
  18.483  
  18.484 -  void setLastActivity(long timestamp)
  18.485 -  {
  18.486 -    this.lastActivity = timestamp;
  18.487 -  }
  18.488 -  
  18.489 +			@Override
  18.490 +			public void run()
  18.491 +			{
  18.492 +				try {
  18.493 +					// Closes the output line of the channel's socket.
  18.494 +					channel.socket().shutdownOutput();
  18.495 +					channel.close();
  18.496 +				} catch (SocketException ex) {
  18.497 +					// Socket was already disconnected
  18.498 +					Log.get().info("NNTPConnection.shutdownOutput(): " + ex);
  18.499 +				} catch (Exception ex) {
  18.500 +					Log.get().warning("NNTPConnection.shutdownOutput(): " + ex);
  18.501 +				}
  18.502 +			}
  18.503 +		}, 3000);
  18.504 +	}
  18.505 +
  18.506 +	public SocketChannel getSocketChannel()
  18.507 +	{
  18.508 +		return this.channel;
  18.509 +	}
  18.510 +
  18.511 +	public Article getCurrentArticle()
  18.512 +	{
  18.513 +		return this.currentArticle;
  18.514 +	}
  18.515 +
  18.516 +	public Charset getCurrentCharset()
  18.517 +	{
  18.518 +		return this.charset;
  18.519 +	}
  18.520 +
  18.521 +	/**
  18.522 +	 * @return The currently selected communication channel (not SocketChannel)
  18.523 +	 */
  18.524 +	public Channel getCurrentChannel()
  18.525 +	{
  18.526 +		return this.currentGroup;
  18.527 +	}
  18.528 +
  18.529 +	public void setCurrentArticle(final Article article)
  18.530 +	{
  18.531 +		this.currentArticle = article;
  18.532 +	}
  18.533 +
  18.534 +	public void setCurrentGroup(final Channel group)
  18.535 +	{
  18.536 +		this.currentGroup = group;
  18.537 +	}
  18.538 +
  18.539 +	public long getLastActivity()
  18.540 +	{
  18.541 +		return this.lastActivity;
  18.542 +	}
  18.543 +
  18.544 +	/**
  18.545 +	 * Due to the readLockGate there is no need to synchronize this method.
  18.546 +	 * @param raw
  18.547 +	 * @throws IllegalArgumentException if raw is null.
  18.548 +	 * @throws IllegalStateException if calling thread does not own the readLock.
  18.549 +	 */
  18.550 +	void lineReceived(byte[] raw)
  18.551 +	{
  18.552 +		if (raw == null) {
  18.553 +			throw new IllegalArgumentException("raw is null");
  18.554 +		}
  18.555 +
  18.556 +		if (readLock == 0 || readLock != Thread.currentThread().hashCode()) {
  18.557 +			throw new IllegalStateException("readLock not properly set");
  18.558 +		}
  18.559 +
  18.560 +		this.lastActivity = System.currentTimeMillis();
  18.561 +
  18.562 +		String line = new String(raw, this.charset);
  18.563 +
  18.564 +		// There might be a trailing \r, but trim() is a bad idea
  18.565 +		// as it removes also leading spaces from long header lines.
  18.566 +		if (line.endsWith("\r")) {
  18.567 +			line = line.substring(0, line.length() - 1);
  18.568 +			raw = Arrays.copyOf(raw, raw.length - 1);
  18.569 +		}
  18.570 +
  18.571 +		Log.get().fine("<< " + line);
  18.572 +
  18.573 +		if (command == null) {
  18.574 +			command = parseCommandLine(line);
  18.575 +			assert command != null;
  18.576 +		}
  18.577 +
  18.578 +		try {
  18.579 +			// The command object will process the line we just received
  18.580 +			try {
  18.581 +				command.processLine(this, line, raw);
  18.582 +			} catch (StorageBackendException ex) {
  18.583 +				Log.get().info("Retry command processing after StorageBackendException");
  18.584 +
  18.585 +				// Try it a second time, so that the backend has time to recover
  18.586 +				command.processLine(this, line, raw);
  18.587 +			}
  18.588 +		} catch (ClosedChannelException ex0) {
  18.589 +			try {
  18.590 +				Log.get().info("Connection to " + channel.socket().getRemoteSocketAddress()
  18.591 +					+ " closed: " + ex0);
  18.592 +			} catch (Exception ex0a) {
  18.593 +				ex0a.printStackTrace();
  18.594 +			}
  18.595 +		} catch (Exception ex1) // This will catch a second StorageBackendException
  18.596 +		{
  18.597 +			try {
  18.598 +				command = null;
  18.599 +				ex1.printStackTrace();
  18.600 +				println("500 Internal server error");
  18.601 +			} catch (Exception ex2) {
  18.602 +				ex2.printStackTrace();
  18.603 +			}
  18.604 +		}
  18.605 +
  18.606 +		if (command == null || command.hasFinished()) {
  18.607 +			command = null;
  18.608 +			charset = Charset.forName("UTF-8"); // Reset to default
  18.609 +		}
  18.610 +	}
  18.611 +
  18.612 +	/**
  18.613 +	 * This method determines the fitting command processing class.
  18.614 +	 * @param line
  18.615 +	 * @return
  18.616 +	 */
  18.617 +	private Command parseCommandLine(String line)
  18.618 +	{
  18.619 +		String cmdStr = line.split(" ")[0];
  18.620 +		return CommandSelector.getInstance().get(cmdStr);
  18.621 +	}
  18.622 +
  18.623 +	/**
  18.624 +	 * Puts the given line into the output buffer, adds a newline character
  18.625 +	 * and returns. The method returns immediately and does not block until
  18.626 +	 * the line was sent. If line is longer than 510 octets it is split up in
  18.627 +	 * several lines. Each line is terminated by \r\n (NNTPConnection.NEWLINE).
  18.628 +	 * @param line
  18.629 +	 */
  18.630 +	public void println(final CharSequence line, final Charset charset)
  18.631 +		throws IOException
  18.632 +	{
  18.633 +		writeToChannel(CharBuffer.wrap(line), charset, line);
  18.634 +		writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
  18.635 +	}
  18.636 +
  18.637 +	/**
  18.638 +	 * Writes the given raw lines to the output buffers and finishes with
  18.639 +	 * a newline character (\r\n).
  18.640 +	 * @param rawLines
  18.641 +	 */
  18.642 +	public void println(final byte[] rawLines)
  18.643 +		throws IOException
  18.644 +	{
  18.645 +		this.lineBuffers.addOutputBuffer(ByteBuffer.wrap(rawLines));
  18.646 +		writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
  18.647 +	}
  18.648 +
  18.649 +	/**
  18.650 +	 * Encodes the given CharBuffer using the given Charset to a bunch of
  18.651 +	 * ByteBuffers (each 512 bytes large) and enqueues them for writing at the
  18.652 +	 * connected SocketChannel.
  18.653 +	 * @throws java.io.IOException
  18.654 +	 */
  18.655 +	private void writeToChannel(CharBuffer characters, final Charset charset,
  18.656 +		CharSequence debugLine)
  18.657 +		throws IOException
  18.658 +	{
  18.659 +		if (!charset.canEncode()) {
  18.660 +			Log.get().severe("FATAL: Charset " + charset + " cannot encode!");
  18.661 +			return;
  18.662 +		}
  18.663 +
  18.664 +		// Write characters to output buffers
  18.665 +		LineEncoder lenc = new LineEncoder(characters, charset);
  18.666 +		lenc.encode(lineBuffers);
  18.667 +
  18.668 +		enableWriteEvents(debugLine);
  18.669 +	}
  18.670 +
  18.671 +	private void enableWriteEvents(CharSequence debugLine)
  18.672 +	{
  18.673 +		// Enable OP_WRITE events so that the buffers are processed
  18.674 +		try {
  18.675 +			this.writeSelKey.interestOps(SelectionKey.OP_WRITE);
  18.676 +			ChannelWriter.getInstance().getSelector().wakeup();
  18.677 +		} catch (Exception ex) // CancelledKeyException and ChannelCloseException
  18.678 +		{
  18.679 +			Log.get().warning("NNTPConnection.writeToChannel(): " + ex);
  18.680 +			return;
  18.681 +		}
  18.682 +
  18.683 +		// Update last activity timestamp
  18.684 +		this.lastActivity = System.currentTimeMillis();
  18.685 +		if (debugLine != null) {
  18.686 +			Log.get().fine(">> " + debugLine);
  18.687 +		}
  18.688 +	}
  18.689 +
  18.690 +	public void println(final CharSequence line)
  18.691 +		throws IOException
  18.692 +	{
  18.693 +		println(line, charset);
  18.694 +	}
  18.695 +
  18.696 +	public void print(final String line)
  18.697 +		throws IOException
  18.698 +	{
  18.699 +		writeToChannel(CharBuffer.wrap(line), charset, line);
  18.700 +	}
  18.701 +
  18.702 +	public void setCurrentCharset(final Charset charset)
  18.703 +	{
  18.704 +		this.charset = charset;
  18.705 +	}
  18.706 +
  18.707 +	void setLastActivity(long timestamp)
  18.708 +	{
  18.709 +		this.lastActivity = timestamp;
  18.710 +	}
  18.711  }
    19.1 --- a/src/org/sonews/daemon/NNTPDaemon.java	Sun Aug 29 17:43:58 2010 +0200
    19.2 +++ b/src/org/sonews/daemon/NNTPDaemon.java	Sun Aug 29 18:17:37 2010 +0200
    19.3 @@ -40,158 +40,130 @@
    19.4  public final class NNTPDaemon extends AbstractDaemon
    19.5  {
    19.6  
    19.7 -  public static final Object RegisterGate = new Object();
    19.8 -  
    19.9 -  private static NNTPDaemon instance = null;
   19.10 -  
   19.11 -  public static synchronized NNTPDaemon createInstance(int port)
   19.12 -  {
   19.13 -    if(instance == null)
   19.14 -    {
   19.15 -      instance = new NNTPDaemon(port);
   19.16 -      return instance;
   19.17 -    }
   19.18 -    else
   19.19 -    {
   19.20 -      throw new RuntimeException("NNTPDaemon.createInstance() called twice");
   19.21 -    }
   19.22 -  }
   19.23 -  
   19.24 -  private int port;
   19.25 -  
   19.26 -  private NNTPDaemon(final int port)
   19.27 -  {
   19.28 -    Log.get().info("Server listening on port " + port);
   19.29 -    this.port = port;
   19.30 -  }
   19.31 +	public static final Object RegisterGate = new Object();
   19.32 +	private static NNTPDaemon instance = null;
   19.33  
   19.34 -  @Override
   19.35 -  public void run()
   19.36 -  {
   19.37 -    try
   19.38 -    {
   19.39 -      // Create a Selector that handles the SocketChannel multiplexing
   19.40 -      final Selector readSelector  = Selector.open();
   19.41 -      final Selector writeSelector = Selector.open();
   19.42 -      
   19.43 -      // Start working threads
   19.44 -      final int workerThreads = Runtime.getRuntime().availableProcessors() * 4;
   19.45 -      ConnectionWorker[] cworkers = new ConnectionWorker[workerThreads];
   19.46 -      for(int n = 0; n < workerThreads; n++)
   19.47 -      {
   19.48 -        cworkers[n] = new ConnectionWorker();
   19.49 -        cworkers[n].start();
   19.50 -      }
   19.51 -      
   19.52 -      ChannelWriter.getInstance().setSelector(writeSelector);
   19.53 -      ChannelReader.getInstance().setSelector(readSelector);
   19.54 -      ChannelWriter.getInstance().start();
   19.55 -      ChannelReader.getInstance().start();
   19.56 -      
   19.57 -      final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
   19.58 -      serverSocketChannel.configureBlocking(true);  // Set to blocking mode
   19.59 -      
   19.60 -      // Configure ServerSocket; bind to socket...
   19.61 -      final ServerSocket serverSocket = serverSocketChannel.socket();
   19.62 -      serverSocket.bind(new InetSocketAddress(this.port));
   19.63 -      
   19.64 -      while(isRunning())
   19.65 -      {
   19.66 -        SocketChannel socketChannel;
   19.67 -        
   19.68 -        try
   19.69 -        {
   19.70 -          // As we set the server socket channel to blocking mode the accept()
   19.71 -          // method will block.
   19.72 -          socketChannel = serverSocketChannel.accept();
   19.73 -          socketChannel.configureBlocking(false);
   19.74 -          assert socketChannel.isConnected();
   19.75 -          assert socketChannel.finishConnect();
   19.76 -        }
   19.77 -        catch(IOException ex)
   19.78 -        {
   19.79 -          // Under heavy load an IOException "Too many open files may
   19.80 -          // be thrown. It most cases we should slow down the connection
   19.81 -          // accepting, to give the worker threads some time to process work.
   19.82 -          Log.get().severe("IOException while accepting connection: " + ex.getMessage());
   19.83 -          Log.get().info("Connection accepting sleeping for seconds...");
   19.84 -          Thread.sleep(5000); // 5 seconds
   19.85 -          continue;
   19.86 -        }
   19.87 -        
   19.88 -        final NNTPConnection conn;
   19.89 -        try
   19.90 -        {
   19.91 -          conn = new NNTPConnection(socketChannel);
   19.92 -          Connections.getInstance().add(conn);
   19.93 -        }
   19.94 -        catch(IOException ex)
   19.95 -        {
   19.96 -          Log.get().warning(ex.toString());
   19.97 -          socketChannel.close();
   19.98 -          continue;
   19.99 -        }
  19.100 -        
  19.101 -        try
  19.102 -        {
  19.103 -          SelectionKey selKeyWrite =
  19.104 -            registerSelector(writeSelector, socketChannel, SelectionKey.OP_WRITE);
  19.105 -          registerSelector(readSelector, socketChannel, SelectionKey.OP_READ);
  19.106 -          
  19.107 -          Log.get().info("Connected: " + socketChannel.socket().getRemoteSocketAddress());
  19.108 +	public static synchronized NNTPDaemon createInstance(int port)
  19.109 +	{
  19.110 +		if (instance == null) {
  19.111 +			instance = new NNTPDaemon(port);
  19.112 +			return instance;
  19.113 +		} else {
  19.114 +			throw new RuntimeException("NNTPDaemon.createInstance() called twice");
  19.115 +		}
  19.116 +	}
  19.117 +	private int port;
  19.118  
  19.119 -          // Set write selection key and send hello to client
  19.120 -          conn.setWriteSelectionKey(selKeyWrite);
  19.121 -          conn.println("200 " + Config.inst().get(Config.HOSTNAME, "localhost")
  19.122 -              + " " + Main.VERSION + " news server ready - (posting ok).");
  19.123 -        }
  19.124 -        catch(CancelledKeyException cke)
  19.125 -        {
  19.126 -          Log.get().warning("CancelledKeyException " + cke.getMessage() + " was thrown: "
  19.127 -            + socketChannel.socket());
  19.128 -        }
  19.129 -        catch(ClosedChannelException cce)
  19.130 -        {
  19.131 -          Log.get().warning("ClosedChannelException " + cce.getMessage() + " was thrown: "
  19.132 -            + socketChannel.socket());
  19.133 -        }
  19.134 -      }
  19.135 -    }
  19.136 -    catch(BindException ex)
  19.137 -    {
  19.138 -      // Could not bind to socket; this is a fatal problem; so perform shutdown
  19.139 -      ex.printStackTrace();
  19.140 -      System.exit(1);
  19.141 -    }
  19.142 -    catch(IOException ex)
  19.143 -    {
  19.144 -      ex.printStackTrace();
  19.145 -    }
  19.146 -    catch(Exception ex)
  19.147 -    {
  19.148 -      ex.printStackTrace();
  19.149 -    }
  19.150 -  }
  19.151 -  
  19.152 -  public static SelectionKey registerSelector(final Selector selector,
  19.153 -    final SocketChannel channel, final int op)
  19.154 -    throws CancelledKeyException, ClosedChannelException
  19.155 -  {
  19.156 -    // Register the selector at the channel, so that it will be notified
  19.157 -    // on the socket's events
  19.158 -    synchronized(RegisterGate)
  19.159 -    {
  19.160 -      // Wakeup the currently blocking reader/writer thread; we have locked
  19.161 -      // the RegisterGate to prevent the awakened thread to block again
  19.162 -      selector.wakeup();
  19.163 -      
  19.164 -      // Lock the selector to prevent the waiting worker threads going into
  19.165 -      // selector.select() which would block the selector.
  19.166 -      synchronized (selector)
  19.167 -      {
  19.168 -        return channel.register(selector, op, null);
  19.169 -      }
  19.170 -    }
  19.171 -  }
  19.172 -  
  19.173 +	private NNTPDaemon(final int port)
  19.174 +	{
  19.175 +		Log.get().info("Server listening on port " + port);
  19.176 +		this.port = port;
  19.177 +	}
  19.178 +
  19.179 +	@Override
  19.180 +	public void run()
  19.181 +	{
  19.182 +		try {
  19.183 +			// Create a Selector that handles the SocketChannel multiplexing
  19.184 +			final Selector readSelector = Selector.open();
  19.185 +			final Selector writeSelector = Selector.open();
  19.186 +
  19.187 +			// Start working threads
  19.188 +			final int workerThreads = Runtime.getRuntime().availableProcessors() * 4;
  19.189 +			ConnectionWorker[] cworkers = new ConnectionWorker[workerThreads];
  19.190 +			for (int n = 0; n < workerThreads; n++) {
  19.191 +				cworkers[n] = new ConnectionWorker();
  19.192 +				cworkers[n].start();
  19.193 +			}
  19.194 +
  19.195 +			ChannelWriter.getInstance().setSelector(writeSelector);
  19.196 +			ChannelReader.getInstance().setSelector(readSelector);
  19.197 +			ChannelWriter.getInstance().start();
  19.198 +			ChannelReader.getInstance().start();
  19.199 +
  19.200 +			final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  19.201 +			serverSocketChannel.configureBlocking(true);  // Set to blocking mode
  19.202 +
  19.203 +			// Configure ServerSocket; bind to socket...
  19.204 +			final ServerSocket serverSocket = serverSocketChannel.socket();
  19.205 +			serverSocket.bind(new InetSocketAddress(this.port));
  19.206 +
  19.207 +			while (isRunning()) {
  19.208 +				SocketChannel socketChannel;
  19.209 +
  19.210 +				try {
  19.211 +					// As we set the server socket channel to blocking mode the accept()
  19.212 +					// method will block.
  19.213 +					socketChannel = serverSocketChannel.accept();
  19.214 +					socketChannel.configureBlocking(false);
  19.215 +					assert socketChannel.isConnected();
  19.216 +					assert socketChannel.finishConnect();
  19.217 +				} catch (IOException ex) {
  19.218 +					// Under heavy load an IOException "Too many open files may
  19.219 +					// be thrown. It most cases we should slow down the connection
  19.220 +					// accepting, to give the worker threads some time to process work.
  19.221 +					Log.get().severe("IOException while accepting connection: " + ex.getMessage());
  19.222 +					Log.get().info("Connection accepting sleeping for seconds...");
  19.223 +					Thread.sleep(5000); // 5 seconds
  19.224 +					continue;
  19.225 +				}
  19.226 +
  19.227 +				final NNTPConnection conn;
  19.228 +				try {
  19.229 +					conn = new NNTPConnection(socketChannel);
  19.230 +					Connections.getInstance().add(conn);
  19.231 +				} catch (IOException ex) {
  19.232 +					Log.get().warning(ex.toString());
  19.233 +					socketChannel.close();
  19.234 +					continue;
  19.235 +				}
  19.236 +
  19.237 +				try {
  19.238 +					SelectionKey selKeyWrite =
  19.239 +						registerSelector(writeSelector, socketChannel, SelectionKey.OP_WRITE);
  19.240 +					registerSelector(readSelector, socketChannel, SelectionKey.OP_READ);
  19.241 +
  19.242 +					Log.get().info("Connected: " + socketChannel.socket().getRemoteSocketAddress());
  19.243 +
  19.244 +					// Set write selection key and send hello to client
  19.245 +					conn.setWriteSelectionKey(selKeyWrite);
  19.246 +					conn.println("200 " + Config.inst().get(Config.HOSTNAME, "localhost")
  19.247 +						+ " " + Main.VERSION + " news server ready - (posting ok).");
  19.248 +				} catch (CancelledKeyException cke) {
  19.249 +					Log.get().warning("CancelledKeyException " + cke.getMessage() + " was thrown: "
  19.250 +						+ socketChannel.socket());
  19.251 +				} catch (ClosedChannelException cce) {
  19.252 +					Log.get().warning("ClosedChannelException " + cce.getMessage() + " was thrown: "
  19.253 +						+ socketChannel.socket());
  19.254 +				}
  19.255 +			}
  19.256 +		} catch (BindException ex) {
  19.257 +			// Could not bind to socket; this is a fatal problem; so perform shutdown
  19.258 +			ex.printStackTrace();
  19.259 +			System.exit(1);
  19.260 +		} catch (IOException ex) {
  19.261 +			ex.printStackTrace();
  19.262 +		} catch (Exception ex) {
  19.263 +			ex.printStackTrace();
  19.264 +		}
  19.265 +	}
  19.266 +
  19.267 +	public static SelectionKey registerSelector(final Selector selector,
  19.268 +		final SocketChannel channel, final int op)
  19.269 +		throws CancelledKeyException, ClosedChannelException
  19.270 +	{
  19.271 +		// Register the selector at the channel, so that it will be notified
  19.272 +		// on the socket's events
  19.273 +		synchronized (RegisterGate) {
  19.274 +			// Wakeup the currently blocking reader/writer thread; we have locked
  19.275 +			// the RegisterGate to prevent the awakened thread to block again
  19.276 +			selector.wakeup();
  19.277 +
  19.278 +			// Lock the selector to prevent the waiting worker threads going into
  19.279 +			// selector.select() which would block the selector.
  19.280 +			synchronized (selector) {
  19.281 +				return channel.register(selector, op, null);
  19.282 +			}
  19.283 +		}
  19.284 +	}
  19.285  }
    20.1 --- a/src/org/sonews/daemon/command/ArticleCommand.java	Sun Aug 29 17:43:58 2010 +0200
    20.2 +++ b/src/org/sonews/daemon/command/ArticleCommand.java	Sun Aug 29 18:17:37 2010 +0200
    20.3 @@ -33,142 +33,120 @@
    20.4  public class ArticleCommand implements Command
    20.5  {
    20.6  
    20.7 -  @Override
    20.8 -  public String[] getSupportedCommandStrings()
    20.9 -  {
   20.10 -    return new String[] {"ARTICLE", "BODY", "HEAD"};
   20.11 -  }
   20.12 +	@Override
   20.13 +	public String[] getSupportedCommandStrings()
   20.14 +	{
   20.15 +		return new String[] {"ARTICLE", "BODY", "HEAD"};
   20.16 +	}
   20.17  
   20.18 -  @Override
   20.19 -  public boolean hasFinished()
   20.20 -  {
   20.21 -    return true;
   20.22 -  }
   20.23 +	@Override
   20.24 +	public boolean hasFinished()
   20.25 +	{
   20.26 +		return true;
   20.27 +	}
   20.28  
   20.29 -  @Override
   20.30 -  public String impliedCapability()
   20.31 -  {
   20.32 -    return null;
   20.33 -  }
   20.34 +	@Override
   20.35 +	public String impliedCapability()
   20.36 +	{
   20.37 +		return null;
   20.38 +	}
   20.39  
   20.40 -  @Override
   20.41 -  public boolean isStateful()
   20.42 -  {
   20.43 -    return false;
   20.44 -  }
   20.45 +	@Override
   20.46 +	public boolean isStateful()
   20.47 +	{
   20.48 +		return false;
   20.49 +	}
   20.50  
   20.51 -  // TODO: Refactor this method to reduce its complexity!
   20.52 -  @Override
   20.53 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   20.54 -    throws IOException
   20.55 -  {
   20.56 -    final String[] command = line.split(" ");
   20.57 -    
   20.58 -    Article article  = null;
   20.59 -    long    artIndex = -1;
   20.60 -    if (command.length == 1)
   20.61 -    {
   20.62 -      article = conn.getCurrentArticle();
   20.63 -      if (article == null)
   20.64 -      {
   20.65 -        conn.println("420 no current article has been selected");
   20.66 -        return;
   20.67 -      }
   20.68 -    }
   20.69 -    else if (command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN))
   20.70 -    {
   20.71 -      // Message-ID
   20.72 -      article = Article.getByMessageID(command[1]);
   20.73 -      if (article == null)
   20.74 -      {
   20.75 -        conn.println("430 no such article found");
   20.76 -        return;
   20.77 -      }
   20.78 -    }
   20.79 -    else
   20.80 -    {
   20.81 -      // Message Number
   20.82 -      try
   20.83 -      {
   20.84 -        Channel currentGroup = conn.getCurrentChannel();
   20.85 -        if(currentGroup == null)
   20.86 -        {
   20.87 -          conn.println("400 no group selected");
   20.88 -          return;
   20.89 -        }
   20.90 -        
   20.91 -        artIndex = Long.parseLong(command[1]);
   20.92 -        article  = currentGroup.getArticle(artIndex);
   20.93 -      }
   20.94 -      catch(NumberFormatException ex)
   20.95 -      {
   20.96 -        ex.printStackTrace();
   20.97 -      }
   20.98 -      catch(StorageBackendException ex)
   20.99 -      {
  20.100 -        ex.printStackTrace();
  20.101 -      }
  20.102 +	// TODO: Refactor this method to reduce its complexity!
  20.103 +	@Override
  20.104 +	public void processLine(NNTPConnection conn, final String line, byte[] raw)
  20.105 +		throws IOException
  20.106 +	{
  20.107 +		final String[] command = line.split(" ");
  20.108  
  20.109 -      if (article == null)
  20.110 -      {
  20.111 -        conn.println("423 no such article number in this group");
  20.112 -        return;
  20.113 -      }
  20.114 -      conn.setCurrentArticle(article);
  20.115 -    }
  20.116 +		Article article = null;
  20.117 +		long artIndex = -1;
  20.118 +		if (command.length == 1) {
  20.119 +			article = conn.getCurrentArticle();
  20.120 +			if (article == null) {
  20.121 +				conn.println("420 no current article has been selected");
  20.122 +				return;
  20.123 +			}
  20.124 +		} else if (command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN)) {
  20.125 +			// Message-ID
  20.126 +			article = Article.getByMessageID(command[1]);
  20.127 +			if (article == null) {
  20.128 +				conn.println("430 no such article found");
  20.129 +				return;
  20.130 +			}
  20.131 +		} else {
  20.132 +			// Message Number
  20.133 +			try {
  20.134 +				Channel currentGroup = conn.getCurrentChannel();
  20.135 +				if (currentGroup == null) {
  20.136 +					conn.println("400 no group selected");
  20.137 +					return;
  20.138 +				}
  20.139  
  20.140 -    if(command[0].equalsIgnoreCase("ARTICLE"))
  20.141 -    {
  20.142 -      conn.println("220 " + artIndex + " " + article.getMessageID()
  20.143 -          + " article retrieved - head and body follow");
  20.144 -      conn.println(article.getHeaderSource());
  20.145 -      conn.println("");
  20.146 -      conn.println(article.getBody());
  20.147 -      conn.println(".");
  20.148 -    }
  20.149 -    else if(command[0].equalsIgnoreCase("BODY"))
  20.150 -    {
  20.151 -      conn.println("222 " + artIndex + " " + article.getMessageID() + " body");
  20.152 -      conn.println(article.getBody());
  20.153 -      conn.println(".");
  20.154 -    }
  20.155 -    
  20.156 -    /*
  20.157 -     * HEAD: This command is mandatory.
  20.158 -     *
  20.159 -     * Syntax
  20.160 -     *    HEAD message-id
  20.161 -     *    HEAD number
  20.162 -     *    HEAD
  20.163 -     *
  20.164 -     * Responses
  20.165 -     *
  20.166 -     * First form (message-id specified)
  20.167 -     *  221 0|n message-id    Headers follow (multi-line)
  20.168 -     *  430                   No article with that message-id
  20.169 -     *
  20.170 -     * Second form (article number specified)
  20.171 -     *  221 n message-id      Headers follow (multi-line)
  20.172 -     *  412                   No newsgroup selected
  20.173 -     *  423                   No article with that number
  20.174 -     *
  20.175 -     * Third form (current article number used)
  20.176 -     *  221 n message-id      Headers follow (multi-line)
  20.177 -     *  412                   No newsgroup selected
  20.178 -     *  420                   Current article number is invalid
  20.179 -     *
  20.180 -     * Parameters
  20.181 -     *  number        Requested article number
  20.182 -     *  n             Returned article number
  20.183 -     *  message-id    Article message-id
  20.184 -     */
  20.185 -    else if(command[0].equalsIgnoreCase("HEAD"))
  20.186 -    {
  20.187 -      conn.println("221 " + artIndex + " " + article.getMessageID()
  20.188 -          + " Headers follow (multi-line)");
  20.189 -      conn.println(article.getHeaderSource());
  20.190 -      conn.println(".");
  20.191 -    }
  20.192 -  }  
  20.193 -  
  20.194 +				artIndex = Long.parseLong(command[1]);
  20.195 +				article = currentGroup.getArticle(artIndex);
  20.196 +			} catch (NumberFormatException ex) {
  20.197 +				ex.printStackTrace();
  20.198 +			} catch (StorageBackendException ex) {
  20.199 +				ex.printStackTrace();
  20.200 +			}
  20.201 +
  20.202 +			if (article == null) {
  20.203 +				conn.println("423 no such article number in this group");
  20.204 +				return;
  20.205 +			}
  20.206 +			conn.setCurrentArticle(article);
  20.207 +		}
  20.208 +
  20.209 +		if (command[0].equalsIgnoreCase("ARTICLE")) {
  20.210 +			conn.println("220 " + artIndex + " " + article.getMessageID()
  20.211 +				+ " article retrieved - head and body follow");
  20.212 +			conn.println(article.getHeaderSource());
  20.213 +			conn.println("");
  20.214 +			conn.println(article.getBody());
  20.215 +			conn.println(".");
  20.216 +		} else if (command[0].equalsIgnoreCase("BODY")) {
  20.217 +			conn.println("222 " + artIndex + " " + article.getMessageID() + " body");
  20.218 +			conn.println(article.getBody());
  20.219 +			conn.println(".");
  20.220 +		} /*
  20.221 +		 * HEAD: This command is mandatory.
  20.222 +		 *
  20.223 +		 * Syntax
  20.224 +		 *    HEAD message-id
  20.225 +		 *    HEAD number
  20.226 +		 *    HEAD
  20.227 +		 *
  20.228 +		 * Responses
  20.229 +		 *
  20.230 +		 * First form (message-id specified)
  20.231 +		 *  221 0|n message-id    Headers follow (multi-line)
  20.232 +		 *  430                   No article with that message-id
  20.233 +		 *
  20.234 +		 * Second form (article number specified)
  20.235 +		 *  221 n message-id      Headers follow (multi-line)
  20.236 +		 *  412                   No newsgroup selected
  20.237 +		 *  423                   No article with that number
  20.238 +		 *
  20.239 +		 * Third form (current article number used)
  20.240 +		 *  221 n message-id      Headers follow (multi-line)
  20.241 +		 *  412                   No newsgroup selected
  20.242 +		 *  420                   Current article number is invalid
  20.243 +		 *
  20.244 +		 * Parameters
  20.245 +		 *  number        Requested article number
  20.246 +		 *  n             Returned article number
  20.247 +		 *  message-id    Article message-id
  20.248 +		 */ else if (command[0].equalsIgnoreCase("HEAD")) {
  20.249 +			conn.println("221 " + artIndex + " " + article.getMessageID()
  20.250 +				+ " Headers follow (multi-line)");
  20.251 +			conn.println(article.getHeaderSource());
  20.252 +			conn.println(".");
  20.253 +		}
  20.254 +	}
  20.255  }
    21.1 --- a/src/org/sonews/daemon/command/CapabilitiesCommand.java	Sun Aug 29 17:43:58 2010 +0200
    21.2 +++ b/src/org/sonews/daemon/command/CapabilitiesCommand.java	Sun Aug 29 18:17:37 2010 +0200
    21.3 @@ -42,52 +42,49 @@
    21.4  public class CapabilitiesCommand implements Command
    21.5  {
    21.6  
    21.7 -  static final String[] CAPABILITIES = new String[]
    21.8 -    {
    21.9 -      "VERSION 2", // MUST be the first one; VERSION 2 refers to RFC3977
   21.10 -      "READER",    // Server implements commands for reading
   21.11 -      "POST",      // Server implements POST command
   21.12 -      "OVER"       // Server implements OVER command
   21.13 -    };
   21.14 +	static final String[] CAPABILITIES = new String[] {
   21.15 +		"VERSION 2", // MUST be the first one; VERSION 2 refers to RFC3977
   21.16 +		"READER", // Server implements commands for reading
   21.17 +		"POST", // Server implements POST command
   21.18 +		"OVER" // Server implements OVER command
   21.19 +	};
   21.20  
   21.21 -  @Override
   21.22 -  public String[] getSupportedCommandStrings()
   21.23 -  {
   21.24 -    return new String[] {"CAPABILITIES"};
   21.25 -  }
   21.26 +	@Override
   21.27 +	public String[] getSupportedCommandStrings()
   21.28 +	{
   21.29 +		return new String[] {"CAPABILITIES"};
   21.30 +	}
   21.31  
   21.32 -  /**
   21.33 -   * First called after one call to processLine().
   21.34 -   * @return
   21.35 -   */
   21.36 -  @Override
   21.37 -  public boolean hasFinished()
   21.38 -  {
   21.39 -    return true;
   21.40 -  }
   21.41 +	/**
   21.42 +	 * First called after one call to processLine().
   21.43 +	 * @return
   21.44 +	 */
   21.45 +	@Override
   21.46 +	public boolean hasFinished()
   21.47 +	{
   21.48 +		return true;
   21.49 +	}
   21.50  
   21.51 -  @Override
   21.52 -  public String impliedCapability()
   21.53 -  {
   21.54 -    return null;
   21.55 -  }
   21.56 -  
   21.57 -  @Override
   21.58 -  public boolean isStateful()
   21.59 -  {
   21.60 -    return false;
   21.61 -  }
   21.62 +	@Override
   21.63 +	public String impliedCapability()
   21.64 +	{
   21.65 +		return null;
   21.66 +	}
   21.67  
   21.68 -  @Override
   21.69 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   21.70 -    throws IOException
   21.71 -  {
   21.72 -    conn.println("101 Capabilities list:");
   21.73 -    for(String cap : CAPABILITIES)
   21.74 -    {
   21.75 -      conn.println(cap);
   21.76 -    }
   21.77 -    conn.println(".");
   21.78 -  }
   21.79 +	@Override
   21.80 +	public boolean isStateful()
   21.81 +	{
   21.82 +		return false;
   21.83 +	}
   21.84  
   21.85 +	@Override
   21.86 +	public void processLine(NNTPConnection conn, final String line, byte[] raw)
   21.87 +		throws IOException
   21.88 +	{
   21.89 +		conn.println("101 Capabilities list:");
   21.90 +		for (String cap : CAPABILITIES) {
   21.91 +			conn.println(cap);
   21.92 +		}
   21.93 +		conn.println(".");
   21.94 +	}
   21.95  }
    22.1 --- a/src/org/sonews/daemon/command/Command.java	Sun Aug 29 17:43:58 2010 +0200
    22.2 +++ b/src/org/sonews/daemon/command/Command.java	Sun Aug 29 18:17:37 2010 +0200
    22.3 @@ -30,22 +30,21 @@
    22.4  public interface Command
    22.5  {
    22.6  
    22.7 -  /**
    22.8 -   * @return true if this instance can be reused.
    22.9 -   */
   22.10 -  boolean hasFinished();
   22.11 +	/**
   22.12 +	 * @return true if this instance can be reused.
   22.13 +	 */
   22.14 +	boolean hasFinished();
   22.15  
   22.16 -  /**
   22.17 -   * Returns capability string that is implied by this command class.
   22.18 -   * MAY return null if the command is required by the NNTP standard.
   22.19 -   */
   22.20 -  String impliedCapability();
   22.21 +	/**
   22.22 +	 * Returns capability string that is implied by this command class.
   22.23 +	 * MAY return null if the command is required by the NNTP standard.
   22.24 +	 */
   22.25 +	String impliedCapability();
   22.26  
   22.27 -  boolean isStateful();
   22.28 +	boolean isStateful();
   22.29  
   22.30 -  String[] getSupportedCommandStrings();
   22.31 +	String[] getSupportedCommandStrings();
   22.32  
   22.33 -  void processLine(NNTPConnection conn, String line, byte[] rawLine)
   22.34 -    throws IOException, StorageBackendException;
   22.35 -
   22.36 +	void processLine(NNTPConnection conn, String line, byte[] rawLine)
   22.37 +		throws IOException, StorageBackendException;
   22.38  }
    23.1 --- a/src/org/sonews/daemon/command/GroupCommand.java	Sun Aug 29 17:43:58 2010 +0200
    23.2 +++ b/src/org/sonews/daemon/command/GroupCommand.java	Sun Aug 29 18:17:37 2010 +0200
    23.3 @@ -48,55 +48,48 @@
    23.4  public class GroupCommand implements Command
    23.5  {
    23.6  
    23.7 -  @Override
    23.8 -  public String[] getSupportedCommandStrings()
    23.9 -  {
   23.10 -    return new String[]{"GROUP"};
   23.11 -  }
   23.12 +	@Override
   23.13 +	public String[] getSupportedCommandStrings()
   23.14 +	{
   23.15 +		return new String[] {"GROUP"};
   23.16 +	}
   23.17  
   23.18 -  @Override
   23.19 -  public boolean hasFinished()
   23.20 -  {
   23.21 -    return true;
   23.22 -  }
   23.23 +	@Override
   23.24 +	public boolean hasFinished()
   23.25 +	{
   23.26 +		return true;
   23.27 +	}
   23.28  
   23.29 -  @Override
   23.30 -  public String impliedCapability()
   23.31 -  {
   23.32 -    return null;
   23.33 -  }
   23.34 +	@Override
   23.35 +	public String impliedCapability()
   23.36 +	{
   23.37 +		return null;
   23.38 +	}
   23.39  
   23.40 -  @Override
   23.41 -  public boolean isStateful()
   23.42 -  {
   23.43 -    return true;
   23.44 -  }
   23.45 -  
   23.46 -  @Override
   23.47 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   23.48 -    throws IOException, StorageBackendException
   23.49 -  {
   23.50 -    final String[] command = line.split(" ");
   23.51 +	@Override
   23.52 +	public boolean isStateful()
   23.53 +	{
   23.54 +		return true;
   23.55 +	}
   23.56  
   23.57 -    Channel group;
   23.58 -    if(command.length >= 2)
   23.59 -    {
   23.60 -      group = Channel.getByName(command[1]);
   23.61 -      if(group == null || group.isDeleted())
   23.62 -      {
   23.63 -        conn.println("411 no such news group");
   23.64 -      }
   23.65 -      else
   23.66 -      {
   23.67 -        conn.setCurrentGroup(group);
   23.68 -        conn.println("211 " + group.getPostingsCount() + " " + group.getFirstArticleNumber()
   23.69 -          + " " + group.getLastArticleNumber() + " " + group.getName() + " group selected");
   23.70 -      }
   23.71 -    }
   23.72 -    else
   23.73 -    {
   23.74 -      conn.println("500 no group name given");
   23.75 -    }
   23.76 -  }
   23.77 +	@Override
   23.78 +	public void processLine(NNTPConnection conn, final String line, byte[] raw)
   23.79 +		throws IOException, StorageBackendException
   23.80 +	{
   23.81 +		final String[] command = line.split(" ");
   23.82  
   23.83 +		Channel group;
   23.84 +		if (command.length >= 2) {
   23.85 +			group = Channel.getByName(command[1]);
   23.86 +			if (group == null || group.isDeleted()) {
   23.87 +				conn.println("411 no such news group");
   23.88 +			} else {
   23.89 +				conn.setCurrentGroup(group);
   23.90 +				conn.println("211 " + group.getPostingsCount() + " " + group.getFirstArticleNumber()
   23.91 +					+ " " + group.getLastArticleNumber() + " " + group.getName() + " group selected");
   23.92 +			}
   23.93 +		} else {
   23.94 +			conn.println("500 no group name given");
   23.95 +		}
   23.96 +	}
   23.97  }
    24.1 --- a/src/org/sonews/daemon/command/HelpCommand.java	Sun Aug 29 17:43:58 2010 +0200
    24.2 +++ b/src/org/sonews/daemon/command/HelpCommand.java	Sun Aug 29 18:17:37 2010 +0200
    24.3 @@ -35,66 +35,56 @@
    24.4  public class HelpCommand implements Command
    24.5  {
    24.6  
    24.7 -  @Override
    24.8 -  public boolean hasFinished()
    24.9 -  {
   24.10 -    return true;
   24.11 -  }
   24.12 +	@Override
   24.13 +	public boolean hasFinished()
   24.14 +	{
   24.15 +		return true;
   24.16 +	}
   24.17  
   24.18 -  @Override
   24.19 -  public String impliedCapability()
   24.20 -  {
   24.21 -    return null;
   24.22 -  }
   24.23 +	@Override
   24.24 +	public String impliedCapability()
   24.25 +	{
   24.26 +		return null;
   24.27 +	}
   24.28  
   24.29 -  @Override
   24.30 -  public boolean isStateful()
   24.31 -  {
   24.32 -    return true;
   24.33 -  }
   24.34 +	@Override
   24.35 +	public boolean isStateful()
   24.36 +	{
   24.37 +		return true;
   24.38 +	}
   24.39  
   24.40 -  @Override
   24.41 -  public String[] getSupportedCommandStrings()
   24.42 -  {
   24.43 -    return new String[]{"HELP"};
   24.44 -  }
   24.45 -  
   24.46 -  @Override
   24.47 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   24.48 -    throws IOException
   24.49 -  {
   24.50 -    final String[] command = line.split(" ");
   24.51 -    conn.println("100 help text follows");
   24.52 +	@Override
   24.53 +	public String[] getSupportedCommandStrings()
   24.54 +	{
   24.55 +		return new String[] {"HELP"};
   24.56 +	}
   24.57  
   24.58 -    if(line.length() <= 1)
   24.59 -    {
   24.60 -      final String[] help = Resource
   24.61 -        .getAsString("helpers/helptext", true).split("\n");
   24.62 -      for(String hstr : help)
   24.63 -      {
   24.64 -        conn.println(hstr);
   24.65 -      }
   24.66 +	@Override
   24.67 +	public void processLine(NNTPConnection conn, final String line, byte[] raw)
   24.68 +		throws IOException
   24.69 +	{
   24.70 +		final String[] command = line.split(" ");
   24.71 +		conn.println("100 help text follows");
   24.72  
   24.73 -      Set<String> commandNames = CommandSelector.getCommandNames();
   24.74 -      for(String cmdName : commandNames)
   24.75 -      {
   24.76 -        conn.println(cmdName);
   24.77 -      }
   24.78 -    }
   24.79 -    else
   24.80 -    {
   24.81 -      Command cmd = CommandSelector.getInstance().get(command[1]);
   24.82 -      if(cmd instanceof HelpfulCommand)
   24.83 -      {
   24.84 -        conn.println(((HelpfulCommand)cmd).getHelpString());
   24.85 -      }
   24.86 -      else
   24.87 -      {
   24.88 -        conn.println("No further help information available.");
   24.89 -      }
   24.90 -    }
   24.91 -    
   24.92 -    conn.println(".");
   24.93 -  }
   24.94 -  
   24.95 +		if (line.length() <= 1) {
   24.96 +			final String[] help = Resource.getAsString("helpers/helptext", true).split("\n");
   24.97 +			for (String hstr : help) {
   24.98 +				conn.println(hstr);
   24.99 +			}
  24.100 +
  24.101 +			Set<String> commandNames = CommandSelector.getCommandNames();
  24.102 +			for (String cmdName : commandNames) {
  24.103 +				conn.println(cmdName);
  24.104 +			}
  24.105 +		} else {
  24.106 +			Command cmd = CommandSelector.getInstance().get(command[1]);
  24.107 +			if (cmd instanceof HelpfulCommand) {
  24.108 +				conn.println(((HelpfulCommand) cmd).getHelpString());
  24.109 +			} else {
  24.110 +				conn.println("No further help information available.");
  24.111 +			}
  24.112 +		}
  24.113 +
  24.114 +		conn.println(".");
  24.115 +	}
  24.116  }
    25.1 --- a/src/org/sonews/daemon/command/HelpfulCommand.java	Sun Aug 29 17:43:58 2010 +0200
    25.2 +++ b/src/org/sonews/daemon/command/HelpfulCommand.java	Sun Aug 29 18:17:37 2010 +0200
    25.3 @@ -26,10 +26,9 @@
    25.4  public interface HelpfulCommand extends Command
    25.5  {
    25.6  
    25.7 -  /**
    25.8 -   * @return A short description of this command, that is
    25.9 -   * used within the output of the HELP command.
   25.10 -   */
   25.11 -  String getHelpString();
   25.12 -
   25.13 +	/**
   25.14 +	 * @return A short description of this command, that is
   25.15 +	 * used within the output of the HELP command.
   25.16 +	 */
   25.17 +	String getHelpString();
   25.18  }
    26.1 --- a/src/org/sonews/daemon/command/ListCommand.java	Sun Aug 29 17:43:58 2010 +0200
    26.2 +++ b/src/org/sonews/daemon/command/ListCommand.java	Sun Aug 29 18:17:37 2010 +0200
    26.3 @@ -37,117 +37,93 @@
    26.4  public class ListCommand implements Command
    26.5  {
    26.6  
    26.7 -  @Override
    26.8 -  public String[] getSupportedCommandStrings()
    26.9 -  {
   26.10 -    return new String[]{"LIST"};
   26.11 -  }
   26.12 +	@Override
   26.13 +	public String[] getSupportedCommandStrings()
   26.14 +	{
   26.15 +		return new String[] {"LIST"};
   26.16 +	}
   26.17  
   26.18 -  @Override
   26.19 -  public boolean hasFinished()
   26.20 -  {
   26.21 -    return true;
   26.22 -  }
   26.23 +	@Override
   26.24 +	public boolean hasFinished()
   26.25 +	{
   26.26 +		return true;
   26.27 +	}
   26.28  
   26.29 -  @Override
   26.30 -  public String impliedCapability()
   26.31 -  {
   26.32 -    return null;
   26.33 -  }
   26.34 +	@Override
   26.35 +	public String impliedCapability()
   26.36 +	{
   26.37 +		return null;
   26.38 +	}
   26.39  
   26.40 -  @Override
   26.41 -  public boolean isStateful()
   26.42 -  {
   26.43 -    return false;
   26.44 -  }
   26.45 -  
   26.46 -  @Override
   26.47 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   26.48 -    throws IOException, StorageBackendException
   26.49 -  {
   26.50 -    final String[] command = line.split(" ");
   26.51 -    
   26.52 -    if(command.length >= 2)
   26.53 -    {
   26.54 -      if(command[1].equalsIgnoreCase("OVERVIEW.FMT"))
   26.55 -      {
   26.56 -        conn.println("215 information follows");
   26.57 -        conn.println("Subject:\nFrom:\nDate:\nMessage-ID:\nReferences:\nBytes:\nLines:\nXref");
   26.58 -        conn.println(".");
   26.59 -      }
   26.60 -      else if(command[1].equalsIgnoreCase("NEWSGROUPS"))
   26.61 -      {
   26.62 -        conn.println("215 information follows");
   26.63 -        final List<Channel> list = Channel.getAll();
   26.64 -        for (Channel g : list)
   26.65 -        {
   26.66 -          conn.println(g.getName() + "\t" + "-");
   26.67 -        }
   26.68 -        conn.println(".");
   26.69 -      }
   26.70 -      else if(command[1].equalsIgnoreCase("SUBSCRIPTIONS"))
   26.71 -      {
   26.72 -        conn.println("215 information follows");
   26.73 -        conn.println(".");
   26.74 -      }
   26.75 -      else if(command[1].equalsIgnoreCase("EXTENSIONS"))
   26.76 -      {
   26.77 -        conn.println("202 Supported NNTP extensions.");
   26.78 -        conn.println("LISTGROUP");
   26.79 -        conn.println("XDAEMON");
   26.80 -        conn.println("XPAT");
   26.81 -        conn.println(".");
   26.82 -      }
   26.83 -      else if(command[1].equalsIgnoreCase("ACTIVE"))
   26.84 -      {
   26.85 -        String  pattern  = command.length == 2
   26.86 -          ? null : command[2].replace("*", "\\w*");
   26.87 -        printGroupInfo(conn, pattern);
   26.88 -      }
   26.89 -      else
   26.90 -      {
   26.91 -        conn.println("500 unknown argument to LIST command");
   26.92 -      }
   26.93 -    }
   26.94 -    else
   26.95 -    {
   26.96 -      printGroupInfo(conn, null);
   26.97 -    }
   26.98 -  }
   26.99 +	@Override
  26.100 +	public boolean isStateful()
  26.101 +	{
  26.102 +		return false;
  26.103 +	}
  26.104  
  26.105 -  private void printGroupInfo(NNTPConnection conn, String pattern)
  26.106 -    throws IOException, StorageBackendException
  26.107 -  {
  26.108 -    final List<Channel> groups = Channel.getAll();
  26.109 -    if(groups != null)
  26.110 -    {
  26.111 -      conn.println("215 list of newsgroups follows");
  26.112 -      for(Channel g : groups)
  26.113 -      {
  26.114 -        try
  26.115 -        {
  26.116 -          Matcher matcher = pattern == null ?
  26.117 -            null : Pattern.compile(pattern).matcher(g.getName());
  26.118 -          if(!g.isDeleted() &&
  26.119 -            (matcher == null || matcher.find()))
  26.120 -          {
  26.121 -            String writeable = g.isWriteable() ? " y" : " n";
  26.122 -            // Indeed first the higher article number then the lower
  26.123 -            conn.println(g.getName() + " " + g.getLastArticleNumber() + " "
  26.124 -              + g.getFirstArticleNumber() + writeable);
  26.125 -          }
  26.126 -        }
  26.127 -        catch(PatternSyntaxException ex)
  26.128 -        {
  26.129 -          Log.get().info(ex.toString());
  26.130 -        }
  26.131 -      }
  26.132 -      conn.println(".");
  26.133 -    }
  26.134 -    else
  26.135 -    {
  26.136 -      conn.println("500 server backend malfunction");
  26.137 -    }
  26.138 -  }
  26.139 +	@Override
  26.140 +	public void processLine(NNTPConnection conn, final String line, byte[] raw)
  26.141 +		throws IOException, StorageBackendException
  26.142 +	{
  26.143 +		final String[] command = line.split(" ");
  26.144  
  26.145 +		if (command.length >= 2) {
  26.146 +			if (command[1].equalsIgnoreCase("OVERVIEW.FMT")) {
  26.147 +				conn.println("215 information follows");
  26.148 +				conn.println("Subject:\nFrom:\nDate:\nMessage-ID:\nReferences:\nBytes:\nLines:\nXref");
  26.149 +				conn.println(".");
  26.150 +			} else if (command[1].equalsIgnoreCase("NEWSGROUPS")) {
  26.151 +				conn.println("215 information follows");
  26.152 +				final List<Channel> list = Channel.getAll();
  26.153 +				for (Channel g : list) {
  26.154 +					conn.println(g.getName() + "\t" + "-");
  26.155 +				}
  26.156 +				conn.println(".");
  26.157 +			} else if (command[1].equalsIgnoreCase("SUBSCRIPTIONS")) {
  26.158 +				conn.println("215 information follows");
  26.159 +				conn.println(".");
  26.160 +			} else if (command[1].equalsIgnoreCase("EXTENSIONS")) {
  26.161 +				conn.println("202 Supported NNTP extensions.");
  26.162 +				conn.println("LISTGROUP");
  26.163 +				conn.println("XDAEMON");
  26.164 +				conn.println("XPAT");
  26.165 +				conn.println(".");
  26.166 +			} else if (command[1].equalsIgnoreCase("ACTIVE")) {
  26.167 +				String pattern = command.length == 2
  26.168 +					? null : command[2].replace("*", "\\w*");
  26.169 +				printGroupInfo(conn, pattern);
  26.170 +			} else {
  26.171 +				conn.println("500 unknown argument to LIST command");
  26.172 +			}
  26.173 +		} else {
  26.174 +			printGroupInfo(conn, null);
  26.175 +		}
  26.176 +	}
  26.177 +
  26.178 +	private void printGroupInfo(NNTPConnection conn, String pattern)
  26.179 +		throws IOException, StorageBackendException
  26.180 +	{
  26.181 +		final List<Channel> groups = Channel.getAll();
  26.182 +		if (groups != null) {
  26.183 +			conn.println("215 list of newsgroups follows");
  26.184 +			for (Channel g : groups) {
  26.185 +				try {
  26.186 +					Matcher matcher = pattern == null
  26.187 +						? null : Pattern.compile(pattern).matcher(g.getName());
  26.188 +					if (!g.isDeleted()
  26.189 +						&& (matcher == null || matcher.find())) {
  26.190 +						String writeable = g.isWriteable() ? " y" : " n";
  26.191 +						// Indeed first the higher article number then the lower
  26.192 +						conn.println(g.getName() + " " + g.getLastArticleNumber() + " "
  26.193 +							+ g.getFirstArticleNumber() + writeable);
  26.194 +					}
  26.195 +				} catch (PatternSyntaxException ex) {
  26.196 +					Log.get().info(ex.toString());
  26.197 +				}
  26.198 +			}
  26.199 +			conn.println(".");
  26.200 +		} else {
  26.201 +			conn.println("500 server backend malfunction");
  26.202 +		}
  26.203 +	}
  26.204  }
    27.1 --- a/src/org/sonews/daemon/command/ListGroupCommand.java	Sun Aug 29 17:43:58 2010 +0200
    27.2 +++ b/src/org/sonews/daemon/command/ListGroupCommand.java	Sun Aug 29 18:17:37 2010 +0200
    27.3 @@ -33,62 +33,56 @@
    27.4  public class ListGroupCommand implements Command
    27.5  {
    27.6  
    27.7 -  @Override
    27.8 -  public String[] getSupportedCommandStrings()
    27.9 -  {
   27.10 -    return new String[]{"LISTGROUP"};
   27.11 -  }
   27.12 +	@Override
   27.13 +	public String[] getSupportedCommandStrings()
   27.14 +	{
   27.15 +		return new String[] {"LISTGROUP"};
   27.16 +	}
   27.17  
   27.18 -  @Override
   27.19 -  public boolean hasFinished()
   27.20 -  {
   27.21 -    return true;
   27.22 -  }
   27.23 +	@Override
   27.24 +	public boolean hasFinished()
   27.25 +	{
   27.26 +		return true;
   27.27 +	}
   27.28  
   27.29 -  @Override
   27.30 -  public String impliedCapability()
   27.31 -  {
   27.32 -    return null;
   27.33 -  }
   27.34 +	@Override
   27.35 +	public String impliedCapability()
   27.36 +	{
   27.37 +		return null;
   27.38 +	}
   27.39  
   27.40 -  @Override
   27.41 -  public boolean isStateful()
   27.42 -  {
   27.43 -    return false;
   27.44 -  }
   27.45 +	@Override
   27.46 +	public boolean isStateful()
   27.47 +	{
   27.48 +		return false;
   27.49 +	}
   27.50  
   27.51 -  @Override
   27.52 -  public void processLine(NNTPConnection conn, final String commandName, byte[] raw)
   27.53 -    throws IOException, StorageBackendException
   27.54 -  {
   27.55 -    final String[] command = commandName.split(" ");
   27.56 +	@Override
   27.57 +	public void processLine(NNTPConnection conn, final String commandName, byte[] raw)
   27.58 +		throws IOException, StorageBackendException
   27.59 +	{
   27.60 +		final String[] command = commandName.split(" ");
   27.61  
   27.62 -    Channel group;
   27.63 -    if(command.length >= 2)
   27.64 -    {
   27.65 -      group = Channel.getByName(command[1]);
   27.66 -    }
   27.67 -    else
   27.68 -    {
   27.69 -      group = conn.getCurrentChannel();
   27.70 -    }
   27.71 +		Channel group;
   27.72 +		if (command.length >= 2) {
   27.73 +			group = Channel.getByName(command[1]);
   27.74 +		} else {
   27.75 +			group = conn.getCurrentChannel();
   27.76 +		}
   27.77  
   27.78 -    if (group == null)
   27.79 -    {
   27.80 -      conn.println("412 no group selected; use GROUP <group> command");
   27.81 -      return;
   27.82 -    }
   27.83 +		if (group == null) {
   27.84 +			conn.println("412 no group selected; use GROUP <group> command");
   27.85 +			return;
   27.86 +		}
   27.87  
   27.88 -    List<Long> ids = group.getArticleNumbers();
   27.89 -    conn.println("211 " + ids.size() + " " +
   27.90 -      group.getFirstArticleNumber() + " " + 
   27.91 -      group.getLastArticleNumber() + " list of article numbers follow");
   27.92 -    for(long id : ids)
   27.93 -    {
   27.94 -      // One index number per line
   27.95 -      conn.println(Long.toString(id));
   27.96 -    }
   27.97 -    conn.println(".");
   27.98 -  }
   27.99 -
  27.100 +		List<Long> ids = group.getArticleNumbers();
  27.101 +		conn.println("211 " + ids.size() + " "
  27.102 +			+ group.getFirstArticleNumber() + " "
  27.103 +			+ group.getLastArticleNumber() + " list of article numbers follow");
  27.104 +		for (long id : ids) {
  27.105 +			// One index number per line
  27.106 +			conn.println(Long.toString(id));
  27.107 +		}
  27.108 +		conn.println(".");
  27.109 +	}
  27.110  }
    28.1 --- a/src/org/sonews/daemon/command/ModeReaderCommand.java	Sun Aug 29 17:43:58 2010 +0200
    28.2 +++ b/src/org/sonews/daemon/command/ModeReaderCommand.java	Sun Aug 29 18:17:37 2010 +0200
    28.3 @@ -30,43 +30,39 @@
    28.4   */
    28.5  public class ModeReaderCommand implements Command
    28.6  {
    28.7 -  
    28.8 -  @Override
    28.9 -  public String[] getSupportedCommandStrings()
   28.10 -  {
   28.11 -    return new String[]{"MODE"};
   28.12 -  }
   28.13  
   28.14 -  @Override
   28.15 -  public boolean hasFinished()
   28.16 -  {
   28.17 -    return true;
   28.18 -  }
   28.19 +	@Override
   28.20 +	public String[] getSupportedCommandStrings()
   28.21 +	{
   28.22 +		return new String[] {"MODE"};
   28.23 +	}
   28.24  
   28.25 -  @Override
   28.26 -  public String impliedCapability()
   28.27 -  {
   28.28 -    return null;
   28.29 -  }
   28.30 +	@Override
   28.31 +	public boolean hasFinished()
   28.32 +	{
   28.33 +		return true;
   28.34 +	}
   28.35  
   28.36 -  @Override
   28.37 -  public boolean isStateful()
   28.38 -  {
   28.39 -    return false;
   28.40 -  }
   28.41 +	@Override
   28.42 +	public String impliedCapability()
   28.43 +	{
   28.44 +		return null;
   28.45 +	}
   28.46  
   28.47 -  @Override
   28.48 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   28.49 -    throws IOException, StorageBackendException
   28.50 -  {
   28.51 -    if(line.equalsIgnoreCase("MODE READER"))
   28.52 -    {
   28.53 -      conn.println("200 hello you can post");
   28.54 -    }
   28.55 -    else
   28.56 -    {
   28.57 -      conn.println("500 I do not know this mode command");
   28.58 -    }
   28.59 -  }
   28.60 +	@Override
   28.61 +	public boolean isStateful()
   28.62 +	{
   28.63 +		return false;
   28.64 +	}
   28.65  
   28.66 +	@Override
   28.67 +	public void processLine(NNTPConnection conn, final String line, byte[] raw)
   28.68 +		throws IOException, StorageBackendException
   28.69 +	{
   28.70 +		if (line.equalsIgnoreCase("MODE READER")) {
   28.71 +			conn.println("200 hello you can post");
   28.72 +		} else {
   28.73 +			conn.println("500 I do not know this mode command");
   28.74 +		}
   28.75 +	}
   28.76  }
    29.1 --- a/src/org/sonews/daemon/command/NewGroupsCommand.java	Sun Aug 29 17:43:58 2010 +0200
    29.2 +++ b/src/org/sonews/daemon/command/NewGroupsCommand.java	Sun Aug 29 18:17:37 2010 +0200
    29.3 @@ -31,48 +31,44 @@
    29.4  public class NewGroupsCommand implements Command
    29.5  {
    29.6  
    29.7 -  @Override
    29.8 -  public String[] getSupportedCommandStrings()
    29.9 -  {
   29.10 -    return new String[]{"NEWGROUPS"};
   29.11 -  }
   29.12 +	@Override
   29.13 +	public String[] getSupportedCommandStrings()
   29.14 +	{
   29.15 +		return new String[] {"NEWGROUPS"};
   29.16 +	}
   29.17  
   29.18 -  @Override
   29.19 -  public boolean hasFinished()
   29.20 -  {
   29.21 -    return true;
   29.22 -  }
   29.23 +	@Override
   29.24 +	public boolean hasFinished()
   29.25 +	{
   29.26 +		return true;
   29.27 +	}
   29.28  
   29.29 -  @Override
   29.30 -  public String impliedCapability()
   29.31 -  {
   29.32 -    return null;
   29.33 -  }
   29.34 +	@Override
   29.35 +	public String impliedCapability()
   29.36 +	{
   29.37 +		return null;
   29.38 +	}
   29.39  
   29.40 -  @Override
   29.41 -  public boolean isStateful()
   29.42 -  {
   29.43 -    return false;
   29.44 -  }
   29.45 +	@Override
   29.46 +	public boolean isStateful()
   29.47 +	{
   29.48 +		return false;
   29.49 +	}
   29.50  
   29.51 -  @Override
   29.52 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   29.53 -    throws IOException, StorageBackendException
   29.54 -  {
   29.55 -    final String[] command = line.split(" ");
   29.56 +	@Override
   29.57 +	public void processLine(NNTPConnection conn, final String line, byte[] raw)
   29.58 +		throws IOException, StorageBackendException
   29.59 +	{
   29.60 +		final String[] command = line.split(" ");
   29.61  
   29.62 -    if(command.length == 3)
   29.63 -    {
   29.64 -      conn.println("231 list of new newsgroups follows");
   29.65 +		if (command.length == 3) {
   29.66 +			conn.println("231 list of new newsgroups follows");
   29.67  
   29.68 -      // Currently we do not store a group's creation date;
   29.69 -      // so we return an empty list which is a valid response
   29.70 -      conn.println(".");
   29.71 -    }
   29.72 -    else
   29.73 -    {
   29.74 -      conn.println("500 invalid command usage");
   29.75 -    }
   29.76 -  }
   29.77 -
   29.78 +			// Currently we do not store a group's creation date;
   29.79 +			// so we return an empty list which is a valid response
   29.80 +			conn.println(".");
   29.81 +		} else {
   29.82 +			conn.println("500 invalid command usage");
   29.83 +		}
   29.84 +	}
   29.85  }
    30.1 --- a/src/org/sonews/daemon/command/NextPrevCommand.java	Sun Aug 29 17:43:58 2010 +0200
    30.2 +++ b/src/org/sonews/daemon/command/NextPrevCommand.java	Sun Aug 29 18:17:37 2010 +0200
    30.3 @@ -33,84 +33,73 @@
    30.4  public class NextPrevCommand implements Command
    30.5  {
    30.6  
    30.7 -  @Override
    30.8 -  public String[] getSupportedCommandStrings()
    30.9 -  {
   30.10 -    return new String[]{"NEXT", "PREV"};
   30.11 -  }
   30.12 +	@Override
   30.13 +	public String[] getSupportedCommandStrings()
   30.14 +	{
   30.15 +		return new String[] {"NEXT", "PREV"};
   30.16 +	}
   30.17  
   30.18 -  @Override
   30.19 -  public boolean hasFinished()
   30.20 -  {
   30.21 -    return true;
   30.22 -  }
   30.23 +	@Override
   30.24 +	public boolean hasFinished()
   30.25 +	{
   30.26 +		return true;
   30.27 +	}
   30.28  
   30.29 -  @Override
   30.30 -  public String impliedCapability()
   30.31 -  {
   30.32 -    return null;
   30.33 -  }
   30.34 +	@Override
   30.35 +	public String impliedCapability()
   30.36 +	{
   30.37 +		return null;
   30.38 +	}
   30.39  
   30.40 -  @Override
   30.41 -  public boolean isStateful()
   30.42 -  {
   30.43 -    return false;
   30.44 -  }
   30.45 +	@Override
   30.46 +	public boolean isStateful()
   30.47 +	{
   30.48 +		return false;
   30.49 +	}
   30.50  
   30.51 -  @Override
   30.52 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   30.53 -    throws IOException, StorageBackendException
   30.54 -  {
   30.55 -    final Article currA = conn.getCurrentArticle();
   30.56 -    final Channel currG = conn.getCurrentChannel();
   30.57 -    
   30.58 -    if (currA == null)
   30.59 -    {
   30.60 -      conn.println("420 no current article has been selected");
   30.61 -      return;
   30.62 -    }
   30.63 -    
   30.64 -    if (currG == null)
   30.65 -    {
   30.66 -      conn.println("412 no newsgroup selected");
   30.67 -      return;
   30.68 -    }
   30.69 -    
   30.70 -    final String[] command = line.split(" ");
   30.71 +	@Override
   30.72 +	public void processLine(NNTPConnection conn, final String line, byte[] raw)
   30.73 +		throws IOException, StorageBackendException
   30.74 +	{
   30.75 +		final Article currA = conn.getCurrentArticle();
   30.76 +		final Channel currG = conn.getCurrentChannel();
   30.77  
   30.78 -    if(command[0].equalsIgnoreCase("NEXT"))
   30.79 -    {
   30.80 -      selectNewArticle(conn, currA, currG, 1);
   30.81 -    }
   30.82 -    else if(command[0].equalsIgnoreCase("PREV"))
   30.83 -    {
   30.84 -      selectNewArticle(conn, currA, currG, -1);
   30.85 -    }
   30.86 -    else
   30.87 -    {
   30.88 -      conn.println("500 internal server error");
   30.89 -    }
   30.90 -  }
   30.91 -  
   30.92 -  private void selectNewArticle(NNTPConnection conn, Article article, Channel grp,
   30.93 -    final int delta)
   30.94 -    throws IOException, StorageBackendException
   30.95 -  {
   30.96 -    assert article != null;
   30.97 +		if (currA == null) {
   30.98 +			conn.println("420 no current article has been selected");
   30.99 +			return;
  30.100 +		}
  30.101  
  30.102 -    article = grp.getArticle(grp.getIndexOf(article) + delta);
  30.103 +		if (currG == null) {
  30.104 +			conn.println("412 no newsgroup selected");
  30.105 +			return;
  30.106 +		}
  30.107  
  30.108 -    if(article == null)
  30.109 -    {
  30.110 -      conn.println("421 no next article in this group");
  30.111 -    }
  30.112 -    else
  30.113 -    {
  30.114 -      conn.setCurrentArticle(article);
  30.115 -      conn.println("223 " + conn.getCurrentChannel().getIndexOf(article)
  30.116 -                    + " " + article.getMessageID()
  30.117 -                    + " article retrieved - request text separately");
  30.118 -    }
  30.119 -  }
  30.120 +		final String[] command = line.split(" ");
  30.121  
  30.122 +		if (command[0].equalsIgnoreCase("NEXT")) {
  30.123 +			selectNewArticle(conn, currA, currG, 1);
  30.124 +		} else if (command[0].equalsIgnoreCase("PREV")) {
  30.125 +			selectNewArticle(conn, currA, currG, -1);
  30.126 +		} else {
  30.127 +			conn.println("500 internal server error");
  30.128 +		}
  30.129 +	}
  30.130 +
  30.131 +	private void selectNewArticle(NNTPConnection conn, Article article, Channel grp,
  30.132 +		final int delta)
  30.133 +		throws IOException, StorageBackendException
  30.134 +	{
  30.135 +		assert article != null;
  30.136 +
  30.137 +		article = grp.getArticle(grp.getIndexOf(article) + delta);
  30.138 +
  30.139 +		if (article == null) {
  30.140 +			conn.println("421 no next article in this group");
  30.141 +		} else {
  30.142 +			conn.setCurrentArticle(article);
  30.143 +			conn.println("223 " + conn.getCurrentChannel().getIndexOf(article)
  30.144 +				+ " " + article.getMessageID()
  30.145 +				+ " article retrieved - request text separately");
  30.146 +		}
  30.147 +	}
  30.148  }
    31.1 --- a/src/org/sonews/daemon/command/OverCommand.java	Sun Aug 29 17:43:58 2010 +0200
    31.2 +++ b/src/org/sonews/daemon/command/OverCommand.java	Sun Aug 29 18:17:37 2010 +0200
    31.3 @@ -109,186 +109,153 @@
    31.4  public class OverCommand implements Command
    31.5  {
    31.6  
    31.7 -  public static final int MAX_LINES_PER_DBREQUEST = 200;
    31.8 +	public static final int MAX_LINES_PER_DBREQUEST = 200;
    31.9  
   31.10 -  @Override
   31.11 -  public String[] getSupportedCommandStrings()
   31.12 -  {
   31.13 -    return new String[]{"OVER", "XOVER"};
   31.14 -  }
   31.15 +	@Override
   31.16 +	public String[] getSupportedCommandStrings()
   31.17 +	{
   31.18 +		return new String[] {"OVER", "XOVER"};
   31.19 +	}
   31.20  
   31.21 -  @Override
   31.22 -  public boolean hasFinished()
   31.23 -  {
   31.24 -    return true;
   31.25 -  }
   31.26 +	@Override
   31.27 +	public boolean hasFinished()
   31.28 +	{
   31.29 +		return true;
   31.30 +	}
   31.31  
   31.32 -  @Override
   31.33 -  public String impliedCapability()
   31.34 -  {
   31.35 -    return null;
   31.36 -  }
   31.37 +	@Override
   31.38 +	public String impliedCapability()
   31.39 +	{
   31.40 +		return null;
   31.41 +	}
   31.42  
   31.43 -  @Override
   31.44 -  public boolean isStateful()
   31.45 -  {
   31.46 -    return false;
   31.47 -  }
   31.48 +	@Override
   31.49 +	public boolean isStateful()
   31.50 +	{
   31.51 +		return false;
   31.52 +	}
   31.53  
   31.54 -  @Override
   31.55 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   31.56 -    throws IOException, StorageBackendException
   31.57 -  {
   31.58 -    if(conn.getCurrentChannel() == null)
   31.59 -    {
   31.60 -      conn.println("412 no newsgroup selected");
   31.61 -    }
   31.62 -    else
   31.63 -    {
   31.64 -      String[] command = line.split(" ");
   31.65 +	@Override
   31.66 +	public void processLine(NNTPConnection conn, final String line, byte[] raw)
   31.67 +		throws IOException, StorageBackendException
   31.68 +	{
   31.69 +		if (conn.getCurrentChannel() == null) {
   31.70 +			conn.println("412 no newsgroup selected");
   31.71 +		} else {
   31.72 +			String[] command = line.split(" ");
   31.73  
   31.74 -      // If no parameter was specified, show information about
   31.75 -      // the currently selected article(s)
   31.76 -      if(command.length == 1)
   31.77 -      {
   31.78 -        final Article art = conn.getCurrentArticle();
   31.79 -        if(art == null)
   31.80 -        {
   31.81 -          conn.println("420 no article(s) selected");
   31.82 -          return;
   31.83 -        }
   31.84 +			// If no parameter was specified, show information about
   31.85 +			// the currently selected article(s)
   31.86 +			if (command.length == 1) {
   31.87 +				final Article art = conn.getCurrentArticle();
   31.88 +				if (art == null) {
   31.89 +					conn.println("420 no article(s) selected");
   31.90 +					return;
   31.91 +				}
   31.92  
   31.93 -        conn.println(buildOverview(art, -1));
   31.94 -      }
   31.95 -      // otherwise print information about the specified range
   31.96 -      else
   31.97 -      {
   31.98 -        long artStart;
   31.99 -        long artEnd   = conn.getCurrentChannel().getLastArticleNumber();
  31.100 -        String[] nums = command[1].split("-");
  31.101 -        if(nums.length >= 1)
  31.102 -        {
  31.103 -          try
  31.104 -          {
  31.105 -            artStart = Integer.parseInt(nums[0]);
  31.106 -          }
  31.107 -          catch(NumberFormatException e) 
  31.108 -          {
  31.109 -            Log.get().info(e.getMessage());
  31.110 -            artStart = Integer.parseInt(command[1]);
  31.111 -          }
  31.112 -        }
  31.113 -        else
  31.114 -        {
  31.115 -          artStart = conn.getCurrentChannel().getFirstArticleNumber();
  31.116 -        }
  31.117 +				conn.println(buildOverview(art, -1));
  31.118 +			} // otherwise print information about the specified range
  31.119 +			else {
  31.120 +				long artStart;
  31.121 +				long artEnd = conn.getCurrentChannel().getLastArticleNumber();
  31.122 +				String[] nums = command[1].split("-");
  31.123 +				if (nums.length >= 1) {
  31.124 +					try {
  31.125 +						artStart = Integer.parseInt(nums[0]);
  31.126 +					} catch (NumberFormatException e) {
  31.127 +						Log.get().info(e.getMessage());
  31.128 +						artStart = Integer.parseInt(command[1]);
  31.129 +					}
  31.130 +				} else {
  31.131 +					artStart = conn.getCurrentChannel().getFirstArticleNumber();
  31.132 +				}
  31.133  
  31.134 -        if(nums.length >=2)
  31.135 -        {
  31.136 -          try
  31.137 -          {
  31.138 -            artEnd = Integer.parseInt(nums[1]);
  31.139 -          }
  31.140 -          catch(NumberFormatException e) 
  31.141 -          {
  31.142 -            e.printStackTrace();
  31.143 -          }
  31.144 -        }
  31.145 +				if (nums.length >= 2) {
  31.146 +					try {
  31.147 +						artEnd = Integer.parseInt(nums[1]);
  31.148 +					} catch (NumberFormatException e) {
  31.149 +						e.printStackTrace();
  31.150 +					}
  31.151 +				}
  31.152  
  31.153 -        if(artStart > artEnd)
  31.154 -        {
  31.155 -          if(command[0].equalsIgnoreCase("OVER"))
  31.156 -          {
  31.157 -            conn.println("423 no articles in that range");
  31.158 -          }
  31.159 -          else
  31.160 -          {
  31.161 -            conn.println("224 (empty) overview information follows:");
  31.162 -            conn.println(".");
  31.163 -          }
  31.164 -        }
  31.165 -        else
  31.166 -        {
  31.167 -          for(long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST)
  31.168 -          {
  31.169 -            long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
  31.170 -            List<Pair<Long, ArticleHead>> articleHeads = conn.getCurrentChannel()
  31.171 -              .getArticleHeads(n, nEnd);
  31.172 -            if(articleHeads.isEmpty() && n == artStart
  31.173 -              && command[0].equalsIgnoreCase("OVER"))
  31.174 -            {
  31.175 -              // This reply is only valid for OVER, not for XOVER command
  31.176 -              conn.println("423 no articles in that range");
  31.177 -              return;
  31.178 -            }
  31.179 -            else if(n == artStart)
  31.180 -            {
  31.181 -              // XOVER replies this although there is no data available
  31.182 -              conn.println("224 overview information follows");
  31.183 -            }
  31.184 +				if (artStart > artEnd) {
  31.185 +					if (command[0].equalsIgnoreCase("OVER")) {
  31.186 +						conn.println("423 no articles in that range");
  31.187 +					} else {
  31.188 +						conn.println("224 (empty) overview information follows:");
  31.189 +						conn.println(".");
  31.190 +					}
  31.191 +				} else {
  31.192 +					for (long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST) {
  31.193 +						long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
  31.194 +						List<Pair<Long, ArticleHead>> articleHeads = conn.getCurrentChannel().getArticleHeads(n, nEnd);
  31.195 +						if (articleHeads.isEmpty() && n == artStart
  31.196 +							&& command[0].equalsIgnoreCase("OVER")) {
  31.197 +							// This reply is only valid for OVER, not for XOVER command
  31.198 +							conn.println("423 no articles in that range");
  31.199 +							return;
  31.200 +						} else if (n == artStart) {
  31.201 +							// XOVER replies this although there is no data available
  31.202 +							conn.println("224 overview information follows");
  31.203 +						}
  31.204  
  31.205 -            for(Pair<Long, ArticleHead> article : articleHeads)
  31.206 -            {
  31.207 -              String overview = buildOverview(article.getB(), article.getA());
  31.208 -              conn.println(overview);
  31.209 -            }
  31.210 -          } // for
  31.211 -          conn.println(".");
  31.212 -        }
  31.213 -      }
  31.214 -    }
  31.215 -  }
  31.216 -  
  31.217 -  private String buildOverview(ArticleHead art, long nr)
  31.218 -  {
  31.219 -    StringBuilder overview = new StringBuilder();
  31.220 -    overview.append(nr);
  31.221 -    overview.append('\t');
  31.222 +						for (Pair<Long, ArticleHead> article : articleHeads) {
  31.223 +							String overview = buildOverview(article.getB(), article.getA());
  31.224 +							conn.println(overview);
  31.225 +						}
  31.226 +					} // for
  31.227 +					conn.println(".");
  31.228 +				}
  31.229 +			}
  31.230 +		}
  31.231 +	}
  31.232  
  31.233 -    String subject = art.getHeader(Headers.SUBJECT)[0];
  31.234 -    if("".equals(subject))
  31.235 -    {
  31.236 -      subject = "<empty>";
  31.237 -    }
  31.238 -    overview.append(escapeString(subject));
  31.239 -    overview.append('\t');
  31.240 +	private String buildOverview(ArticleHead art, long nr)
  31.241 +	{
  31.242 +		StringBuilder overview = new StringBuilder();
  31.243 +		overview.append(nr);
  31.244 +		overview.append('\t');
  31.245  
  31.246 -    overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
  31.247 -    overview.append('\t');
  31.248 -    overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
  31.249 -    overview.append('\t');
  31.250 -    overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
  31.251 -    overview.append('\t');
  31.252 -    overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
  31.253 -    overview.append('\t');
  31.254 +		String subject = art.getHeader(Headers.SUBJECT)[0];
  31.255 +		if ("".equals(subject)) {
  31.256 +			subject = "<empty>";
  31.257 +		}
  31.258 +		overview.append(escapeString(subject));
  31.259 +		overview.append('\t');
  31.260  
  31.261 -    String bytes = art.getHeader(Headers.BYTES)[0];
  31.262 -    if("".equals(bytes))
  31.263 -    {
  31.264 -      bytes = "0";
  31.265 -    }
  31.266 -    overview.append(escapeString(bytes));
  31.267 -    overview.append('\t');
  31.268 +		overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
  31.269 +		overview.append('\t');
  31.270 +		overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
  31.271 +		overview.append('\t');
  31.272 +		overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
  31.273 +		overview.append('\t');
  31.274 +		overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
  31.275 +		overview.append('\t');
  31.276  
  31.277 -    String lines = art.getHeader(Headers.LINES)[0];
  31.278 -    if("".equals(lines))
  31.279 -    {
  31.280 -      lines = "0";
  31.281 -    }
  31.282 -    overview.append(escapeString(lines));
  31.283 -    overview.append('\t');
  31.284 -    overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
  31.285 +		String bytes = art.getHeader(Headers.BYTES)[0];
  31.286 +		if ("".equals(bytes)) {
  31.287 +			bytes = "0";
  31.288 +		}
  31.289 +		overview.append(escapeString(bytes));
  31.290 +		overview.append('\t');
  31.291  
  31.292 -    // Remove trailing tabs if some data is empty
  31.293 -    return overview.toString().trim();
  31.294 -  }
  31.295 -  
  31.296 -  private String escapeString(String str)
  31.297 -  {
  31.298 -    String nstr = str.replace("\r", "");
  31.299 -    nstr = nstr.replace('\n', ' ');
  31.300 -    nstr = nstr.replace('\t', ' ');
  31.301 -    return nstr.trim();
  31.302 -  }
  31.303 -  
  31.304 +		String lines = art.getHeader(Headers.LINES)[0];
  31.305 +		if ("".equals(lines)) {
  31.306 +			lines = "0";
  31.307 +		}
  31.308 +		overview.append(escapeString(lines));
  31.309 +		overview.append('\t');
  31.310 +		overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
  31.311 +
  31.312 +		// Remove trailing tabs if some data is empty
  31.313 +		return overview.toString().trim();
  31.314 +	}
  31.315 +
  31.316 +	private String escapeString(String str)
  31.317 +	{
  31.318 +		String nstr = str.replace("\r", "");
  31.319 +		nstr = nstr.replace('\n', ' ');
  31.320 +		nstr = nstr.replace('\t', ' ');
  31.321 +		return nstr.trim();
  31.322 +	}
  31.323  }
    32.1 --- a/src/org/sonews/daemon/command/PostCommand.java	Sun Aug 29 17:43:58 2010 +0200
    32.2 +++ b/src/org/sonews/daemon/command/PostCommand.java	Sun Aug 29 18:17:37 2010 +0200
    32.3 @@ -47,286 +47,237 @@
    32.4   */
    32.5  public class PostCommand implements Command
    32.6  {
    32.7 -  
    32.8 -  private final Article article   = new Article();
    32.9 -  private int           lineCount = 0;
   32.10 -  private long          bodySize  = 0;
   32.11 -  private InternetHeaders headers = null;
   32.12 -  private long          maxBodySize  = 
   32.13 -    Config.inst().get(Config.ARTICLE_MAXSIZE, 128) * 1024L; // Size in bytes
   32.14 -  private PostState     state     = PostState.WaitForLineOne;
   32.15 -  private final ByteArrayOutputStream bufBody   = new ByteArrayOutputStream();
   32.16 -  private final StringBuilder         strHead   = new StringBuilder();
   32.17  
   32.18 -  @Override
   32.19 -  public String[] getSupportedCommandStrings()
   32.20 -  {
   32.21 -    return new String[]{"POST"};
   32.22 -  }
   32.23 +	private final Article article = new Article();
   32.24 +	private int lineCount = 0;
   32.25 +	private long bodySize = 0;
   32.26 +	private InternetHeaders headers = null;
   32.27 +	private long maxBodySize =
   32.28 +		Config.inst().get(Config.ARTICLE_MAXSIZE, 128) * 1024L; // Size in bytes
   32.29 +	private PostState state = PostState.WaitForLineOne;
   32.30 +	private final ByteArrayOutputStream bufBody = new ByteArrayOutputStream();
   32.31 +	private final StringBuilder strHead = new StringBuilder();
   32.32  
   32.33 -  @Override
   32.34 -  public boolean hasFinished()
   32.35 -  {
   32.36 -    return this.state == PostState.Finished;
   32.37 -  }
   32.38 +	@Override
   32.39 +	public String[] getSupportedCommandStrings()
   32.40 +	{
   32.41 +		return new String[] {"POST"};
   32.42 +	}
   32.43  
   32.44 -  @Override
   32.45 -  public String impliedCapability()
   32.46 -  {
   32.47 -    return null;
   32.48 -  }
   32.49 +	@Override
   32.50 +	public boolean hasFinished()
   32.51 +	{
   32.52 +		return this.state == PostState.Finished;
   32.53 +	}
   32.54  
   32.55 -  @Override
   32.56 -  public boolean isStateful()
   32.57 -  {
   32.58 -    return true;
   32.59 -  }
   32.60 +	@Override
   32.61 +	public String impliedCapability()
   32.62 +	{
   32.63 +		return null;
   32.64 +	}
   32.65  
   32.66 -  /**
   32.67 -   * Process the given line String. line.trim() was called by NNTPConnection.
   32.68 -   * @param line
   32.69 -   * @throws java.io.IOException
   32.70 -   * @throws java.sql.SQLException
   32.71 -   */
   32.72 -  @Override // TODO: Refactor this method to reduce complexity!
   32.73 -  public void processLine(NNTPConnection conn, String line, byte[] raw)
   32.74 -    throws IOException, StorageBackendException
   32.75 -  {
   32.76 -    switch(state)
   32.77 -    {
   32.78 -      case WaitForLineOne:
   32.79 -      {
   32.80 -        if(line.equalsIgnoreCase("POST"))
   32.81 -        {
   32.82 -          conn.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
   32.83 -          state = PostState.ReadingHeaders;
   32.84 -        }
   32.85 -        else
   32.86 -        {
   32.87 -          conn.println("500 invalid command usage");
   32.88 -        }
   32.89 -        break;
   32.90 -      }
   32.91 -      case ReadingHeaders:
   32.92 -      {
   32.93 -        strHead.append(line);
   32.94 -        strHead.append(NNTPConnection.NEWLINE);
   32.95 -        
   32.96 -        if("".equals(line) || ".".equals(line))
   32.97 -        {
   32.98 -          // we finally met the blank line
   32.99 -          // separating headers from body
  32.100 -          
  32.101 -          try
  32.102 -          {
  32.103 -            // Parse the header using the InternetHeader class from JavaMail API
  32.104 -            headers = new InternetHeaders(
  32.105 -              new ByteArrayInputStream(strHead.toString().trim()
  32.106 -                .getBytes(conn.getCurrentCharset())));
  32.107 +	@Override
  32.108 +	public boolean isStateful()
  32.109 +	{
  32.110 +		return true;
  32.111 +	}
  32.112  
  32.113 -            // add the header entries for the article
  32.114 -            article.setHeaders(headers);
  32.115 -          }
  32.116 -          catch (MessagingException e)
  32.117 -          {
  32.118 -            e.printStackTrace();
  32.119 -            conn.println("500 posting failed - invalid header");
  32.120 -            state = PostState.Finished;
  32.121 -            break;
  32.122 -          }
  32.123 +	/**
  32.124 +	 * Process the given line String. line.trim() was called by NNTPConnection.
  32.125 +	 * @param line
  32.126 +	 * @throws java.io.IOException
  32.127 +	 * @throws java.sql.SQLException
  32.128 +	 */
  32.129 +	@Override // TODO: Refactor this method to reduce complexity!
  32.130 +	public void processLine(NNTPConnection conn, String line, byte[] raw)
  32.131 +		throws IOException, StorageBackendException
  32.132 +	{
  32.133 +		switch (state) {
  32.134 +			case WaitForLineOne: {
  32.135 +				if (line.equalsIgnoreCase("POST")) {
  32.136 +					conn.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
  32.137 +					state = PostState.ReadingHeaders;
  32.138 +				} else {
  32.139 +					conn.println("500 invalid command usage");
  32.140 +				}
  32.141 +				break;
  32.142 +			}
  32.143 +			case ReadingHeaders: {
  32.144 +				strHead.append(line);
  32.145 +				strHead.append(NNTPConnection.NEWLINE);
  32.146  
  32.147 -          // Change charset for reading body; 
  32.148 -          // for multipart messages UTF-8 is returned
  32.149 -          //conn.setCurrentCharset(article.getBodyCharset());
  32.150 -          
  32.151 -          state = PostState.ReadingBody;
  32.152 -          
  32.153 -          if(".".equals(line))
  32.154 -          {
  32.155 -            // Post an article without body
  32.156 -            postArticle(conn, article);
  32.157 -            state = PostState.Finished;
  32.158 -          }
  32.159 -        }
  32.160 -        break;
  32.161 -      }
  32.162 -      case ReadingBody:
  32.163 -      {
  32.164 -        if(".".equals(line))
  32.165 -        {    
  32.166 -          // Set some headers needed for Over command
  32.167 -          headers.setHeader(Headers.LINES, Integer.toString(lineCount));
  32.168 -          headers.setHeader(Headers.BYTES, Long.toString(bodySize));
  32.169 +				if ("".equals(line) || ".".equals(line)) {
  32.170 +					// we finally met the blank line
  32.171 +					// separating headers from body
  32.172  
  32.173 -          byte[] body = bufBody.toByteArray();
  32.174 -          if(body.length >= 2)
  32.175 -          {
  32.176 -            // Remove trailing CRLF
  32.177 -            body = Arrays.copyOf(body, body.length - 2);
  32.178 -          }
  32.179 -          article.setBody(body); // set the article body
  32.180 -          
  32.181 -          postArticle(conn, article);
  32.182 -          state = PostState.Finished;
  32.183 -        }
  32.184 -        else
  32.185 -        {
  32.186 -          bodySize += line.length() + 1;
  32.187 -          lineCount++;
  32.188 -          
  32.189 -          // Add line to body buffer
  32.190 -          bufBody.write(raw, 0, raw.length);
  32.191 -          bufBody.write(NNTPConnection.NEWLINE.getBytes());
  32.192 -          
  32.193 -          if(bodySize > maxBodySize)
  32.194 -          {
  32.195 -            conn.println("500 article is too long");
  32.196 -            state = PostState.Finished;
  32.197 -            break;
  32.198 -          }
  32.199 -        }
  32.200 -        break;
  32.201 -      }
  32.202 -      default:
  32.203 -      {
  32.204 -        // Should never happen
  32.205 -        Log.get().severe("PostCommand::processLine(): already finished...");
  32.206 -      }
  32.207 -    }
  32.208 -  }
  32.209 -  
  32.210 -  /**
  32.211 -   * Article is a control message and needs special handling.
  32.212 -   * @param article
  32.213 -   */
  32.214 -  private void controlMessage(NNTPConnection conn, Article article)
  32.215 -    throws IOException
  32.216 -  {
  32.217 -    String[] ctrl = article.getHeader(Headers.CONTROL)[0].split(" ");
  32.218 -    if(ctrl.length == 2) // "cancel <mid>"
  32.219 -    {
  32.220 -      try
  32.221 -      {
  32.222 -        StorageManager.current().delete(ctrl[1]);
  32.223 -        
  32.224 -        // Move cancel message to "control" group
  32.225 -        article.setHeader(Headers.NEWSGROUPS, "control");
  32.226 -        StorageManager.current().addArticle(article);
  32.227 -        conn.println("240 article cancelled");
  32.228 -      }
  32.229 -      catch(StorageBackendException ex)
  32.230 -      {
  32.231 -        Log.get().severe(ex.toString());
  32.232 -        conn.println("500 internal server error");
  32.233 -      }
  32.234 -    }
  32.235 -    else
  32.236 -    {
  32.237 -      conn.println("441 unknown control header");
  32.238 -    }
  32.239 -  }
  32.240 -  
  32.241 -  private void supersedeMessage(NNTPConnection conn, Article article)
  32.242 -    throws IOException
  32.243 -  {
  32.244 -    try
  32.245 -    {
  32.246 -      String oldMsg = article.getHeader(Headers.SUPERSEDES)[0];
  32.247 -      StorageManager.current().delete(oldMsg);
  32.248 -      StorageManager.current().addArticle(article);
  32.249 -      conn.println("240 article replaced");
  32.250 -    }
  32.251 -    catch(StorageBackendException ex)
  32.252 -    {
  32.253 -      Log.get().severe(ex.toString());
  32.254 -      conn.println("500 internal server error");
  32.255 -    }
  32.256 -  }
  32.257 -  
  32.258 -  private void postArticle(NNTPConnection conn, Article article)
  32.259 -    throws IOException
  32.260 -  {
  32.261 -    if(article.getHeader(Headers.CONTROL)[0].length() > 0)
  32.262 -    {
  32.263 -      controlMessage(conn, article);
  32.264 -    }
  32.265 -    else if(article.getHeader(Headers.SUPERSEDES)[0].length() > 0)
  32.266 -    {
  32.267 -      supersedeMessage(conn, article);
  32.268 -    }
  32.269 -    else // Post the article regularily
  32.270 -    {
  32.271 -      // Circle check; note that Path can already contain the hostname here
  32.272 -      String host = Config.inst().get(Config.HOSTNAME, "localhost");
  32.273 -      if(article.getHeader(Headers.PATH)[0].indexOf(host + "!", 1) > 0)
  32.274 -      {
  32.275 -        Log.get().info(article.getMessageID() + " skipped for host " + host);
  32.276 -        conn.println("441 I know this article already");
  32.277 -        return;
  32.278 -      }
  32.279 +					try {
  32.280 +						// Parse the header using the InternetHeader class from JavaMail API
  32.281 +						headers = new InternetHeaders(
  32.282 +							new ByteArrayInputStream(strHead.toString().trim().getBytes(conn.getCurrentCharset())));
  32.283  
  32.284 -      // Try to create the article in the database or post it to
  32.285 -      // appropriate mailing list
  32.286 -      try
  32.287 -      {
  32.288 -        boolean success = false;
  32.289 -        String[] groupnames = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
  32.290 -        for(String groupname : groupnames)
  32.291 -        {          
  32.292 -          Group group = StorageManager.current().getGroup(groupname);
  32.293 -          if(group != null && !group.isDeleted())
  32.294 -          {
  32.295 -            if(group.isMailingList() && !conn.isLocalConnection())
  32.296 -            {
  32.297 -              // Send to mailing list; the Dispatcher writes 
  32.298 -              // statistics to database
  32.299 -              Dispatcher.toList(article, group.getName());
  32.300 -              success = true;
  32.301 -            }
  32.302 -            else
  32.303 -            {
  32.304 -              // Store in database
  32.305 -              if(!StorageManager.current().isArticleExisting(article.getMessageID()))
  32.306 -              {
  32.307 -                StorageManager.current().addArticle(article);
  32.308 +						// add the header entries for the article
  32.309 +						article.setHeaders(headers);
  32.310 +					} catch (MessagingException e) {
  32.311 +						e.printStackTrace();
  32.312 +						conn.println("500 posting failed - invalid header");
  32.313 +						state = PostState.Finished;
  32.314 +						break;
  32.315 +					}
  32.316  
  32.317 -                // Log this posting to statistics
  32.318 -                Stats.getInstance().mailPosted(
  32.319 -                  article.getHeader(Headers.NEWSGROUPS)[0]);
  32.320 -              }
  32.321 -              success = true;
  32.322 -            }
  32.323 -          }
  32.324 -        } // end for
  32.325 +					// Change charset for reading body;
  32.326 +					// for multipart messages UTF-8 is returned
  32.327 +					//conn.setCurrentCharset(article.getBodyCharset());
  32.328  
  32.329 -        if(success)
  32.330 -        {
  32.331 -          conn.println("240 article posted ok");
  32.332 -          FeedManager.queueForPush(article);
  32.333 -        }
  32.334 -        else
  32.335 -        {
  32.336 -          conn.println("441 newsgroup not found");
  32.337 -        }
  32.338 -      }
  32.339 -      catch(AddressException ex)
  32.340 -      {
  32.341 -        Log.get().warning(ex.getMessage());
  32.342 -        conn.println("441 invalid sender address");
  32.343 -      }
  32.344 -      catch(MessagingException ex)
  32.345 -      {
  32.346 -        // A MessageException is thrown when the sender email address is
  32.347 -        // invalid or something is wrong with the SMTP server.
  32.348 -        System.err.println(ex.getLocalizedMessage());
  32.349 -        conn.println("441 " + ex.getClass().getCanonicalName() + ": " + ex.getLocalizedMessage());
  32.350 -      }
  32.351 -      catch(StorageBackendException ex)
  32.352 -      {
  32.353 -        ex.printStackTrace();
  32.354 -        conn.println("500 internal server error");
  32.355 -      }
  32.356 -    }
  32.357 -  }
  32.358 +					state = PostState.ReadingBody;
  32.359  
  32.360 +					if (".".equals(line)) {
  32.361 +						// Post an article without body
  32.362 +						postArticle(conn, article);
  32.363 +						state = PostState.Finished;
  32.364 +					}
  32.365 +				}
  32.366 +				break;
  32.367 +			}
  32.368 +			case ReadingBody: {
  32.369 +				if (".".equals(line)) {
  32.370 +					// Set some headers needed for Over command
  32.371 +					headers.setHeader(Headers.LINES, Integer.toString(lineCount));
  32.372 +					headers.setHeader(Headers.BYTES, Long.toString(bodySize));
  32.373 +
  32.374 +					byte[] body = bufBody.toByteArray();
  32.375 +					if (body.length >= 2) {
  32.376 +						// Remove trailing CRLF
  32.377 +						body = Arrays.copyOf(body, body.length - 2);
  32.378 +					}
  32.379 +					article.setBody(body); // set the article body
  32.380 +
  32.381 +					postArticle(conn, article);
  32.382 +					state = PostState.Finished;
  32.383 +				} else {
  32.384 +					bodySize += line.length() + 1;
  32.385 +					lineCount++;
  32.386 +
  32.387 +					// Add line to body buffer
  32.388 +					bufBody.write(raw, 0, raw.length);
  32.389 +					bufBody.write(NNTPConnection.NEWLINE.getBytes());
  32.390 +
  32.391 +					if (bodySize > maxBodySize) {
  32.392 +						conn.println("500 article is too long");
  32.393 +						state = PostState.Finished;
  32.394 +						break;
  32.395 +					}
  32.396 +				}
  32.397 +				break;
  32.398 +			}
  32.399 +			default: {
  32.400 +				// Should never happen
  32.401 +				Log.get().severe("PostCommand::processLine(): already finished...");
  32.402 +			}
  32.403 +		}
  32.404 +	}
  32.405 +
  32.406 +	/**
  32.407 +	 * Article is a control message and needs special handling.
  32.408 +	 * @param article
  32.409 +	 */
  32.410 +	private void controlMessage(NNTPConnection conn, Article article)
  32.411 +		throws IOException
  32.412 +	{
  32.413 +		String[] ctrl = article.getHeader(Headers.CONTROL)[0].split(" ");
  32.414 +		if (ctrl.length == 2) // "cancel <mid>"
  32.415 +		{
  32.416 +			try {
  32.417 +				StorageManager.current().delete(ctrl[1]);
  32.418 +
  32.419 +				// Move cancel message to "control" group
  32.420 +				article.setHeader(Headers.NEWSGROUPS, "control");
  32.421 +				StorageManager.current().addArticle(article);
  32.422 +				conn.println("240 article cancelled");
  32.423 +			} catch (StorageBackendException ex) {
  32.424 +				Log.get().severe(ex.toString());
  32.425 +				conn.println("500 internal server error");
  32.426 +			}
  32.427 +		} else {
  32.428 +			conn.println("441 unknown control header");
  32.429 +		}
  32.430 +	}
  32.431 +
  32.432 +	private void supersedeMessage(NNTPConnection conn, Article article)
  32.433 +		throws IOException
  32.434 +	{
  32.435 +		try {
  32.436 +			String oldMsg = article.getHeader(Headers.SUPERSEDES)[0];
  32.437 +			StorageManager.current().delete(oldMsg);
  32.438 +			StorageManager.current().addArticle(article);
  32.439 +			conn.println("240 article replaced");
  32.440 +		} catch (StorageBackendException ex) {
  32.441 +			Log.get().severe(ex.toString());
  32.442 +			conn.println("500 internal server error");
  32.443 +		}
  32.444 +	}
  32.445 +
  32.446 +	private void postArticle(NNTPConnection conn, Article article)
  32.447 +		throws IOException
  32.448 +	{
  32.449 +		if (article.getHeader(Headers.CONTROL)[0].length() > 0) {
  32.450 +			controlMessage(conn, article);
  32.451 +		} else if (article.getHeader(Headers.SUPERSEDES)[0].length() > 0) {
  32.452 +			supersedeMessage(conn, article);
  32.453 +		} else // Post the article regularily
  32.454 +		{
  32.455 +			// Circle check; note that Path can already contain the hostname here
  32.456 +			String host = Config.inst().get(Config.HOSTNAME, "localhost");
  32.457 +			if (article.getHeader(Headers.PATH)[0].indexOf(host + "!", 1) > 0) {
  32.458 +				Log.get().info(article.getMessageID() + " skipped for host " + host);
  32.459 +				conn.println("441 I know this article already");
  32.460 +				return;
  32.461 +			}
  32.462 +
  32.463 +			// Try to create the article in the database or post it to
  32.464 +			// appropriate mailing list
  32.465 +			try {
  32.466 +				boolean success = false;
  32.467 +				String[] groupnames = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
  32.468 +				for (String groupname : groupnames) {
  32.469 +					Group group = StorageManager.current().getGroup(groupname);
  32.470 +					if (group != null && !group.isDeleted()) {
  32.471 +						if (group.isMailingList() && !conn.isLocalConnection()) {
  32.472 +							// Send to mailing list; the Dispatcher writes
  32.473 +							// statistics to database
  32.474 +							Dispatcher.toList(article, group.getName());
  32.475 +							success = true;
  32.476 +						} else {
  32.477 +							// Store in database
  32.478 +							if (!StorageManager.current().isArticleExisting(article.getMessageID())) {
  32.479 +								StorageManager.current().addArticle(article);
  32.480 +
  32.481 +								// Log this posting to statistics
  32.482 +								Stats.getInstance().mailPosted(
  32.483 +									article.getHeader(Headers.NEWSGROUPS)[0]);
  32.484 +							}
  32.485 +							success = true;
  32.486 +						}
  32.487 +					}
  32.488 +				} // end for
  32.489 +
  32.490 +				if (success) {
  32.491 +					conn.println("240 article posted ok");
  32.492 +					FeedManager.queueForPush(article);
  32.493 +				} else {
  32.494 +					conn.println("441 newsgroup not found");
  32.495 +				}
  32.496 +			} catch (AddressException ex) {
  32.497 +				Log.get().warning(ex.getMessage());
  32.498 +				conn.println("441 invalid sender address");
  32.499 +			} catch (MessagingException ex) {
  32.500 +				// A MessageException is thrown when the sender email address is
  32.501 +				// invalid or something is wrong with the SMTP server.
  32.502 +				System.err.println(ex.getLocalizedMessage());
  32.503 +				conn.println("441 " + ex.getClass().getCanonicalName() + ": " + ex.getLocalizedMessage());
  32.504 +			} catch (StorageBackendException ex) {
  32.505 +				ex.printStackTrace();
  32.506 +				conn.println("500 internal server error");
  32.507 +			}
  32.508 +		}
  32.509 +	}
  32.510  }
    33.1 --- a/src/org/sonews/daemon/command/PostState.java	Sun Aug 29 17:43:58 2010 +0200
    33.2 +++ b/src/org/sonews/daemon/command/PostState.java	Sun Aug 29 18:17:37 2010 +0200
    33.3 @@ -25,5 +25,6 @@
    33.4   */
    33.5  enum PostState
    33.6  {
    33.7 -  WaitForLineOne, ReadingHeaders, ReadingBody, Finished
    33.8 +
    33.9 +	WaitForLineOne, ReadingHeaders, ReadingBody, Finished
   33.10  }
    34.1 --- a/src/org/sonews/daemon/command/QuitCommand.java	Sun Aug 29 17:43:58 2010 +0200
    34.2 +++ b/src/org/sonews/daemon/command/QuitCommand.java	Sun Aug 29 18:17:37 2010 +0200
    34.3 @@ -30,38 +30,37 @@
    34.4  public class QuitCommand implements Command
    34.5  {
    34.6  
    34.7 -  @Override
    34.8 -  public String[] getSupportedCommandStrings()
    34.9 -  {
   34.10 -    return new String[]{"QUIT"};
   34.11 -  }
   34.12 -  
   34.13 -  @Override
   34.14 -  public boolean hasFinished()
   34.15 -  {
   34.16 -    return true;
   34.17 -  }
   34.18 +	@Override
   34.19 +	public String[] getSupportedCommandStrings()
   34.20 +	{
   34.21 +		return new String[] {"QUIT"};
   34.22 +	}
   34.23  
   34.24 -  @Override
   34.25 -  public String impliedCapability()
   34.26 -  {
   34.27 -    return null;
   34.28 -  }
   34.29 +	@Override
   34.30 +	public boolean hasFinished()
   34.31 +	{
   34.32 +		return true;
   34.33 +	}
   34.34  
   34.35 -  @Override
   34.36 -  public boolean isStateful()
   34.37 -  {
   34.38 -    return false;
   34.39 -  }
   34.40 +	@Override
   34.41 +	public String impliedCapability()
   34.42 +	{
   34.43 +		return null;
   34.44 +	}
   34.45  
   34.46 -  @Override
   34.47 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   34.48 -    throws IOException, StorageBackendException
   34.49 -  {    
   34.50 -    conn.println("205 cya");
   34.51 -    
   34.52 -    conn.shutdownInput();
   34.53 -    conn.shutdownOutput();
   34.54 -  }
   34.55 +	@Override
   34.56 +	public boolean isStateful()
   34.57 +	{
   34.58 +		return false;
   34.59 +	}
   34.60  
   34.61 +	@Override
   34.62 +	public void processLine(NNTPConnection conn, final String line, byte[] raw)
   34.63 +		throws IOException, StorageBackendException
   34.64 +	{
   34.65 +		conn.println("205 cya");
   34.66 +
   34.67 +		conn.shutdownInput();
   34.68 +		conn.shutdownOutput();
   34.69 +	}
   34.70  }
    35.1 --- a/src/org/sonews/daemon/command/StatCommand.java	Sun Aug 29 17:43:58 2010 +0200
    35.2 +++ b/src/org/sonews/daemon/command/StatCommand.java	Sun Aug 29 18:17:37 2010 +0200
    35.3 @@ -31,84 +31,70 @@
    35.4  public class StatCommand implements Command
    35.5  {
    35.6  
    35.7 -  @Override
    35.8 -  public String[] getSupportedCommandStrings()
    35.9 -  {
   35.10 -    return new String[]{"STAT"};
   35.11 -  }
   35.12 +	@Override
   35.13 +	public String[] getSupportedCommandStrings()
   35.14 +	{
   35.15 +		return new String[] {"STAT"};
   35.16 +	}
   35.17  
   35.18 -  @Override
   35.19 -  public boolean hasFinished()
   35.20 -  {
   35.21 -    return true;
   35.22 -  }
   35.23 +	@Override
   35.24 +	public boolean hasFinished()
   35.25 +	{
   35.26 +		return true;
   35.27 +	}
   35.28  
   35.29 -  @Override
   35.30 -  public String impliedCapability()
   35.31 -  {
   35.32 -    return null;
   35.33 -  }
   35.34 +	@Override
   35.35 +	public String impliedCapability()
   35.36 +	{
   35.37 +		return null;
   35.38 +	}
   35.39  
   35.40 -  @Override
   35.41 -  public boolean isStateful()
   35.42 -  {
   35.43 -    return false;
   35.44 -  }
   35.45 +	@Override
   35.46 +	public boolean isStateful()
   35.47 +	{
   35.48 +		return false;
   35.49 +	}
   35.50  
   35.51 -  // TODO: Method has various exit points => Refactor!
   35.52 -  @Override
   35.53 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   35.54 -    throws IOException, StorageBackendException
   35.55 -  {
   35.56 -    final String[] command = line.split(" ");
   35.57 +	// TODO: Method has various exit points => Refactor!
   35.58 +	@Override
   35.59 +	public void processLine(NNTPConnection conn, final String line, byte[] raw)
   35.60 +		throws IOException, StorageBackendException
   35.61 +	{
   35.62 +		final String[] command = line.split(" ");
   35.63  
   35.64 -    Article article = null;
   35.65 -    if(command.length == 1)
   35.66 -    {
   35.67 -      article = conn.getCurrentArticle();
   35.68 -      if(article == null)
   35.69 -      {
   35.70 -        conn.println("420 no current article has been selected");
   35.71 -        return;
   35.72 -      }
   35.73 -    }
   35.74 -    else if(command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN))
   35.75 -    {
   35.76 -      // Message-ID
   35.77 -      article = Article.getByMessageID(command[1]);
   35.78 -      if (article == null)
   35.79 -      {
   35.80 -        conn.println("430 no such article found");
   35.81 -        return;
   35.82 -      }
   35.83 -    }
   35.84 -    else
   35.85 -    {
   35.86 -      // Message Number
   35.87 -      try
   35.88 -      {
   35.89 -        long aid = Long.parseLong(command[1]);
   35.90 -        article = conn.getCurrentChannel().getArticle(aid);
   35.91 -      }
   35.92 -      catch(NumberFormatException ex)
   35.93 -      {
   35.94 -        ex.printStackTrace();
   35.95 -      }
   35.96 -      catch(StorageBackendException ex)
   35.97 -      {
   35.98 -        ex.printStackTrace();
   35.99 -      }
  35.100 -      if (article == null)
  35.101 -      {
  35.102 -        conn.println("423 no such article number in this group");
  35.103 -        return;
  35.104 -      }
  35.105 -      conn.setCurrentArticle(article);
  35.106 -    }
  35.107 -    
  35.108 -    conn.println("223 " + conn.getCurrentChannel().getIndexOf(article) + " "
  35.109 -      + article.getMessageID()
  35.110 -      + " article retrieved - request text separately");
  35.111 -  }
  35.112 -  
  35.113 +		Article article = null;
  35.114 +		if (command.length == 1) {
  35.115 +			article = conn.getCurrentArticle();
  35.116 +			if (article == null) {
  35.117 +				conn.println("420 no current article has been selected");
  35.118 +				return;
  35.119 +			}
  35.120 +		} else if (command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN)) {
  35.121 +			// Message-ID
  35.122 +			article = Article.getByMessageID(command[1]);
  35.123 +			if (article == null) {
  35.124 +				conn.println("430 no such article found");
  35.125 +				return;
  35.126 +			}
  35.127 +		} else {
  35.128 +			// Message Number
  35.129 +			try {
  35.130 +				long aid = Long.parseLong(command[1]);
  35.131 +				article = conn.getCurrentChannel().getArticle(aid);
  35.132 +			} catch (NumberFormatException ex) {
  35.133 +				ex.printStackTrace();
  35.134 +			} catch (StorageBackendException ex) {
  35.135 +				ex.printStackTrace();
  35.136 +			}
  35.137 +			if (article == null) {
  35.138 +				conn.println("423 no such article number in this group");
  35.139 +				return;
  35.140 +			}
  35.141 +			conn.setCurrentArticle(article);
  35.142 +		}
  35.143 +
  35.144 +		conn.println("223 " + conn.getCurrentChannel().getIndexOf(article) + " "
  35.145 +			+ article.getMessageID()
  35.146 +			+ " article retrieved - request text separately");
  35.147 +	}
  35.148  }
    36.1 --- a/src/org/sonews/daemon/command/UnsupportedCommand.java	Sun Aug 29 17:43:58 2010 +0200
    36.2 +++ b/src/org/sonews/daemon/command/UnsupportedCommand.java	Sun Aug 29 18:17:37 2010 +0200
    36.3 @@ -29,39 +29,38 @@
    36.4   */
    36.5  public class UnsupportedCommand implements Command
    36.6  {
    36.7 -  
    36.8 -  /**
    36.9 -   * @return Always returns null.
   36.10 -   */
   36.11 -  @Override
   36.12 -  public String[] getSupportedCommandStrings()
   36.13 -  {
   36.14 -    return null;
   36.15 -  }
   36.16  
   36.17 -  @Override
   36.18 -  public boolean hasFinished()
   36.19 -  {
   36.20 -    return true;
   36.21 -  }
   36.22 +	/**
   36.23 +	 * @return Always returns null.
   36.24 +	 */
   36.25 +	@Override
   36.26 +	public String[] getSupportedCommandStrings()
   36.27 +	{
   36.28 +		return null;
   36.29 +	}
   36.30  
   36.31 -  @Override
   36.32 -  public String impliedCapability()
   36.33 -  {
   36.34 -    return null;
   36.35 -  }
   36.36 +	@Override
   36.37 +	public boolean hasFinished()
   36.38 +	{
   36.39 +		return true;
   36.40 +	}
   36.41  
   36.42 -  @Override
   36.43 -  public boolean isStateful()
   36.44 -  {
   36.45 -    return false;
   36.46 -  }
   36.47 +	@Override
   36.48 +	public String impliedCapability()
   36.49 +	{
   36.50 +		return null;
   36.51 +	}
   36.52  
   36.53 -  @Override
   36.54 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   36.55 -    throws IOException
   36.56 -  {
   36.57 -    conn.println("500 command not supported");
   36.58 -  }
   36.59 -  
   36.60 +	@Override
   36.61 +	public boolean isStateful()
   36.62 +	{
   36.63 +		return false;
   36.64 +	}
   36.65 +
   36.66 +	@Override
   36.67 +	public void processLine(NNTPConnection conn, final String line, byte[] raw)
   36.68 +		throws IOException
   36.69 +	{
   36.70 +		conn.println("500 command not supported");
   36.71 +	}
   36.72  }
    37.1 --- a/src/org/sonews/daemon/command/XDaemonCommand.java	Sun Aug 29 17:43:58 2010 +0200
    37.2 +++ b/src/org/sonews/daemon/command/XDaemonCommand.java	Sun Aug 29 18:17:37 2010 +0200
    37.3 @@ -43,228 +43,162 @@
    37.4  public class XDaemonCommand implements Command
    37.5  {
    37.6  
    37.7 -  @Override
    37.8 -  public String[] getSupportedCommandStrings()
    37.9 -  {
   37.10 -    return new String[]{"XDAEMON"};
   37.11 -  }
   37.12 +	@Override
   37.13 +	public String[] getSupportedCommandStrings()
   37.14 +	{
   37.15 +		return new String[] {"XDAEMON"};
   37.16 +	}
   37.17  
   37.18 -  @Override
   37.19 -  public boolean hasFinished()
   37.20 -  {
   37.21 -    return true;
   37.22 -  }
   37.23 +	@Override
   37.24 +	public boolean hasFinished()
   37.25 +	{
   37.26 +		return true;
   37.27 +	}
   37.28  
   37.29 -  @Override
   37.30 -  public String impliedCapability()
   37.31 -  {
   37.32 -    return null;
   37.33 -  }
   37.34 +	@Override
   37.35 +	public String impliedCapability()
   37.36 +	{
   37.37 +		return null;
   37.38 +	}
   37.39  
   37.40 -  @Override
   37.41 -  public boolean isStateful()
   37.42 -  {
   37.43 -    return false;
   37.44 -  }
   37.45 +	@Override
   37.46 +	public boolean isStateful()
   37.47 +	{
   37.48 +		return false;
   37.49 +	}
   37.50  
   37.51 -  private void channelAdd(String[] commands, NNTPConnection conn)
   37.52 -    throws IOException, StorageBackendException
   37.53 -  {
   37.54 -    String groupName = commands[2];
   37.55 -    if(StorageManager.current().isGroupExisting(groupName))
   37.56 -    {
   37.57 -      conn.println("400 group " + groupName + " already existing!");
   37.58 -    }
   37.59 -    else
   37.60 -    {
   37.61 -      StorageManager.current().addGroup(groupName, Integer.parseInt(commands[3]));
   37.62 -      conn.println("200 group " + groupName + " created");
   37.63 -    }
   37.64 -  }
   37.65 +	private void channelAdd(String[] commands, NNTPConnection conn)
   37.66 +		throws IOException, StorageBackendException
   37.67 +	{
   37.68 +		String groupName = commands[2];
   37.69 +		if (StorageManager.current().isGroupExisting(groupName)) {
   37.70 +			conn.println("400 group " + groupName + " already existing!");
   37.71 +		} else {
   37.72 +			StorageManager.current().addGroup(groupName, Integer.parseInt(commands[3]));
   37.73 +			conn.println("200 group " + groupName + " created");
   37.74 +		}
   37.75 +	}
   37.76  
   37.77 -  // TODO: Refactor this method to reduce complexity!
   37.78 -  @Override
   37.79 -  public void processLine(NNTPConnection conn, String line, byte[] raw)
   37.80 -    throws IOException, StorageBackendException
   37.81 -  {
   37.82 -    InetSocketAddress addr = (InetSocketAddress)conn.getSocketChannel().socket()
   37.83 -      .getRemoteSocketAddress();
   37.84 -    if(addr.getHostName().equals(
   37.85 -      Config.inst().get(Config.XDAEMON_HOST, "localhost")))
   37.86 -    {
   37.87 -      String[] commands = line.split(" ", 4);
   37.88 -      if(commands.length == 3 && commands[1].equalsIgnoreCase("LIST"))
   37.89 -      {
   37.90 -        if(commands[2].equalsIgnoreCase("CONFIGKEYS"))
   37.91 -        {
   37.92 -          conn.println("100 list of available config keys follows");
   37.93 -          for(String key : Config.AVAILABLE_KEYS)
   37.94 -          {
   37.95 -            conn.println(key);
   37.96 -          }
   37.97 -          conn.println(".");
   37.98 -        }
   37.99 -        else if(commands[2].equalsIgnoreCase("PEERINGRULES"))
  37.100 -        {
  37.101 -          List<Subscription> pull = 
  37.102 -            StorageManager.current().getSubscriptions(FeedManager.TYPE_PULL);
  37.103 -          List<Subscription> push =
  37.104 -            StorageManager.current().getSubscriptions(FeedManager.TYPE_PUSH);
  37.105 -          conn.println("100 list of peering rules follows");
  37.106 -          for(Subscription sub : pull)
  37.107 -          {
  37.108 -            conn.println("PULL " + sub.getHost() + ":" + sub.getPort()
  37.109 -              + " " + sub.getGroup());
  37.110 -          }
  37.111 -          for(Subscription sub : push)
  37.112 -          {
  37.113 -            conn.println("PUSH " + sub.getHost() + ":" + sub.getPort()
  37.114 -              + " " + sub.getGroup());
  37.115 -          }
  37.116 -          conn.println(".");
  37.117 -        }
  37.118 -        else
  37.119 -        {
  37.120 -          conn.println("401 unknown sub command");
  37.121 -        }
  37.122 -      }
  37.123 -      else if(commands.length == 3 && commands[1].equalsIgnoreCase("DELETE"))
  37.124 -      {
  37.125 -        StorageManager.current().delete(commands[2]);
  37.126 -        conn.println("200 article " + commands[2] + " deleted");
  37.127 -      }
  37.128 -      else if(commands.length == 4 && commands[1].equalsIgnoreCase("GROUPADD"))
  37.129 -      {
  37.130 -        channelAdd(commands, conn);
  37.131 -      }
  37.132 -      else if(commands.length == 3 && commands[1].equalsIgnoreCase("GROUPDEL"))
  37.133 -      {
  37.134 -        Group group = StorageManager.current().getGroup(commands[2]);
  37.135 -        if(group == null)
  37.136 -        {
  37.137 -          conn.println("400 group not found");
  37.138 -        }
  37.139 -        else
  37.140 -        {
  37.141 -          group.setFlag(Group.DELETED);
  37.142 -          group.update();
  37.143 -          conn.println("200 group " + commands[2] + " marked as deleted");
  37.144 -        }
  37.145 -      }
  37.146 -      else if(commands.length == 4 && commands[1].equalsIgnoreCase("SET"))
  37.147 -      {
  37.148 -        String key = commands[2];
  37.149 -        String val = commands[3];
  37.150 -        Config.inst().set(key, val);
  37.151 -        conn.println("200 new config value set");
  37.152 -      }
  37.153 -      else if(commands.length == 3 && commands[1].equalsIgnoreCase("GET"))
  37.154 -      {
  37.155 -        String key = commands[2];
  37.156 -        String val = Config.inst().get(key, null);
  37.157 -        if(val != null)
  37.158 -        {
  37.159 -          conn.println("100 config value for " + key + " follows");
  37.160 -          conn.println(val);
  37.161 -          conn.println(".");
  37.162 -        }
  37.163 -        else
  37.164 -        {
  37.165 -          conn.println("400 config value not set");
  37.166 -        }
  37.167 -      }
  37.168 -      else if(commands.length >= 3 && commands[1].equalsIgnoreCase("LOG"))
  37.169 -      {
  37.170 -        Group group = null;
  37.171 -        if(commands.length > 3)
  37.172 -        {
  37.173 -          group = (Group)Channel.getByName(commands[3]);
  37.174 -        }
  37.175 +	// TODO: Refactor this method to reduce complexity!
  37.176 +	@Override
  37.177 +	public void processLine(NNTPConnection conn, String line, byte[] raw)
  37.178 +		throws IOException, StorageBackendException
  37.179 +	{
  37.180 +		InetSocketAddress addr = (InetSocketAddress) conn.getSocketChannel().socket().getRemoteSocketAddress();
  37.181 +		if (addr.getHostName().equals(
  37.182 +			Config.inst().get(Config.XDAEMON_HOST, "localhost"))) {
  37.183 +			String[] commands = line.split(" ", 4);
  37.184 +			if (commands.length == 3 && commands[1].equalsIgnoreCase("LIST")) {
  37.185 +				if (commands[2].equalsIgnoreCase("CONFIGKEYS")) {
  37.186 +					conn.println("100 list of available config keys follows");
  37.187 +					for (String key : Config.AVAILABLE_KEYS) {
  37.188 +						conn.println(key);
  37.189 +					}
  37.190 +					conn.println(".");
  37.191 +				} else if (commands[2].equalsIgnoreCase("PEERINGRULES")) {
  37.192 +					List<Subscription> pull =
  37.193 +						StorageManager.current().getSubscriptions(FeedManager.TYPE_PULL);
  37.194 +					List<Subscription> push =
  37.195 +						StorageManager.current().getSubscriptions(FeedManager.TYPE_PUSH);
  37.196 +					conn.println("100 list of peering rules follows");
  37.197 +					for (Subscription sub : pull) {
  37.198 +						conn.println("PULL " + sub.getHost() + ":" + sub.getPort()
  37.199 +							+ " " + sub.getGroup());
  37.200 +					}
  37.201 +					for (Subscription sub : push) {
  37.202 +						conn.println("PUSH " + sub.getHost() + ":" + sub.getPort()
  37.203 +							+ " " + sub.getGroup());
  37.204 +					}
  37.205 +					conn.println(".");
  37.206 +				} else {
  37.207 +					conn.println("401 unknown sub command");
  37.208 +				}
  37.209 +			} else if (commands.length == 3 && commands[1].equalsIgnoreCase("DELETE")) {
  37.210 +				StorageManager.current().delete(commands[2]);
  37.211 +				conn.println("200 article " + commands[2] + " deleted");
  37.212 +			} else if (commands.length == 4 && commands[1].equalsIgnoreCase("GROUPADD")) {
  37.213 +				channelAdd(commands, conn);
  37.214 +			} else if (commands.length == 3 && commands[1].equalsIgnoreCase("GROUPDEL")) {
  37.215 +				Group group = StorageManager.current().getGroup(commands[2]);
  37.216 +				if (group == null) {
  37.217 +					conn.println("400 group not found");
  37.218 +				} else {
  37.219 +					group.setFlag(Group.DELETED);
  37.220 +					group.update();
  37.221 +					conn.println("200 group " + commands[2] + " marked as deleted");
  37.222 +				}
  37.223 +			} else if (commands.length == 4 && commands[1].equalsIgnoreCase("SET")) {
  37.224 +				String key = commands[2];
  37.225 +				String val = commands[3];
  37.226 +				Config.inst().set(key, val);
  37.227 +				conn.println("200 new config value set");
  37.228 +			} else if (commands.length == 3 && commands[1].equalsIgnoreCase("GET")) {
  37.229 +				String key = commands[2];
  37.230 +				String val = Config.inst().get(key, null);
  37.231 +				if (val != null) {
  37.232 +					conn.println("100 config value for " + key + " follows");
  37.233 +					conn.println(val);
  37.234 +					conn.println(".");
  37.235 +				} else {
  37.236 +					conn.println("400 config value not set");
  37.237 +				}
  37.238 +			} else if (commands.length >= 3 && commands[1].equalsIgnoreCase("LOG")) {
  37.239 +				Group group = null;
  37.240 +				if (commands.length > 3) {
  37.241 +					group = (Group) Channel.getByName(commands[3]);
  37.242 +				}
  37.243  
  37.244 -        if(commands[2].equalsIgnoreCase("CONNECTED_CLIENTS"))
  37.245 -        {
  37.246 -          conn.println("100 number of connections follow");
  37.247 -          conn.println(Integer.toString(Stats.getInstance().connectedClients()));
  37.248 -          conn.println(".");
  37.249 -        }
  37.250 -        else if(commands[2].equalsIgnoreCase("POSTED_NEWS"))
  37.251 -        {
  37.252 -          conn.println("100 hourly numbers of posted news yesterday");
  37.253 -          for(int n = 0; n < 24; n++)
  37.254 -          {
  37.255 -            conn.println(n + " " + Stats.getInstance()
  37.256 -              .getYesterdaysEvents(Stats.POSTED_NEWS, n, group));
  37.257 -          }
  37.258 -          conn.println(".");
  37.259 -        }
  37.260 -        else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS"))
  37.261 -        {
  37.262 -          conn.println("100 hourly numbers of gatewayed news yesterday");
  37.263 -          for(int n = 0; n < 24; n++)
  37.264 -          {
  37.265 -            conn.println(n + " " + Stats.getInstance()
  37.266 -              .getYesterdaysEvents(Stats.GATEWAYED_NEWS, n, group));
  37.267 -          }
  37.268 -          conn.println(".");
  37.269 -        }
  37.270 -        else if(commands[2].equalsIgnoreCase("TRANSMITTED_NEWS"))
  37.271 -        {
  37.272 -          conn.println("100 hourly numbers of news transmitted to peers yesterday");
  37.273 -          for(int n = 0; n < 24; n++)
  37.274 -          {
  37.275 -            conn.println(n + " " + Stats.getInstance()
  37.276 -              .getYesterdaysEvents(Stats.FEEDED_NEWS, n, group));
  37.277 -          }
  37.278 -          conn.println(".");
  37.279 -        }
  37.280 -        else if(commands[2].equalsIgnoreCase("HOSTED_NEWS"))
  37.281 -        {
  37.282 -          conn.println("100 number of overall hosted news");
  37.283 -          conn.println(Integer.toString(Stats.getInstance().getNumberOfNews()));
  37.284 -          conn.println(".");
  37.285 -        }
  37.286 -        else if(commands[2].equalsIgnoreCase("HOSTED_GROUPS"))
  37.287 -        {
  37.288 -          conn.println("100 number of hosted groups");
  37.289 -          conn.println(Integer.toString(Stats.getInstance().getNumberOfGroups()));
  37.290 -          conn.println(".");
  37.291 -        }
  37.292 -        else if(commands[2].equalsIgnoreCase("POSTED_NEWS_PER_HOUR"))
  37.293 -        {
  37.294 -          conn.println("100 posted news per hour");
  37.295 -          conn.println(Double.toString(Stats.getInstance().postedPerHour(-1)));
  37.296 -          conn.println(".");
  37.297 -        }
  37.298 -        else if(commands[2].equalsIgnoreCase("FEEDED_NEWS_PER_HOUR"))
  37.299 -        {
  37.300 -          conn.println("100 feeded news per hour");
  37.301 -          conn.println(Double.toString(Stats.getInstance().feededPerHour(-1)));
  37.302 -          conn.println(".");
  37.303 -        }
  37.304 -        else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS_PER_HOUR"))
  37.305 -        {
  37.306 -          conn.println("100 gatewayed news per hour");
  37.307 -          conn.println(Double.toString(Stats.getInstance().gatewayedPerHour(-1)));
  37.308 -          conn.println(".");
  37.309 -        }
  37.310 -        else
  37.311 -        {
  37.312 -          conn.println("401 unknown sub command");
  37.313 -        }
  37.314 -      }
  37.315 -      else if(commands.length >= 3 && commands[1].equalsIgnoreCase("PLUGIN"))
  37.316 -      {
  37.317 -        
  37.318 -      }
  37.319 -      else
  37.320 -      {
  37.321 -        conn.println("400 invalid command usage");
  37.322 -      }
  37.323 -    }
  37.324 -    else
  37.325 -    {
  37.326 -      conn.println("501 not allowed");
  37.327 -    }
  37.328 -  }
  37.329 -  
  37.330 +				if (commands[2].equalsIgnoreCase("CONNECTED_CLIENTS")) {
  37.331 +					conn.println("100 number of connections follow");
  37.332 +					conn.println(Integer.toString(Stats.getInstance().connectedClients()));
  37.333 +					conn.println(".");
  37.334 +				} else if (commands[2].equalsIgnoreCase("POSTED_NEWS")) {
  37.335 +					conn.println("100 hourly numbers of posted news yesterday");
  37.336 +					for (int n = 0; n < 24; n++) {
  37.337 +						conn.println(n + " " + Stats.getInstance().getYesterdaysEvents(Stats.POSTED_NEWS, n, group));
  37.338 +					}
  37.339 +					conn.println(".");
  37.340 +				} else if (commands[2].equalsIgnoreCase("GATEWAYED_NEWS")) {
  37.341 +					conn.println("100 hourly numbers of gatewayed news yesterday");
  37.342 +					for (int n = 0; n < 24; n++) {
  37.343 +						conn.println(n + " " + Stats.getInstance().getYesterdaysEvents(Stats.GATEWAYED_NEWS, n, group));
  37.344 +					}
  37.345 +					conn.println(".");
  37.346 +				} else if (commands[2].equalsIgnoreCase("TRANSMITTED_NEWS")) {
  37.347 +					conn.println("100 hourly numbers of news transmitted to peers yesterday");
  37.348 +					for (int n = 0; n < 24; n++) {
  37.349 +						conn.println(n + " " + Stats.getInstance().getYesterdaysEvents(Stats.FEEDED_NEWS, n, group));
  37.350 +					}
  37.351 +					conn.println(".");
  37.352 +				} else if (commands[2].equalsIgnoreCase("HOSTED_NEWS")) {
  37.353 +					conn.println("100 number of overall hosted news");
  37.354 +					conn.println(Integer.toString(Stats.getInstance().getNumberOfNews()));
  37.355 +					conn.println(".");
  37.356 +				} else if (commands[2].equalsIgnoreCase("HOSTED_GROUPS")) {
  37.357 +					conn.println("100 number of hosted groups");
  37.358 +					conn.println(Integer.toString(Stats.getInstance().getNumberOfGroups()));
  37.359 +					conn.println(".");
  37.360 +				} else if (commands[2].equalsIgnoreCase("POSTED_NEWS_PER_HOUR")) {
  37.361 +					conn.println("100 posted news per hour");
  37.362 +					conn.println(Double.toString(Stats.getInstance().postedPerHour(-1)));
  37.363 +					conn.println(".");
  37.364 +				} else if (commands[2].equalsIgnoreCase("FEEDED_NEWS_PER_HOUR")) {
  37.365 +					conn.println("100 feeded news per hour");
  37.366 +					conn.println(Double.toString(Stats.getInstance().feededPerHour(-1)));
  37.367 +					conn.println(".");
  37.368 +				} else if (commands[2].equalsIgnoreCase("GATEWAYED_NEWS_PER_HOUR")) {
  37.369 +					conn.println("100 gatewayed news per hour");
  37.370 +					conn.println(Double.toString(Stats.getInstance().gatewayedPerHour(-1)));
  37.371 +					conn.println(".");
  37.372 +				} else {
  37.373 +					conn.println("401 unknown sub command");
  37.374 +				}
  37.375 +			} else if (commands.length >= 3 && commands[1].equalsIgnoreCase("PLUGIN")) {
  37.376 +			} else {
  37.377 +				conn.println("400 invalid command usage");
  37.378 +			}
  37.379 +		} else {
  37.380 +			conn.println("501 not allowed");
  37.381 +		}
  37.382 +	}
  37.383  }
    38.1 --- a/src/org/sonews/daemon/command/XPatCommand.java	Sun Aug 29 17:43:58 2010 +0200
    38.2 +++ b/src/org/sonews/daemon/command/XPatCommand.java	Sun Aug 29 18:17:37 2010 +0200
    38.3 @@ -78,93 +78,79 @@
    38.4  public class XPatCommand implements Command
    38.5  {
    38.6  
    38.7 -  @Override
    38.8 -  public String[] getSupportedCommandStrings()
    38.9 -  {
   38.10 -    return new String[]{"XPAT"};
   38.11 -  }
   38.12 -  
   38.13 -  @Override
   38.14 -  public boolean hasFinished()
   38.15 -  {
   38.16 -    return true;
   38.17 -  }
   38.18 +	@Override
   38.19 +	public String[] getSupportedCommandStrings()
   38.20 +	{
   38.21 +		return new String[] {"XPAT"};
   38.22 +	}
   38.23  
   38.24 -  @Override
   38.25 -  public String impliedCapability()
   38.26 -  {
   38.27 -    return null;
   38.28 -  }
   38.29 +	@Override
   38.30 +	public boolean hasFinished()
   38.31 +	{
   38.32 +		return true;
   38.33 +	}
   38.34  
   38.35 -  @Override
   38.36 -  public boolean isStateful()
   38.37 -  {
   38.38 -    return false;
   38.39 -  }
   38.40 +	@Override
   38.41 +	public String impliedCapability()
   38.42 +	{
   38.43 +		return null;
   38.44 +	}
   38.45  
   38.46 -  @Override
   38.47 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   38.48 -    throws IOException, StorageBackendException
   38.49 -  {
   38.50 -    if(conn.getCurrentChannel() == null)
   38.51 -    {
   38.52 -      conn.println("430 no group selected");
   38.53 -      return;
   38.54 -    }
   38.55 +	@Override
   38.56 +	public boolean isStateful()
   38.57 +	{
   38.58 +		return false;
   38.59 +	}
   38.60  
   38.61 -    String[] command = line.split("\\p{Space}+");
   38.62 +	@Override
   38.63 +	public void processLine(NNTPConnection conn, final String line, byte[] raw)
   38.64 +		throws IOException, StorageBackendException
   38.65 +	{
   38.66 +		if (conn.getCurrentChannel() == null) {
   38.67 +			conn.println("430 no group selected");
   38.68 +			return;
   38.69 +		}
   38.70  
   38.71 -    // There may be multiple patterns and Thunderbird produces
   38.72 -    // additional spaces between range and pattern
   38.73 -    if(command.length >= 4)
   38.74 -    {
   38.75 -      String header  = command[1].toLowerCase(Locale.US);
   38.76 -      String range   = command[2];
   38.77 -      String pattern = command[3];
   38.78 +		String[] command = line.split("\\p{Space}+");
   38.79  
   38.80 -      long start = -1;
   38.81 -      long end   = -1;
   38.82 -      if(range.contains("-"))
   38.83 -      {
   38.84 -        String[] rsplit = range.split("-", 2);
   38.85 -        start = Long.parseLong(rsplit[0]);
   38.86 -        if(rsplit[1].length() > 0)
   38.87 -        {
   38.88 -          end = Long.parseLong(rsplit[1]);
   38.89 -        }
   38.90 -      }
   38.91 -      else // TODO: Handle Message-IDs
   38.92 -      {
   38.93 -        start = Long.parseLong(range);
   38.94 -      }
   38.95 +		// There may be multiple patterns and Thunderbird produces
   38.96 +		// additional spaces between range and pattern
   38.97 +		if (command.length >= 4) {
   38.98 +			String header = command[1].toLowerCase(Locale.US);
   38.99 +			String range = command[2];
  38.100 +			String pattern = command[3];
  38.101  
  38.102 -      try
  38.103 -      {
  38.104 -        List<Pair<Long, String>> heads = StorageManager.current().
  38.105 -          getArticleHeaders(conn.getCurrentChannel(), start, end, header, pattern);
  38.106 -        
  38.107 -        conn.println("221 header follows");
  38.108 -        for(Pair<Long, String> head : heads)
  38.109 -        {
  38.110 -          conn.println(head.getA() + " " + head.getB());
  38.111 -        }
  38.112 -        conn.println(".");
  38.113 -      }
  38.114 -      catch(PatternSyntaxException ex)
  38.115 -      {
  38.116 -        ex.printStackTrace();
  38.117 -        conn.println("500 invalid pattern syntax");
  38.118 -      }
  38.119 -      catch(StorageBackendException ex)
  38.120 -      {
  38.121 -        ex.printStackTrace();
  38.122 -        conn.println("500 internal server error");
  38.123 -      }
  38.124 -    }
  38.125 -    else
  38.126 -    {
  38.127 -      conn.println("430 invalid command usage");
  38.128 -    }
  38.129 -  }
  38.130 +			long start = -1;
  38.131 +			long end = -1;
  38.132 +			if (range.contains("-")) {
  38.133 +				String[] rsplit = range.split("-", 2);
  38.134 +				start = Long.parseLong(rsplit[0]);
  38.135 +				if (rsplit[1].length() > 0) {
  38.136 +					end = Long.parseLong(rsplit[1]);
  38.137 +				}
  38.138 +			} else // TODO: Handle Message-IDs
  38.139 +			{
  38.140 +				start = Long.parseLong(range);
  38.141 +			}
  38.142  
  38.143 +			try {
  38.144 +				List<Pair<Long, String>> heads = StorageManager.current().
  38.145 +					getArticleHeaders(conn.getCurrentChannel(), start, end, header, pattern);
  38.146 +
  38.147 +				conn.println("221 header follows");
  38.148 +				for (Pair<Long, String> head : heads) {
  38.149 +					conn.println(head.getA() + " " + head.getB());
  38.150 +				}
  38.151 +				conn.println(".");
  38.152 +			} catch (PatternSyntaxException ex) {
  38.153 +				ex.printStackTrace();
  38.154 +				conn.println("500 invalid pattern syntax");
  38.155 +			} catch (StorageBackendException ex) {
  38.156 +				ex.printStackTrace();
  38.157 +				conn.println("500 internal server error");
  38.158 +			}
  38.159 +		} else {
  38.160 +			conn.println("430 invalid command usage");
  38.161 +		}
  38.162 +	}
  38.163  }
    39.1 --- a/src/org/sonews/feed/FeedManager.java	Sun Aug 29 17:43:58 2010 +0200
    39.2 +++ b/src/org/sonews/feed/FeedManager.java	Sun Aug 29 18:17:37 2010 +0200
    39.3 @@ -25,30 +25,30 @@
    39.4   * @author Christian Lins
    39.5   * @since sonews/0.5.0
    39.6   */
    39.7 -public final class FeedManager 
    39.8 +public final class FeedManager
    39.9  {
   39.10  
   39.11 -  public static final int TYPE_PULL = 0;
   39.12 -  public static final int TYPE_PUSH = 1;
   39.13 -  
   39.14 -  private static PullFeeder pullFeeder = new PullFeeder();
   39.15 -  private static PushFeeder pushFeeder = new PushFeeder();
   39.16 -  
   39.17 -  /**
   39.18 -   * Reads the peer subscriptions from database and starts the appropriate
   39.19 -   * PullFeeder or PushFeeder.
   39.20 -   */
   39.21 -  public static synchronized void startFeeding()
   39.22 -  {
   39.23 -    pullFeeder.start();
   39.24 -    pushFeeder.start();
   39.25 -  }
   39.26 -  
   39.27 -  public static void queueForPush(Article article)
   39.28 -  {
   39.29 -    pushFeeder.queueForPush(article);
   39.30 -  }
   39.31 -  
   39.32 -  private FeedManager() {}
   39.33 -  
   39.34 +	public static final int TYPE_PULL = 0;
   39.35 +	public static final int TYPE_PUSH = 1;
   39.36 +	private static PullFeeder pullFeeder = new PullFeeder();
   39.37 +	private static PushFeeder pushFeeder = new PushFeeder();
   39.38 +
   39.39 +	/**
   39.40 +	 * Reads the peer subscriptions from database and starts the appropriate
   39.41 +	 * PullFeeder or PushFeeder.
   39.42 +	 */
   39.43 +	public static synchronized void startFeeding()
   39.44 +	{
   39.45 +		pullFeeder.start();
   39.46 +		pushFeeder.start();
   39.47 +	}
   39.48 +
   39.49 +	public static void queueForPush(Article article)
   39.50 +	{
   39.51 +		pushFeeder.queueForPush(article);
   39.52 +	}
   39.53 +
   39.54 +	private FeedManager()
   39.55 +	{
   39.56 +	}
   39.57  }
    40.1 --- a/src/org/sonews/feed/PullFeeder.java	Sun Aug 29 17:43:58 2010 +0200
    40.2 +++ b/src/org/sonews/feed/PullFeeder.java	Sun Aug 29 18:17:37 2010 +0200
    40.3 @@ -49,228 +49,192 @@
    40.4   */
    40.5  class PullFeeder extends AbstractDaemon
    40.6  {
    40.7 -  
    40.8 -  private Map<Subscription, Integer> highMarks = new HashMap<Subscription, Integer>();
    40.9 -  private BufferedReader             in;
   40.10 -  private PrintWriter                out;
   40.11 -  private Set<Subscription>          subscriptions = new HashSet<Subscription>();
   40.12 -  
   40.13 -  private void addSubscription(final Subscription sub)
   40.14 -  {
   40.15 -    subscriptions.add(sub);
   40.16  
   40.17 -    if(!highMarks.containsKey(sub))
   40.18 -    {
   40.19 -      // Set a initial highMark
   40.20 -      this.highMarks.put(sub, 0);
   40.21 -    }
   40.22 -  }
   40.23 -  
   40.24 -  /**
   40.25 -   * Changes to the given group and returns its high mark.
   40.26 -   * @param groupName
   40.27 -   * @return
   40.28 -   */
   40.29 -  private int changeGroup(String groupName)
   40.30 -    throws IOException
   40.31 -  {
   40.32 -    this.out.print("GROUP " + groupName + "\r\n");
   40.33 -    this.out.flush();
   40.34 -    
   40.35 -    String line = this.in.readLine();
   40.36 -    if(line.startsWith("211 "))
   40.37 -    {
   40.38 -      int highmark = Integer.parseInt(line.split(" ")[3]);
   40.39 -      return highmark;
   40.40 -    }
   40.41 -    else
   40.42 -    {
   40.43 -      throw new IOException("GROUP " + groupName + " returned: " + line);
   40.44 -    }
   40.45 -  }
   40.46 -  
   40.47 -  private void connectTo(final String host, final int port)
   40.48 -    throws IOException, UnknownHostException
   40.49 -  {
   40.50 -    Socket socket = new Socket(host, port);
   40.51 -    this.out = new PrintWriter(socket.getOutputStream());
   40.52 -    this.in  = new BufferedReader(new InputStreamReader(socket.getInputStream()));
   40.53 +	private Map<Subscription, Integer> highMarks = new HashMap<Subscription, Integer>();
   40.54 +	private BufferedReader in;
   40.55 +	private PrintWriter out;
   40.56 +	private Set<Subscription> subscriptions = new HashSet<Subscription>();
   40.57  
   40.58 -    String line = in.readLine();
   40.59 -    if(!(line.charAt(0) == '2')) // Could be 200 or 2xx if posting is not allowed
   40.60 -    {
   40.61 -      throw new IOException(line);
   40.62 -    }
   40.63 +	private void addSubscription(final Subscription sub)
   40.64 +	{
   40.65 +		subscriptions.add(sub);
   40.66  
   40.67 -    // Send MODE READER to peer, some newsservers are friendlier then
   40.68 -    this.out.println("MODE READER\r\n");
   40.69 -    this.out.flush();
   40.70 -    line = this.in.readLine();
   40.71 -  }
   40.72 -  
   40.73 -  private void disconnect()
   40.74 -    throws IOException
   40.75 -  {
   40.76 -    this.out.print("QUIT\r\n");
   40.77 -    this.out.flush();
   40.78 -    this.out.close();
   40.79 -    this.in.close();
   40.80 -    
   40.81 -    this.out = null;
   40.82 -    this.in  = null;
   40.83 -  }
   40.84 -  
   40.85 -  /**
   40.86 -   * Uses the OVER or XOVER command to get a list of message overviews that
   40.87 -   * may be unknown to this feeder and are about to be peered.
   40.88 -   * @param start
   40.89 -   * @param end
   40.90 -   * @return A list of message ids with potentially interesting messages.
   40.91 -   */
   40.92 -  private List<String> over(int start, int end)
   40.93 -    throws IOException
   40.94 -  {
   40.95 -    this.out.print("OVER " + start + "-" + end + "\r\n");
   40.96 -    this.out.flush();
   40.97 -    
   40.98 -    String line = this.in.readLine();
   40.99 -    if(line.startsWith("500 ")) // OVER not supported
  40.100 -    {
  40.101 -      this.out.print("XOVER " + start + "-" + end + "\r\n");
  40.102 -      this.out.flush();
  40.103 -      
  40.104 -      line = this.in.readLine();
  40.105 -    }
  40.106 -    
  40.107 -    if(line.startsWith("224 "))
  40.108 -    {
  40.109 -      List<String> messages = new ArrayList<String>();
  40.110 -      line = this.in.readLine();
  40.111 -      while(!".".equals(line))
  40.112 -      {
  40.113 -        String mid = line.split("\t")[4]; // 5th should be the Message-ID
  40.114 -        messages.add(mid);
  40.115 -        line = this.in.readLine();
  40.116 -      }
  40.117 -      return messages;
  40.118 -    }
  40.119 -    else
  40.120 -    {
  40.121 -      throw new IOException("Server return for OVER/XOVER: " + line);
  40.122 -    }
  40.123 -  }
  40.124 -  
  40.125 -  @Override
  40.126 -  public void run()
  40.127 -  {
  40.128 -    while(isRunning())
  40.129 -    {
  40.130 -      int pullInterval = 1000 * 
  40.131 -        Config.inst().get(Config.FEED_PULLINTERVAL, 3600);
  40.132 -      String host = "localhost";
  40.133 -      int    port = 119;
  40.134 -      
  40.135 -      Log.get().info("Start PullFeeder run...");
  40.136 +		if (!highMarks.containsKey(sub)) {
  40.137 +			// Set a initial highMark
  40.138 +			this.highMarks.put(sub, 0);
  40.139 +		}
  40.140 +	}
  40.141  
  40.142 -      try
  40.143 -      {
  40.144 -        this.subscriptions.clear();
  40.145 -        List<Subscription> subsPull = StorageManager.current()
  40.146 -          .getSubscriptions(FeedManager.TYPE_PULL);
  40.147 -        for(Subscription sub : subsPull)
  40.148 -        {
  40.149 -          addSubscription(sub);
  40.150 -        }
  40.151 -      }
  40.152 -      catch(StorageBackendException ex)
  40.153 -      {
  40.154 -        Log.get().log(Level.SEVERE, host, ex);
  40.155 -      }
  40.156 +	/**
  40.157 +	 * Changes to the given group and returns its high mark.
  40.158 +	 * @param groupName
  40.159 +	 * @return
  40.160 +	 */
  40.161 +	private int changeGroup(String groupName)
  40.162 +		throws IOException
  40.163 +	{
  40.164 +		this.out.print("GROUP " + groupName + "\r\n");
  40.165 +		this.out.flush();
  40.166  
  40.167 -      try
  40.168 -      {
  40.169 -        for(Subscription sub : this.subscriptions)
  40.170 -        {
  40.171 -          host = sub.getHost();
  40.172 -          port = sub.getPort();
  40.173 +		String line = this.in.readLine();
  40.174 +		if (line.startsWith("211 ")) {
  40.175 +			int highmark = Integer.parseInt(line.split(" ")[3]);
  40.176 +			return highmark;
  40.177 +		} else {
  40.178 +			throw new IOException("GROUP " + groupName + " returned: " + line);
  40.179 +		}
  40.180 +	}
  40.181  
  40.182 -          try
  40.183 -          {
  40.184 -            Log.get().info("Feeding " + sub.getGroup() + " from " + sub.getHost());
  40.185 -            try
  40.186 -            {
  40.187 -              connectTo(host, port);
  40.188 -            }
  40.189 -            catch(SocketException ex)
  40.190 -            {
  40.191 -              Log.get().info("Skipping " + sub.getHost() + ": " + ex);
  40.192 -              continue;
  40.193 -            }
  40.194 -            
  40.195 -            int oldMark = this.highMarks.get(sub);
  40.196 -            int newMark = changeGroup(sub.getGroup());
  40.197 -            
  40.198 -            if(oldMark != newMark)
  40.199 -            {
  40.200 -              List<String> messageIDs = over(oldMark, newMark);
  40.201 +	private void connectTo(final String host, final int port)
  40.202 +		throws IOException, UnknownHostException
  40.203 +	{
  40.204 +		Socket socket = new Socket(host, port);
  40.205 +		this.out = new PrintWriter(socket.getOutputStream());
  40.206 +		this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  40.207  
  40.208 -              for(String messageID : messageIDs)
  40.209 -              {
  40.210 -                if(!StorageManager.current().isArticleExisting(messageID))
  40.211 -                {
  40.212 -                  try
  40.213 -                  {
  40.214 -                    // Post the message via common socket connection
  40.215 -                    ArticleReader aread =
  40.216 -                      new ArticleReader(sub.getHost(), sub.getPort(), messageID);
  40.217 -                    byte[] abuf = aread.getArticleData();
  40.218 -                    if(abuf == null)
  40.219 -                    {
  40.220 -                      Log.get().warning("Could not feed " + messageID
  40.221 -                        + " from " + sub.getHost());
  40.222 -                    }
  40.223 -                    else
  40.224 -                    {
  40.225 -                      Log.get().info("Feeding " + messageID);
  40.226 -                      ArticleWriter awrite = new ArticleWriter(
  40.227 -                        "localhost", Config.inst().get(Config.PORT, 119));
  40.228 -                      awrite.writeArticle(abuf);
  40.229 -                      awrite.close();
  40.230 -                    }
  40.231 -                    Stats.getInstance().mailFeeded(sub.getGroup());
  40.232 -                  }
  40.233 -                  catch(IOException ex)
  40.234 -                  {
  40.235 -                    // There may be a temporary network failure
  40.236 -                    ex.printStackTrace();
  40.237 -                    Log.get().warning("Skipping mail " + messageID + " due to exception.");
  40.238 -                  }
  40.239 -                }
  40.240 -              } // for(;;)
  40.241 -              this.highMarks.put(sub, newMark);
  40.242 -            }
  40.243 -            
  40.244 -            disconnect();
  40.245 -          }
  40.246 -          catch(StorageBackendException ex)
  40.247 -          {
  40.248 -            ex.printStackTrace();
  40.249 -          }
  40.250 -          catch(IOException ex)
  40.251 -          {
  40.252 -            ex.printStackTrace();
  40.253 -            Log.get().severe("PullFeeder run stopped due to exception.");
  40.254 -          }
  40.255 -        } // for(Subscription sub : subscriptions)
  40.256 -        
  40.257 -        Log.get().info("PullFeeder run ended. Waiting " + pullInterval / 1000 + "s");
  40.258 -        Thread.sleep(pullInterval);
  40.259 -      }
  40.260 -      catch(InterruptedException ex)
  40.261 -      {
  40.262 -        Log.get().warning(ex.getMessage());
  40.263 -      }
  40.264 -    }
  40.265 -  }
  40.266 -  
  40.267 +		String line = in.readLine();
  40.268 +		if (!(line.charAt(0) == '2')) // Could be 200 or 2xx if posting is not allowed
  40.269 +		{
  40.270 +			throw new IOException(line);
  40.271 +		}
  40.272 +
  40.273 +		// Send MODE READER to peer, some newsservers are friendlier then
  40.274 +		this.out.println("MODE READER\r\n");
  40.275 +		this.out.flush();
  40.276 +		line = this.in.readLine();
  40.277 +	}
  40.278 +
  40.279 +	private void disconnect()
  40.280 +		throws IOException
  40.281 +	{
  40.282 +		this.out.print("QUIT\r\n");
  40.283 +		this.out.flush();
  40.284 +		this.out.close();
  40.285 +		this.in.close();
  40.286 +
  40.287 +		this.out = null;
  40.288 +		this.in = null;
  40.289 +	}
  40.290 +
  40.291 +	/**
  40.292 +	 * Uses the OVER or XOVER command to get a list of message overviews that
  40.293 +	 * may be unknown to this feeder and are about to be peered.
  40.294 +	 * @param start
  40.295 +	 * @param end
  40.296 +	 * @return A list of message ids with potentially interesting messages.
  40.297 +	 */
  40.298 +	private List<String> over(int start, int end)
  40.299 +		throws IOException
  40.300 +	{
  40.301 +		this.out.print("OVER " + start + "-" + end + "\r\n");
  40.302 +		this.out.flush();
  40.303 +
  40.304 +		String line = this.in.readLine();
  40.305 +		if (line.startsWith("500 ")) // OVER not supported
  40.306 +		{
  40.307 +			this.out.print("XOVER " + start + "-" + end + "\r\n");
  40.308 +			this.out.flush();
  40.309 +
  40.310 +			line = this.in.readLine();
  40.311 +		}
  40.312 +
  40.313 +		if (line.startsWith("224 ")) {
  40.314 +			List<String> messages = new ArrayList<String>();
  40.315 +			line = this.in.readLine();
  40.316 +			while (!".".equals(line)) {
  40.317 +				String mid = line.split("\t")[4]; // 5th should be the Message-ID
  40.318 +				messages.add(mid);
  40.319 +				line = this.in.readLine();
  40.320 +			}
  40.321 +			return messages;
  40.322 +		} else {
  40.323 +			throw new IOException("Server return for OVER/XOVER: " + line);
  40.324 +		}
  40.325 +	}
  40.326 +
  40.327 +	@Override
  40.328 +	public void run()
  40.329 +	{
  40.330 +		while (isRunning()) {
  40.331 +			int pullInterval = 1000
  40.332 +				* Config.inst().get(Config.FEED_PULLINTERVAL, 3600);
  40.333 +			String host = "localhost";
  40.334 +			int port = 119;
  40.335 +
  40.336 +			Log.get().info("Start PullFeeder run...");
  40.337 +
  40.338 +			try {
  40.339 +				this.subscriptions.clear();
  40.340 +				List<Subscription> subsPull = StorageManager.current().getSubscriptions(FeedManager.TYPE_PULL);
  40.341 +				for (Subscription sub : subsPull) {
  40.342 +					addSubscription(sub);
  40.343 +				}
  40.344 +			} catch (StorageBackendException ex) {
  40.345 +				Log.get().log(Level.SEVERE, host, ex);
  40.346 +			}
  40.347 +
  40.348 +			try {
  40.349 +				for (Subscription sub : this.subscriptions) {
  40.350 +					host = sub.getHost();
  40.351 +					port = sub.getPort();
  40.352 +
  40.353 +					try {
  40.354 +						Log.get().info("Feeding " + sub.getGroup() + " from " + sub.getHost());
  40.355 +						try {
  40.356 +							connectTo(host, port);
  40.357 +						} catch (SocketException ex) {
  40.358 +							Log.get().info("Skipping " + sub.getHost() + ": " + ex);
  40.359 +							continue;
  40.360 +						}
  40.361 +
  40.362 +						int oldMark = this.highMarks.get(sub);
  40.363 +						int newMark = changeGroup(sub.getGroup());
  40.364 +
  40.365 +						if (oldMark != newMark) {
  40.366 +							List<String> messageIDs = over(oldMark, newMark);
  40.367 +
  40.368 +							for (String messageID : messageIDs) {
  40.369 +								if (!StorageManager.current().isArticleExisting(messageID)) {
  40.370 +									try {
  40.371 +										// Post the message via common socket connection
  40.372 +										ArticleReader aread =
  40.373 +											new ArticleReader(sub.getHost(), sub.getPort(), messageID);
  40.374 +										byte[] abuf = aread.getArticleData();
  40.375 +										if (abuf == null) {
  40.376 +											Log.get().warning("Could not feed " + messageID
  40.377 +												+ " from " + sub.getHost());
  40.378 +										} else {
  40.379 +											Log.get().info("Feeding " + messageID);
  40.380 +											ArticleWriter awrite = new ArticleWriter(
  40.381 +												"localhost", Config.inst().get(Config.PORT, 119));
  40.382 +											awrite.writeArticle(abuf);
  40.383 +											awrite.close();
  40.384 +										}
  40.385 +										Stats.getInstance().mailFeeded(sub.getGroup());
  40.386 +									} catch (IOException ex) {
  40.387 +										// There may be a temporary network failure
  40.388 +										ex.printStackTrace();
  40.389 +										Log.get().warning("Skipping mail " + messageID + " due to exception.");
  40.390 +									}
  40.391 +								}
  40.392 +							} // for(;;)
  40.393 +							this.highMarks.put(sub, newMark);
  40.394 +						}
  40.395 +
  40.396 +						disconnect();
  40.397 +					} catch (StorageBackendException ex) {
  40.398 +						ex.printStackTrace();
  40.399 +					} catch (IOException ex) {
  40.400 +						ex.printStackTrace();
  40.401 +						Log.get().severe("PullFeeder run stopped due to exception.");
  40.402 +					}
  40.403 +				} // for(Subscription sub : subscriptions)
  40.404 +
  40.405 +				Log.get().info("PullFeeder run ended. Waiting " + pullInterval / 1000 + "s");
  40.406 +				Thread.sleep(pullInterval);
  40.407 +			} catch (InterruptedException ex) {
  40.408 +				Log.get().warning(ex.getMessage());
  40.409 +			}
  40.410 +		}
  40.411 +	}
  40.412  }
    41.1 --- a/src/org/sonews/feed/PushFeeder.java	Sun Aug 29 17:43:58 2010 +0200
    41.2 +++ b/src/org/sonews/feed/PushFeeder.java	Sun Aug 29 18:17:37 2010 +0200
    41.3 @@ -37,82 +37,65 @@
    41.4   */
    41.5  class PushFeeder extends AbstractDaemon
    41.6  {
    41.7 -  
    41.8 -  private ConcurrentLinkedQueue<Article> articleQueue = 
    41.9 -    new ConcurrentLinkedQueue<Article>();
   41.10 -  
   41.11 -  @Override
   41.12 -  public void run()
   41.13 -  {
   41.14 -    while(isRunning())
   41.15 -    {
   41.16 -      try
   41.17 -      {
   41.18 -        synchronized(this)
   41.19 -        {
   41.20 -          this.wait();
   41.21 -        }
   41.22 -        
   41.23 -        List<Subscription> subscriptions = StorageManager.current()
   41.24 -          .getSubscriptions(FeedManager.TYPE_PUSH);
   41.25  
   41.26 -        Article  article = this.articleQueue.poll();
   41.27 -        String[] groups  = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
   41.28 -        Log.get().info("PushFeed: " + article.getMessageID());
   41.29 -        for(Subscription sub : subscriptions)
   41.30 -        {
   41.31 -          // Circle check
   41.32 -          if(article.getHeader(Headers.PATH)[0].contains(sub.getHost()))
   41.33 -          {
   41.34 -            Log.get().info(article.getMessageID() + " skipped for host "
   41.35 -              + sub.getHost());
   41.36 -            continue;
   41.37 -          }
   41.38 +	private ConcurrentLinkedQueue<Article> articleQueue =
   41.39 +		new ConcurrentLinkedQueue<Article>();
   41.40  
   41.41 -          try
   41.42 -          {
   41.43 -            for(String group : groups)
   41.44 -            {
   41.45 -              if(sub.getGroup().equals(group))
   41.46 -              {
   41.47 -                // Delete headers that may cause problems
   41.48 -                article.removeHeader(Headers.NNTP_POSTING_DATE);
   41.49 -                article.removeHeader(Headers.NNTP_POSTING_HOST);
   41.50 -                article.removeHeader(Headers.X_COMPLAINTS_TO);
   41.51 -                article.removeHeader(Headers.X_TRACE);
   41.52 -                article.removeHeader(Headers.XREF);
   41.53 -                
   41.54 -                // POST the message to remote server
   41.55 -                ArticleWriter awriter = new ArticleWriter(sub.getHost(), sub.getPort());
   41.56 -                awriter.writeArticle(article);
   41.57 -                break;
   41.58 -              }
   41.59 -            }
   41.60 -          }
   41.61 -          catch(IOException ex)
   41.62 -          {
   41.63 -            Log.get().warning(ex.toString());
   41.64 -          }
   41.65 -        }
   41.66 -      }
   41.67 -      catch(StorageBackendException ex)
   41.68 -      {
   41.69 -        Log.get().severe(ex.toString());
   41.70 -      }
   41.71 -      catch(InterruptedException ex)
   41.72 -      {
   41.73 -        Log.get().warning("PushFeeder interrupted: " + ex);
   41.74 -      }
   41.75 -    }
   41.76 -  }
   41.77 -  
   41.78 -  public void queueForPush(Article article)
   41.79 -  {
   41.80 -    this.articleQueue.add(article);
   41.81 -    synchronized(this)
   41.82 -    {
   41.83 -      this.notifyAll();
   41.84 -    }
   41.85 -  }
   41.86 -  
   41.87 +	@Override
   41.88 +	public void run()
   41.89 +	{
   41.90 +		while (isRunning()) {
   41.91 +			try {
   41.92 +				synchronized (this) {
   41.93 +					this.wait();
   41.94 +				}
   41.95 +
   41.96 +				List<Subscription> subscriptions = StorageManager.current().getSubscriptions(FeedManager.TYPE_PUSH);
   41.97 +
   41.98 +				Article article = this.articleQueue.poll();
   41.99 +				String[] groups = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
  41.100 +				Log.get().info("PushFeed: " + article.getMessageID());
  41.101 +				for (Subscription sub : subscriptions) {
  41.102 +					// Circle check
  41.103 +					if (article.getHeader(Headers.PATH)[0].contains(sub.getHost())) {
  41.104 +						Log.get().info(article.getMessageID() + " skipped for host "
  41.105 +							+ sub.getHost());
  41.106 +						continue;
  41.107 +					}
  41.108 +
  41.109 +					try {
  41.110 +						for (String group : groups) {
  41.111 +							if (sub.getGroup().equals(group)) {
  41.112 +								// Delete headers that may cause problems
  41.113 +								article.removeHeader(Headers.NNTP_POSTING_DATE);
  41.114 +								article.removeHeader(Headers.NNTP_POSTING_HOST);
  41.115 +								article.removeHeader(Headers.X_COMPLAINTS_TO);
  41.116 +								article.removeHeader(Headers.X_TRACE);
  41.117 +								article.removeHeader(Headers.XREF);
  41.118 +
  41.119 +								// POST the message to remote server
  41.120 +								ArticleWriter awriter = new ArticleWriter(sub.getHost(), sub.getPort());
  41.121 +								awriter.writeArticle(article);
  41.122 +								break;
  41.123 +							}
  41.124 +						}
  41.125 +					} catch (IOException ex) {
  41.126 +						Log.get().warning(ex.toString());
  41.127 +					}
  41.128 +				}
  41.129 +			} catch (StorageBackendException ex) {
  41.130 +				Log.get().severe(ex.toString());
  41.131 +			} catch (InterruptedException ex) {
  41.132 +				Log.get().warning("PushFeeder interrupted: " + ex);
  41.133 +			}
  41.134 +		}
  41.135 +	}
  41.136 +
  41.137 +	public void queueForPush(Article article)
  41.138 +	{
  41.139 +		this.articleQueue.add(article);
  41.140 +		synchronized (this) {
  41.141 +			this.notifyAll();
  41.142 +		}
  41.143 +	}
  41.144  }
    42.1 --- a/src/org/sonews/feed/Subscription.java	Sun Aug 29 17:43:58 2010 +0200
    42.2 +++ b/src/org/sonews/feed/Subscription.java	Sun Aug 29 18:17:37 2010 +0200
    42.3 @@ -24,61 +24,57 @@
    42.4   * @author Christian Lins
    42.5   * @since sonews/0.5.0
    42.6   */
    42.7 -public class Subscription 
    42.8 +public class Subscription
    42.9  {
   42.10  
   42.11 -  private String host;
   42.12 -  private int    port;
   42.13 -  private int    feedtype;
   42.14 -  private String group;
   42.15 -  
   42.16 -  public Subscription(String host, int port, int feedtype, String group)
   42.17 -  {
   42.18 -    this.host     = host;
   42.19 -    this.port     = port;
   42.20 -    this.feedtype = feedtype;
   42.21 -    this.group    = group;
   42.22 -  }
   42.23 +	private String host;
   42.24 +	private int port;
   42.25 +	private int feedtype;
   42.26 +	private String group;
   42.27  
   42.28 -  @Override
   42.29 -  public boolean equals(Object obj)
   42.30 -  {
   42.31 -    if(obj instanceof Subscription)
   42.32 -    {
   42.33 -      Subscription sub = (Subscription)obj;
   42.34 -      return sub.host.equals(host) && sub.group.equals(group) 
   42.35 -        && sub.port == port && sub.feedtype == feedtype;
   42.36 -    }
   42.37 -    else
   42.38 -    {
   42.39 -      return false;
   42.40 -    }
   42.41 -  }
   42.42 +	public Subscription(String host, int port, int feedtype, String group)
   42.43 +	{
   42.44 +		this.host = host;
   42.45 +		this.port = port;
   42.46 +		this.feedtype = feedtype;
   42.47 +		this.group = group;
   42.48 +	}
   42.49  
   42.50 -  @Override
   42.51 -  public int hashCode()
   42.52 -  {
   42.53 -    return host.hashCode() + port + feedtype + group.hashCode();
   42.54 -  }
   42.55 +	@Override
   42.56 +	public boolean equals(Object obj)
   42.57 +	{
   42.58 +		if (obj instanceof Subscription) {
   42.59 +			Subscription sub = (Subscription) obj;
   42.60 +			return sub.host.equals(host) && sub.group.equals(group)
   42.61 +				&& sub.port == port && sub.feedtype == feedtype;
   42.62 +		} else {
   42.63 +			return false;
   42.64 +		}
   42.65 +	}
   42.66  
   42.67 -  public int getFeedtype()
   42.68 -  {
   42.69 -    return feedtype;
   42.70 -  }
   42.71 +	@Override
   42.72 +	public int hashCode()
   42.73 +	{
   42.74 +		return host.hashCode() + port + feedtype + group.hashCode();
   42.75 +	}
   42.76  
   42.77 -  public String getGroup()
   42.78 -  {
   42.79 -    return group;
   42.80 -  }
   42.81 +	public int getFeedtype()
   42.82 +	{
   42.83 +		return feedtype;
   42.84 +	}
   42.85  
   42.86 -  public String getHost()
   42.87 -  {
   42.88 -    return host;
   42.89 -  }
   42.90 +	public String getGroup()
   42.91 +	{
   42.92 +		return group;
   42.93 +	}
   42.94  
   42.95 -  public int getPort()
   42.96 -  {
   42.97 -    return port;
   42.98 -  }
   42.99 -  
  42.100 +	public String getHost()
  42.101 +	{
  42.102 +		return host;
  42.103 +	}
  42.104 +
  42.105 +	public int getPort()
  42.106 +	{
  42.107 +		return port;
  42.108 +	}
  42.109  }
    43.1 --- a/src/org/sonews/mlgw/Dispatcher.java	Sun Aug 29 17:43:58 2010 +0200
    43.2 +++ b/src/org/sonews/mlgw/Dispatcher.java	Sun Aug 29 18:17:37 2010 +0200
    43.3 @@ -43,267 +43,233 @@
    43.4   * @author Christian Lins
    43.5   * @since sonews/0.5.0
    43.6   */
    43.7 -public class Dispatcher 
    43.8 +public class Dispatcher
    43.9  {
   43.10  
   43.11 -  static class PasswordAuthenticator extends Authenticator
   43.12 -  {
   43.13 -    
   43.14 -    @Override
   43.15 -    public PasswordAuthentication getPasswordAuthentication()
   43.16 -    {
   43.17 -      final String username = 
   43.18 -        Config.inst().get(Config.MLSEND_USER, "user");
   43.19 -      final String password = 
   43.20 -        Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
   43.21 +	static class PasswordAuthenticator extends Authenticator
   43.22 +	{
   43.23  
   43.24 -      return new PasswordAuthentication(username, password);
   43.25 -    }
   43.26 -    
   43.27 -  }
   43.28 +		@Override
   43.29 +		public PasswordAuthentication getPasswordAuthentication()
   43.30 +		{
   43.31 +			final String username =
   43.32 +				Config.inst().get(Config.MLSEND_USER, "user");
   43.33 +			final String password =
   43.34 +				Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
   43.35  
   43.36 -  /**
   43.37 -   * Chunks out the email address of the full List-Post header field.
   43.38 -   * @param listPostValue
   43.39 -   * @return The matching email address or null
   43.40 -   */
   43.41 -  private static String chunkListPost(String listPostValue)
   43.42 -  {
   43.43 -    // listPostValue is of form "<mailto:dev@openoffice.org>"
   43.44 -    Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
   43.45 -    Matcher mailMatcher = mailPattern.matcher(listPostValue);
   43.46 -    if(mailMatcher.find())
   43.47 -    {
   43.48 -      return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
   43.49 -    }
   43.50 -    else
   43.51 -    {
   43.52 -      return null;
   43.53 -    }
   43.54 -  }
   43.55 +			return new PasswordAuthentication(username, password);
   43.56 +		}
   43.57 +	}
   43.58  
   43.59 -  /**
   43.60 -   * This method inspects the header of the given message, trying
   43.61 -   * to find the most appropriate recipient.
   43.62 -   * @param msg
   43.63 -   * @param fallback If this is false only List-Post and X-List-Post headers
   43.64 -   *                 are examined.
   43.65 -   * @return null or fitting group name for the given message.
   43.66 -   */
   43.67 -  private static List<String> getGroupFor(final Message msg, final boolean fallback)
   43.68 -    throws MessagingException, StorageBackendException
   43.69 -  {
   43.70 -    List<String> groups = null;
   43.71 +	/**
   43.72 +	 * Chunks out the email address of the full List-Post header field.
   43.73 +	 * @param listPostValue
   43.74 +	 * @return The matching email address or null
   43.75 +	 */
   43.76 +	private static String chunkListPost(String listPostValue)
   43.77 +	{
   43.78 +		// listPostValue is of form "<mailto:dev@openoffice.org>"
   43.79 +		Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
   43.80 +		Matcher mailMatcher = mailPattern.matcher(listPostValue);
   43.81 +		if (mailMatcher.find()) {
   43.82 +			return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
   43.83 +		} else {
   43.84 +			return null;
   43.85 +		}
   43.86 +	}
   43.87  
   43.88 -    // Is there a List-Post header?
   43.89 -    String[]        listPost = msg.getHeader(Headers.LIST_POST);
   43.90 -    InternetAddress listPostAddr;
   43.91 +	/**
   43.92 +	 * This method inspects the header of the given message, trying
   43.93 +	 * to find the most appropriate recipient.
   43.94 +	 * @param msg
   43.95 +	 * @param fallback If this is false only List-Post and X-List-Post headers
   43.96 +	 *                 are examined.
   43.97 +	 * @return null or fitting group name for the given message.
   43.98 +	 */
   43.99 +	private static List<String> getGroupFor(final Message msg, final boolean fallback)
  43.100 +		throws MessagingException, StorageBackendException
  43.101 +	{
  43.102 +		List<String> groups = null;
  43.103  
  43.104 -    if(listPost == null || listPost.length == 0 || "".equals(listPost[0]))
  43.105 -    {
  43.106 -      // Is there a X-List-Post header?
  43.107 -      listPost = msg.getHeader(Headers.X_LIST_POST);
  43.108 -    }
  43.109 +		// Is there a List-Post header?
  43.110 +		String[] listPost = msg.getHeader(Headers.LIST_POST);
  43.111 +		InternetAddress listPostAddr;
  43.112  
  43.113 -    if(listPost != null && listPost.length > 0 
  43.114 -      && !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null)
  43.115 -    {
  43.116 -      // listPost[0] is of form "<mailto:dev@openoffice.org>"
  43.117 -      listPost[0]  = chunkListPost(listPost[0]);
  43.118 -      listPostAddr = new InternetAddress(listPost[0], false);
  43.119 -      groups = StorageManager.current().getGroupsForList(listPostAddr.getAddress());
  43.120 -    }
  43.121 -    else if(fallback)
  43.122 -    {
  43.123 -      Log.get().info("Using fallback recipient discovery for: " + msg.getSubject());
  43.124 -      groups = new ArrayList<String>();
  43.125 -      // Fallback to TO/CC/BCC addresses
  43.126 -      Address[] to = msg.getAllRecipients();
  43.127 -      for(Address toa : to) // Address can have '<' '>' around
  43.128 -      {
  43.129 -        if(toa instanceof InternetAddress)
  43.130 -        {
  43.131 -          List<String> g = StorageManager.current()
  43.132 -            .getGroupsForList(((InternetAddress)toa).getAddress());
  43.133 -          groups.addAll(g);
  43.134 -        }
  43.135 -      }
  43.136 -    }
  43.137 -    
  43.138 -    return groups;
  43.139 -  }
  43.140 -  
  43.141 -  /**
  43.142 -   * Posts a message that was received from a mailing list to the 
  43.143 -   * appropriate newsgroup.
  43.144 -   * If the message already exists in the storage, this message checks
  43.145 -   * if it must be posted in an additional group. This can happen for
  43.146 -   * crosspostings in different mailing lists.
  43.147 -   * @param msg
  43.148 -   */
  43.149 -  public static boolean toGroup(final Message msg)
  43.150 -  {
  43.151 -    if(msg == null)
  43.152 +		if (listPost == null || listPost.length == 0 || "".equals(listPost[0])) {
  43.153 +			// Is there a X-List-Post header?
  43.154 +			listPost = msg.getHeader(Headers.X_LIST_POST);
  43.155 +		}
  43.156 +
  43.157 +		if (listPost != null && listPost.length > 0
  43.158 +			&& !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null) {
  43.159 +			// listPost[0] is of form "<mailto:dev@openoffice.org>"
  43.160 +			listPost[0] = chunkListPost(listPost[0]);
  43.161 +			listPostAddr = new InternetAddress(listPost[0], false);
  43.162 +			groups = StorageManager.current().getGroupsForList(listPostAddr.getAddress());
  43.163 +		} else if (fallback) {
  43.164 +			Log.get().info("Using fallback recipient discovery for: " + msg.getSubject());
  43.165 +			groups = new ArrayList<String>();
  43.166 +			// Fallback to TO/CC/BCC addresses
  43.167 +			Address[] to = msg.getAllRecipients();
  43.168 +			for (Address toa : to) // Address can have '<' '>' around
  43.169 +			{
  43.170 +				if (toa instanceof InternetAddress) {
  43.171 +					List<String> g = StorageManager.current().getGroupsForList(((InternetAddress) toa).getAddress());
  43.172 +					groups.addAll(g);
  43.173 +				}
  43.174 +			}
  43.175 +		}
  43.176 +
  43.177 +		return groups;
  43.178 +	}
  43.179 +
  43.180 +	/**
  43.181 +	 * Posts a message that was received from a mailing list to the
  43.182 +	 * appropriate newsgroup.
  43.183 +	 * If the message already exists in the storage, this message checks
  43.184 +	 * if it must be posted in an additional group. This can happen for
  43.185 +	 * crosspostings in different mailing lists.
  43.186 +	 * @param msg
  43.187 +	 */
  43.188 +	public static boolean toGroup(final Message msg)
  43.189  	{
  43.190 -      throw new IllegalArgumentException("Argument 'msg' must not be null!");
  43.191 -    }
  43.192 +		if (msg == null) {
  43.193 +			throw new IllegalArgumentException("Argument 'msg' must not be null!");
  43.194 +		}
  43.195  
  43.196 -    try
  43.197 -    {
  43.198 -      // Create new Article object
  43.199 -      Article article = new Article(msg);
  43.200 -      boolean posted  = false;
  43.201 +		try {
  43.202 +			// Create new Article object
  43.203 +			Article article = new Article(msg);
  43.204 +			boolean posted = false;
  43.205  
  43.206 -      // Check if this mail is already existing the storage
  43.207 -      boolean updateReq = 
  43.208 -        StorageManager.current().isArticleExisting(article.getMessageID());
  43.209 +			// Check if this mail is already existing the storage
  43.210 +			boolean updateReq =
  43.211 +				StorageManager.current().isArticleExisting(article.getMessageID());
  43.212  
  43.213 -      List<String> newsgroups = getGroupFor(msg, !updateReq);
  43.214 -      List<String> oldgroups  = new ArrayList<String>();
  43.215 -      if(updateReq)
  43.216 -      {
  43.217 -        // Check for duplicate entries of the same group
  43.218 -        Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
  43.219 -        if(oldArticle != null)
  43.220 -		{
  43.221 -          List<Group> oldGroups = oldArticle.getGroups();
  43.222 -          for(Group oldGroup : oldGroups)
  43.223 -          {
  43.224 -            if(!newsgroups.contains(oldGroup.getName()))
  43.225 -            {
  43.226 -              oldgroups.add(oldGroup.getName());
  43.227 -            }
  43.228 -          }
  43.229 +			List<String> newsgroups = getGroupFor(msg, !updateReq);
  43.230 +			List<String> oldgroups = new ArrayList<String>();
  43.231 +			if (updateReq) {
  43.232 +				// Check for duplicate entries of the same group
  43.233 +				Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
  43.234 +				if (oldArticle != null) {
  43.235 +					List<Group> oldGroups = oldArticle.getGroups();
  43.236 +					for (Group oldGroup : oldGroups) {
  43.237 +						if (!newsgroups.contains(oldGroup.getName())) {
  43.238 +							oldgroups.add(oldGroup.getName());
  43.239 +						}
  43.240 +					}
  43.241 +				}
  43.242 +			}
  43.243 +
  43.244 +			if (newsgroups.size() > 0) {
  43.245 +				newsgroups.addAll(oldgroups);
  43.246 +				StringBuilder groups = new StringBuilder();
  43.247 +				for (int n = 0; n < newsgroups.size(); n++) {
  43.248 +					groups.append(newsgroups.get(n));
  43.249 +					if (n + 1 != newsgroups.size()) {
  43.250 +						groups.append(',');
  43.251 +					}
  43.252 +				}
  43.253 +				Log.get().info("Posting to group " + groups.toString());
  43.254 +
  43.255 +				article.setGroup(groups.toString());
  43.256 +				//article.removeHeader(Headers.REPLY_TO);
  43.257 +				//article.removeHeader(Headers.TO);
  43.258 +
  43.259 +				// Write article to database
  43.260 +				if (updateReq) {
  43.261 +					Log.get().info("Updating " + article.getMessageID()
  43.262 +						+ " with additional groups");
  43.263 +					StorageManager.current().delete(article.getMessageID());
  43.264 +					StorageManager.current().addArticle(article);
  43.265 +				} else {
  43.266 +					Log.get().info("Gatewaying " + article.getMessageID() + " to "
  43.267 +						+ article.getHeader(Headers.NEWSGROUPS)[0]);
  43.268 +					StorageManager.current().addArticle(article);
  43.269 +					Stats.getInstance().mailGatewayed(
  43.270 +						article.getHeader(Headers.NEWSGROUPS)[0]);
  43.271 +				}
  43.272 +				posted = true;
  43.273 +			} else {
  43.274 +				StringBuilder buf = new StringBuilder();
  43.275 +				for (Address toa : msg.getAllRecipients()) {
  43.276 +					buf.append(' ');
  43.277 +					buf.append(toa.toString());
  43.278 +				}
  43.279 +				buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
  43.280 +				Log.get().warning("No group for" + buf.toString());
  43.281 +			}
  43.282 +			return posted;
  43.283 +		} catch (Exception ex) {
  43.284 +			ex.printStackTrace();
  43.285 +			return false;
  43.286  		}
  43.287 -      }
  43.288 +	}
  43.289  
  43.290 -      if(newsgroups.size() > 0)
  43.291 -      {
  43.292 -        newsgroups.addAll(oldgroups);
  43.293 -        StringBuilder groups = new StringBuilder();
  43.294 -        for(int n = 0; n < newsgroups.size(); n++)
  43.295 -        {
  43.296 -          groups.append(newsgroups.get(n));
  43.297 -          if (n + 1 != newsgroups.size())
  43.298 -          {
  43.299 -            groups.append(',');
  43.300 -          }
  43.301 -        }
  43.302 -        Log.get().info("Posting to group " + groups.toString());
  43.303 +	/**
  43.304 +	 * Mails a message received through NNTP to the appropriate mailing list.
  43.305 +	 * This method MAY be called several times by PostCommand for the same
  43.306 +	 * article.
  43.307 +	 */
  43.308 +	public static void toList(Article article, String group)
  43.309 +		throws IOException, MessagingException, StorageBackendException
  43.310 +	{
  43.311 +		// Get mailing lists for the group of this article
  43.312 +		List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
  43.313  
  43.314 -        article.setGroup(groups.toString());
  43.315 -        //article.removeHeader(Headers.REPLY_TO);
  43.316 -        //article.removeHeader(Headers.TO);
  43.317 +		if (rcptAddresses == null || rcptAddresses.size() == 0) {
  43.318 +			Log.get().warning("No ML-address for " + group + " found.");
  43.319 +			return;
  43.320 +		}
  43.321  
  43.322 -        // Write article to database
  43.323 -        if(updateReq)
  43.324 -        {
  43.325 -          Log.get().info("Updating " + article.getMessageID()
  43.326 -            + " with additional groups");
  43.327 -          StorageManager.current().delete(article.getMessageID());
  43.328 -          StorageManager.current().addArticle(article);
  43.329 -        }
  43.330 -        else
  43.331 -        {
  43.332 -          Log.get().info("Gatewaying " + article.getMessageID() + " to "
  43.333 -            + article.getHeader(Headers.NEWSGROUPS)[0]);
  43.334 -          StorageManager.current().addArticle(article);
  43.335 -          Stats.getInstance().mailGatewayed(
  43.336 -            article.getHeader(Headers.NEWSGROUPS)[0]);
  43.337 -        }
  43.338 -        posted = true;
  43.339 -      }
  43.340 -      else
  43.341 -      {
  43.342 -        StringBuilder buf = new StringBuilder();
  43.343 -        for (Address toa : msg.getAllRecipients())
  43.344 -        {
  43.345 -          buf.append(' ');
  43.346 -          buf.append(toa.toString());
  43.347 -        }
  43.348 -        buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
  43.349 -        Log.get().warning("No group for" + buf.toString());
  43.350 -      }
  43.351 -      return posted;
  43.352 -    }
  43.353 -    catch(Exception ex)
  43.354 -    {
  43.355 -      ex.printStackTrace();
  43.356 -      return false;
  43.357 -    }
  43.358 -  }
  43.359 -  
  43.360 -  /**
  43.361 -   * Mails a message received through NNTP to the appropriate mailing list.
  43.362 -   * This method MAY be called several times by PostCommand for the same
  43.363 -   * article.
  43.364 -   */
  43.365 -  public static void toList(Article article, String group)
  43.366 -    throws IOException, MessagingException, StorageBackendException
  43.367 -  {
  43.368 -    // Get mailing lists for the group of this article
  43.369 -    List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
  43.370 +		for (String rcptAddress : rcptAddresses) {
  43.371 +			// Compose message and send it via given SMTP-Host
  43.372 +			String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
  43.373 +			int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
  43.374 +			String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
  43.375 +			String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
  43.376 +			String smtpFrom = Config.inst().get(
  43.377 +				Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
  43.378  
  43.379 -    if(rcptAddresses == null || rcptAddresses.size() == 0)
  43.380 -    {
  43.381 -      Log.get().warning("No ML-address for " + group + " found.");
  43.382 -      return;
  43.383 -    }
  43.384 +			// TODO: Make Article cloneable()
  43.385 +			article.getMessageID(); // Make sure an ID is existing
  43.386 +			article.removeHeader(Headers.NEWSGROUPS);
  43.387 +			article.removeHeader(Headers.PATH);
  43.388 +			article.removeHeader(Headers.LINES);
  43.389 +			article.removeHeader(Headers.BYTES);
  43.390  
  43.391 -    for(String rcptAddress : rcptAddresses)
  43.392 -    {
  43.393 -      // Compose message and send it via given SMTP-Host
  43.394 -      String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
  43.395 -      int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
  43.396 -      String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
  43.397 -      String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
  43.398 -      String smtpFrom = Config.inst().get(
  43.399 -        Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
  43.400 +			article.setHeader("To", rcptAddress);
  43.401 +			//article.setHeader("Reply-To", listAddress);
  43.402  
  43.403 -      // TODO: Make Article cloneable()
  43.404 -      article.getMessageID(); // Make sure an ID is existing
  43.405 -      article.removeHeader(Headers.NEWSGROUPS);
  43.406 -      article.removeHeader(Headers.PATH);
  43.407 -      article.removeHeader(Headers.LINES);
  43.408 -      article.removeHeader(Headers.BYTES);
  43.409 +			if (Config.inst().get(Config.MLSEND_RW_SENDER, false)) {
  43.410 +				rewriteSenderAddress(article); // Set the SENDER address
  43.411 +			}
  43.412  
  43.413 -      article.setHeader("To", rcptAddress);
  43.414 -      //article.setHeader("Reply-To", listAddress);
  43.415 +			SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
  43.416 +			smtpTransport.send(article, smtpFrom, rcptAddress);
  43.417 +			smtpTransport.close();
  43.418  
  43.419 -      if (Config.inst().get(Config.MLSEND_RW_SENDER, false))
  43.420 -      {
  43.421 -        rewriteSenderAddress(article); // Set the SENDER address
  43.422 -      }
  43.423 +			Stats.getInstance().mailGatewayed(group);
  43.424 +			Log.get().info("MLGateway: Mail " + article.getHeader("Subject")[0]
  43.425 +				+ " was delivered to " + rcptAddress + ".");
  43.426 +		}
  43.427 +	}
  43.428  
  43.429 -      SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
  43.430 -      smtpTransport.send(article, smtpFrom, rcptAddress);
  43.431 -      smtpTransport.close();
  43.432 +	/**
  43.433 +	 * Sets the SENDER header of the given MimeMessage. This might be necessary
  43.434 +	 * for moderated groups that does not allow the "normal" FROM sender.
  43.435 +	 * @param msg
  43.436 +	 * @throws javax.mail.MessagingException
  43.437 +	 */
  43.438 +	private static void rewriteSenderAddress(Article msg)
  43.439 +		throws MessagingException
  43.440 +	{
  43.441 +		String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
  43.442  
  43.443 -      Stats.getInstance().mailGatewayed(group);
  43.444 -      Log.get().info("MLGateway: Mail " + article.getHeader("Subject")[0]
  43.445 -        + " was delivered to " + rcptAddress + ".");
  43.446 -    }
  43.447 -  }
  43.448 -  
  43.449 -  /**
  43.450 -   * Sets the SENDER header of the given MimeMessage. This might be necessary
  43.451 -   * for moderated groups that does not allow the "normal" FROM sender.
  43.452 -   * @param msg
  43.453 -   * @throws javax.mail.MessagingException
  43.454 -   */
  43.455 -  private static void rewriteSenderAddress(Article msg)
  43.456 -    throws MessagingException
  43.457 -  {
  43.458 -    String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
  43.459 -
  43.460 -    if(mlAddress != null)
  43.461 -    {
  43.462 -      msg.setHeader(Headers.SENDER, mlAddress);
  43.463 -    }
  43.464 -    else
  43.465 -    {
  43.466 -      throw new MessagingException("Cannot rewrite SENDER header!");
  43.467 -    }
  43.468 -  }
  43.469 -  
  43.470 +		if (mlAddress != null) {
  43.471 +			msg.setHeader(Headers.SENDER, mlAddress);
  43.472 +		} else {
  43.473 +			throw new MessagingException("Cannot rewrite SENDER header!");
  43.474 +		}
  43.475 +	}
  43.476  }
    44.1 --- a/src/org/sonews/mlgw/MailPoller.java	Sun Aug 29 17:43:58 2010 +0200
    44.2 +++ b/src/org/sonews/mlgw/MailPoller.java	Sun Aug 29 18:17:37 2010 +0200
    44.3 @@ -42,110 +42,94 @@
    44.4  public class MailPoller extends AbstractDaemon
    44.5  {
    44.6  
    44.7 -  static class PasswordAuthenticator extends Authenticator
    44.8 -  {
    44.9 -    
   44.10 -    @Override
   44.11 -    public PasswordAuthentication getPasswordAuthentication()
   44.12 -    {
   44.13 -      final String username = 
   44.14 -        Config.inst().get(Config.MLPOLL_USER, "user");
   44.15 -      final String password = 
   44.16 -        Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
   44.17 +	static class PasswordAuthenticator extends Authenticator
   44.18 +	{
   44.19  
   44.20 -      return new PasswordAuthentication(username, password);
   44.21 -    }
   44.22 -    
   44.23 -  }
   44.24 -  
   44.25 -  @Override
   44.26 -  public void run()
   44.27 -  {
   44.28 -    Log.get().info("Starting Mailinglist Poller...");
   44.29 -    int errors = 0;
   44.30 -    while(isRunning())
   44.31 -    {
   44.32 -      try
   44.33 -      {
   44.34 -        // Wait some time between runs. At the beginning has advantages,
   44.35 -        // because the wait is not skipped if an exception occurs.
   44.36 -        Thread.sleep(60000 * (errors + 1)); // one minute * errors
   44.37 -        
   44.38 -        final String host     = 
   44.39 -          Config.inst().get(Config.MLPOLL_HOST, "samplehost");
   44.40 -        final String username = 
   44.41 -          Config.inst().get(Config.MLPOLL_USER, "user");
   44.42 -        final String password = 
   44.43 -          Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
   44.44 -        
   44.45 -        Stats.getInstance().mlgwRunStart();
   44.46 -        
   44.47 -        // Create empty properties
   44.48 -        Properties props = System.getProperties();
   44.49 -        props.put("mail.pop3.host", host);
   44.50 -        props.put("mail.mime.address.strict", "false");
   44.51 +		@Override
   44.52 +		public PasswordAuthentication getPasswordAuthentication()
   44.53 +		{
   44.54 +			final String username =
   44.55 +				Config.inst().get(Config.MLPOLL_USER, "user");
   44.56 +			final String password =
   44.57 +				Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
   44.58  
   44.59 -        // Get session
   44.60 -        Session session = Session.getInstance(props);
   44.61 +			return new PasswordAuthentication(username, password);
   44.62 +		}
   44.63 +	}
   44.64  
   44.65 -        // Get the store
   44.66 -        Store store = session.getStore("pop3");
   44.67 -        store.connect(host, 110, username, password);
   44.68 +	@Override
   44.69 +	public void run()
   44.70 +	{
   44.71 +		Log.get().info("Starting Mailinglist Poller...");
   44.72 +		int errors = 0;
   44.73 +		while (isRunning()) {
   44.74 +			try {
   44.75 +				// Wait some time between runs. At the beginning has advantages,
   44.76 +				// because the wait is not skipped if an exception occurs.
   44.77 +				Thread.sleep(60000 * (errors + 1)); // one minute * errors
   44.78  
   44.79 -        // Get folder
   44.80 -        Folder folder = store.getFolder("INBOX");
   44.81 -        folder.open(Folder.READ_WRITE);
   44.82 +				final String host =
   44.83 +					Config.inst().get(Config.MLPOLL_HOST, "samplehost");
   44.84 +				final String username =
   44.85 +					Config.inst().get(Config.MLPOLL_USER, "user");
   44.86 +				final String password =
   44.87 +					Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
   44.88  
   44.89 -        // Get directory
   44.90 -        Message[] messages = folder.getMessages();
   44.91 +				Stats.getInstance().mlgwRunStart();
   44.92  
   44.93 -        // Dispatch messages and delete it afterwards on the inbox
   44.94 -        for(Message message : messages)
   44.95 -        {
   44.96 -          if(Dispatcher.toGroup(message)
   44.97 -            || Config.inst().get(Config.MLPOLL_DELETEUNKNOWN, false))
   44.98 -          {
   44.99 -            // Delete the message
  44.100 -            message.setFlag(Flag.DELETED, true);
  44.101 -          }
  44.102 -        }
  44.103 +				// Create empty properties
  44.104 +				Properties props = System.getProperties();
  44.105 +				props.put("mail.pop3.host", host);
  44.106 +				props.put("mail.mime.address.strict", "false");
  44.107  
  44.108 -        // Close connection 
  44.109 -        folder.close(true); // true to expunge deleted messages
  44.110 -        store.close();
  44.111 -        errors = 0;
  44.112 -        
  44.113 -        Stats.getInstance().mlgwRunEnd();
  44.114 -      }
  44.115 -      catch(NoSuchProviderException ex)
  44.116 -      {
  44.117 -        Log.get().severe(ex.toString());
  44.118 -        shutdown();
  44.119 -      }
  44.120 -      catch(AuthenticationFailedException ex)
  44.121 -      {
  44.122 -        // AuthentificationFailedException may be thrown if credentials are
  44.123 -        // bad or if the Mailbox is in use (locked).
  44.124 -        ex.printStackTrace();
  44.125 -        errors = errors < 5 ? errors + 1 : errors;
  44.126 -      }
  44.127 -      catch(InterruptedException ex)
  44.128 -      {
  44.129 -        System.out.println("sonews: " + this + " returns: " + ex);
  44.130 -        return;
  44.131 -      }
  44.132 -      catch(MessagingException ex)
  44.133 -      {
  44.134 -        ex.printStackTrace();
  44.135 -        errors = errors < 5 ? errors + 1 : errors;
  44.136 -      }
  44.137 -      catch(Exception ex)
  44.138 -      {
  44.139 -        ex.printStackTrace();
  44.140 -        errors = errors < 5 ? errors + 1 : errors;
  44.141 -      }
  44.142 -    }
  44.143 -    Log.get().severe("MailPoller exited.");
  44.144 -  }
  44.145 -  
  44.146 +				// Get session
  44.147 +				Session session = Session.getInstance(props);
  44.148 +
  44.149 +				// Get the store
  44.150 +				Store store = session.getStore("pop3");
  44.151 +				store.connect(host, 110, username, password);
  44.152 +
  44.153 +				// Get folder
  44.154 +				Folder folder = store.getFolder("INBOX");
  44.155 +				folder.open(Folder.READ_WRITE);
  44.156 +
  44.157 +				// Get directory
  44.158 +				Message[] messages = folder.getMessages();
  44.159 +
  44.160 +				// Dispatch messages and delete it afterwards on the inbox
  44.161 +				for (Message message : messages) {
  44.162 +					if (Dispatcher.toGroup(message)
  44.163 +						|| Config.inst().get(Config.MLPOLL_DELETEUNKNOWN, false)) {
  44.164 +						// Delete the message
  44.165 +						message.setFlag(Flag.DELETED, true);
  44.166 +					}
  44.167 +				}
  44.168 +
  44.169 +				// Close connection
  44.170 +				folder.close(true); // true to expunge deleted messages
  44.171 +				store.close();
  44.172 +				errors = 0;
  44.173 +
  44.174 +				Stats.getInstance().mlgwRunEnd();
  44.175 +			} catch (NoSuchProviderException ex) {
  44.176 +				Log.get().severe(ex.toString());
  44.177 +				shutdown();
  44.178 +			} catch (AuthenticationFailedException ex) {
  44.179 +				// AuthentificationFailedException may be thrown if credentials are
  44.180 +				// bad or if the Mailbox is in use (locked).
  44.181 +				ex.printStackTrace();
  44.182 +				errors = errors < 5 ? errors + 1 : errors;
  44.183 +			} catch (InterruptedException ex) {
  44.184 +				System.out.println("sonews: " + this + " returns: " + ex);
  44.185 +				return;
  44.186 +			} catch (MessagingException ex) {
  44.187 +				ex.printStackTrace();
  44.188 +				errors = errors < 5 ? errors + 1 : errors;
  44.189 +			} catch (Exception ex) {
  44.190 +				ex.printStackTrace();
  44.191 +				errors = errors < 5 ? errors + 1 : errors;
  44.192 +			}
  44.193 +		}
  44.194 +		Log.get().severe("MailPoller exited.");
  44.195 +	}
  44.196  }
    45.1 --- a/src/org/sonews/mlgw/SMTPTransport.java	Sun Aug 29 17:43:58 2010 +0200
    45.2 +++ b/src/org/sonews/mlgw/SMTPTransport.java	Sun Aug 29 18:17:37 2010 +0200
    45.3 @@ -36,98 +36,90 @@
    45.4  class SMTPTransport
    45.5  {
    45.6  
    45.7 -  protected BufferedReader       in;
    45.8 -  protected BufferedOutputStream out;
    45.9 -  protected Socket               socket;
   45.10 +	protected BufferedReader in;
   45.11 +	protected BufferedOutputStream out;
   45.12 +	protected Socket socket;
   45.13  
   45.14 -  public SMTPTransport(String host, int port)
   45.15 -    throws IOException, UnknownHostException
   45.16 -  {
   45.17 -    socket = new Socket(host, port);
   45.18 -    this.in  = new BufferedReader(new InputStreamReader(socket.getInputStream()));
   45.19 -    this.out = new BufferedOutputStream(socket.getOutputStream());
   45.20 +	public SMTPTransport(String host, int port)
   45.21 +		throws IOException, UnknownHostException
   45.22 +	{
   45.23 +		socket = new Socket(host, port);
   45.24 +		this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
   45.25 +		this.out = new BufferedOutputStream(socket.getOutputStream());
   45.26  
   45.27 -    // Read helo from server
   45.28 -    String line = this.in.readLine();
   45.29 -    if(line == null || !line.startsWith("220 "))
   45.30 -    {
   45.31 -      throw new IOException("Invalid helo from server: " + line);
   45.32 -    }
   45.33 +		// Read helo from server
   45.34 +		String line = this.in.readLine();
   45.35 +		if (line == null || !line.startsWith("220 ")) {
   45.36 +			throw new IOException("Invalid helo from server: " + line);
   45.37 +		}
   45.38  
   45.39 -    // Send HELO to server
   45.40 -    this.out.write(
   45.41 -      ("HELO " + Config.inst().get(Config.HOSTNAME, "localhost") + "\r\n").getBytes("UTF-8"));
   45.42 -    this.out.flush();
   45.43 -    line = this.in.readLine();
   45.44 -    if(line == null || !line.startsWith("250 "))
   45.45 -    {
   45.46 -      throw new IOException("Unexpected reply: " + line);
   45.47 -    }
   45.48 -  }
   45.49 +		// Send HELO to server
   45.50 +		this.out.write(
   45.51 +			("HELO " + Config.inst().get(Config.HOSTNAME, "localhost") + "\r\n").getBytes("UTF-8"));
   45.52 +		this.out.flush();
   45.53 +		line = this.in.readLine();
   45.54 +		if (line == null || !line.startsWith("250 ")) {
   45.55 +			throw new IOException("Unexpected reply: " + line);
   45.56 +		}
   45.57 +	}
   45.58  
   45.59 -  public SMTPTransport(String host)
   45.60 -    throws IOException
   45.61 -  {
   45.62 -    this(host, 25);
   45.63 -  }
   45.64 +	public SMTPTransport(String host)
   45.65 +		throws IOException
   45.66 +	{
   45.67 +		this(host, 25);
   45.68 +	}
   45.69  
   45.70 -  public void close()
   45.71 -    throws IOException
   45.72 -  {
   45.73 -    this.out.write("QUIT".getBytes("UTF-8"));
   45.74 -    this.out.flush();
   45.75 -    this.in.readLine();
   45.76 +	public void close()
   45.77 +		throws IOException
   45.78 +	{
   45.79 +		this.out.write("QUIT".getBytes("UTF-8"));
   45.80 +		this.out.flush();
   45.81 +		this.in.readLine();
   45.82  
   45.83 -    this.socket.close();
   45.84 -  }
   45.85 +		this.socket.close();
   45.86 +	}
   45.87  
   45.88 -  public void send(Article article, String mailFrom, String rcptTo)
   45.89 -    throws IOException
   45.90 -  {
   45.91 -    assert(article != null);
   45.92 -    assert(mailFrom != null);
   45.93 -    assert(rcptTo != null);
   45.94 +	public void send(Article article, String mailFrom, String rcptTo)
   45.95 +		throws IOException
   45.96 +	{
   45.97 +		assert (article != null);
   45.98 +		assert (mailFrom != null);
   45.99 +		assert (rcptTo != null);
  45.100  
  45.101 -    this.out.write(("MAIL FROM: " + mailFrom).getBytes("UTF-8"));
  45.102 -    this.out.flush();
  45.103 -    String line = this.in.readLine();
  45.104 -    if(line == null || !line.startsWith("250 "))
  45.105 -    {
  45.106 -      throw new IOException("Unexpected reply: " + line);
  45.107 -    }
  45.108 +		this.out.write(("MAIL FROM: " + mailFrom).getBytes("UTF-8"));
  45.109 +		this.out.flush();
  45.110 +		String line = this.in.readLine();
  45.111 +		if (line == null || !line.startsWith("250 ")) {
  45.112 +			throw new IOException("Unexpected reply: " + line);
  45.113 +		}
  45.114  
  45.115 -    this.out.write(("RCPT TO: " + rcptTo).getBytes("UTF-8"));
  45.116 -    this.out.flush();
  45.117 -    line  = this.in.readLine();
  45.118 -    if(line == null || !line.startsWith("250 "))
  45.119 -    {
  45.120 -      throw new IOException("Unexpected reply: " + line);
  45.121 -    }
  45.122 +		this.out.write(("RCPT TO: " + rcptTo).getBytes("UTF-8"));
  45.123 +		this.out.flush();
  45.124 +		line = this.in.readLine();
  45.125 +		if (line == null || !line.startsWith("250 ")) {
  45.126 +			throw new IOException("Unexpected reply: " + line);
  45.127 +		}
  45.128  
  45.129 -    this.out.write("DATA".getBytes("UTF-8"));
  45.130 -    this.out.flush();
  45.131 -    line = this.in.readLine();
  45.132 -    if(line == null || !line.startsWith("354 "))
  45.133 -    {
  45.134 -      throw new IOException("Unexpected reply: " + line);
  45.135 -    }
  45.136 +		this.out.write("DATA".getBytes("UTF-8"));
  45.137 +		this.out.flush();
  45.138 +		line = this.in.readLine();
  45.139 +		if (line == null || !line.startsWith("354 ")) {
  45.140 +			throw new IOException("Unexpected reply: " + line);
  45.141 +		}
  45.142  
  45.143 -    ArticleInputStream   artStream = new ArticleInputStream(article);
  45.144 -    for(int b = artStream.read(); b >= 0; b = artStream.read())
  45.145 -    {
  45.146 -      this.out.write(b);
  45.147 -    }
  45.148 +		ArticleInputStream artStream = new ArticleInputStream(article);
  45.149 +		for (int b = artStream.read(); b >= 0; b = artStream.read()) {
  45.150 +			this.out.write(b);
  45.151 +		}
  45.152  
  45.153 -    // Flush the binary stream; important because otherwise the output
  45.154 -    // will be mixed with the PrintWriter.
  45.155 -    this.out.flush();
  45.156 -    this.out.write("\r\n.\r\n".getBytes("UTF-8"));
  45.157 -    this.out.flush();
  45.158 -    line = this.in.readLine();
  45.159 -    if(line == null || !line.startsWith("250 "))
  45.160 -    {
  45.161 -      throw new IOException("Unexpected reply: " + line);
  45.162 -    }
  45.163 -  }
  45.164 -
  45.165 +		// Flush the binary stream; important because otherwise the output
  45.166 +		// will be mixed with the PrintWriter.
  45.167 +		this.out.flush();
  45.168 +		this.out.write("\r\n.\r\n".getBytes("UTF-8"));
  45.169 +		this.out.flush();
  45.170 +		line = this.in.readLine();
  45.171 +		if (line == null || !line.startsWith("250 ")) {
  45.172 +			throw new IOException("Unexpected reply: " + line);
  45.173 +		}
  45.174 +	}
  45.175  }
    46.1 --- a/src/org/sonews/plugin/Plugin.java	Sun Aug 29 17:43:58 2010 +0200
    46.2 +++ b/src/org/sonews/plugin/Plugin.java	Sun Aug 29 18:17:37 2010 +0200
    46.3 @@ -28,15 +28,14 @@
    46.4  public interface Plugin
    46.5  {
    46.6  
    46.7 -  /**
    46.8 -   * Called when the Plugin is loaded by sonews. This method can be used
    46.9 -   * by implementing classes to install additional or required plugins.
   46.10 -   */
   46.11 -  void load();
   46.12 +	/**
   46.13 +	 * Called when the Plugin is loaded by sonews. This method can be used
   46.14 +	 * by implementing classes to install additional or required plugins.
   46.15 +	 */
   46.16 +	void load();
   46.17  
   46.18 -  /**
   46.19 -   * Called when the Plugin is unloaded by sonews.
   46.20 -   */
   46.21 -  void unload();
   46.22 -
   46.23 +	/**
   46.24 +	 * Called when the Plugin is unloaded by sonews.
   46.25 +	 */
   46.26 +	void unload();
   46.27  }
    47.1 --- a/src/org/sonews/storage/Article.java	Sun Aug 29 17:43:58 2010 +0200
    47.2 +++ b/src/org/sonews/storage/Article.java	Sun Aug 29 18:17:37 2010 +0200
    47.3 @@ -41,213 +41,196 @@
    47.4   */
    47.5  public class Article extends ArticleHead
    47.6  {
    47.7 -  
    47.8 -  /**
    47.9 -   * Loads the Article identified by the given ID from the JDBCDatabase.
   47.10 -   * @param messageID
   47.11 -   * @return null if Article is not found or if an error occurred.
   47.12 -   */
   47.13 -  public static Article getByMessageID(final String messageID)
   47.14 -  {
   47.15 -    try
   47.16 -    {
   47.17 -      return StorageManager.current().getArticle(messageID);
   47.18 -    }
   47.19 -    catch(StorageBackendException ex)
   47.20 -    {
   47.21 -      ex.printStackTrace();
   47.22 -      return null;
   47.23 -    }
   47.24 -  }
   47.25 -  
   47.26 -  private byte[] body       = new byte[0];
   47.27 -  
   47.28 -  /**
   47.29 -   * Default constructor.
   47.30 -   */
   47.31 -  public Article()
   47.32 -  {
   47.33 -  }
   47.34 -  
   47.35 -  /**
   47.36 -   * Creates a new Article object using the date from the given
   47.37 -   * raw data.
   47.38 -   */
   47.39 -  public Article(String headers, byte[] body)
   47.40 -  {
   47.41 -    try
   47.42 -    {
   47.43 -      this.body  = body;
   47.44  
   47.45 -      // Parse the header
   47.46 -      this.headers = new InternetHeaders(
   47.47 -        new ByteArrayInputStream(headers.getBytes()));
   47.48 -      
   47.49 -      this.headerSrc = headers;
   47.50 -    }
   47.51 -    catch(MessagingException ex)
   47.52 -    {
   47.53 -      ex.printStackTrace();
   47.54 -    }
   47.55 -  }
   47.56 +	/**
   47.57 +	 * Loads the Article identified by the given ID from the JDBCDatabase.
   47.58 +	 * @param messageID
   47.59 +	 * @return null if Article is not found or if an error occurred.
   47.60 +	 */
   47.61 +	public static Article getByMessageID(final String messageID)
   47.62 +	{
   47.63 +		try {
   47.64 +			return StorageManager.current().getArticle(messageID);
   47.65 +		} catch (StorageBackendException ex) {
   47.66 +			ex.printStackTrace();
   47.67 +			return null;
   47.68 +		}
   47.69 +	}
   47.70 +	private byte[] body = new byte[0];
   47.71  
   47.72 -  /**
   47.73 -   * Creates an Article instance using the data from the javax.mail.Message
   47.74 -   * object. This constructor is called by the Mailinglist gateway.
   47.75 -   * @see javax.mail.Message
   47.76 -   * @param msg
   47.77 -   * @throws IOException
   47.78 -   * @throws MessagingException
   47.79 -   */
   47.80 -  public Article(final Message msg)
   47.81 -    throws IOException, MessagingException
   47.82 -  {
   47.83 -    this.headers = new InternetHeaders();
   47.84 +	/**
   47.85 +	 * Default constructor.
   47.86 +	 */
   47.87 +	public Article()
   47.88 +	{
   47.89 +	}
   47.90  
   47.91 -    for(Enumeration e = msg.getAllHeaders() ; e.hasMoreElements();) 
   47.92 -    {
   47.93 -      final Header header = (Header)e.nextElement();
   47.94 -      this.headers.addHeader(header.getName(), header.getValue());
   47.95 -    }
   47.96 +	/**
   47.97 +	 * Creates a new Article object using the date from the given
   47.98 +	 * raw data.
   47.99 +	 */
  47.100 +	public Article(String headers, byte[] body)
  47.101 +	{
  47.102 +		try {
  47.103 +			this.body = body;
  47.104  
  47.105 -	// Reads the raw byte body using Message.writeTo(OutputStream out)
  47.106 -	this.body = readContent(msg);
  47.107 -    
  47.108 -    // Validate headers
  47.109 -    validateHeaders();
  47.110 -  }
  47.111 +			// Parse the header
  47.112 +			this.headers = new InternetHeaders(
  47.113 +				new ByteArrayInputStream(headers.getBytes()));
  47.114  
  47.115 -  /**
  47.116 -   * Reads from the given Message into a byte array.
  47.117 -   * @param in
  47.118 -   * @return
  47.119 -   * @throws IOException
  47.120 -   */
  47.121 -  private byte[] readContent(Message in)
  47.122 -    throws IOException, MessagingException
  47.123 -  {
  47.124 -    ByteArrayOutputStream out = new ByteArrayOutputStream();
  47.125 -    in.writeTo(out);
  47.126 -    return out.toByteArray();
  47.127 -  }
  47.128 +			this.headerSrc = headers;
  47.129 +		} catch (MessagingException ex) {
  47.130 +			ex.printStackTrace();
  47.131 +		}
  47.132 +	}
  47.133  
  47.134 -  /**
  47.135 -   * Removes the header identified by the given key.
  47.136 -   * @param headerKey
  47.137 -   */
  47.138 -  public void removeHeader(final String headerKey)
  47.139 -  {
  47.140 -    this.headers.removeHeader(headerKey);
  47.141 -    this.headerSrc = null;
  47.142 -  }
  47.143 +	/**
  47.144 +	 * Creates an Article instance using the data from the javax.mail.Message
  47.145 +	 * object. This constructor is called by the Mailinglist gateway.
  47.146 +	 * @see javax.mail.Message
  47.147 +	 * @param msg
  47.148 +	 * @throws IOException
  47.149 +	 * @throws MessagingException
  47.150 +	 */
  47.151 +	public Article(final Message msg)
  47.152 +		throws IOException, MessagingException
  47.153 +	{
  47.154 +		this.headers = new InternetHeaders();
  47.155  
  47.156 -  /**
  47.157 -   * Generates a message id for this article and sets it into
  47.158 -   * the header object. You have to update the JDBCDatabase manually to make this
  47.159 -   * change persistent.
  47.160 -   * Note: a Message-ID should never be changed and only generated once.
  47.161 -   */
  47.162 -  private String generateMessageID()
  47.163 -  {
  47.164 -    String randomString;
  47.165 -    MessageDigest md5;
  47.166 -    try
  47.167 -    {
  47.168 -      md5 = MessageDigest.getInstance("MD5");
  47.169 -      md5.reset();
  47.170 -      md5.update(getBody());
  47.171 -      md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
  47.172 -      md5.update(getHeader(Headers.FROM)[0].getBytes());
  47.173 -      byte[] result = md5.digest();
  47.174 -      StringBuffer hexString = new StringBuffer();
  47.175 -      for (int i = 0; i < result.length; i++)
  47.176 -      {
  47.177 -        hexString.append(Integer.toHexString(0xFF & result[i]));
  47.178 -      }
  47.179 -      randomString = hexString.toString();
  47.180 -    }
  47.181 -    catch (NoSuchAlgorithmException e)
  47.182 -    {
  47.183 -      e.printStackTrace();
  47.184 -      randomString = UUID.randomUUID().toString();
  47.185 -    }
  47.186 -    String msgID = "<" + randomString + "@"
  47.187 -        + Config.inst().get(Config.HOSTNAME, "localhost") + ">";
  47.188 -    
  47.189 -    this.headers.setHeader(Headers.MESSAGE_ID, msgID);
  47.190 -    
  47.191 -    return msgID;
  47.192 -  }
  47.193 +		for (Enumeration e = msg.getAllHeaders(); e.hasMoreElements();) {
  47.194 +			final Header header = (Header) e.nextElement();
  47.195 +			this.headers.addHeader(header.getName(), header.getValue());
  47.196 +		}
  47.197  
  47.198 -  /**
  47.199 -   * Returns the body string.
  47.200 -   */
  47.201 -  public byte[] getBody()
  47.202 -  {
  47.203 -    return body;
  47.204 -  }
  47.205 -  
  47.206 -  /**
  47.207 -   * @return Numerical IDs of the newsgroups this Article belongs to.
  47.208 -   */
  47.209 -  public List<Group> getGroups()
  47.210 -  {
  47.211 -    String[]         groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
  47.212 -    ArrayList<Group> groups     = new ArrayList<Group>();
  47.213 +		// Reads the raw byte body using Message.writeTo(OutputStream out)
  47.214 +		this.body = readContent(msg);
  47.215  
  47.216 -    try
  47.217 -    {
  47.218 -      for(String newsgroup : groupnames)
  47.219 -      {
  47.220 -        newsgroup = newsgroup.trim();
  47.221 -        Group group = StorageManager.current().getGroup(newsgroup);
  47.222 -        if(group != null &&         // If the server does not provide the group, ignore it
  47.223 -          !groups.contains(group))  // Yes, there may be duplicates
  47.224 -        {
  47.225 -          groups.add(group);
  47.226 -        }
  47.227 -      }
  47.228 -    }
  47.229 -    catch(StorageBackendException ex)
  47.230 -    {
  47.231 -      ex.printStackTrace();
  47.232 -      return null;
  47.233 -    }
  47.234 -    return groups;
  47.235 -  }
  47.236 +		// Validate headers
  47.237 +		validateHeaders();
  47.238 +	}
  47.239  
  47.240 -  public void setBody(byte[] body)
  47.241 -  {
  47.242 -    this.body = body;
  47.243 -  }
  47.244 -  
  47.245 -  /**
  47.246 -   * 
  47.247 -   * @param groupname Name(s) of newsgroups
  47.248 -   */
  47.249 -  public void setGroup(String groupname)
  47.250 -  {
  47.251 -    this.headers.setHeader(Headers.NEWSGROUPS, groupname);
  47.252 -  }
  47.253 +	/**
  47.254 +	 * Reads from the given Message into a byte array.
  47.255 +	 * @param in
  47.256 +	 * @return
  47.257 +	 * @throws IOException
  47.258 +	 */
  47.259 +	private byte[] readContent(Message in)
  47.260 +		throws IOException, MessagingException
  47.261 +	{
  47.262 +		ByteArrayOutputStream out = new ByteArrayOutputStream();
  47.263 +		in.writeTo(out);
  47.264 +		return out.toByteArray();
  47.265 +	}
  47.266  
  47.267 -  /**
  47.268 -   * Returns the Message-ID of this Article. If the appropriate header
  47.269 -   * is empty, a new Message-ID is created.
  47.270 -   * @return Message-ID of this Article.
  47.271 -   */
  47.272 -  public String getMessageID()
  47.273 -  {
  47.274 -    String[] msgID = getHeader(Headers.MESSAGE_ID);
  47.275 -    return msgID[0].equals("") ? generateMessageID() : msgID[0];
  47.276 -  }
  47.277 -  
  47.278 -  /**
  47.279 -   * @return String containing the Message-ID.
  47.280 -   */
  47.281 -  @Override
  47.282 -  public String toString()
  47.283 -  {
  47.284 -    return getMessageID();
  47.285 -  }
  47.286 +	/**
  47.287 +	 * Removes the header identified by the given key.
  47.288 +	 * @param headerKey
  47.289 +	 */
  47.290 +	public void removeHeader(final String headerKey)
  47.291 +	{
  47.292 +		this.headers.removeHeader(headerKey);
  47.293 +		this.headerSrc = null;
  47.294 +	}
  47.295  
  47.296 +	/**
  47.297 +	 * Generates a message id for this article and sets it into
  47.298 +	 * the header object. You have to update the JDBCDatabase manually to make this
  47.299 +	 * change persistent.
  47.300 +	 * Note: a Message-ID should never be changed and only generated once.
  47.301 +	 */
  47.302 +	private String generateMessageID()
  47.303 +	{
  47.304 +		String randomString;
  47.305 +		MessageDigest md5;
  47.306 +		try {
  47.307 +			md5 = MessageDigest.getInstance("MD5");
  47.308 +			md5.reset();
  47.309 +			md5.update(getBody());
  47.310 +			md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
  47.311 +			md5.update(getHeader(Headers.FROM)[0].getBytes());
  47.312 +			byte[] result = md5.digest();
  47.313 +			StringBuffer hexString = new StringBuffer();
  47.314 +			for (int i = 0; i < result.length; i++) {
  47.315 +				hexString.append(Integer.toHexString(0xFF & result[i]));
  47.316 +			}
  47.317 +			randomString = hexString.toString();
  47.318 +		} catch (NoSuchAlgorithmException e) {
  47.319 +			e.printStackTrace();
  47.320 +			randomString = UUID.randomUUID().toString();
  47.321 +		}
  47.322 +		String msgID = "<" + randomString + "@"
  47.323 +			+ Config.inst().get(Config.HOSTNAME, "localhost") + ">";
  47.324 +
  47.325 +		this.headers.setHeader(Headers.MESSAGE_ID, msgID);
  47.326 +
  47.327 +		return msgID;
  47.328 +	}
  47.329 +
  47.330 +	/**
  47.331 +	 * Returns the body string.
  47.332 +	 */
  47.333 +	public byte[] getBody()
  47.334 +	{
  47.335 +		return body;
  47.336 +	}
  47.337 +
  47.338 +	/**
  47.339 +	 * @return Numerical IDs of the newsgroups this Article belongs to.
  47.340 +	 */
  47.341 +	public List<Group> getGroups()
  47.342 +	{
  47.343 +		String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
  47.344 +		ArrayList<Group> groups = new ArrayList<Group>();
  47.345 +
  47.346 +		try {
  47.347 +			for (String newsgroup : groupnames) {
  47.348 +				newsgroup = newsgroup.trim();
  47.349 +				Group group = StorageManager.current().getGroup(newsgroup);
  47.350 +				if (group != null && // If the server does not provide the group, ignore it
  47.351 +					!groups.contains(group)) // Yes, there may be duplicates
  47.352 +				{
  47.353 +					groups.add(group);
  47.354 +				}
  47.355 +			}
  47.356 +		} catch (StorageBackendException ex) {
  47.357 +			ex.printStackTrace();
  47.358 +			return null;
  47.359 +		}
  47.360 +		return groups;
  47.361 +	}
  47.362 +
  47.363 +	public void setBody(byte[] body)
  47.364 +	{
  47.365 +		this.body = body;
  47.366 +	}
  47.367 +
  47.368 +	/**
  47.369 +	 *
  47.370 +	 * @param groupname Name(s) of newsgroups
  47.371 +	 */
  47.372 +	public void setGroup(String groupname)
  47.373 +	{
  47.374 +		this.headers.setHeader(Headers.NEWSGROUPS, groupname);
  47.375 +	}
  47.376 +
  47.377 +	/**
  47.378 +	 * Returns the Message-ID of this Article. If the appropriate header
  47.379 +	 * is empty, a new Message-ID is created.
  47.380 +	 * @return Message-ID of this Article.
  47.381 +	 */
  47.382 +	public String getMessageID()
  47.383 +	{
  47.384 +		String[] msgID = getHeader(Headers.MESSAGE_ID);
  47.385 +		return msgID[0].equals("") ? generateMessageID() : msgID[0];
  47.386 +	}
  47.387 +
  47.388 +	/**
  47.389 +	 * @return String containing the Message-ID.
  47.390 +	 */
  47.391 +	@Override
  47.392 +	public String toString()
  47.393 +	{
  47.394 +		return getMessageID();
  47.395 +	}
  47.396  }
    48.1 --- a/src/org/sonews/storage/ArticleHead.java	Sun Aug 29 17:43:58 2010 +0200
    48.2 +++ b/src/org/sonews/storage/ArticleHead.java	Sun Aug 29 18:17:37 2010 +0200
    48.3 @@ -31,131 +31,122 @@
    48.4   * @author Christian Lins
    48.5   * @since sonews/0.5.0
    48.6   */
    48.7 -public class ArticleHead 
    48.8 +public class ArticleHead
    48.9  {
   48.10  
   48.11 -  protected InternetHeaders headers   = null;
   48.12 -  protected String          headerSrc = null;
   48.13 -  
   48.14 -  protected ArticleHead()
   48.15 -  {
   48.16 -  }
   48.17 -  
   48.18 -  public ArticleHead(String headers)
   48.19 -  {
   48.20 -    try
   48.21 -    {
   48.22 -      // Parse the header
   48.23 -      this.headers = new InternetHeaders(
   48.24 -          new ByteArrayInputStream(headers.getBytes()));
   48.25 -    }
   48.26 -    catch(MessagingException ex)
   48.27 -    {
   48.28 -      ex.printStackTrace();
   48.29 -    }
   48.30 -  }
   48.31 -  
   48.32 -  /**
   48.33 -   * Returns the header field with given name.
   48.34 -   * @param name Name of the header field(s).
   48.35 -   * @param returnNull If set to true, this method will return null instead
   48.36 -   *                   of an empty array if there is no header field found.
   48.37 -   * @return Header values or empty string.
   48.38 -   */
   48.39 -  public String[] getHeader(String name, boolean returnNull)
   48.40 -  {
   48.41 -    String[] ret = this.headers.getHeader(name);
   48.42 -    if(ret == null && !returnNull)
   48.43 -    {
   48.44 -      ret = new String[]{""};
   48.45 -    }
   48.46 -    return ret;
   48.47 -  }
   48.48 +	protected InternetHeaders headers = null;
   48.49 +	protected String headerSrc = null;
   48.50  
   48.51 -  public String[] getHeader(String name)
   48.52 -  {
   48.53 -    return getHeader(name, false);
   48.54 -  }
   48.55 -  
   48.56 -  /**
   48.57 -   * Sets the header value identified through the header name.
   48.58 -   * @param name
   48.59 -   * @param value
   48.60 -   */
   48.61 -  public void setHeader(String name, String value)
   48.62 -  {
   48.63 -    this.headers.setHeader(name, value);
   48.64 -    this.headerSrc = null;
   48.65 -  }
   48.66 +	protected ArticleHead()
   48.67 +	{
   48.68 +	}
   48.69  
   48.70 -    public Enumeration getAllHeaders()
   48.71 -  {
   48.72 -    return this.headers.getAllHeaders();
   48.73 -  }
   48.74 +	public ArticleHead(String headers)
   48.75 +	{
   48.76 +		try {
   48.77 +			// Parse the header
   48.78 +			this.headers = new InternetHeaders(
   48.79 +				new ByteArrayInputStream(headers.getBytes()));
   48.80 +		} catch (MessagingException ex) {
   48.81 +			ex.printStackTrace();
   48.82 +		}
   48.83 +	}
   48.84  
   48.85 -  /**
   48.86 -   * @return Header source code of this Article.
   48.87 -   */
   48.88 -  public String getHeaderSource()
   48.89 -  {
   48.90 -    if(this.headerSrc != null)
   48.91 -    {
   48.92 -      return this.headerSrc;
   48.93 -    }
   48.94 +	/**
   48.95 +	 * Returns the header field with given name.
   48.96 +	 * @param name Name of the header field(s).
   48.97 +	 * @param returnNull If set to true, this method will return null instead
   48.98 +	 *                   of an empty array if there is no header field found.
   48.99 +	 * @return Header values or empty string.
  48.100 +	 */
  48.101 +	public String[] getHeader(String name, boolean returnNull)
  48.102 +	{
  48.103 +		String[] ret = this.headers.getHeader(name);
  48.104 +		if (ret == null && !returnNull) {
  48.105 +			ret = new String[] {""};
  48.106 +		}
  48.107 +		return ret;
  48.108 +	}
  48.109  
  48.110 -    StringBuffer buf = new StringBuffer();
  48.111 +	public String[] getHeader(String name)
  48.112 +	{
  48.113 +		return getHeader(name, false);
  48.114 +	}
  48.115  
  48.116 -    for(Enumeration en = this.headers.getAllHeaders(); en.hasMoreElements();)
  48.117 -    {
  48.118 -      Header entry = (Header)en.nextElement();
  48.119 +	/**
  48.120 +	 * Sets the header value identified through the header name.
  48.121 +	 * @param name
  48.122 +	 * @param value
  48.123 +	 */
  48.124 +	public void setHeader(String name, String value)
  48.125 +	{
  48.126 +		this.headers.setHeader(name, value);
  48.127 +		this.headerSrc = null;
  48.128 +	}
  48.129  
  48.130 -      String value = entry.getValue().replaceAll("[\r\n]", " ");
  48.131 -      buf.append(entry.getName());
  48.132 -      buf.append(": ");
  48.133 -      buf.append(MimeUtility.fold(entry.getName().length() + 2, value));
  48.134 +	public Enumeration getAllHeaders()
  48.135 +	{
  48.136 +		return this.headers.getAllHeaders();
  48.137 +	}
  48.138  
  48.139 -      if(en.hasMoreElements())
  48.140 -      {
  48.141 -        buf.append("\r\n");
  48.142 -      }
  48.143 -    }
  48.144 +	/**
  48.145 +	 * @return Header source code of this Article.
  48.146 +	 */
  48.147 +	public String getHeaderSource()
  48.148 +	{
  48.149 +		if (this.headerSrc != null) {
  48.150 +			return this.headerSrc;
  48.151 +		}
  48.152  
  48.153 -    this.headerSrc = buf.toString();
  48.154 -    return this.headerSrc;
  48.155 -  }
  48.156 +		StringBuffer buf = new StringBuffer();
  48.157  
  48.158 -  /**
  48.159 -   * Sets the headers of this Article. If headers contain no
  48.160 -   * Message-Id a new one is created.
  48.161 -   * @param headers
  48.162 -   */
  48.163 -  public void setHeaders(InternetHeaders headers)
  48.164 -  {
  48.165 -    this.headers   = headers;
  48.166 -    this.headerSrc = null;
  48.167 -    validateHeaders();
  48.168 -  }
  48.169 +		for (Enumeration en = this.headers.getAllHeaders(); en.hasMoreElements();) {
  48.170 +			Header entry = (Header) en.nextElement();
  48.171  
  48.172 -  /**
  48.173 -   * Checks some headers for their validity and generates an
  48.174 -   * appropriate Path-header for this host if not yet existing.
  48.175 -   * This method is called by some Article constructors and the
  48.176 -   * method setHeaders().
  48.177 -   * @return true if something on the headers was changed.
  48.178 -   */
  48.179 -  protected void validateHeaders()
  48.180 -  {
  48.181 -    // Check for valid Path-header
  48.182 -    final String path = getHeader(Headers.PATH)[0];
  48.183 -    final String host = Config.inst().get(Config.HOSTNAME, "localhost");
  48.184 -    if(!path.startsWith(host))
  48.185 -    {
  48.186 -      StringBuffer pathBuf = new StringBuffer();
  48.187 -      pathBuf.append(host);
  48.188 -      pathBuf.append('!');
  48.189 -      pathBuf.append(path);
  48.190 -      this.headers.setHeader(Headers.PATH, pathBuf.toString());
  48.191 -    }
  48.192 -  }
  48.193 -  
  48.194 +			String value = entry.getValue().replaceAll("[\r\n]", " ");
  48.195 +			buf.append(entry.getName());
  48.196 +			buf.append(": ");
  48.197 +			buf.append(MimeUtility.fold(entry.getName().length() + 2, value));
  48.198 +
  48.199 +			if (en.hasMoreElements()) {
  48.200 +				buf.append("\r\n");
  48.201 +			}
  48.202 +		}
  48.203 +
  48.204 +		this.headerSrc = buf.toString();
  48.205 +		return this.headerSrc;
  48.206 +	}
  48.207 +
  48.208 +	/**
  48.209 +	 * Sets the headers of this Article. If headers contain no
  48.210 +	 * Message-Id a new one is created.
  48.211 +	 * @param headers
  48.212 +	 */
  48.213 +	public void setHeaders(InternetHeaders headers)
  48.214 +	{
  48.215 +		this.headers = headers;
  48.216 +		this.headerSrc = null;
  48.217 +		validateHeaders();
  48.218 +	}
  48.219 +
  48.220 +	/**
  48.221 +	 * Checks some headers for their validity and generates an
  48.222 +	 * appropriate Path-header for this host if not yet existing.
  48.223 +	 * This method is called by some Article constructors and the
  48.224 +	 * method setHeaders().
  48.225 +	 * @return true if something on the headers was changed.
  48.226 +	 */
  48.227 +	protected void validateHeaders()
  48.228 +	{
  48.229 +		// Check for valid Path-header
  48.230 +		final String path = getHeader(Headers.PATH)[0];
  48.231 +		final String host = Config.inst().get(Config.HOSTNAME, "localhost");
  48.232 +		if (!path.startsWith(host)) {
  48.233 +			StringBuffer pathBuf = new StringBuffer();
  48.234 +			pathBuf.append(host);
  48.235 +			pathBuf.append('!');
  48.236 +			pathBuf.append(path);
  48.237 +			this.headers.setHeader(Headers.PATH, pathBuf.toString());
  48.238 +		}
  48.239 +	}
  48.240  }
    49.1 --- a/src/org/sonews/storage/Channel.java	Sun Aug 29 17:43:58 2010 +0200
    49.2 +++ b/src/org/sonews/storage/Channel.java	Sun Aug 29 18:17:37 2010 +0200
    49.3 @@ -33,79 +33,75 @@
    49.4  public abstract class Channel
    49.5  {
    49.6  
    49.7 -  /**
    49.8 -   * If this flag is set the Group is no real newsgroup but a mailing list
    49.9 -   * mirror. In that case every posting and receiving mails must go through
   49.10 -   * the mailing list gateway.
   49.11 -   */
   49.12 -  public static final int MAILINGLIST = 0x1;
   49.13 +	/**
   49.14 +	 * If this flag is set the Group is no real newsgroup but a mailing list
   49.15 +	 * mirror. In that case every posting and receiving mails must go through
   49.16 +	 * the mailing list gateway.
   49.17 +	 */
   49.18 +	public static final int MAILINGLIST = 0x1;
   49.19 +	/**
   49.20 +	 * If this flag is set the Group is marked as readonly and the posting
   49.21 +	 * is prohibited. This can be useful for groups that are synced only in
   49.22 +	 * one direction.
   49.23 +	 */
   49.24 +	public static final int READONLY = 0x2;
   49.25 +	/**
   49.26 +	 * If this flag is set the Group is marked as deleted and must not occur
   49.27 +	 * in any output. The deletion is done lazily by a low priority daemon.
   49.28 +	 */
   49.29 +	public static final int DELETED = 0x80;
   49.30  
   49.31 -  /**
   49.32 -   * If this flag is set the Group is marked as readonly and the posting
   49.33 -   * is prohibited. This can be useful for groups that are synced only in
   49.34 -   * one direction.
   49.35 -   */
   49.36 -  public static final int READONLY    = 0x2;
   49.37 +	public static List<Channel> getAll()
   49.38 +	{
   49.39 +		List<Channel> all = new ArrayList<Channel>();
   49.40  
   49.41 -  /**
   49.42 -   * If this flag is set the Group is marked as deleted and must not occur
   49.43 -   * in any output. The deletion is done lazily by a low priority daemon.
   49.44 -   */
   49.45 -  public static final int DELETED     = 0x80;
   49.46 +		/*List<Channel> agroups = AggregatedGroup.getAll();
   49.47 +		if(agroups != null)
   49.48 +		{
   49.49 +		all.addAll(agroups);
   49.50 +		}*/
   49.51  
   49.52 -  public static List<Channel> getAll()
   49.53 -  {
   49.54 -    List<Channel> all = new ArrayList<Channel>();
   49.55 +		List<Channel> groups = Group.getAll();
   49.56 +		if (groups != null) {
   49.57 +			all.addAll(groups);
   49.58 +		}
   49.59  
   49.60 -    /*List<Channel> agroups = AggregatedGroup.getAll();
   49.61 -    if(agroups != null)
   49.62 -    {
   49.63 -      all.addAll(agroups);
   49.64 -    }*/
   49.65 +		return all;
   49.66 +	}
   49.67  
   49.68 -    List<Channel> groups = Group.getAll();
   49.69 -    if(groups != null)
   49.70 -    {
   49.71 -      all.addAll(groups);
   49.72 -    }
   49.73 +	public static Channel getByName(String name)
   49.74 +		throws StorageBackendException
   49.75 +	{
   49.76 +		return StorageManager.current().getGroup(name);
   49.77 +	}
   49.78  
   49.79 -    return all;
   49.80 -  }
   49.81 +	public abstract Article getArticle(long idx)
   49.82 +		throws StorageBackendException;
   49.83  
   49.84 -  public static Channel getByName(String name)
   49.85 -    throws StorageBackendException
   49.86 -  {
   49.87 -    return StorageManager.current().getGroup(name);
   49.88 -  }
   49.89 +	public abstract List<Pair<Long, ArticleHead>> getArticleHeads(
   49.90 +		final long first, final long last)
   49.91 +		throws StorageBackendException;
   49.92  
   49.93 -  public abstract Article getArticle(long idx)
   49.94 -    throws StorageBackendException;
   49.95 +	public abstract List<Long> getArticleNumbers()
   49.96 +		throws StorageBackendException;
   49.97  
   49.98 -  public abstract List<Pair<Long, ArticleHead>> getArticleHeads(
   49.99 -    final long first, final long last)
  49.100 -    throws StorageBackendException;
  49.101 +	public abstract long getFirstArticleNumber()
  49.102 +		throws StorageBackendException;
  49.103  
  49.104 -  public abstract List<Long> getArticleNumbers()
  49.105 -    throws StorageBackendException;
  49.106 +	public abstract long getIndexOf(Article art)
  49.107 +		throws StorageBackendException;
  49.108  
  49.109 -  public abstract long getFirstArticleNumber()
  49.110 -    throws StorageBackendException;
  49.111 +	public abstract long getInternalID();
  49.112  
  49.113 -  public abstract long getIndexOf(Article art)
  49.114 -    throws StorageBackendException;
  49.115 +	public abstract long getLastArticleNumber()
  49.116 +		throws StorageBackendException;
  49.117  
  49.118 -  public abstract long getInternalID();
  49.119 +	public abstract String getName();
  49.120  
  49.121 -  public abstract long getLastArticleNumber()
  49.122 -    throws StorageBackendException;
  49.123 +	public abstract long getPostingsCount()
  49.124 +		throws StorageBackendException;
  49.125  
  49.126 -  public abstract String getName();
  49.127 -  
  49.128 -  public abstract long getPostingsCount()
  49.129 -    throws StorageBackendException;
  49.130 +	public abstract boolean isDeleted();
  49.131  
  49.132 -  public abstract boolean isDeleted();
  49.133 -
  49.134 -  public abstract boolean isWriteable();
  49.135 -
  49.136 +	public abstract boolean isWriteable();
  49.137  }
    50.1 --- a/src/org/sonews/storage/Group.java	Sun Aug 29 17:43:58 2010 +0200
    50.2 +++ b/src/org/sonews/storage/Group.java	Sun Aug 29 18:17:37 2010 +0200
    50.3 @@ -31,154 +31,147 @@
    50.4  // TODO: This class should not be public!
    50.5  public class Group extends Channel
    50.6  {
    50.7 -  
    50.8 -  private long   id     = 0;
    50.9 -  private int    flags  = -1;
   50.10 -  private String name   = null;
   50.11  
   50.12 -  /**
   50.13 -   * @return List of all groups this server handles.
   50.14 -   */
   50.15 -  public static List<Channel> getAll()
   50.16 -  {
   50.17 -    try
   50.18 -    {
   50.19 -      return StorageManager.current().getGroups();
   50.20 -    }
   50.21 -    catch(StorageBackendException ex)
   50.22 -    {
   50.23 -      Log.get().severe(ex.getMessage());
   50.24 -      return null;
   50.25 -    }
   50.26 -  }
   50.27 -  
   50.28 -  /**
   50.29 -   * @param name
   50.30 -   * @param id
   50.31 -   */
   50.32 -  public Group(final String name, final long id, final int flags)
   50.33 -  {
   50.34 -    this.id    = id;
   50.35 -    this.flags = flags;
   50.36 -    this.name  = name;
   50.37 -  }
   50.38 +	private long id = 0;
   50.39 +	private int flags = -1;
   50.40 +	private String name = null;
   50.41  
   50.42 -  @Override
   50.43 -  public boolean equals(Object obj)
   50.44 -  {
   50.45 -    if(obj instanceof Group)
   50.46 -    {
   50.47 -      return ((Group)obj).id == this.id;
   50.48 -    }
   50.49 -    else
   50.50 -    {
   50.51 -      return false;
   50.52 -    }
   50.53 -  }
   50.54 +	/**
   50.55 +	 * @return List of all groups this server handles.
   50.56 +	 */
   50.57 +	public static List<Channel> getAll()
   50.58 +	{
   50.59 +		try {
   50.60 +			return StorageManager.current().getGroups();
   50.61 +		} catch (StorageBackendException ex) {
   50.62 +			Log.get().severe(ex.getMessage());
   50.63 +			return null;
   50.64 +		}
   50.65 +	}
   50.66  
   50.67 -  public Article getArticle(long idx)
   50.68 -    throws StorageBackendException
   50.69 -  {
   50.70 -    return StorageManager.current().getArticle(idx, this.id);
   50.71 -  }
   50.72 +	/**
   50.73 +	 * @param name
   50.74 +	 * @param id
   50.75 +	 */
   50.76 +	public Group(final String name, final long id, final int flags)
   50.77 +	{
   50.78 +		this.id = id;
   50.79 +		this.flags = flags;
   50.80 +		this.name = name;
   50.81 +	}
   50.82  
   50.83 -  public List<Pair<Long, ArticleHead>> getArticleHeads(final long first, final long last)
   50.84 -    throws StorageBackendException
   50.85 -  {
   50.86 -    return StorageManager.current().getArticleHeads(this, first, last);
   50.87 -  }
   50.88 -  
   50.89 -  public List<Long> getArticleNumbers()
   50.90 -    throws StorageBackendException
   50.91 -  {
   50.92 -    return StorageManager.current().getArticleNumbers(id);
   50.93 -  }
   50.94 +	@Override
   50.95 +	public boolean equals(Object obj)
   50.96 +	{
   50.97 +		if (obj instanceof Group) {
   50.98 +			return ((Group) obj).id == this.id;
   50.99 +		} else {
  50.100 +			return false;
  50.101 +		}
  50.102 +	}
  50.103  
  50.104 -  public long getFirstArticleNumber()
  50.105 -    throws StorageBackendException
  50.106 -  {
  50.107 -    return StorageManager.current().getFirstArticleNumber(this);
  50.108 -  }
  50.109 +	public Article getArticle(long idx)
  50.110 +		throws StorageBackendException
  50.111 +	{
  50.112 +		return StorageManager.current().getArticle(idx, this.id);
  50.113 +	}
  50.114  
  50.115 -  public int getFlags()
  50.116 -  {
  50.117 -    return this.flags;
  50.118 -  }
  50.119 +	public List<Pair<Long, ArticleHead>> getArticleHeads(final long first, final long last)
  50.120 +		throws StorageBackendException
  50.121 +	{
  50.122 +		return StorageManager.current().getArticleHeads(this, first, last);
  50.123 +	}
  50.124  
  50.125 -  public long getIndexOf(Article art)
  50.126 -    throws StorageBackendException
  50.127 -  {
  50.128 -    return StorageManager.current().getArticleIndex(art, this);
  50.129 -  }
  50.130 +	public List<Long> getArticleNumbers()
  50.131 +		throws StorageBackendException
  50.132 +	{
  50.133 +		return StorageManager.current().getArticleNumbers(id);
  50.134 +	}
  50.135  
  50.136 -  /**
  50.137 -   * Returns the group id.
  50.138 -   */
  50.139 -  public long getInternalID()
  50.140 -  {
  50.141 -    assert id > 0;
  50.142 +	public long getFirstArticleNumber()
  50.143 +		throws StorageBackendException
  50.144 +	{
  50.145 +		return StorageManager.current().getFirstArticleNumber(this);
  50.146 +	}
  50.147  
  50.148 -    return id;
  50.149 -  }
  50.150 +	public int getFlags()
  50.151 +	{
  50.152 +		return this.flags;
  50.153 +	}
  50.154  
  50.155 -  public boolean isDeleted()
  50.156 -  {
  50.157 -    return (this.flags & DELETED) != 0;
  50.158 -  }
  50.159 +	public long getIndexOf(Article art)
  50.160 +		throws StorageBackendException
  50.161 +	{
  50.162 +		return StorageManager.current().getArticleIndex(art, this);
  50.163 +	}
  50.164  
  50.165 -  public boolean isMailingList()
  50.166 -  {
  50.167 -    return (this.flags & MAILINGLIST) != 0;
  50.168 -  }
  50.169 +	/**
  50.170 +	 * Returns the group id.
  50.171 +	 */
  50.172 +	public long getInternalID()
  50.173 +	{
  50.174 +		assert id > 0;
  50.175  
  50.176 -  public boolean isWriteable()
  50.177 -  {
  50.178 -    return true;
  50.179 -  }
  50.180 +		return id;
  50.181 +	}
  50.182  
  50.183 -  public long getLastArticleNumber()
  50.184 -    throws StorageBackendException
  50.185 -  {
  50.186 -    return StorageManager.current().getLastArticleNumber(this);
  50.187 -  }
  50.188 +	public boolean isDeleted()
  50.189 +	{
  50.190 +		return (this.flags & DELETED) != 0;
  50.191 +	}
  50.192  
  50.193 -  public String getName()
  50.194 -  {
  50.195 -    return name;
  50.196 -  }
  50.197 +	public boolean isMailingList()
  50.198 +	{
  50.199 +		return (this.flags & MAILINGLIST) != 0;
  50.200 +	}
  50.201  
  50.202 -  /**
  50.203 -   * Performs this.flags |= flag to set a specified flag and updates the data
  50.204 -   * in the JDBCDatabase.
  50.205 -   * @param flag
  50.206 -   */
  50.207 -  public void setFlag(final int flag)
  50.208 -  {
  50.209 -    this.flags |= flag;
  50.210 -  }
  50.211 +	public boolean isWriteable()
  50.212 +	{
  50.213 +		return true;
  50.214 +	}
  50.215  
  50.216 -  public void setName(final String name)
  50.217 -  {
  50.218 -    this.name = name;
  50.219 -  }
  50.220 +	public long getLastArticleNumber()
  50.221 +		throws StorageBackendException
  50.222 +	{
  50.223 +		return StorageManager.current().getLastArticleNumber(this);
  50.224 +	}
  50.225  
  50.226 -  /**
  50.227 -   * @return Number of posted articles in this group.
  50.228 -   * @throws java.sql.SQLException
  50.229 -   */
  50.230 -  public long getPostingsCount()
  50.231 -    throws StorageBackendException
  50.232 -  {
  50.233 -    return StorageManager.current().getPostingsCount(this.name);
  50.234 -  }
  50.235 +	public String getName()
  50.236 +	{
  50.237 +		return name;
  50.238 +	}
  50.239  
  50.240 -  /**
  50.241 -   * Updates flags and name in the backend.
  50.242 -   */
  50.243 -  public void update()
  50.244 -    throws StorageBackendException
  50.245 -  {
  50.246 -    StorageManager.current().update(this);
  50.247 -  }
  50.248 +	/**
  50.249 +	 * Performs this.flags |= flag to set a specified flag and updates the data
  50.250 +	 * in the JDBCDatabase.
  50.251 +	 * @param flag
  50.252 +	 */
  50.253 +	public void setFlag(final int flag)
  50.254 +	{
  50.255 +		this.flags |= flag;
  50.256 +	}
  50.257  
  50.258 +	public void setName(final String name)
  50.259 +	{
  50.260 +		this.name = name;
  50.261 +	}
  50.262 +
  50.263 +	/**
  50.264 +	 * @return Number of posted articles in this group.
  50.265 +	 * @throws java.sql.SQLException
  50.266 +	 */
  50.267 +	public long getPostingsCount()
  50.268 +		throws StorageBackendException
  50.269 +	{
  50.270 +		return StorageManager.current().getPostingsCount(this.name);
  50.271 +	}
  50.272 +
  50.273 +	/**
  50.274 +	 * Updates flags and name in the backend.
  50.275 +	 */
  50.276 +	public void update()
  50.277 +		throws StorageBackendException
  50.278 +	{
  50.279 +		StorageManager.current().update(this);
  50.280 +	}
  50.281  }
    51.1 --- a/src/org/sonews/storage/Headers.java	Sun Aug 29 17:43:58 2010 +0200
    51.2 +++ b/src/org/sonews/storage/Headers.java	Sun Aug 29 18:17:37 2010 +0200
    51.3 @@ -27,30 +27,30 @@
    51.4  public final class Headers
    51.5  {
    51.6  
    51.7 -  public static final String BYTES             = "bytes";
    51.8 -  public static final String CONTENT_TYPE      = "content-type";
    51.9 -  public static final String CONTROL           = "control";
   51.10 -  public static final String DATE              = "date";
   51.11 -  public static final String FROM              = "from";
   51.12 -  public static final String LINES             = "lines";
   51.13 -  public static final String LIST_POST         = "list-post";
   51.14 -  public static final String MESSAGE_ID        = "message-id";
   51.15 -  public static final String NEWSGROUPS        = "newsgroups";
   51.16 -  public static final String NNTP_POSTING_DATE = "nntp-posting-date";
   51.17 -  public static final String NNTP_POSTING_HOST = "nntp-posting-host";
   51.18 -  public static final String PATH              = "path";
   51.19 -  public static final String REFERENCES        = "references";
   51.20 -  public static final String REPLY_TO          = "reply-to";
   51.21 -  public static final String SENDER            = "sender";
   51.22 -  public static final String SUBJECT           = "subject";
   51.23 -  public static final String SUPERSEDES        = "subersedes";
   51.24 -  public static final String TO                = "to";
   51.25 -  public static final String X_COMPLAINTS_TO   = "x-complaints-to";
   51.26 -  public static final String X_LIST_POST       = "x-list-post";
   51.27 -  public static final String X_TRACE           = "x-trace";
   51.28 -  public static final String XREF              = "xref";
   51.29 +	public static final String BYTES = "bytes";
   51.30 +	public static final String CONTENT_TYPE = "content-type";
   51.31 +	public static final String CONTROL = "control";
   51.32 +	public static final String DATE = "date";
   51.33 +	public static final String FROM = "from";
   51.34 +	public static final String LINES = "lines";
   51.35 +	public static final String LIST_POST = "list-post";
   51.36 +	public static final String MESSAGE_ID = "message-id";
   51.37 +	public static final String NEWSGROUPS = "newsgroups";
   51.38 +	public static final String NNTP_POSTING_DATE = "nntp-posting-date";
   51.39 +	public static final String NNTP_POSTING_HOST = "nntp-posting-host";
   51.40 +	public static final String PATH = "path";
   51.41 +	public static final String REFERENCES = "references";
   51.42 +	public static final String REPLY_TO = "reply-to";
   51.43 +	public static final String SENDER = "sender";
   51.44 +	public static final String SUBJECT = "subject";
   51.45 +	public static final String SUPERSEDES = "subersedes";
   51.46 +	public static final String TO = "to";
   51.47 +	public static final String X_COMPLAINTS_TO = "x-complaints-to";
   51.48 +	public static final String X_LIST_POST = "x-list-post";
   51.49 +	public static final String X_TRACE = "x-trace";
   51.50 +	public static final String XREF = "xref";
   51.51  
   51.52 -  private Headers()
   51.53 -  {}
   51.54 -
   51.55 +	private Headers()
   51.56 +	{
   51.57 +	}
   51.58  }
    52.1 --- a/src/org/sonews/storage/Storage.java	Sun Aug 29 17:43:58 2010 +0200
    52.2 +++ b/src/org/sonews/storage/Storage.java	Sun Aug 29 18:17:37 2010 +0200
    52.3 @@ -30,121 +30,120 @@
    52.4  public interface Storage
    52.5  {
    52.6  
    52.7 -  /**
    52.8 -   * Stores the given Article in the storage.
    52.9 -   * @param art
   52.10 -   * @throws StorageBackendException
   52.11 -   */
   52.12 -  void addArticle(Article art)
   52.13 -    throws StorageBackendException;
   52.14 +	/**
   52.15 +	 * Stores the given Article in the storage.
   52.16 +	 * @param art
   52.17 +	 * @throws StorageBackendException
   52.18 +	 */
   52.19 +	void addArticle(Article art)
   52.20 +		throws StorageBackendException;
   52.21  
   52.22 -  void addEvent(long timestamp, int type, long groupID)
   52.23 -    throws StorageBackendException;
   52.24 +	void addEvent(long timestamp, int type, long groupID)
   52.25 +		throws StorageBackendException;
   52.26  
   52.27 -  void addGroup(String groupname, int flags)
   52.28 -    throws StorageBackendException;
   52.29 +	void addGroup(String groupname, int flags)
   52.30 +		throws StorageBackendException;
   52.31  
   52.32 -  int countArticles()
   52.33 -    throws StorageBackendException;
   52.34 +	int countArticles()
   52.35 +		throws StorageBackendException;
   52.36  
   52.37 -  int countGroups()
   52.38 -    throws StorageBackendException;
   52.39 +	int countGroups()
   52.40 +		throws StorageBackendException;
   52.41  
   52.42 -  void delete(String messageID)
   52.43 -    throws StorageBackendException;
   52.44 +	void delete(String messageID)
   52.45 +		throws StorageBackendException;
   52.46  
   52.47 -  Article getArticle(String messageID)
   52.48 -    throws StorageBackendException;
   52.49 +	Article getArticle(String messageID)
   52.50 +		throws StorageBackendException;
   52.51  
   52.52 -  Article getArticle(long articleIndex, long groupID)
   52.53 -    throws StorageBackendException;
   52.54 +	Article getArticle(long articleIndex, long groupID)
   52.55 +		throws StorageBackendException;
   52.56  
   52.57 -  List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first, long last)
   52.58 -    throws StorageBackendException;
   52.59 +	List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first, long last)
   52.60 +		throws StorageBackendException;
   52.61  
   52.62 -  List<Pair<Long, String>> getArticleHeaders(Channel channel, long start, long end,
   52.63 -    String header, String pattern)
   52.64 -    throws StorageBackendException;
   52.65 +	List<Pair<Long, String>> getArticleHeaders(Channel channel, long start, long end,
   52.66 +		String header, String pattern)
   52.67 +		throws StorageBackendException;
   52.68  
   52.69 -  long getArticleIndex(Article art, Group group)
   52.70 -    throws StorageBackendException;
   52.71 +	long getArticleIndex(Article art, Group group)
   52.72 +		throws StorageBackendException;
   52.73  
   52.74 -  List<Long> getArticleNumbers(long groupID)
   52.75 -    throws StorageBackendException;
   52.76 +	List<Long> getArticleNumbers(long groupID)
   52.77 +		throws StorageBackendException;
   52.78  
   52.79 -  String getConfigValue(String key)
   52.80 -    throws StorageBackendException;
   52.81 +	String getConfigValue(String key)
   52.82 +		throws StorageBackendException;
   52.83  
   52.84 -  int getEventsCount(int eventType, long startTimestamp, long endTimestamp,
   52.85 -    Channel channel)
   52.86 -    throws StorageBackendException;
   52.87 +	int getEventsCount(int eventType, long startTimestamp, long endTimestamp,
   52.88 +		Channel channel)
   52.89 +		throws StorageBackendException;
   52.90  
   52.91 -  double getEventsPerHour(int key, long gid)
   52.92 -    throws StorageBackendException;
   52.93 +	double getEventsPerHour(int key, long gid)
   52.94 +		throws StorageBackendException;
   52.95  
   52.96 -  int getFirstArticleNumber(Group group)
   52.97 -    throws StorageBackendException;
   52.98 +	int getFirstArticleNumber(Group group)
   52.99 +		throws StorageBackendException;
  52.100  
  52.101 -  Group getGroup(String name)
  52.102 -    throws StorageBackendException;
  52.103 +	Group getGroup(String name)
  52.104 +		throws StorageBackendException;
  52.105  
  52.106 -  List<Channel> getGroups()
  52.107 -    throws StorageBackendException;
  52.108 +	List<Channel> getGroups()
  52.109 +		throws StorageBackendException;
  52.110  
  52.111 -  /**
  52.112 -   * Retrieves the collection of groupnames that are associated with the
  52.113 -   * given list address.
  52.114 -   * @param inetaddress
  52.115 -   * @return
  52.116 -   * @throws StorageBackendException
  52.117 -   */
  52.118 -  List<String> getGroupsForList(String listAddress)
  52.119 -    throws StorageBackendException;
  52.120 +	/**
  52.121 +	 * Retrieves the collection of groupnames that are associated with the
  52.122 +	 * given list address.
  52.123 +	 * @param inetaddress
  52.124 +	 * @return
  52.125 +	 * @throws StorageBackendException
  52.126 +	 */
  52.127 +	List<String> getGroupsForList(String listAddress)
  52.128 +		throws StorageBackendException;
  52.129  
  52.130 -  int getLastArticleNumber(Group group)
  52.131 -    throws StorageBackendException;
  52.132 +	int getLastArticleNumber(Group group)
  52.133 +		throws StorageBackendException;
  52.134  
  52.135 -  /**
  52.136 -   * Returns a list of email addresses that are related to the given
  52.137 -   * groupname. In most cases the list may contain only one entry.
  52.138 -   * @param groupname
  52.139 -   * @return
  52.140 -   * @throws StorageBackendException
  52.141 -   */
  52.142 -  List<String> getListsForGroup(String groupname)
  52.143 -    throws StorageBackendException;
  52.144 +	/**
  52.145 +	 * Returns a list of email addresses that are related to the given
  52.146 +	 * groupname. In most cases the list may contain only one entry.
  52.147 +	 * @param groupname
  52.148 +	 * @return
  52.149 +	 * @throws StorageBackendException
  52.150 +	 */
  52.151 +	List<String> getListsForGroup(String groupname)
  52.152 +		throws StorageBackendException;
  52.153  
  52.154 -  String getOldestArticle()
  52.155 -    throws StorageBackendException;
  52.156 +	String getOldestArticle()
  52.157 +		throws StorageBackendException;
  52.158  
  52.159 -  int getPostingsCount(String groupname)
  52.160 -    throws StorageBackendException;
  52.161 +	int getPostingsCount(String groupname)
  52.162 +		throws StorageBackendException;
  52.163  
  52.164 -  List<Subscription> getSubscriptions(int type)
  52.165 -    throws StorageBackendException;
  52.166 +	List<Subscription> getSubscriptions(int type)
  52.167 +		throws StorageBackendException;
  52.168  
  52.169 -  boolean isArticleExisting(String messageID)
  52.170 -    throws StorageBackendException;
  52.171 +	boolean isArticleExisting(String messageID)
  52.172 +		throws StorageBackendException;
  52.173  
  52.174 -  boolean isGroupExisting(String groupname)
  52.175 -    throws StorageBackendException;
  52.176 +	boolean isGroupExisting(String groupname)
  52.177 +		throws StorageBackendException;
  52.178  
  52.179 -  void purgeGroup(Group group)
  52.180 -    throws StorageBackendException;
  52.181 +	void purgeGroup(Group group)
  52.182 +		throws StorageBackendException;
  52.183  
  52.184 -  void setConfigValue(String key, String value)
  52.185 -    throws StorageBackendException;
  52.186 +	void setConfigValue(String key, String value)
  52.187 +		throws StorageBackendException;
  52.188  
  52.189 -  /**
  52.190 -   * Updates headers and channel references of the given article.
  52.191 -   * @param article
  52.192 -   * @return
  52.193 -   * @throws StorageBackendException
  52.194 -   */
  52.195 -  boolean update(Article article)
  52.196 -    throws StorageBackendException;
  52.197 +	/**
  52.198 +	 * Updates headers and channel references of the given article.
  52.199 +	 * @param article
  52.200 +	 * @return
  52.201 +	 * @throws StorageBackendException
  52.202 +	 */
  52.203 +	boolean update(Article article)
  52.204 +		throws StorageBackendException;
  52.205  
  52.206 -  boolean update(Group group)
  52.207 -    throws StorageBackendException;
  52.208 -
  52.209 +	boolean update(Group group)
  52.210 +		throws StorageBackendException;
  52.211  }
    53.1 --- a/src/org/sonews/storage/StorageBackendException.java	Sun Aug 29 17:43:58 2010 +0200
    53.2 +++ b/src/org/sonews/storage/StorageBackendException.java	Sun Aug 29 18:17:37 2010 +0200
    53.3 @@ -19,21 +19,20 @@
    53.4  package org.sonews.storage;
    53.5  
    53.6  /**
    53.7 - *
    53.8 + * Exception caused by the storage backend.
    53.9   * @author Christian Lins
   53.10   * @since sonews/1.0
   53.11   */
   53.12  public class StorageBackendException extends Exception
   53.13  {
   53.14  
   53.15 -  public StorageBackendException(Throwable cause)
   53.16 -  {
   53.17 -    super(cause);
   53.18 -  }
   53.19 +	public StorageBackendException(Throwable cause)
   53.20 +	{
   53.21 +		super(cause);
   53.22 +	}
   53.23  
   53.24 -  public StorageBackendException(String msg)
   53.25 -  {
   53.26 -    super(msg);
   53.27 -  }
   53.28 -
   53.29 +	public StorageBackendException(String msg)
   53.30 +	{
   53.31 +		super(msg);
   53.32 +	}
   53.33  }
    54.1 --- a/src/org/sonews/storage/StorageManager.java	Sun Aug 29 17:43:58 2010 +0200
    54.2 +++ b/src/org/sonews/storage/StorageManager.java	Sun Aug 29 18:17:37 2010 +0200
    54.3 @@ -26,64 +26,53 @@
    54.4  public final class StorageManager
    54.5  {
    54.6  
    54.7 -  private static StorageProvider provider;
    54.8 +	private static StorageProvider provider;
    54.9  
   54.10 -  public static Storage current()
   54.11 -    throws StorageBackendException
   54.12 -  {
   54.13 -    synchronized(StorageManager.class)
   54.14 -    {
   54.15 -      if(provider == null)
   54.16 -      {
   54.17 -        return null;
   54.18 -      }
   54.19 -      else
   54.20 -      {
   54.21 -        return provider.storage(Thread.currentThread());
   54.22 -      }
   54.23 -    }
   54.24 -  }
   54.25 +	public static Storage current()
   54.26 +		throws StorageBackendException
   54.27 +	{
   54.28 +		synchronized (StorageManager.class) {
   54.29 +			if (provider == null) {
   54.30 +				return null;
   54.31 +			} else {
   54.32 +				return provider.storage(Thread.currentThread());
   54.33 +			}
   54.34 +		}
   54.35 +	}
   54.36  
   54.37 -  public static StorageProvider loadProvider(String pluginClassName)
   54.38 -  {
   54.39 -    try
   54.40 -    {
   54.41 -      Class<?> clazz = Class.forName(pluginClassName);
   54.42 -      Object   inst  = clazz.newInstance();
   54.43 -      return (StorageProvider)inst;
   54.44 -    }
   54.45 -    catch(Exception ex)
   54.46 -    {
   54.47 -      System.err.println(ex);
   54.48 -      return null;
   54.49 -    }
   54.50 -  }
   54.51 +	public static StorageProvider loadProvider(String pluginClassName)
   54.52 +	{
   54.53 +		try {
   54.54 +			Class<?> clazz = Class.forName(pluginClassName);
   54.55 +			Object inst = clazz.newInstance();
   54.56 +			return (StorageProvider) inst;
   54.57 +		} catch (Exception ex) {
   54.58 +			System.err.println(ex);
   54.59 +			return null;
   54.60 +		}
   54.61 +	}
   54.62  
   54.63 -  /**
   54.64 -   * Sets the current storage provider.
   54.65 -   * @param provider
   54.66 -   */
   54.67 -  public static void enableProvider(StorageProvider provider)
   54.68 -  {
   54.69 -    synchronized(StorageManager.class)
   54.70 -    {
   54.71 -      if(StorageManager.provider != null)
   54.72 -      {
   54.73 -        disableProvider();
   54.74 -      }
   54.75 -      StorageManager.provider = provider;
   54.76 -    }
   54.77 -  }
   54.78 +	/**
   54.79 +	 * Sets the current storage provider.
   54.80 +	 * @param provider
   54.81 +	 */
   54.82 +	public static void enableProvider(StorageProvider provider)
   54.83 +	{
   54.84 +		synchronized (StorageManager.class) {
   54.85 +			if (StorageManager.provider != null) {
   54.86 +				disableProvider();
   54.87 +			}
   54.88 +			StorageManager.provider = provider;
   54.89 +		}
   54.90 +	}
   54.91  
   54.92 -  /**
   54.93 -   * Disables the current provider.
   54.94 -   */
   54.95 -  public static void disableProvider()
   54.96 -  {
   54.97 -    synchronized(StorageManager.class)
   54.98 -    {
   54.99 -      provider = null;
  54.100 -    }
  54.101 -  }
  54.102 -
  54.103 +	/**
  54.104 +	 * Disables the current provider.
  54.105 +	 */
  54.106 +	public static void disableProvider()
  54.107 +	{
  54.108 +		synchronized (StorageManager.class) {
  54.109 +			provider = null;
  54.110 +		}
  54.111 +	}
  54.112  }
    55.1 --- a/src/org/sonews/storage/StorageProvider.java	Sun Aug 29 17:43:58 2010 +0200
    55.2 +++ b/src/org/sonews/storage/StorageProvider.java	Sun Aug 29 18:17:37 2010 +0200
    55.3 @@ -26,15 +26,14 @@
    55.4  public interface StorageProvider
    55.5  {
    55.6  
    55.7 -  public boolean isSupported(String uri);
    55.8 +	public boolean isSupported(String uri);
    55.9  
   55.10 -  /**
   55.11 -   * This method returns the reference to the associated storage.
   55.12 -   * The reference MAY be unique for each thread. In any case it MUST be
   55.13 -   * thread-safe to use this method.
   55.14 -   * @return The reference to the associated Storage.
   55.15 -   */
   55.16 -  public Storage storage(Thread thread)
   55.17 -    throws StorageBackendException;
   55.18 -
   55.19 +	/**
   55.20 +	 * This method returns the reference to the associated storage.
   55.21 +	 * The reference MAY be unique for each thread. In any case it MUST be
   55.22 +	 * thread-safe to use this method.
   55.23 +	 * @return The reference to the associated Storage.
   55.24 +	 */
   55.25 +	public Storage storage(Thread thread)
   55.26 +		throws StorageBackendException;
   55.27  }
    56.1 --- a/src/org/sonews/storage/impl/JDBCDatabase.java	Sun Aug 29 17:43:58 2010 +0200
    56.2 +++ b/src/org/sonews/storage/impl/JDBCDatabase.java	Sun Aug 29 18:17:37 2010 +0200
    56.3 @@ -52,1731 +52,1378 @@
    56.4  public class JDBCDatabase implements Storage
    56.5  {
    56.6  
    56.7 -  public static final int MAX_RESTARTS = 2;
    56.8 -  
    56.9 -  private Connection        conn = null;
   56.10 -  private PreparedStatement pstmtAddArticle1 = null;
   56.11 -  private PreparedStatement pstmtAddArticle2 = null;
   56.12 -  private PreparedStatement pstmtAddArticle3 = null;
   56.13 -  private PreparedStatement pstmtAddArticle4 = null;
   56.14 -  private PreparedStatement pstmtAddGroup0   = null;
   56.15 -  private PreparedStatement pstmtAddEvent = null;
   56.16 -  private PreparedStatement pstmtCountArticles = null;
   56.17 -  private PreparedStatement pstmtCountGroups   = null;
   56.18 -  private PreparedStatement pstmtDeleteArticle0 = null;
   56.19 -  private PreparedStatement pstmtDeleteArticle1 = null;
   56.20 -  private PreparedStatement pstmtDeleteArticle2 = null;
   56.21 -  private PreparedStatement pstmtDeleteArticle3 = null;
   56.22 -  private PreparedStatement pstmtGetArticle0 = null;
   56.23 -  private PreparedStatement pstmtGetArticle1 = null;
   56.24 -  private PreparedStatement pstmtGetArticleHeaders0 = null;
   56.25 -  private PreparedStatement pstmtGetArticleHeaders1 = null;
   56.26 -  private PreparedStatement pstmtGetArticleHeads = null;
   56.27 -  private PreparedStatement pstmtGetArticleIDs   = null;
   56.28 -  private PreparedStatement pstmtGetArticleIndex    = null;
   56.29 -  private PreparedStatement pstmtGetConfigValue = null;
   56.30 -  private PreparedStatement pstmtGetEventsCount0 = null;
   56.31 -  private PreparedStatement pstmtGetEventsCount1 = null;
   56.32 -  private PreparedStatement pstmtGetGroupForList = null;
   56.33 -  private PreparedStatement pstmtGetGroup0     = null;
   56.34 -  private PreparedStatement pstmtGetGroup1     = null;
   56.35 -  private PreparedStatement pstmtGetFirstArticleNumber = null;
   56.36 -  private PreparedStatement pstmtGetListForGroup       = null;
   56.37 -  private PreparedStatement pstmtGetLastArticleNumber  = null;
   56.38 -  private PreparedStatement pstmtGetMaxArticleID       = null;
   56.39 -  private PreparedStatement pstmtGetMaxArticleIndex    = null;
   56.40 -  private PreparedStatement pstmtGetOldestArticle      = null;
   56.41 -  private PreparedStatement pstmtGetPostingsCount      = null;
   56.42 -  private PreparedStatement pstmtGetSubscriptions  = null;
   56.43 -  private PreparedStatement pstmtIsArticleExisting = null;
   56.44 -  private PreparedStatement pstmtIsGroupExisting = null;
   56.45 -  private PreparedStatement pstmtPurgeGroup0     = null;
   56.46 -  private PreparedStatement pstmtPurgeGroup1     = null;
   56.47 -  private PreparedStatement pstmtSetConfigValue0 = null;
   56.48 -  private PreparedStatement pstmtSetConfigValue1 = null;
   56.49 -  private PreparedStatement pstmtUpdateGroup     = null;
   56.50 -  
   56.51 -  /** How many times the database connection was reinitialized */
   56.52 -  private int restarts = 0;
   56.53 -  
   56.54 -  /**
   56.55 -   * Rises the database: reconnect and recreate all prepared statements.
   56.56 -   * @throws java.lang.SQLException
   56.57 -   */
   56.58 -  protected void arise()
   56.59 -    throws SQLException
   56.60 -  {
   56.61 -    try
   56.62 -    {
   56.63 -      // Load database driver
   56.64 -      Class.forName(
   56.65 -        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DBMSDRIVER, "java.lang.Object"));
   56.66 +	public static final int MAX_RESTARTS = 2;
   56.67 +	private Connection conn = null;
   56.68 +	private PreparedStatement pstmtAddArticle1 = null;
   56.69 +	private PreparedStatement pstmtAddArticle2 = null;
   56.70 +	private PreparedStatement pstmtAddArticle3 = null;
   56.71 +	private PreparedStatement pstmtAddArticle4 = null;
   56.72 +	private PreparedStatement pstmtAddGroup0 = null;
   56.73 +	private PreparedStatement pstmtAddEvent = null;
   56.74 +	private PreparedStatement pstmtCountArticles = null;
   56.75 +	private PreparedStatement pstmtCountGroups = null;
   56.76 +	private PreparedStatement pstmtDeleteArticle0 = null;
   56.77 +	private PreparedStatement pstmtDeleteArticle1 = null;
   56.78 +	private PreparedStatement pstmtDeleteArticle2 = null;
   56.79 +	private PreparedStatement pstmtDeleteArticle3 = null;
   56.80 +	private PreparedStatement pstmtGetArticle0 = null;
   56.81 +	private PreparedStatement pstmtGetArticle1 = null;
   56.82 +	private PreparedStatement pstmtGetArticleHeaders0 = null;
   56.83 +	private PreparedStatement pstmtGetArticleHeaders1 = null;
   56.84 +	private PreparedStatement pstmtGetArticleHeads = null;
   56.85 +	private PreparedStatement pstmtGetArticleIDs = null;
   56.86 +	private PreparedStatement pstmtGetArticleIndex = null;
   56.87 +	private PreparedStatement pstmtGetConfigValue = null;
   56.88 +	private PreparedStatement pstmtGetEventsCount0 = null;
   56.89 +	private PreparedStatement pstmtGetEventsCount1 = null;
   56.90 +	private PreparedStatement pstmtGetGroupForList = null;
   56.91 +	private PreparedStatement pstmtGetGroup0 = null;
   56.92 +	private PreparedStatement pstmtGetGroup1 = null;
   56.93 +	private PreparedStatement pstmtGetFirstArticleNumber = null;
   56.94 +	private PreparedStatement pstmtGetListForGroup = null;
   56.95 +	private PreparedStatement pstmtGetLastArticleNumber = null;
   56.96 +	private PreparedStatement pstmtGetMaxArticleID = null;
   56.97 +	private PreparedStatement pstmtGetMaxArticleIndex = null;
   56.98 +	private PreparedStatement pstmtGetOldestArticle = null;
   56.99 +	private PreparedStatement pstmtGetPostingsCount = null;
  56.100 +	private PreparedStatement pstmtGetSubscriptions = null;
  56.101 +	private PreparedStatement pstmtIsArticleExisting = null;
  56.102 +	private PreparedStatement pstmtIsGroupExisting = null;
  56.103 +	private PreparedStatement pstmtPurgeGroup0 = null;
  56.104 +	private PreparedStatement pstmtPurgeGroup1 = null;
  56.105 +	private PreparedStatement pstmtSetConfigValue0 = null;
  56.106 +	private PreparedStatement pstmtSetConfigValue1 = null;
  56.107 +	private PreparedStatement pstmtUpdateGroup = null;
  56.108 +	/** How many times the database connection was reinitialized */
  56.109 +	private int restarts = 0;
  56.110  
  56.111 -      // Establish database connection
  56.112 -      this.conn = DriverManager.getConnection(
  56.113 -        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DATABASE, "<not specified>"),
  56.114 -        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_USER, "root"),
  56.115 -        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_PASSWORD, ""));
  56.116 +	/**
  56.117 +	 * Rises the database: reconnect and recreate all prepared statements.
  56.118 +	 * @throws java.lang.SQLException
  56.119 +	 */
  56.120 +	protected void arise()
  56.121 +		throws SQLException
  56.122 +	{
  56.123 +		try {
  56.124 +			// Load database driver
  56.125 +			Class.forName(
  56.126 +				Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DBMSDRIVER, "java.lang.Object"));
  56.127  
  56.128 -      this.conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
  56.129 -      if(this.conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE)
  56.130 -      {
  56.131 -        Log.get().warning("Database is NOT fully serializable!");
  56.132 -      }
  56.133 +			// Establish database connection
  56.134 +			this.conn = DriverManager.getConnection(
  56.135 +				Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DATABASE, "<not specified>"),
  56.136 +				Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_USER, "root"),
  56.137 +				Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_PASSWORD, ""));
  56.138  
  56.139 -      // Prepare statements for method addArticle()
  56.140 -      this.pstmtAddArticle1 = conn.prepareStatement(
  56.141 -        "INSERT INTO articles (article_id, body) VALUES(?, ?)");
  56.142 -      this.pstmtAddArticle2 = conn.prepareStatement(
  56.143 -        "INSERT INTO headers (article_id, header_key, header_value, header_index) " +
  56.144 -        "VALUES (?, ?, ?, ?)");
  56.145 -      this.pstmtAddArticle3 = conn.prepareStatement(
  56.146 -        "INSERT INTO postings (group_id, article_id, article_index)" +
  56.147 -        "VALUES (?, ?, ?)");
  56.148 -      this.pstmtAddArticle4 = conn.prepareStatement(
  56.149 -        "INSERT INTO article_ids (article_id, message_id) VALUES (?, ?)");
  56.150 +			this.conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
  56.151 +			if (this.conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE) {
  56.152 +				Log.get().warning("Database is NOT fully serializable!");
  56.153 +			}
  56.154  
  56.155 -      // Prepare statement for method addStatValue()
  56.156 -      this.pstmtAddEvent = conn.prepareStatement(
  56.157 -        "INSERT INTO events VALUES (?, ?, ?)");
  56.158 -     
  56.159 -      // Prepare statement for method addGroup()
  56.160 -      this.pstmtAddGroup0 = conn.prepareStatement(
  56.161 -        "INSERT INTO groups (name, flags) VALUES (?, ?)");
  56.162 -      
  56.163 -      // Prepare statement for method countArticles()
  56.164 -      this.pstmtCountArticles = conn.prepareStatement(
  56.165 -        "SELECT Count(article_id) FROM article_ids");
  56.166 -      
  56.167 -      // Prepare statement for method countGroups()
  56.168 -      this.pstmtCountGroups = conn.prepareStatement(
  56.169 -        "SELECT Count(group_id) FROM groups WHERE " +
  56.170 -        "flags & " + Channel.DELETED + " = 0");
  56.171 -      
  56.172 -      // Prepare statements for method delete(article)
  56.173 -      this.pstmtDeleteArticle0 = conn.prepareStatement(
  56.174 -        "DELETE FROM articles WHERE article_id = " +
  56.175 -        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
  56.176 -      this.pstmtDeleteArticle1 = conn.prepareStatement(
  56.177 -        "DELETE FROM headers WHERE article_id = " +
  56.178 -        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
  56.179 -      this.pstmtDeleteArticle2 = conn.prepareStatement(
  56.180 -        "DELETE FROM postings WHERE article_id = " +
  56.181 -        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
  56.182 -      this.pstmtDeleteArticle3 = conn.prepareStatement(
  56.183 -        "DELETE FROM article_ids WHERE message_id = ?");
  56.184 +			// Prepare statements for method addArticle()
  56.185 +			this.pstmtAddArticle1 = conn.prepareStatement(
  56.186 +				"INSERT INTO articles (article_id, body) VALUES(?, ?)");
  56.187 +			this.pstmtAddArticle2 = conn.prepareStatement(
  56.188 +				"INSERT INTO headers (article_id, header_key, header_value, header_index) "
  56.189 +				+ "VALUES (?, ?, ?, ?)");
  56.190 +			this.pstmtAddArticle3 = conn.prepareStatement(
  56.191 +				"INSERT INTO postings (group_id, article_id, article_index)"
  56.192 +				+ "VALUES (?, ?, ?)");
  56.193 +			this.pstmtAddArticle4 = conn.prepareStatement(
  56.194 +				"INSERT INTO article_ids (article_id, message_id) VALUES (?, ?)");
  56.195  
  56.196 -      // Prepare statements for methods getArticle()
  56.197 -      this.pstmtGetArticle0 = conn.prepareStatement(
  56.198 -        "SELECT * FROM articles  WHERE article_id = " +
  56.199 -        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
  56.200 -      this.pstmtGetArticle1 = conn.prepareStatement(
  56.201 -        "SELECT * FROM articles WHERE article_id = " +
  56.202 -        "(SELECT article_id FROM postings WHERE " +
  56.203 -        "article_index = ? AND group_id = ?)");
  56.204 -      
  56.205 -      // Prepare statement for method getArticleHeaders()
  56.206 -      this.pstmtGetArticleHeaders0 = conn.prepareStatement(
  56.207 -        "SELECT header_key, header_value FROM headers WHERE article_id = ? " +
  56.208 -        "ORDER BY header_index ASC");
  56.209 +			// Prepare statement for method addStatValue()
  56.210 +			this.pstmtAddEvent = conn.prepareStatement(
  56.211 +				"INSERT INTO events VALUES (?, ?, ?)");
  56.212  
  56.213 -      // Prepare statement for method getArticleHeaders(regular expr pattern)
  56.214 -      this.pstmtGetArticleHeaders1 = conn.prepareStatement(
  56.215 -        "SELECT p.article_index, h.header_value FROM headers h " +
  56.216 -          "INNER JOIN postings p ON h.article_id = p.article_id " +
  56.217 -          "INNER JOIN groups g ON p.group_id = g.group_id " +
  56.218 -            "WHERE g.name          =  ? AND " +
  56.219 -                  "h.header_key    =  ? AND " +
  56.220 -                  "p.article_index >= ? " +
  56.221 -        "ORDER BY p.article_index ASC");
  56.222 +			// Prepare statement for method addGroup()
  56.223 +			this.pstmtAddGroup0 = conn.prepareStatement(
  56.224 +				"INSERT INTO groups (name, flags) VALUES (?, ?)");
  56.225  
  56.226 -      this.pstmtGetArticleIDs = conn.prepareStatement(
  56.227 -        "SELECT article_index FROM postings WHERE group_id = ?");
  56.228 -      
  56.229 -      // Prepare statement for method getArticleIndex
  56.230 -      this.pstmtGetArticleIndex = conn.prepareStatement(
  56.231 -              "SELECT article_index FROM postings WHERE " +
  56.232 -              "article_id = (SELECT article_id FROM article_ids " +
  56.233 -              "WHERE message_id = ?) " +
  56.234 -              " AND group_id = ?");
  56.235 +			// Prepare statement for method countArticles()
  56.236 +			this.pstmtCountArticles = conn.prepareStatement(
  56.237 +				"SELECT Count(article_id) FROM article_ids");
  56.238  
  56.239 -      // Prepare statements for method getArticleHeads()
  56.240 -      this.pstmtGetArticleHeads = conn.prepareStatement(
  56.241 -        "SELECT article_id, article_index FROM postings WHERE " +
  56.242 -        "postings.group_id = ? AND article_index >= ? AND " +
  56.243 -        "article_index <= ?");
  56.244 +			// Prepare statement for method countGroups()
  56.245 +			this.pstmtCountGroups = conn.prepareStatement(
  56.246 +				"SELECT Count(group_id) FROM groups WHERE "
  56.247 +				+ "flags & " + Channel.DELETED + " = 0");
  56.248  
  56.249 -      // Prepare statements for method getConfigValue()
  56.250 -      this.pstmtGetConfigValue = conn.prepareStatement(
  56.251 -        "SELECT config_value FROM config WHERE config_key = ?");
  56.252 +			// Prepare statements for method delete(article)
  56.253 +			this.pstmtDeleteArticle0 = conn.prepareStatement(
  56.254 +				"DELETE FROM articles WHERE article_id = "
  56.255 +				+ "(SELECT article_id FROM article_ids WHERE message_id = ?)");
  56.256 +			this.pstmtDeleteArticle1 = conn.prepareStatement(
  56.257 +				"DELETE FROM headers WHERE article_id = "
  56.258 +				+ "(SELECT article_id FROM article_ids WHERE message_id = ?)");
  56.259 +			this.pstmtDeleteArticle2 = conn.prepareStatement(
  56.260 +				"DELETE FROM postings WHERE article_id = "
  56.261 +				+ "(SELECT article_id FROM article_ids WHERE message_id = ?)");
  56.262 +			this.pstmtDeleteArticle3 = conn.prepareStatement(
  56.263 +				"DELETE FROM article_ids WHERE message_id = ?");
  56.264  
  56.265 -      // Prepare statements for method getEventsCount()
  56.266 -      this.pstmtGetEventsCount0 = conn.prepareStatement(
  56.267 -        "SELECT Count(*) FROM events WHERE event_key = ? AND " +
  56.268 -        "event_time >= ? AND event_time < ?");
  56.269 +			// Prepare statements for methods getArticle()
  56.270 +			this.pstmtGetArticle0 = conn.prepareStatement(
  56.271 +				"SELECT * FROM articles  WHERE article_id = "
  56.272 +				+ "(SELECT article_id FROM article_ids WHERE message_id = ?)");
  56.273 +			this.pstmtGetArticle1 = conn.prepareStatement(
  56.274 +				"SELECT * FROM articles WHERE article_id = "
  56.275 +				+ "(SELECT article_id FROM postings WHERE "
  56.276 +				+ "article_index = ? AND group_id = ?)");
  56.277  
  56.278 -      this.pstmtGetEventsCount1 = conn.prepareStatement(
  56.279 -        "SELECT Count(*) FROM events WHERE event_key = ? AND " +
  56.280 -        "event_time >= ? AND event_time < ? AND group_id = ?");
  56.281 -      
  56.282 -      // Prepare statement for method getGroupForList()
  56.283 -      this.pstmtGetGroupForList = conn.prepareStatement(
  56.284 -        "SELECT name FROM groups INNER JOIN groups2list " +
  56.285 -        "ON groups.group_id = groups2list.group_id " +
  56.286 -        "WHERE groups2list.listaddress = ?");
  56.287 +			// Prepare statement for method getArticleHeaders()
  56.288 +			this.pstmtGetArticleHeaders0 = conn.prepareStatement(
  56.289 +				"SELECT header_key, header_value FROM headers WHERE article_id = ? "
  56.290 +				+ "ORDER BY header_index ASC");
  56.291  
  56.292 -      // Prepare statement for method getGroup()
  56.293 -      this.pstmtGetGroup0 = conn.prepareStatement(
  56.294 -        "SELECT group_id, flags FROM groups WHERE Name = ?");
  56.295 -      this.pstmtGetGroup1 = conn.prepareStatement(
  56.296 -        "SELECT name FROM groups WHERE group_id = ?");
  56.297 +			// Prepare statement for method getArticleHeaders(regular expr pattern)
  56.298 +			this.pstmtGetArticleHeaders1 = conn.prepareStatement(
  56.299 +				"SELECT p.article_index, h.header_value FROM headers h "
  56.300 +				+ "INNER JOIN postings p ON h.article_id = p.article_id "
  56.301 +				+ "INNER JOIN groups g ON p.group_id = g.group_id "
  56.302 +				+ "WHERE g.name          =  ? AND "
  56.303 +				+ "h.header_key    =  ? AND "
  56.304 +				+ "p.article_index >= ? "
  56.305 +				+ "ORDER BY p.article_index ASC");
  56.306  
  56.307 -      // Prepare statement for method getLastArticleNumber()
  56.308 -      this.pstmtGetLastArticleNumber = conn.prepareStatement(
  56.309 -        "SELECT Max(article_index) FROM postings WHERE group_id = ?");
  56.310 +			this.pstmtGetArticleIDs = conn.prepareStatement(
  56.311 +				"SELECT article_index FROM postings WHERE group_id = ?");
  56.312  
  56.313 -      // Prepare statement for method getListForGroup()
  56.314 -      this.pstmtGetListForGroup = conn.prepareStatement(
  56.315 -        "SELECT listaddress FROM groups2list INNER JOIN groups " +
  56.316 -        "ON groups.group_id = groups2list.group_id WHERE name = ?");
  56.317 +			// Prepare statement for method getArticleIndex
  56.318 +			this.pstmtGetArticleIndex = conn.prepareStatement(
  56.319 +				"SELECT article_index FROM postings WHERE "
  56.320 +				+ "article_id = (SELECT article_id FROM article_ids "
  56.321 +				+ "WHERE message_id = ?) "
  56.322 +				+ " AND group_id = ?");
  56.323  
  56.324 -      // Prepare statement for method getMaxArticleID()
  56.325 -      this.pstmtGetMaxArticleID = conn.prepareStatement(
  56.326 -        "SELECT Max(article_id) FROM articles");
  56.327 -      
  56.328 -      // Prepare statement for method getMaxArticleIndex()
  56.329 -      this.pstmtGetMaxArticleIndex = conn.prepareStatement(
  56.330 -        "SELECT Max(article_index) FROM postings WHERE group_id = ?");
  56.331 -      
  56.332 -      // Prepare statement for method getOldestArticle()
  56.333 -      this.pstmtGetOldestArticle = conn.prepareStatement(
  56.334 -        "SELECT message_id FROM article_ids WHERE article_id = " +
  56.335 -        "(SELECT Min(article_id) FROM article_ids)");
  56.336 +			// Prepare statements for method getArticleHeads()
  56.337 +			this.pstmtGetArticleHeads = conn.prepareStatement(
  56.338 +				"SELECT article_id, article_index FROM postings WHERE "
  56.339 +				+ "postings.group_id = ? AND article_index >= ? AND "
  56.340 +				+ "article_index <= ?");
  56.341  
  56.342 -      // Prepare statement for method getFirstArticleNumber()
  56.343 -      this.pstmtGetFirstArticleNumber = conn.prepareStatement(
  56.344 -        "SELECT Min(article_index) FROM postings WHERE group_id = ?");
  56.345 -      
  56.346 -      // Prepare statement for method getPostingsCount()
  56.347 -      this.pstmtGetPostingsCount = conn.prepareStatement(
  56.348 -        "SELECT Count(*) FROM postings NATURAL JOIN groups " +
  56.349 -        "WHERE groups.name = ?");
  56.350 -      
  56.351 -      // Prepare statement for method getSubscriptions()
  56.352 -      this.pstmtGetSubscriptions = conn.prepareStatement(
  56.353 -        "SELECT host, port, name FROM peers NATURAL JOIN " +
  56.354 -        "peer_subscriptions NATURAL JOIN groups WHERE feedtype = ?");
  56.355 -      
  56.356 -      // Prepare statement for method isArticleExisting()
  56.357 -      this.pstmtIsArticleExisting = conn.prepareStatement(
  56.358 -        "SELECT Count(article_id) FROM article_ids WHERE message_id = ?");
  56.359 -      
  56.360 -      // Prepare statement for method isGroupExisting()
  56.361 -      this.pstmtIsGroupExisting = conn.prepareStatement(
  56.362 -        "SELECT * FROM groups WHERE name = ?");
  56.363 -      
  56.364 -      // Prepare statement for method setConfigValue()
  56.365 -      this.pstmtSetConfigValue0 = conn.prepareStatement(
  56.366 -        "DELETE FROM config WHERE config_key = ?");
  56.367 -      this.pstmtSetConfigValue1 = conn.prepareStatement(
  56.368 -        "INSERT INTO config VALUES(?, ?)");
  56.369 +			// Prepare statements for method getConfigValue()
  56.370 +			this.pstmtGetConfigValue = conn.prepareStatement(
  56.371 +				"SELECT config_value FROM config WHERE config_key = ?");
  56.372  
  56.373 -      // Prepare statements for method purgeGroup()
  56.374 -      this.pstmtPurgeGroup0 = conn.prepareStatement(
  56.375 -        "DELETE FROM peer_subscriptions WHERE group_id = ?");
  56.376 -      this.pstmtPurgeGroup1 = conn.prepareStatement(
  56.377 -        "DELETE FROM groups WHERE group_id = ?");
  56.378 +			// Prepare statements for method getEventsCount()
  56.379 +			this.pstmtGetEventsCount0 = conn.prepareStatement(
  56.380 +				"SELECT Count(*) FROM events WHERE event_key = ? AND "
  56.381 +				+ "event_time >= ? AND event_time < ?");
  56.382  
  56.383 -      // Prepare statement for method update(Group)
  56.384 -      this.pstmtUpdateGroup = conn.prepareStatement(
  56.385 -        "UPDATE groups SET flags = ?, name = ? WHERE group_id = ?");
  56.386 -    }
  56.387 -    catch(ClassNotFoundException ex)
  56.388 -    {
  56.389 -      throw new Error("JDBC Driver not found!", ex);
  56.390 -    }
  56.391 -  }
  56.392 -  
  56.393 -  /**
  56.394 -   * Adds an article to the database.
  56.395 -   * @param article
  56.396 -   * @return
  56.397 -   * @throws java.sql.SQLException
  56.398 -   */
  56.399 -  @Override
  56.400 -  public void addArticle(final Article article)
  56.401 -    throws StorageBackendException
  56.402 -  {
  56.403 -    try
  56.404 -    {
  56.405 -      this.conn.setAutoCommit(false);
  56.406 +			this.pstmtGetEventsCount1 = conn.prepareStatement(
  56.407 +				"SELECT Count(*) FROM events WHERE event_key = ? AND "
  56.408 +				+ "event_time >= ? AND event_time < ? AND group_id = ?");
  56.409  
  56.410 -      int newArticleID = getMaxArticleID() + 1;
  56.411 +			// Prepare statement for method getGroupForList()
  56.412 +			this.pstmtGetGroupForList = conn.prepareStatement(
  56.413 +				"SELECT name FROM groups INNER JOIN groups2list "
  56.414 +				+ "ON groups.group_id = groups2list.group_id "
  56.415 +				+ "WHERE groups2list.listaddress = ?");
  56.416  
  56.417 -      // Fill prepared statement with values;
  56.418 -      // writes body to article table
  56.419 -      pstmtAddArticle1.setInt(1, newArticleID);
  56.420 -      pstmtAddArticle1.setBytes(2, article.getBody());
  56.421 -      pstmtAddArticle1.execute();
  56.422 +			// Prepare statement for method getGroup()
  56.423 +			this.pstmtGetGroup0 = conn.prepareStatement(
  56.424 +				"SELECT group_id, flags FROM groups WHERE Name = ?");
  56.425 +			this.pstmtGetGroup1 = conn.prepareStatement(
  56.426 +				"SELECT name FROM groups WHERE group_id = ?");
  56.427  
  56.428 -      // Add headers
  56.429 -      Enumeration headers = article.getAllHeaders();
  56.430 -      for(int n = 0; headers.hasMoreElements(); n++)
  56.431 -      {
  56.432 -        Header header = (Header)headers.nextElement();
  56.433 -        pstmtAddArticle2.setInt(1, newArticleID);
  56.434 -        pstmtAddArticle2.setString(2, header.getName().toLowerCase());
  56.435 -        pstmtAddArticle2.setString(3, 
  56.436 -          header.getValue().replaceAll("[\r\n]", ""));
  56.437 -        pstmtAddArticle2.setInt(4, n);
  56.438 -        pstmtAddArticle2.execute();
  56.439 -      }
  56.440 -      
  56.441 -      // For each newsgroup add a reference
  56.442 -      List<Group> groups = article.getGroups();
  56.443 -      for(Group group : groups)
  56.444 -      {
  56.445 -        pstmtAddArticle3.setLong(1, group.getInternalID());
  56.446 -        pstmtAddArticle3.setInt(2, newArticleID);
  56.447 -        pstmtAddArticle3.setLong(3, getMaxArticleIndex(group.getInternalID()) + 1);
  56.448 -        pstmtAddArticle3.execute();
  56.449 -      }
  56.450 -      
  56.451 -      // Write message-id to article_ids table
  56.452 -      this.pstmtAddArticle4.setInt(1, newArticleID);
  56.453 -      this.pstmtAddArticle4.setString(2, article.getMessageID());
  56.454 -      this.pstmtAddArticle4.execute();
  56.455 +			// Prepare statement for method getLastArticleNumber()
  56.456 +			this.pstmtGetLastArticleNumber = conn.prepareStatement(
  56.457 +				"SELECT Max(article_index) FROM postings WHERE group_id = ?");
  56.458  
  56.459 -      this.conn.commit();
  56.460 -      this.conn.setAutoCommit(true);
  56.461 +			// Prepare statement for method getListForGroup()
  56.462 +			this.pstmtGetListForGroup = conn.prepareStatement(
  56.463 +				"SELECT listaddress FROM groups2list INNER JOIN groups "
  56.464 +				+ "ON groups.group_id = groups2list.group_id WHERE name = ?");
  56.465  
  56.466 -      this.restarts = 0; // Reset error count
  56.467 -    }
  56.468 -    catch(SQLException ex)
  56.469 -    {
  56.470 -      try
  56.471 -      {
  56.472 -        this.conn.rollback();  // Rollback changes
  56.473 -      }
  56.474 -      catch(SQLException ex2)
  56.475 -      {
  56.476 -        Log.get().severe("Rollback of addArticle() failed: " + ex2);
  56.477 -      }
  56.478 -      
  56.479 -      try
  56.480 -      {
  56.481 -        this.conn.setAutoCommit(true); // and release locks
  56.482 -      }
  56.483 -      catch(SQLException ex2)
  56.484 -      {
  56.485 -        Log.get().severe("setAutoCommit(true) of addArticle() failed: " + ex2);
  56.486 -      }
  56.487 +			// Prepare statement for method getMaxArticleID()
  56.488 +			this.pstmtGetMaxArticleID = conn.prepareStatement(
  56.489 +				"SELECT Max(article_id) FROM articles");
  56.490  
  56.491 -      restartConnection(ex);
  56.492 -      addArticle(article);
  56.493 -    }
  56.494 -  }
  56.495 -  
  56.496 -  /**
  56.497 -   * Adds a group to the JDBCDatabase. This method is not accessible via NNTP.
  56.498 -   * @param name
  56.499 -   * @throws java.sql.SQLException
  56.500 -   */
  56.501 -  @Override
  56.502 -  public void addGroup(String name, int flags)
  56.503 -    throws StorageBackendException
  56.504 -  {
  56.505 -    try
  56.506 -    {
  56.507 -      this.conn.setAutoCommit(false);
  56.508 -      pstmtAddGroup0.setString(1, name);
  56.509 -      pstmtAddGroup0.setInt(2, flags);
  56.510 +			// Prepare statement for method getMaxArticleIndex()
  56.511 +			this.pstmtGetMaxArticleIndex = conn.prepareStatement(
  56.512 +				"SELECT Max(article_index) FROM postings WHERE group_id = ?");
  56.513  
  56.514 -      pstmtAddGroup0.executeUpdate();
  56.515 -      this.conn.commit();
  56.516 -      this.conn.setAutoCommit(true);
  56.517 -      this.restarts = 0; // Reset error count
  56.518 -    }
  56.519 -    catch(SQLException ex)
  56.520 -    {
  56.521 -      try
  56.522 -      {
  56.523 -        this.conn.rollback();
  56.524 -        this.conn.setAutoCommit(true);
  56.525 -      }
  56.526 -      catch(SQLException ex2)
  56.527 -      {
  56.528 -        ex2.printStackTrace();
  56.529 -      }
  56.530 +			// Prepare statement for method getOldestArticle()
  56.531 +			this.pstmtGetOldestArticle = conn.prepareStatement(
  56.532 +				"SELECT message_id FROM article_ids WHERE article_id = "
  56.533 +				+ "(SELECT Min(article_id) FROM article_ids)");
  56.534  
  56.535 -      restartConnection(ex);
  56.536 -      addGroup(name, flags);
  56.537 -    }
  56.538 -  }
  56.539 +			// Prepare statement for method getFirstArticleNumber()
  56.540 +			this.pstmtGetFirstArticleNumber = conn.prepareStatement(
  56.541 +				"SELECT Min(article_index) FROM postings WHERE group_id = ?");
  56.542  
  56.543 -  @Override
  56.544 -  public void addEvent(long time, int type, long gid)
  56.545 -    throws StorageBackendException
  56.546 -  {
  56.547 -    try
  56.548 -    {
  56.549 -      this.conn.setAutoCommit(false);
  56.550 -      this.pstmtAddEvent.setLong(1, time);
  56.551 -      this.pstmtAddEvent.setInt(2, type);
  56.552 -      this.pstmtAddEvent.setLong(3, gid);
  56.553 -      this.pstmtAddEvent.executeUpdate();
  56.554 -      this.conn.commit();
  56.555 -      this.conn.setAutoCommit(true);
  56.556 -      this.restarts = 0;
  56.557 -    }
  56.558 -    catch(SQLException ex)
  56.559 -    {
  56.560 -      try
  56.561 -      {
  56.562 -        this.conn.rollback();
  56.563 -        this.conn.setAutoCommit(true);
  56.564 -      }
  56.565 -      catch(SQLException ex2)
  56.566 -      {
  56.567 -        ex2.printStackTrace();
  56.568 -      }
  56.569 +			// Prepare statement for method getPostingsCount()
  56.570 +			this.pstmtGetPostingsCount = conn.prepareStatement(
  56.571 +				"SELECT Count(*) FROM postings NATURAL JOIN groups "
  56.572 +				+ "WHERE groups.name = ?");
  56.573  
  56.574 -      restartConnection(ex);
  56.575 -      addEvent(time, type, gid);
  56.576 -    }
  56.577 -  }
  56.578 +			// Prepare statement for method getSubscriptions()
  56.579 +			this.pstmtGetSubscriptions = conn.prepareStatement(
  56.580 +				"SELECT host, port, name FROM peers NATURAL JOIN "
  56.581 +				+ "peer_subscriptions NATURAL JOIN groups WHERE feedtype = ?");
  56.582  
  56.583 -  @Override
  56.584 -  public int countArticles()
  56.585 -    throws StorageBackendException
  56.586 -  {
  56.587 -    ResultSet rs = null;
  56.588 +			// Prepare statement for method isArticleExisting()
  56.589 +			this.pstmtIsArticleExisting = conn.prepareStatement(
  56.590 +				"SELECT Count(article_id) FROM article_ids WHERE message_id = ?");
  56.591  
  56.592 -    try
  56.593 -    {
  56.594 -      rs = this.pstmtCountArticles.executeQuery();
  56.595 -      if(rs.next())
  56.596 -      {
  56.597 -        return rs.getInt(1);
  56.598 -      }
  56.599 -      else
  56.600 -      {
  56.601 -        return -1;
  56.602 -      }
  56.603 -    }
  56.604 -    catch(SQLException ex)
  56.605 -    {
  56.606 -      restartConnection(ex);
  56.607 -      return countArticles();
  56.608 -    }
  56.609 -    finally
  56.610 -    {
  56.611 -      if(rs != null)
  56.612 -      {
  56.613 -        try
  56.614 -        {
  56.615 -          rs.close();
  56.616 -        }
  56.617 -        catch(SQLException ex)
  56.618 -        {
  56.619 -          ex.printStackTrace();
  56.620 -        }
  56.621 -        restarts = 0;
  56.622 -      }
  56.623 -    }
  56.624 -  }
  56.625 +			// Prepare statement for method isGroupExisting()
  56.626 +			this.pstmtIsGroupExisting = conn.prepareStatement(
  56.627 +				"SELECT * FROM groups WHERE name = ?");
  56.628  
  56.629 -  @Override
  56.630 -  public int countGroups()
  56.631 -    throws StorageBackendException
  56.632 -  {
  56.633 -    ResultSet rs = null;
  56.634 +			// Prepare statement for method setConfigValue()
  56.635 +			this.pstmtSetConfigValue0 = conn.prepareStatement(
  56.636 +				"DELETE FROM config WHERE config_key = ?");
  56.637 +			this.pstmtSetConfigValue1 = conn.prepareStatement(
  56.638 +				"INSERT INTO config VALUES(?, ?)");
  56.639  
  56.640 -    try
  56.641 -    {
  56.642 -      rs = this.pstmtCountGroups.executeQuery();
  56.643 -      if(rs.next())
  56.644 -      {
  56.645 -        return rs.getInt(1);
  56.646 -      }
  56.647 -      else
  56.648 -      {
  56.649 -        return -1;
  56.650 -      }
  56.651 -    }
  56.652 -    catch(SQLException ex)
  56.653 -    {
  56.654 -      restartConnection(ex);
  56.655 -      return countGroups();
  56.656 -    }
  56.657 -    finally
  56.658 -    {
  56.659 -      if(rs != null)
  56.660 -      {
  56.661 -        try
  56.662 -        {
  56.663 -          rs.close();
  56.664 -        }
  56.665 -        catch(SQLException ex)
  56.666 -        {
  56.667 -          ex.printStackTrace();
  56.668 -        }
  56.669 -        restarts = 0;
  56.670 -      }
  56.671 -    }
  56.672 -  }
  56.673 +			// Prepare statements for method purgeGroup()
  56.674 +			this.pstmtPurgeGroup0 = conn.prepareStatement(
  56.675 +				"DELETE FROM peer_subscriptions WHERE group_id = ?");
  56.676 +			this.pstmtPurgeGroup1 = conn.prepareStatement(
  56.677 +				"DELETE FROM groups WHERE group_id = ?");
  56.678  
  56.679 -  @Override
  56.680 -  public void delete(final String messageID)
  56.681 -    throws StorageBackendException
  56.682 -  {
  56.683 -    try
  56.684 -    {
  56.685 -      this.conn.setAutoCommit(false);
  56.686 -      
  56.687 -      this.pstmtDeleteArticle0.setString(1, messageID);
  56.688 -      int rs = this.pstmtDeleteArticle0.executeUpdate();
  56.689 -      
  56.690 -      // We do not trust the ON DELETE CASCADE functionality to delete
  56.691 -      // orphaned references...
  56.692 -      this.pstmtDeleteArticle1.setString(1, messageID);
  56.693 -      rs = this.pstmtDeleteArticle1.executeUpdate();
  56.694 +			// Prepare statement for method update(Group)
  56.695 +			this.pstmtUpdateGroup = conn.prepareStatement(
  56.696 +				"UPDATE groups SET flags = ?, name = ? WHERE group_id = ?");
  56.697 +		} catch (ClassNotFoundException ex) {
  56.698 +			throw new Error("JDBC Driver not found!", ex);
  56.699 +		}
  56.700 +	}
  56.701  
  56.702 -      this.pstmtDeleteArticle2.setString(1, messageID);
  56.703 -      rs = this.pstmtDeleteArticle2.executeUpdate();
  56.704 +	/**
  56.705 +	 * Adds an article to the database.
  56.706 +	 * @param article
  56.707 +	 * @return
  56.708 +	 * @throws java.sql.SQLException
  56.709 +	 */
  56.710 +	@Override
  56.711 +	public void addArticle(final Article article)
  56.712 +		throws StorageBackendException
  56.713 +	{
  56.714 +		try {
  56.715 +			this.conn.setAutoCommit(false);
  56.716  
  56.717 -      this.pstmtDeleteArticle3.setString(1, messageID);
  56.718 -      rs = this.pstmtDeleteArticle3.executeUpdate();
  56.719 -      
  56.720 -      this.conn.commit();
  56.721 -      this.conn.setAutoCommit(true);
  56.722 -    }
  56.723 -    catch(SQLException ex)
  56.724 -    {
  56.725 -      throw new StorageBackendException(ex);
  56.726 -    }
  56.727 -  }
  56.728 +			int newArticleID = getMaxArticleID() + 1;
  56.729  
  56.730 -  @Override
  56.731 -  public Article getArticle(String messageID)
  56.732 -    throws StorageBackendException
  56.733 -  {
  56.734 -    ResultSet rs = null;
  56.735 -    try
  56.736 -    {
  56.737 -      pstmtGetArticle0.setString(1, messageID);
  56.738 -      rs = pstmtGetArticle0.executeQuery();
  56.739 +			// Fill prepared statement with values;
  56.740 +			// writes body to article table
  56.741 +			pstmtAddArticle1.setInt(1, newArticleID);
  56.742 +			pstmtAddArticle1.setBytes(2, article.getBody());
  56.743 +			pstmtAddArticle1.execute();
  56.744  
  56.745 -      if(!rs.next())
  56.746 -      {
  56.747 -        return null;
  56.748 -      }
  56.749 -      else
  56.750 -      {
  56.751 -        byte[] body     = rs.getBytes("body");
  56.752 -        String headers  = getArticleHeaders(rs.getInt("article_id"));
  56.753 -        return new Article(headers, body);
  56.754 -      }
  56.755 -    }
  56.756 -    catch(SQLException ex)
  56.757 -    {
  56.758 -      restartConnection(ex);
  56.759 -      return getArticle(messageID);
  56.760 -    }
  56.761 -    finally
  56.762 -    {
  56.763 -      if(rs != null)
  56.764 -      {
  56.765 -        try
  56.766 -        {
  56.767 -          rs.close();
  56.768 -        }
  56.769 -        catch(SQLException ex)
  56.770 -        {
  56.771 -          ex.printStackTrace();
  56.772 -        }
  56.773 -        restarts = 0; // Reset error count
  56.774 -      }
  56.775 -    }
  56.776 -  }
  56.777 -  
  56.778 -  /**
  56.779 -   * Retrieves an article by its ID.
  56.780 -   * @param articleID
  56.781 -   * @return
  56.782 -   * @throws StorageBackendException
  56.783 -   */
  56.784 -  @Override
  56.785 -  public Article getArticle(long articleIndex, long gid)
  56.786 -    throws StorageBackendException
  56.787 -  {  
  56.788 -    ResultSet rs = null;
  56.789 +			// Add headers
  56.790 +			Enumeration headers = article.getAllHeaders();
  56.791 +			for (int n = 0; headers.hasMoreElements(); n++) {
  56.792 +				Header header = (Header) headers.nextElement();
  56.793 +				pstmtAddArticle2.setInt(1, newArticleID);
  56.794 +				pstmtAddArticle2.setString(2, header.getName().toLowerCase());
  56.795 +				pstmtAddArticle2.setString(3,
  56.796 +					header.getValue().replaceAll("[\r\n]", ""));
  56.797 +				pstmtAddArticle2.setInt(4, n);
  56.798 +				pstmtAddArticle2.execute();
  56.799 +			}
  56.800  
  56.801 -    try
  56.802 -    {
  56.803 -      this.pstmtGetArticle1.setLong(1, articleIndex);
  56.804 -      this.pstmtGetArticle1.setLong(2, gid);
  56.805 +			// For each newsgroup add a reference
  56.806 +			List<Group> groups = article.getGroups();
  56.807 +			for (Group group : groups) {
  56.808 +				pstmtAddArticle3.setLong(1, group.getInternalID());
  56.809 +				pstmtAddArticle3.setInt(2, newArticleID);
  56.810 +				pstmtAddArticle3.setLong(3, getMaxArticleIndex(group.getInternalID()) + 1);
  56.811 +				pstmtAddArticle3.execute();
  56.812 +			}
  56.813  
  56.814 -      rs = this.pstmtGetArticle1.executeQuery();
  56.815 +			// Write message-id to article_ids table
  56.816 +			this.pstmtAddArticle4.setInt(1, newArticleID);
  56.817 +			this.pstmtAddArticle4.setString(2, article.getMessageID());
  56.818 +			this.pstmtAddArticle4.execute();
  56.819  
  56.820 -      if(rs.next())
  56.821 -      {
  56.822 -        byte[] body    = rs.getBytes("body");
  56.823 -        String headers = getArticleHeaders(rs.getInt("article_id"));
  56.824 -        return new Article(headers, body);
  56.825 -      }
  56.826 -      else
  56.827 -      {
  56.828 -        return null;
  56.829 -      }
  56.830 -    }
  56.831 -    catch(SQLException ex)
  56.832 -    {
  56.833 -      restartConnection(ex);
  56.834 -      return getArticle(articleIndex, gid);
  56.835 -    }
  56.836 -    finally
  56.837 -    {
  56.838 -      if(rs != null)
  56.839 -      {
  56.840 -        try
  56.841 -        {
  56.842 -          rs.close();
  56.843 -        }
  56.844 -        catch(SQLException ex)
  56.845 -        {
  56.846 -          ex.printStackTrace();
  56.847 -        }
  56.848 -        restarts = 0;
  56.849 -      }
  56.850 -    }
  56.851 -  }
  56.852 +			this.conn.commit();
  56.853 +			this.conn.setAutoCommit(true);
  56.854  
  56.855 -  /**
  56.856 -   * Searches for fitting header values using the given regular expression.
  56.857 -   * @param group
  56.858 -   * @param start
  56.859 -   * @param end
  56.860 -   * @param headerKey
  56.861 -   * @param pattern
  56.862 -   * @return
  56.863 -   * @throws StorageBackendException
  56.864 -   */
  56.865 -  @Override
  56.866 -  public List<Pair<Long, String>> getArticleHeaders(Channel group, long start,
  56.867 -    long end, String headerKey, String patStr)
  56.868 -    throws StorageBackendException, PatternSyntaxException
  56.869 -  {
  56.870 -    ResultSet rs = null;
  56.871 -    List<Pair<Long, String>> heads = new ArrayList<Pair<Long, String>>();
  56.872 +			this.restarts = 0; // Reset error count
  56.873 +		} catch (SQLException ex) {
  56.874 +			try {
  56.875 +				this.conn.rollback();  // Rollback changes
  56.876 +			} catch (SQLException ex2) {
  56.877 +				Log.get().severe("Rollback of addArticle() failed: " + ex2);
  56.878 +			}
  56.879  
  56.880 -    try
  56.881 -    {
  56.882 -      this.pstmtGetArticleHeaders1.setString(1, group.getName());
  56.883 -      this.pstmtGetArticleHeaders1.setString(2, headerKey);
  56.884 -      this.pstmtGetArticleHeaders1.setLong(3, start);
  56.885 +			try {
  56.886 +				this.conn.setAutoCommit(true); // and release locks
  56.887 +			} catch (SQLException ex2) {
  56.888 +				Log.get().severe("setAutoCommit(true) of addArticle() failed: " + ex2);
  56.889 +			}
  56.890  
  56.891 -      rs = this.pstmtGetArticleHeaders1.executeQuery();
  56.892 +			restartConnection(ex);
  56.893 +			addArticle(article);
  56.894 +		}
  56.895 +	}
  56.896  
  56.897 -      // Convert the "NNTP" regex to Java regex
  56.898 -      patStr = patStr.replace("*", ".*");
  56.899 -      Pattern pattern = Pattern.compile(patStr);
  56.900 +	/**
  56.901 +	 * Adds a group to the JDBCDatabase. This method is not accessible via NNTP.
  56.902 +	 * @param name
  56.903 +	 * @throws java.sql.SQLException
  56.904 +	 */
  56.905 +	@Override
  56.906 +	public void addGroup(String name, int flags)
  56.907 +		throws StorageBackendException
  56.908 +	{
  56.909 +		try {
  56.910 +			this.conn.setAutoCommit(false);
  56.911 +			pstmtAddGroup0.setString(1, name);
  56.912 +			pstmtAddGroup0.setInt(2, flags);
  56.913  
  56.914 -      while(rs.next())
  56.915 -      {
  56.916 -        Long articleIndex = rs.getLong(1);
  56.917 -        if(end < 0 || articleIndex <= end) // Match start is done via SQL
  56.918 -        {
  56.919 -          String headerValue  = rs.getString(2);
  56.920 -          Matcher matcher = pattern.matcher(headerValue);
  56.921 -          if(matcher.matches())
  56.922 -          {
  56.923 -            heads.add(new Pair<Long, String>(articleIndex, headerValue));
  56.924 -          }
  56.925 -        }
  56.926 -      }
  56.927 -    }
  56.928 -    catch(SQLException ex)
  56.929 -    {
  56.930 -      restartConnection(ex);
  56.931 -      return getArticleHeaders(group, start, end, headerKey, patStr);
  56.932 -    }
  56.933 -    finally
  56.934 -    {
  56.935 -      if(rs != null)
  56.936 -      {
  56.937 -        try
  56.938 -        {
  56.939 -          rs.close();
  56.940 -        }
  56.941 -        catch(SQLException ex)
  56.942 -        {
  56.943 -          ex.printStackTrace();
  56.944 -        }
  56.945 -      }
  56.946 -    }
  56.947 +			pstmtAddGroup0.executeUpdate();
  56.948 +			this.conn.commit();
  56.949 +			this.conn.setAutoCommit(true);
  56.950 +			this.restarts = 0; // Reset error count
  56.951 +		} catch (SQLException ex) {
  56.952 +			try {
  56.953 +				this.conn.rollback();
  56.954 +				this.conn.setAutoCommit(true);
  56.955 +			} catch (SQLException ex2) {
  56.956 +				ex2.printStackTrace();
  56.957 +			}
  56.958  
  56.959 -    return heads;
  56.960 -  }
  56.961 +			restartConnection(ex);
  56.962 +			addGroup(name, flags);
  56.963 +		}
  56.964 +	}
  56.965  
  56.966 -  private String getArticleHeaders(long articleID)
  56.967 -    throws StorageBackendException
  56.968 -  {
  56.969 -    ResultSet rs = null;
  56.970 -    
  56.971 -    try
  56.972 -    {
  56.973 -      this.pstmtGetArticleHeaders0.setLong(1, articleID);
  56.974 -      rs = this.pstmtGetArticleHeaders0.executeQuery();
  56.975 -      
  56.976 -      StringBuilder buf = new StringBuilder();
  56.977 -      if(rs.next())
  56.978 -      {
  56.979 -        for(;;)
  56.980 -        {
  56.981 -          buf.append(rs.getString(1)); // key
  56.982 -          buf.append(": ");
  56.983 -          String foldedValue = MimeUtility.fold(0, rs.getString(2));
  56.984 -          buf.append(foldedValue); // value
  56.985 -          if(rs.next())
  56.986 -          {
  56.987 -            buf.append("\r\n");
  56.988 -          }
  56.989 -          else
  56.990 -          {
  56.991 -            break;
  56.992 -          }
  56.993 -        }
  56.994 -      }
  56.995 -      
  56.996 -      return buf.toString();
  56.997 -    }
  56.998 -    catch(SQLException ex)
  56.999 -    {
 56.1000 -      restartConnection(ex);
 56.1001 -      return getArticleHeaders(articleID);
 56.1002 -    }
 56.1003 -    finally
 56.1004 -    {
 56.1005 -      if(rs != null)
 56.1006 -      {
 56.1007 -        try
 56.1008 -        {
 56.1009 -          rs.close();
 56.1010 -        }
 56.1011 -        catch(SQLException ex)
 56.1012 -        {
 56.1013 -          ex.printStackTrace();
 56.1014 -        }
 56.1015 -      }
 56.1016 -    }
 56.1017 -  }
 56.1018 +	@Override
 56.1019 +	public void addEvent(long time, int type, long gid)
 56.1020 +		throws StorageBackendException
 56.1021 +	{
 56.1022 +		try {
 56.1023 +			this.conn.setAutoCommit(false);
 56.1024 +			this.pstmtAddEvent.setLong(1, time);
 56.1025 +			this.pstmtAddEvent.setInt(2, type);
 56.1026 +			this.pstmtAddEvent.setLong(3, gid);
 56.1027 +			this.pstmtAddEvent.executeUpdate();
 56.1028 +			this.conn.commit();
 56.1029 +			this.conn.setAutoCommit(true);
 56.1030 +			this.restarts = 0;
 56.1031 +		} catch (SQLException ex) {
 56.1032 +			try {
 56.1033 +				this.conn.rollback();
 56.1034 +				this.conn.setAutoCommit(true);
 56.1035 +			} catch (SQLException ex2) {
 56.1036 +				ex2.printStackTrace();
 56.1037 +			}
 56.1038  
 56.1039 -  @Override
 56.1040 -  public long getArticleIndex(Article article, Group group)
 56.1041 -    throws StorageBackendException
 56.1042 -  {
 56.1043 -    ResultSet rs = null;
 56.1044 +			restartConnection(ex);
 56.1045 +			addEvent(time, type, gid);
 56.1046 +		}
 56.1047 +	}
 56.1048  
 56.1049 -    try
 56.1050 -    {
 56.1051 -      this.pstmtGetArticleIndex.setString(1, article.getMessageID());
 56.1052 -      this.pstmtGetArticleIndex.setLong(2, group.getInternalID());
 56.1053 -      
 56.1054 -      rs = this.pstmtGetArticleIndex.executeQuery();
 56.1055 -      if(rs.next())
 56.1056 -      {
 56.1057 -        return rs.getLong(1);
 56.1058 -      }
 56.1059 -      else
 56.1060 -      {
 56.1061 -        return -1;
 56.1062 -      }
 56.1063 -    }
 56.1064 -    catch(SQLException ex)
 56.1065 -    {
 56.1066 -      restartConnection(ex);
 56.1067 -      return getArticleIndex(article, group);
 56.1068 -    }
 56.1069 -    finally
 56.1070 -    {
 56.1071 -      if(rs != null)
 56.1072 -      {
 56.1073 -        try
 56.1074 -        {
 56.1075 -          rs.close();
 56.1076 -        }
 56.1077 -        catch(SQLException ex)
 56.1078 -        {
 56.1079 -          ex.printStackTrace();
 56.1080 -        }
 56.1081 -      }
 56.1082 -    }
 56.1083 -  }
 56.1084 -  
 56.1085 -  /**
 56.1086 -   * Returns a list of Long/Article Pairs.
 56.1087 -   * @throws java.sql.SQLException
 56.1088 -   */
 56.1089 -  @Override
 56.1090 -  public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first,
 56.1091 -    long last)
 56.1092 -    throws StorageBackendException
 56.1093 -  {
 56.1094 -    ResultSet rs = null;
 56.1095 +	@Override
 56.1096 +	public int countArticles()
 56.1097 +		throws StorageBackendException
 56.1098 +	{
 56.1099 +		ResultSet rs = null;
 56.1100  
 56.1101 -    try
 56.1102 -    {
 56.1103 -      this.pstmtGetArticleHeads.setLong(1, group.getInternalID());
 56.1104 -      this.pstmtGetArticleHeads.setLong(2, first);
 56.1105 -      this.pstmtGetArticleHeads.setLong(3, last);
 56.1106 -      rs = pstmtGetArticleHeads.executeQuery();
 56.1107 +		try {
 56.1108 +			rs = this.pstmtCountArticles.executeQuery();
 56.1109 +			if (rs.next()) {
 56.1110 +				return rs.getInt(1);
 56.1111 +			} else {
 56.1112 +				return -1;
 56.1113 +			}
 56.1114 +		} catch (SQLException ex) {
 56.1115 +			restartConnection(ex);
 56.1116 +			return countArticles();
 56.1117 +		} finally {
 56.1118 +			if (rs != null) {
 56.1119 +				try {
 56.1120 +					rs.close();
 56.1121 +				} catch (SQLException ex) {
 56.1122 +					ex.printStackTrace();
 56.1123 +				}
 56.1124 +				restarts = 0;
 56.1125 +			}
 56.1126 +		}
 56.1127 +	}
 56.1128  
 56.1129 -      List<Pair<Long, ArticleHead>> articles 
 56.1130 -        = new ArrayList<Pair<Long, ArticleHead>>();
 56.1131 +	@Override
 56.1132 +	public int countGroups()
 56.1133 +		throws StorageBackendException
 56.1134 +	{
 56.1135 +		ResultSet rs = null;
 56.1136  
 56.1137 -      while (rs.next())
 56.1138 -      {
 56.1139 -        long aid  = rs.getLong("article_id");
 56.1140 -        long aidx = rs.getLong("article_index");
 56.1141 -        String headers = getArticleHeaders(aid);
 56.1142 -        articles.add(new Pair<Long, ArticleHead>(aidx, 
 56.1143 -                        new ArticleHead(headers)));
 56.1144 -      }
 56.1145 +		try {
 56.1146 +			rs = this.pstmtCountGroups.executeQuery();
 56.1147 +			if (rs.next()) {
 56.1148 +				return rs.getInt(1);
 56.1149 +			} else {
 56.1150 +				return -1;
 56.1151 +			}
 56.1152 +		} catch (SQLException ex) {
 56.1153 +			restartConnection(ex);
 56.1154 +			return countGroups();
 56.1155 +		} finally {
 56.1156 +			if (rs != null) {
 56.1157 +				try {
 56.1158 +					rs.close();
 56.1159 +				} catch (SQLException ex) {
 56.1160 +					ex.printStackTrace();
 56.1161 +				}
 56.1162 +				restarts = 0;
 56.1163 +			}
 56.1164 +		}
 56.1165 +	}
 56.1166  
 56.1167 -      return articles;
 56.1168 -    }
 56.1169 -    catch(SQLException ex)
 56.1170 -    {
 56.1171 -      restartConnection(ex);
 56.1172 -      return getArticleHeads(group, first, last);
 56.1173 -    }
 56.1174 -    finally
 56.1175 -    {
 56.1176 -      if(rs != null)
 56.1177 -      {
 56.1178 -        try
 56.1179 -        {
 56.1180 -          rs.close();
 56.1181 -        }
 56.1182 -        catch(SQLException ex)
 56.1183 -        {
 56.1184 -          ex.printStackTrace();
 56.1185 -        }
 56.1186 -      }
 56.1187 -    }
 56.1188 -  }
 56.1189 +	@Override
 56.1190 +	public void delete(final String messageID)
 56.1191 +		throws StorageBackendException
 56.1192 +	{
 56.1193 +		try {
 56.1194 +			this.conn.setAutoCommit(false);
 56.1195  
 56.1196 -  @Override
 56.1197 -  public List<Long> getArticleNumbers(long gid)
 56.1198 -    throws StorageBackendException
 56.1199 -  {
 56.1200 -    ResultSet rs = null;
 56.1201 -    try
 56.1202 -    {
 56.1203 -      List<Long> ids = new ArrayList<Long>();
 56.1204 -      this.pstmtGetArticleIDs.setLong(1, gid);
 56.1205 -      rs = this.pstmtGetArticleIDs.executeQuery();
 56.1206 -      while(rs.next())
 56.1207 -      {
 56.1208 -        ids.add(rs.getLong(1));
 56.1209 -      }
 56.1210 -      return ids;
 56.1211 -    }
 56.1212 -    catch(SQLException ex)
 56.1213 -    {
 56.1214 -      restartConnection(ex);
 56.1215 -      return getArticleNumbers(gid);
 56.1216 -    }
 56.1217 -    finally
 56.1218 -    {
 56.1219 -      if(rs != null)
 56.1220 -      {
 56.1221 -        try
 56.1222 -        {
 56.1223 -          rs.close();
 56.1224 -          restarts = 0; // Clear the restart count after successful request
 56.1225 -        }
 56.1226 -        catch(SQLException ex)
 56.1227 -        {
 56.1228 -          ex.printStackTrace();
 56.1229 -        }
 56.1230 -      }
 56.1231 -    }
 56.1232 -  }
 56.1233 +			this.pstmtDeleteArticle0.setString(1, messageID);
 56.1234 +			int rs = this.pstmtDeleteArticle0.executeUpdate();
 56.1235  
 56.1236 -  @Override
 56.1237 -  public String getConfigValue(String key)
 56.1238 -    throws StorageBackendException
 56.1239 -  {
 56.1240 -    ResultSet rs = null;
 56.1241 -    try
 56.1242 -    {
 56.1243 -      this.pstmtGetConfigValue.setString(1, key);
 56.1244 +			// We do not trust the ON DELETE CASCADE functionality to delete
 56.1245 +			// orphaned references...
 56.1246 +			this.pstmtDeleteArticle1.setString(1, messageID);
 56.1247 +			rs = this.pstmtDeleteArticle1.executeUpdate();
 56.1248  
 56.1249 -      rs = this.pstmtGetConfigValue.executeQuery();
 56.1250 -      if(rs.next())
 56.1251 -      {
 56.1252 -        return rs.getString(1); // First data on index 1 not 0
 56.1253 -      }
 56.1254 -      else
 56.1255 -      {
 56.1256 -        return null;
 56.1257 -      }
 56.1258 -    }
 56.1259 -    catch(SQLException ex)
 56.1260 -    {
 56.1261 -      restartConnection(ex);
 56.1262 -      return getConfigValue(key);
 56.1263 -    }
 56.1264 -    finally
 56.1265 -    {
 56.1266 -      if(rs != null)
 56.1267 -      {
 56.1268 -        try
 56.1269 -        {
 56.1270 -          rs.close();
 56.1271 -        }
 56.1272 -        catch(SQLException ex)
 56.1273 -        {
 56.1274 -          ex.printStackTrace();
 56.1275 -        }
 56.1276 -        restarts = 0; // Clear the restart count after successful request
 56.1277 -      }
 56.1278 -    }
 56.1279 -  }
 56.1280 +			this.pstmtDeleteArticle2.setString(1, messageID);
 56.1281 +			rs = this.pstmtDeleteArticle2.executeUpdate();
 56.1282  
 56.1283 -  @Override
 56.1284 -  public int getEventsCount(int type, long start, long end, Channel channel)
 56.1285 -    throws StorageBackendException
 56.1286 -  {
 56.1287 -    ResultSet rs = null;
 56.1288 -    
 56.1289 -    try
 56.1290 -    {
 56.1291 -      if(channel == null)
 56.1292 -      {
 56.1293 -        this.pstmtGetEventsCount0.setInt(1, type);
 56.1294 -        this.pstmtGetEventsCount0.setLong(2, start);
 56.1295 -        this.pstmtGetEventsCount0.setLong(3, end);
 56.1296 -        rs = this.pstmtGetEventsCount0.executeQuery();
 56.1297 -      }
 56.1298 -      else
 56.1299 -      {
 56.1300 -        this.pstmtGetEventsCount1.setInt(1, type);
 56.1301 -        this.pstmtGetEventsCount1.setLong(2, start);
 56.1302 -        this.pstmtGetEventsCount1.setLong(3, end);
 56.1303 -        this.pstmtGetEventsCount1.setLong(4, channel.getInternalID());
 56.1304 -        rs = this.pstmtGetEventsCount1.executeQuery();
 56.1305 -      }
 56.1306 -      
 56.1307 -      if(rs.next())
 56.1308 -      {
 56.1309 -        return rs.getInt(1);
 56.1310 -      }
 56.1311 -      else
 56.1312 -      {
 56.1313 -        return -1;
 56.1314 -      }
 56.1315 -    }
 56.1316 -    catch(SQLException ex)
 56.1317 -    {
 56.1318 -      restartConnection(ex);
 56.1319 -      return getEventsCount(type, start, end, channel);
 56.1320 -    }
 56.1321 -    finally
 56.1322 -    {
 56.1323 -      if(rs != null)
 56.1324 -      {
 56.1325 -        try
 56.1326 -        {
 56.1327 -          rs.close();
 56.1328 -        }
 56.1329 -        catch(SQLException ex)
 56.1330 -        {
 56.1331 -          ex.printStackTrace();
 56.1332 -        }
 56.1333 -      }
 56.1334 -    }
 56.1335 -  }
 56.1336 -  
 56.1337 -  /**
 56.1338 -   * Reads all Groups from the JDBCDatabase.
 56.1339 -   * @return
 56.1340 -   * @throws StorageBackendException
 56.1341 -   */
 56.1342 -  @Override
 56.1343 -  public List<Channel> getGroups()
 56.1344 -    throws StorageBackendException
 56.1345 -  {
 56.1346 -    ResultSet   rs;
 56.1347 -    List<Channel> buffer = new ArrayList<Channel>();
 56.1348 -    Statement   stmt   = null;
 56.1349 +			this.pstmtDeleteArticle3.setString(1, messageID);
 56.1350 +			rs = this.pstmtDeleteArticle3.executeUpdate();
 56.1351  
 56.1352 -    try
 56.1353 -    {
 56.1354 -      stmt = conn.createStatement();
 56.1355 -      rs = stmt.executeQuery("SELECT * FROM groups ORDER BY name");
 56.1356 +			this.conn.commit();
 56.1357 +			this.conn.setAutoCommit(true);
 56.1358 +		} catch (SQLException ex) {
 56.1359 +			throw new StorageBackendException(ex);
 56.1360 +		}
 56.1361 +	}
 56.1362  
 56.1363 -      while(rs.next())
 56.1364 -      {
 56.1365 -        String name  = rs.getString("name");
 56.1366 -        long   id    = rs.getLong("group_id");
 56.1367 -        int    flags = rs.getInt("flags");
 56.1368 -        
 56.1369 -        Group group = new Group(name, id, flags);
 56.1370 -        buffer.add(group);
 56.1371 -      }
 56.1372 +	@Override
 56.1373 +	public Article getArticle(String messageID)
 56.1374 +		throws StorageBackendException
 56.1375 +	{
 56.1376 +		ResultSet rs = null;
 56.1377 +		try {
 56.1378 +			pstmtGetArticle0.setString(1, messageID);
 56.1379 +			rs = pstmtGetArticle0.executeQuery();
 56.1380  
 56.1381 -      return buffer;
 56.1382 -    }
 56.1383 -    catch(SQLException ex)
 56.1384 -    {
 56.1385 -      restartConnection(ex);
 56.1386 -      return getGroups();
 56.1387 -    }
 56.1388 -    finally
 56.1389 -    {
 56.1390 -      if(stmt != null)
 56.1391 -      {
 56.1392 -        try
 56.1393 -        {
 56.1394 -          stmt.close(); // Implicitely closes ResultSets
 56.1395 -        }
 56.1396 -        catch(SQLException ex)
 56.1397 -        {
 56.1398 -          ex.printStackTrace();
 56.1399 -        }
 56.1400 -      }
 56.1401 -    }
 56.1402 -  }
 56.1403 +			if (!rs.next()) {
 56.1404 +				return null;
 56.1405 +			} else {
 56.1406 +				byte[] body = rs.getBytes("body");
 56.1407 +				String headers = getArticleHeaders(rs.getInt("article_id"));
 56.1408 +				return new Article(headers, body);
 56.1409 +			}
 56.1410 +		} catch (SQLException ex) {
 56.1411 +			restartConnection(ex);
 56.1412 +			return getArticle(messageID);
 56.1413 +		} finally {
 56.1414 +			if (rs != null) {
 56.1415 +				try {
 56.1416 +					rs.close();
 56.1417 +				} catch (SQLException ex) {
 56.1418 +					ex.printStackTrace();
 56.1419 +				}
 56.1420 +				restarts = 0; // Reset error count
 56.1421 +			}
 56.1422 +		}
 56.1423 +	}
 56.1424  
 56.1425 -  @Override
 56.1426 -  public List<String> getGroupsForList(String listAddress)
 56.1427 -    throws StorageBackendException
 56.1428 -  {
 56.1429 -    ResultSet rs = null;
 56.1430 -    
 56.1431 -    try
 56.1432 -    {
 56.1433 -      this.pstmtGetGroupForList.setString(1, listAddress);
 56.1434 +	/**
 56.1435 +	 * Retrieves an article by its ID.
 56.1436 +	 * @param articleID
 56.1437 +	 * @return
 56.1438 +	 * @throws StorageBackendException
 56.1439 +	 */
 56.1440 +	@Override
 56.1441 +	public Article getArticle(long articleIndex, long gid)
 56.1442 +		throws StorageBackendException
 56.1443 +	{
 56.1444 +		ResultSet rs = null;
 56.1445  
 56.1446 -      rs = this.pstmtGetGroupForList.executeQuery();
 56.1447 -      List<String> groups = new ArrayList<String>();
 56.1448 -      while(rs.next())
 56.1449 -      {
 56.1450 -        String group = rs.getString(1);
 56.1451 -        groups.add(group);
 56.1452 -      }
 56.1453 -      return groups;
 56.1454 -    }
 56.1455 -    catch(SQLException ex)
 56.1456 -    {
 56.1457 -      restartConnection(ex);
 56.1458 -      return getGroupsForList(listAddress);
 56.1459 -    }
 56.1460 -    finally
 56.1461 -    {
 56.1462 -      if(rs != null)
 56.1463 -      {
 56.1464 -        try
 56.1465 -        {
 56.1466 -          rs.close();
 56.1467 -        }
 56.1468 -        catch(SQLException ex)
 56.1469 -        {
 56.1470 -          ex.printStackTrace();
 56.1471 -        }
 56.1472 -      }
 56.1473 -    }
 56.1474 -  }
 56.1475 -  
 56.1476 -  /**
 56.1477 -   * Returns the Group that is identified by the name.
 56.1478 -   * @param name
 56.1479 -   * @return
 56.1480 -   * @throws StorageBackendException
 56.1481 -   */
 56.1482 -  @Override
 56.1483 -  public Group getGroup(String name)
 56.1484 -    throws StorageBackendException
 56.1485 -  {
 56.1486 -    ResultSet rs = null;
 56.1487 -    
 56.1488 -    try
 56.1489 -    {
 56.1490 -      this.pstmtGetGroup0.setString(1, name);
 56.1491 -      rs = this.pstmtGetGroup0.executeQuery();
 56.1492 +		try {
 56.1493 +			this.pstmtGetArticle1.setLong(1, articleIndex);
 56.1494 +			this.pstmtGetArticle1.setLong(2, gid);
 56.1495  
 56.1496 -      if (!rs.next())
 56.1497 -      {
 56.1498 -        return null;
 56.1499 -      }
 56.1500 -      else
 56.1501 -      {
 56.1502 -        long id = rs.getLong("group_id");
 56.1503 -        int flags = rs.getInt("flags");
 56.1504 -        return new Group(name, id, flags);
 56.1505 -      }
 56.1506 -    }
 56.1507 -    catch(SQLException ex)
 56.1508 -    {
 56.1509 -      restartConnection(ex);
 56.1510 -      return getGroup(name);
 56.1511 -    }
 56.1512 -    finally
 56.1513 -    {
 56.1514 -      if(rs != null)
 56.1515 -      {
 56.1516 -        try
 56.1517 -        {
 56.1518 -          rs.close();
 56.1519 -        }
 56.1520 -        catch(SQLException ex)
 56.1521 -        {
 56.1522 -          ex.printStackTrace();
 56.1523 -        }
 56.1524 -      }
 56.1525 -    }
 56.1526 -  }
 56.1527 +			rs = this.pstmtGetArticle1.executeQuery();
 56.1528  
 56.1529 -  @Override
 56.1530 -  public List<String> getListsForGroup(String group)
 56.1531 -    throws StorageBackendException
 56.1532 -  {
 56.1533 -    ResultSet     rs    = null;
 56.1534 -    List<String>  lists = new ArrayList<String>();
 56.1535 +			if (rs.next()) {
 56.1536 +				byte[] body = rs.getBytes("body");
 56.1537 +				String headers = getArticleHeaders(rs.getInt("article_id"));
 56.1538 +				return new Article(headers, body);
 56.1539 +			} else {
 56.1540 +				return null;
 56.1541 +			}
 56.1542 +		} catch (SQLException ex) {
 56.1543 +			restartConnection(ex);
 56.1544 +			return getArticle(articleIndex, gid);
 56.1545 +		} finally {
 56.1546 +			if (rs != null) {
 56.1547 +				try {
 56.1548 +					rs.close();
 56.1549 +				} catch (SQLException ex) {
 56.1550 +					ex.printStackTrace();
 56.1551 +				}
 56.1552 +				restarts = 0;
 56.1553 +			}
 56.1554 +		}
 56.1555 +	}
 56.1556  
 56.1557 -    try
 56.1558 -    {
 56.1559 -      this.pstmtGetListForGroup.setString(1, group);
 56.1560 -      rs = this.pstmtGetListForGroup.executeQuery();
 56.1561 +	/**
 56.1562 +	 * Searches for fitting header values using the given regular expression.
 56.1563 +	 * @param group
 56.1564 +	 * @param start
 56.1565 +	 * @param end
 56.1566 +	 * @param headerKey
 56.1567 +	 * @param pattern
 56.1568 +	 * @return
 56.1569 +	 * @throws StorageBackendException
 56.1570 +	 */
 56.1571 +	@Override
 56.1572 +	public List<Pair<Long, String>> getArticleHeaders(Channel group, long start,
 56.1573 +		long end, String headerKey, String patStr)
 56.1574 +		throws StorageBackendException, PatternSyntaxException
 56.1575 +	{
 56.1576 +		ResultSet rs = null;
 56.1577 +		List<Pair<Long, String>> heads = new ArrayList<Pair<Long, String>>();
 56.1578  
 56.1579 -      while(rs.next())
 56.1580 -      {
 56.1581 -        lists.add(rs.getString(1));
 56.1582 -      }
 56.1583 -      return lists;
 56.1584 -    }
 56.1585 -    catch(SQLException ex)
 56.1586 -    {
 56.1587 -      restartConnection(ex);
 56.1588 -      return getListsForGroup(group);
 56.1589 -    }
 56.1590 -    finally
 56.1591 -    {
 56.1592 -      if(rs != null)
 56.1593 -      {
 56.1594 -        try
 56.1595 -        {
 56.1596 -          rs.close();
 56.1597 -        }
 56.1598 -        catch(SQLException ex)
 56.1599 -        {
 56.1600 -          ex.printStackTrace();
 56.1601 -        }
 56.1602 -      }
 56.1603 -    }
 56.1604 -  }
 56.1605 -  
 56.1606 -  private int getMaxArticleIndex(long groupID)
 56.1607 -    throws StorageBackendException
 56.1608 -  {
 56.1609 -    ResultSet rs    = null;
 56.1610 +		try {
 56.1611 +			this.pstmtGetArticleHeaders1.setString(1, group.getName());
 56.1612 +			this.pstmtGetArticleHeaders1.setString(2, headerKey);
 56.1613 +			this.pstmtGetArticleHeaders1.setLong(3, start);
 56.1614  
 56.1615 -    try
 56.1616 -    {
 56.1617 -      this.pstmtGetMaxArticleIndex.setLong(1, groupID);
 56.1618 -      rs = this.pstmtGetMaxArticleIndex.executeQuery();
 56.1619 +			rs = this.pstmtGetArticleHeaders1.executeQuery();
 56.1620  
 56.1621 -      int maxIndex = 0;
 56.1622 -      if (rs.next())
 56.1623 -      {
 56.1624 -        maxIndex = rs.getInt(1);
 56.1625 -      }
 56.1626 +			// Convert the "NNTP" regex to Java regex
 56.1627 +			patStr = patStr.replace("*", ".*");
 56.1628 +			Pattern pattern = Pattern.compile(patStr);
 56.1629  
 56.1630 -      return maxIndex;
 56.1631 -    }
 56.1632 -    catch(SQLException ex)
 56.1633 -    {
 56.1634 -      restartConnection(ex);
 56.1635 -      return getMaxArticleIndex(groupID);
 56.1636 -    }
 56.1637 -    finally
 56.1638 -    {
 56.1639 -      if(rs != null)
 56.1640 -      {
 56.1641 -        try
 56.1642 -        {
 56.1643 -          rs.close();
 56.1644 -        }
 56.1645 -        catch(SQLException ex)
 56.1646 -        {
 56.1647 -          ex.printStackTrace();
 56.1648 -        }
 56.1649 -      }
 56.1650 -    }
 56.1651 -  }
 56.1652 -  
 56.1653 -  private int getMaxArticleID()
 56.1654 -    throws StorageBackendException
 56.1655 -  {
 56.1656 -    ResultSet rs    = null;
 56.1657 +			while (rs.next()) {
 56.1658 +				Long articleIndex = rs.getLong(1);
 56.1659 +				if (end < 0 || articleIndex <= end) // Match start is done via SQL
 56.1660 +				{
 56.1661 +					String headerValue = rs.getString(2);
 56.1662 +					Matcher matcher = pattern.matcher(headerValue);
 56.1663 +					if (matcher.matches()) {
 56.1664 +						heads.add(new Pair<Long, String>(articleIndex, headerValue));
 56.1665 +					}
 56.1666 +				}
 56.1667 +			}
 56.1668 +		} catch (SQLException ex) {
 56.1669 +			restartConnection(ex);
 56.1670 +			return getArticleHeaders(group, start, end, headerKey, patStr);
 56.1671 +		} finally {
 56.1672 +			if (rs != null) {
 56.1673 +				try {
 56.1674 +					rs.close();
 56.1675 +				} catch (SQLException ex) {
 56.1676 +					ex.printStackTrace();
 56.1677 +				}
 56.1678 +			}
 56.1679 +		}
 56.1680  
 56.1681 -    try
 56.1682 -    {
 56.1683 -      rs = this.pstmtGetMaxArticleID.executeQuery();
 56.1684 +		return heads;
 56.1685 +	}
 56.1686  
 56.1687 -      int maxIndex = 0;
 56.1688 -      if (rs.next())
 56.1689 -      {
 56.1690 -        maxIndex = rs.getInt(1);
 56.1691 -      }
 56.1692 +	private String getArticleHeaders(long articleID)
 56.1693 +		throws StorageBackendException
 56.1694 +	{
 56.1695 +		ResultSet rs = null;
 56.1696  
 56.1697 -      return maxIndex;
 56.1698 -    }
 56.1699 -    catch(SQLException ex)
 56.1700 -    {
 56.1701 -      restartConnection(ex);
 56.1702 -      return getMaxArticleID();
 56.1703 -    }
 56.1704 -    finally
 56.1705 -    {
 56.1706 -      if(rs != null)
 56.1707 -      {
 56.1708 -        try
 56.1709 -        {
 56.1710 -          rs.close();
 56.1711 -        }
 56.1712 -        catch(SQLException ex)
 56.1713 -        {
 56.1714 -          ex.printStackTrace();
 56.1715 -        }
 56.1716 -      }
 56.1717 -    }
 56.1718 -  }
 56.1719 +		try {
 56.1720 +			this.pstmtGetArticleHeaders0.setLong(1, articleID);
 56.1721 +			rs = this.pstmtGetArticleHeaders0.executeQuery();
 56.1722  
 56.1723 -  @Override
 56.1724 -  public int getLastArticleNumber(Group group)
 56.1725 -    throws StorageBackendException
 56.1726 -  {
 56.1727 -    ResultSet rs = null;
 56.1728 +			StringBuilder buf = new StringBuilder();
 56.1729 +			if (rs.next()) {
 56.1730 +				for (;;) {
 56.1731 +					buf.append(rs.getString(1)); // key
 56.1732 +					buf.append(": ");
 56.1733 +					String foldedValue = MimeUtility.fold(0, rs.getString(2));
 56.1734 +					buf.append(foldedValue); // value
 56.1735 +					if (rs.next()) {
 56.1736 +						buf.append("\r\n");
 56.1737 +					} else {
 56.1738 +						break;
 56.1739 +					}
 56.1740 +				}
 56.1741 +			}
 56.1742  
 56.1743 -    try
 56.1744 -    {
 56.1745 -      this.pstmtGetLastArticleNumber.setLong(1, group.getInternalID());
 56.1746 -      rs = this.pstmtGetLastArticleNumber.executeQuery();
 56.1747 -      if (rs.next())
 56.1748 -      {
 56.1749 -        return rs.getInt(1);
 56.1750 -      }
 56.1751 -      else
 56.1752 -      {
 56.1753 -        return 0;
 56.1754 -      }
 56.1755 -    }
 56.1756 -    catch(SQLException ex)
 56.1757 -    {
 56.1758 -      restartConnection(ex);
 56.1759 -      return getLastArticleNumber(group);
 56.1760 -    }
 56.1761 -    finally
 56.1762 -    {
 56.1763 -      if(rs != null)
 56.1764 -      {
 56.1765 -        try
 56.1766 -        {
 56.1767 -          rs.close();
 56.1768 -        }
 56.1769 -        catch(SQLException ex)
 56.1770 -        {
 56.1771 -          ex.printStackTrace();
 56.1772 -        }
 56.1773 -      }
 56.1774 -    }
 56.1775 -  }
 56.1776 +			return buf.toString();
 56.1777 +		} catch (SQLException ex) {
 56.1778 +			restartConnection(ex);
 56.1779 +			return getArticleHeaders(articleID);
 56.1780 +		} finally {
 56.1781 +			if (rs != null) {
 56.1782 +				try {
 56.1783 +					rs.close();
 56.1784 +				} catch (SQLException ex) {
 56.1785 +					ex.printStackTrace();
 56.1786 +				}
 56.1787 +			}
 56.1788 +		}
 56.1789 +	}
 56.1790  
 56.1791 -  @Override
 56.1792 -  public int getFirstArticleNumber(Group group)
 56.1793 -    throws StorageBackendException
 56.1794 -  {
 56.1795 -    ResultSet rs = null;
 56.1796 -    try
 56.1797 -    {
 56.1798 -      this.pstmtGetFirstArticleNumber.setLong(1, group.getInternalID());
 56.1799 -      rs = this.pstmtGetFirstArticleNumber.executeQuery();
 56.1800 -      if(rs.next())
 56.1801 -      {
 56.1802 -        return rs.getInt(1);
 56.1803 -      }
 56.1804 -      else
 56.1805 -      {
 56.1806 -        return 0;
 56.1807 -      }
 56.1808 -    }
 56.1809 -    catch(SQLException ex)
 56.1810 -    {
 56.1811 -      restartConnection(ex);
 56.1812 -      return getFirstArticleNumber(group);
 56.1813 -    }
 56.1814 -    finally
 56.1815 -    {
 56.1816 -      if(rs != null)
 56.1817 -      {
 56.1818 -        try
 56.1819 -        {
 56.1820 -          rs.close();
 56.1821 -        }
 56.1822 -        catch(SQLException ex)
 56.1823 -        {
 56.1824 -          ex.printStackTrace();
 56.1825 -        }
 56.1826 -      }
 56.1827 -    }
 56.1828 -  }
 56.1829 -  
 56.1830 -  /**
 56.1831 -   * Returns a group name identified by the given id.
 56.1832 -   * @param id
 56.1833 -   * @return
 56.1834 -   * @throws StorageBackendException
 56.1835 -   */
 56.1836 -  public String getGroup(int id)
 56.1837 -    throws StorageBackendException
 56.1838 -  {
 56.1839 -    ResultSet rs = null;
 56.1840 +	@Override
 56.1841 +	public long getArticleIndex(Article article, Group group)
 56.1842 +		throws StorageBackendException
 56.1843 +	{
 56.1844 +		ResultSet rs = null;
 56.1845  
 56.1846 -    try
 56.1847 -    {
 56.1848 -      this.pstmtGetGroup1.setInt(1, id);
 56.1849 -      rs = this.pstmtGetGroup1.executeQuery();
 56.1850 +		try {
 56.1851 +			this.pstmtGetArticleIndex.setString(1, article.getMessageID());
 56.1852 +			this.pstmtGetArticleIndex.setLong(2, group.getInternalID());
 56.1853  
 56.1854 -      if (rs.next())
 56.1855 -      {
 56.1856 -        return rs.getString(1);
 56.1857 -      }
 56.1858 -      else
 56.1859 -      {
 56.1860 -        return null;
 56.1861 -      }
 56.1862 -    }
 56.1863 -    catch(SQLException ex)
 56.1864 -    {
 56.1865 -      restartConnection(ex);
 56.1866 -      return getGroup(id);
 56.1867 -    }
 56.1868 -    finally
 56.1869 -    {
 56.1870 -      if(rs != null)
 56.1871 -      {
 56.1872 -        try
 56.1873 -        {
 56.1874 -          rs.close();
 56.1875 -        }
 56.1876 -        catch(SQLException ex)
 56.1877 -        {
 56.1878 -          ex.printStackTrace();
 56.1879 -        }
 56.1880 -      }
 56.1881 -    }
 56.1882 -  }
 56.1883 +			rs = this.pstmtGetArticleIndex.executeQuery();
 56.1884 +			if (rs.next()) {
 56.1885 +				return rs.getLong(1);
 56.1886 +			} else {
 56.1887 +				return -1;
 56.1888 +			}
 56.1889 +		} catch (SQLException ex) {
 56.1890 +			restartConnection(ex);
 56.1891 +			return getArticleIndex(article, group);
 56.1892 +		} finally {
 56.1893 +			if (rs != null) {
 56.1894 +				try {
 56.1895 +					rs.close();
 56.1896 +				} catch (SQLException ex) {
 56.1897 +					ex.printStackTrace();
 56.1898 +				}
 56.1899 +			}
 56.1900 +		}
 56.1901 +	}
 56.1902  
 56.1903 -  @Override
 56.1904 -  public double getEventsPerHour(int key, long gid)
 56.1905 -    throws StorageBackendException
 56.1906 -  {
 56.1907 -    String gidquery = "";
 56.1908 -    if(gid >= 0)
 56.1909 -    {
 56.1910 -      gidquery = " AND group_id = " + gid;
 56.1911 -    }
 56.1912 -    
 56.1913 -    Statement stmt = null;
 56.1914 -    ResultSet rs   = null;
 56.1915 -    
 56.1916 -    try
 56.1917 -    {
 56.1918 -      stmt = this.conn.createStatement();
 56.1919 -      rs = stmt.executeQuery("SELECT Count(*) / (Max(event_time) - Min(event_time))" +
 56.1920 -        " * 1000 * 60 * 60 FROM events WHERE event_key = " + key + gidquery);
 56.1921 -      
 56.1922 -      if(rs.next())
 56.1923 -      {
 56.1924 -        restarts = 0; // reset error count
 56.1925 -        return rs.getDouble(1);
 56.1926 -      }
 56.1927 -      else
 56.1928 -      {
 56.1929 -        return Double.NaN;
 56.1930 -      }
 56.1931 -    }
 56.1932 -    catch(SQLException ex)
 56.1933 -    {
 56.1934 -      restartConnection(ex);
 56.1935 -      return getEventsPerHour(key, gid);
 56.1936 -    }
 56.1937 -    finally
 56.1938 -    {
 56.1939 -      try
 56.1940 -      {
 56.1941 -        if(stmt != null)
 56.1942 -        {
 56.1943 -          stmt.close(); // Implicitely closes the result sets
 56.1944 -        }
 56.1945 -      }
 56.1946 -      catch(SQLException ex)
 56.1947 -      {
 56.1948 -        ex.printStackTrace();
 56.1949 -      }
 56.1950 -    }
 56.1951 -  }
 56.1952 +	/**
 56.1953 +	 * Returns a list of Long/Article Pairs.
 56.1954 +	 * @throws java.sql.SQLException
 56.1955 +	 */
 56.1956 +	@Override
 56.1957 +	public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first,
 56.1958 +		long last)
 56.1959 +		throws StorageBackendException
 56.1960 +	{
 56.1961 +		ResultSet rs = null;
 56.1962  
 56.1963 -  @Override
 56.1964 -  public String getOldestArticle()
 56.1965 -    throws StorageBackendException
 56.1966 -  {
 56.1967 -    ResultSet rs = null;
 56.1968 +		try {
 56.1969 +			this.pstmtGetArticleHeads.setLong(1, group.getInternalID());
 56.1970 +			this.pstmtGetArticleHeads.setLong(2, first);
 56.1971 +			this.pstmtGetArticleHeads.setLong(3, last);
 56.1972 +			rs = pstmtGetArticleHeads.executeQuery();
 56.1973  
 56.1974 -    try
 56.1975 -    {
 56.1976 -      rs = this.pstmtGetOldestArticle.executeQuery();
 56.1977 -      if(rs.next())
 56.1978 -      {
 56.1979 -        return rs.getString(1);
 56.1980 -      }
 56.1981 -      else
 56.1982 -      {
 56.1983 -        return null;
 56.1984 -      }
 56.1985 -    }
 56.1986 -    catch(SQLException ex)
 56.1987 -    {
 56.1988 -      restartConnection(ex);
 56.1989 -      return getOldestArticle();
 56.1990 -    }
 56.1991 -    finally
 56.1992 -    {
 56.1993 -      if(rs != null)
 56.1994 -      {
 56.1995 -        try
 56.1996 -        {
 56.1997 -          rs.close();
 56.1998 -        }
 56.1999 -        catch(SQLException ex)
 56.2000 -        {
 56.2001 -          ex.printStackTrace();
 56.2002 -        }
 56.2003 -      }
 56.2004 -    }
 56.2005 -  }
 56.2006 +			List<Pair<Long, ArticleHead>> articles = new ArrayList<Pair<Long, ArticleHead>>();
 56.2007  
 56.2008 -  @Override
 56.2009 -  public int getPostingsCount(String groupname)
 56.2010 -    throws StorageBackendException
 56.2011 -  {
 56.2012 -    ResultSet rs = null;
 56.2013 -    
 56.2014 -    try
 56.2015 -    {
 56.2016 -      this.pstmtGetPostingsCount.setString(1, groupname);
 56.2017 -      rs = this.pstmtGetPostingsCount.executeQuery();
 56.2018 -      if(rs.next())
 56.2019 -      {
 56.2020 -        return rs.getInt(1);
 56.2021 -      }
 56.2022 -      else
 56.2023 -      {
 56.2024 -        Log.get().warning("Count on postings return nothing!");
 56.2025 -        return 0;
 56.2026 -      }
 56.2027 -    }
 56.2028 -    catch(SQLException ex)
 56.2029 -    {
 56.2030 -      restartConnection(ex);
 56.2031 -      return getPostingsCount(groupname);
 56.2032 -    }
 56.2033 -    finally
 56.2034 -    {
 56.2035 -      if(rs != null)
 56.2036 -      {
 56.2037 -        try
 56.2038 -        {
 56.2039 -          rs.close();
 56.2040 -        }
 56.2041 -        catch(SQLException ex)
 56.2042 -        {
 56.2043 -          ex.printStackTrace();
 56.2044 -        }
 56.2045 -      }
 56.2046 -    }
 56.2047 -  }
 56.2048 +			while (rs.next()) {
 56.2049 +				long aid = rs.getLong("article_id");
 56.2050 +				long aidx = rs.getLong("article_index");
 56.2051 +				String headers = getArticleHeaders(aid);
 56.2052 +				articles.add(new Pair<Long, ArticleHead>(aidx,
 56.2053 +					new ArticleHead(headers)));
 56.2054 +			}
 56.2055  
 56.2056 -  @Override
 56.2057 -  public List<Subscription> getSubscriptions(int feedtype)
 56.2058 -    throws StorageBackendException
 56.2059 -  {
 56.2060 -    ResultSet rs = null;
 56.2061 -    
 56.2062 -    try
 56.2063 -    {
 56.2064 -      List<Subscription> subs = new ArrayList<Subscription>();
 56.2065 -      this.pstmtGetSubscriptions.setInt(1, feedtype);
 56.2066 -      rs = this.pstmtGetSubscriptions.executeQuery();
 56.2067 -      
 56.2068 -      while(rs.next())
 56.2069 -      {
 56.2070 -        String host  = rs.getString("host");
 56.2071 -        String group = rs.getString("name");
 56.2072 -        int    port  = rs.getInt("port");
 56.2073 -        subs.add(new Subscription(host, port, feedtype, group));
 56.2074 -      }
 56.2075 -      
 56.2076 -      return subs;
 56.2077 -    }
 56.2078 -    catch(SQLException ex)
 56.2079 -    {
 56.2080 -      restartConnection(ex);
 56.2081 -      return getSubscriptions(feedtype);
 56.2082 -    }
 56.2083 -    finally
 56.2084 -    {
 56.2085 -      if(rs != null)
 56.2086 -      {
 56.2087 -        try
 56.2088 -        {
 56.2089 -          rs.close();
 56.2090 -        }
 56.2091 -        catch(SQLException ex)
 56.2092 -        {
 56.2093 -          ex.printStackTrace();
 56.2094 -        }
 56.2095 -      }
 56.2096 -    }
 56.2097 -  }
 56.2098 +			return articles;
 56.2099 +		} catch (SQLException ex) {
 56.2100 +			restartConnection(ex);
 56.2101 +			return getArticleHeads(group, first, last);
 56.2102 +		} finally {
 56.2103 +			if (rs != null) {
 56.2104 +				try {
 56.2105 +					rs.close();
 56.2106 +				} catch (SQLException ex) {
 56.2107 +					ex.printStackTrace();
 56.2108 +				}
 56.2109 +			}
 56.2110 +		}
 56.2111 +	}
 56.2112  
 56.2113 -  /**
 56.2114 -   * Checks if there is an article with the given messageid in the JDBCDatabase.
 56.2115 -   * @param name
 56.2116 -   * @return
 56.2117 -   * @throws StorageBackendException
 56.2118 -   */
 56.2119 -  @Override
 56.2120 -  public boolean isArticleExisting(String messageID)
 56.2121 -    throws StorageBackendException
 56.2122 -  {
 56.2123 -    ResultSet rs = null;
 56.2124 -    
 56.2125 -    try
 56.2126 -    {
 56.2127 -      this.pstmtIsArticleExisting.setString(1, messageID);
 56.2128 -      rs = this.pstmtIsArticleExisting.executeQuery();
 56.2129 -      return rs.next() && rs.getInt(1) == 1;
 56.2130 -    }
 56.2131 -    catch(SQLException ex)
 56.2132 -    {
 56.2133 -      restartConnection(ex);
 56.2134 -      return isArticleExisting(messageID);
 56.2135 -    }
 56.2136 -    finally
 56.2137 -    {
 56.2138 -      if(rs != null)
 56.2139 -      {
 56.2140 -        try
 56.2141 -        {
 56.2142 -          rs.close();
 56.2143 -        }
 56.2144 -        catch(SQLException ex)
 56.2145 -        {
 56.2146 -          ex.printStackTrace();
 56.2147 -        }
 56.2148 -      }
 56.2149 -    }
 56.2150 -  }
 56.2151 -  
 56.2152 -  /**
 56.2153 -   * Checks if there is a group with the given name in the JDBCDatabase.
 56.2154 -   * @param name
 56.2155 -   * @return
 56.2156 -   * @throws StorageBackendException
 56.2157 -   */
 56.2158 -  @Override
 56.2159 -  public boolean isGroupExisting(String name)
 56.2160 -    throws StorageBackendException
 56.2161 -  {
 56.2162 -    ResultSet rs = null;
 56.2163 -    
 56.2164 -    try
 56.2165 -    {
 56.2166 -      this.pstmtIsGroupExisting.setString(1, name);
 56.2167 -      rs = this.pstmtIsGroupExisting.executeQuery();
 56.2168 -      return rs.next();
 56.2169 -    }
 56.2170 -    catch(SQLException ex)
 56.2171 -    {
 56.2172 -      restartConnection(ex);
 56.2173 -      return isGroupExisting(name);
 56.2174 -    }
 56.2175 -    finally
 56.2176 -    {
 56.2177 -      if(rs != null)
 56.2178 -      {
 56.2179 -        try
 56.2180 -        {
 56.2181 -          rs.close();
 56.2182 -        }
 56.2183 -        catch(SQLException ex)
 56.2184 -        {
 56.2185 -          ex.printStackTrace();
 56.2186 -        }
 56.2187 -      }
 56.2188 -    }
 56.2189 -  }
 56.2190 +	@Override
 56.2191 +	public List<Long> getArticleNumbers(long gid)
 56.2192 +		throws StorageBackendException
 56.2193 +	{
 56.2194 +		ResultSet rs = null;
 56.2195 +		try {
 56.2196 +			List<Long> ids = new ArrayList<Long>();
 56.2197 +			this.pstmtGetArticleIDs.setLong(1, gid);
 56.2198 +			rs = this.pstmtGetArticleIDs.executeQuery();
 56.2199 +			while (rs.next()) {
 56.2200 +				ids.add(rs.getLong(1));
 56.2201 +			}
 56.2202 +			return ids;
 56.2203 +		} catch (SQLException ex) {
 56.2204 +			restartConnection(ex);
 56.2205 +			return getArticleNumbers(gid);
 56.2206 +		} finally {
 56.2207 +			if (rs != null) {
 56.2208 +				try {
 56.2209 +					rs.close();
 56.2210 +					restarts = 0; // Clear the restart count after successful request
 56.2211 +				} catch (SQLException ex) {
 56.2212 +					ex.printStackTrace();
 56.2213 +				}
 56.2214 +			}
 56.2215 +		}
 56.2216 +	}
 56.2217  
 56.2218 -  @Override
 56.2219 -  public void setConfigValue(String key, String value)
 56.2220 -    throws StorageBackendException
 56.2221 -  {
 56.2222 -    try
 56.2223 -    {
 56.2224 -      conn.setAutoCommit(false);
 56.2225 -      this.pstmtSetConfigValue0.setString(1, key);
 56.2226 -      this.pstmtSetConfigValue0.execute();
 56.2227 -      this.pstmtSetConfigValue1.setString(1, key);
 56.2228 -      this.pstmtSetConfigValue1.setString(2, value);
 56.2229 -      this.pstmtSetConfigValue1.execute();
 56.2230 -      conn.commit();
 56.2231 -      conn.setAutoCommit(true);
 56.2232 -    }
 56.2233 -    catch(SQLException ex)
 56.2234 -    {
 56.2235 -      restartConnection(ex);
 56.2236 -      setConfigValue(key, value);
 56.2237 -    }
 56.2238 -  }
 56.2239 -  
 56.2240 -  /**
 56.2241 -   * Closes the JDBCDatabase connection.
 56.2242 -   */
 56.2243 -  public void shutdown()
 56.2244 -    throws StorageBackendException
 56.2245 -  {
 56.2246 -    try
 56.2247 -    {
 56.2248 -      if(this.conn != null)
 56.2249 -      {
 56.2250 -        this.conn.close();
 56.2251 -      }
 56.2252 -    }
 56.2253 -    catch(SQLException ex)
 56.2254 -    {
 56.2255 -      throw new StorageBackendException(ex);
 56.2256 -    }
 56.2257 -  }
 56.2258 +	@Override
 56.2259 +	public String getConfigValue(String key)
 56.2260 +		throws StorageBackendException
 56.2261 +	{
 56.2262 +		ResultSet rs = null;
 56.2263 +		try {
 56.2264 +			this.pstmtGetConfigValue.setString(1, key);
 56.2265  
 56.2266 -  @Override
 56.2267 -  public void purgeGroup(Group group)
 56.2268 -    throws StorageBackendException
 56.2269 -  {
 56.2270 -    try
 56.2271 -    {
 56.2272 -      this.pstmtPurgeGroup0.setLong(1, group.getInternalID());
 56.2273 -      this.pstmtPurgeGroup0.executeUpdate();
 56.2274 +			rs = this.pstmtGetConfigValue.executeQuery();
 56.2275 +			if (rs.next()) {
 56.2276 +				return rs.getString(1); // First data on index 1 not 0
 56.2277 +			} else {
 56.2278 +				return null;
 56.2279 +			}
 56.2280 +		} catch (SQLException ex) {
 56.2281 +			restartConnection(ex);
 56.2282 +			return getConfigValue(key);
 56.2283 +		} finally {
 56.2284 +			if (rs != null) {
 56.2285 +				try {
 56.2286 +					rs.close();
 56.2287 +				} catch (SQLException ex) {
 56.2288 +					ex.printStackTrace();
 56.2289 +				}
 56.2290 +				restarts = 0; // Clear the restart count after successful request
 56.2291 +			}
 56.2292 +		}
 56.2293 +	}
 56.2294  
 56.2295 -      this.pstmtPurgeGroup1.setLong(1, group.getInternalID());
 56.2296 -      this.pstmtPurgeGroup1.executeUpdate();
 56.2297 -    }
 56.2298 -    catch(SQLException ex)
 56.2299 -    {
 56.2300 -      restartConnection(ex);
 56.2301 -      purgeGroup(group);
 56.2302 -    }
 56.2303 -  }
 56.2304 -  
 56.2305 -  private void restartConnection(SQLException cause)
 56.2306 -    throws StorageBackendException
 56.2307 -  {
 56.2308 -    restarts++;
 56.2309 -    Log.get().severe(Thread.currentThread()
 56.2310 -      + ": Database connection was closed (restart " + restarts + ").");
 56.2311 -    
 56.2312 -    if(restarts >= MAX_RESTARTS)
 56.2313 -    {
 56.2314 -      // Delete the current, probably broken JDBCDatabase instance.
 56.2315 -      // So no one can use the instance any more.
 56.2316 -      JDBCDatabaseProvider.instances.remove(Thread.currentThread());
 56.2317 -      
 56.2318 -      // Throw the exception upwards
 56.2319 -      throw new StorageBackendException(cause);
 56.2320 -    }
 56.2321 -    
 56.2322 -    try
 56.2323 -    {
 56.2324 -      Thread.sleep(1500L * restarts);
 56.2325 -    }
 56.2326 -    catch(InterruptedException ex)
 56.2327 -    {
 56.2328 -      Log.get().warning("Interrupted: " + ex.getMessage());
 56.2329 -    }
 56.2330 -    
 56.2331 -    // Try to properly close the old database connection
 56.2332 -    try
 56.2333 -    {
 56.2334 -      if(this.conn != null)
 56.2335 -      {
 56.2336 -        this.conn.close();
 56.2337 -      }
 56.2338 -    }
 56.2339 -    catch(SQLException ex)
 56.2340 -    {
 56.2341 -      Log.get().warning(ex.getMessage());
 56.2342 -    }
 56.2343 -    
 56.2344 -    try
 56.2345 -    {
 56.2346 -      // Try to reinitialize database connection
 56.2347 -      arise();
 56.2348 -    }
 56.2349 -    catch(SQLException ex)
 56.2350 -    {
 56.2351 -      Log.get().warning(ex.getMessage());
 56.2352 -      restartConnection(ex);
 56.2353 -    }
 56.2354 -  }
 56.2355 +	@Override
 56.2356 +	public int getEventsCount(int type, long start, long end, Channel channel)
 56.2357 +		throws StorageBackendException
 56.2358 +	{
 56.2359 +		ResultSet rs = null;
 56.2360  
 56.2361 -  @Override
 56.2362 -  public boolean update(Article article)
 56.2363 -    throws StorageBackendException
 56.2364 -  {
 56.2365 -    // DELETE FROM headers WHERE article_id = ?
 56.2366 +		try {
 56.2367 +			if (channel == null) {
 56.2368 +				this.pstmtGetEventsCount0.setInt(1, type);
 56.2369 +				this.pstmtGetEventsCount0.setLong(2, start);
 56.2370 +				this.pstmtGetEventsCount0.setLong(3, end);
 56.2371 +				rs = this.pstmtGetEventsCount0.executeQuery();
 56.2372 +			} else {
 56.2373 +				this.pstmtGetEventsCount1.setInt(1, type);
 56.2374 +				this.pstmtGetEventsCount1.setLong(2, start);
 56.2375 +				this.pstmtGetEventsCount1.setLong(3, end);
 56.2376 +				this.pstmtGetEventsCount1.setLong(4, channel.getInternalID());
 56.2377 +				rs = this.pstmtGetEventsCount1.executeQuery();
 56.2378 +			}
 56.2379  
 56.2380 -    // INSERT INTO headers ...
 56.2381 +			if (rs.next()) {
 56.2382 +				return rs.getInt(1);
 56.2383 +			} else {
 56.2384 +				return -1;
 56.2385 +			}
 56.2386 +		} catch (SQLException ex) {
 56.2387 +			restartConnection(ex);
 56.2388 +			return getEventsCount(type, start, end, channel);
 56.2389 +		} finally {
 56.2390 +			if (rs != null) {
 56.2391 +				try {
 56.2392 +					rs.close();
 56.2393 +				} catch (SQLException ex) {
 56.2394 +					ex.printStackTrace();
 56.2395 +				}
 56.2396 +			}
 56.2397 +		}
 56.2398 +	}
 56.2399  
 56.2400 -    // SELECT * FROM postings WHERE article_id = ? AND group_id = ?
 56.2401 -    return false;
 56.2402 -  }
 56.2403 +	/**
 56.2404 +	 * Reads all Groups from the JDBCDatabase.
 56.2405 +	 * @return
 56.2406 +	 * @throws StorageBackendException
 56.2407 +	 */
 56.2408 +	@Override
 56.2409 +	public List<Channel> getGroups()
 56.2410 +		throws StorageBackendException
 56.2411 +	{
 56.2412 +		ResultSet rs;
 56.2413 +		List<Channel> buffer = new ArrayList<Channel>();
 56.2414 +		Statement stmt = null;
 56.2415  
 56.2416 -  /**
 56.2417 -   * Writes the flags and the name of the given group to the database.
 56.2418 -   * @param group
 56.2419 -   * @throws StorageBackendException
 56.2420 -   */
 56.2421 -  @Override
 56.2422 -  public boolean update(Group group)
 56.2423 -    throws StorageBackendException
 56.2424 -  {
 56.2425 -    try
 56.2426 -    {
 56.2427 -      this.pstmtUpdateGroup.setInt(1, group.getFlags());
 56.2428 -      this.pstmtUpdateGroup.setString(2, group.getName());
 56.2429 -      this.pstmtUpdateGroup.setLong(3, group.getInternalID());
 56.2430 -      int rs = this.pstmtUpdateGroup.executeUpdate();
 56.2431 -      return rs == 1;
 56.2432 -    }
 56.2433 -    catch(SQLException ex)
 56.2434 -    {
 56.2435 -      restartConnection(ex);
 56.2436 -      return update(group);
 56.2437 -    }
 56.2438 -  }
 56.2439 +		try {
 56.2440 +			stmt = conn.createStatement();
 56.2441 +			rs = stmt.executeQuery("SELECT * FROM groups ORDER BY name");
 56.2442  
 56.2443 +			while (rs.next()) {
 56.2444 +				String name = rs.getString("name");
 56.2445 +				long id = rs.getLong("group_id");
 56.2446 +				int flags = rs.getInt("flags");
 56.2447 +
 56.2448 +				Group group = new Group(name, id, flags);
 56.2449 +				buffer.add(group);
 56.2450 +			}
 56.2451 +
 56.2452 +			return buffer;
 56.2453 +		} catch (SQLException ex) {
 56.2454 +			restartConnection(ex);
 56.2455 +			return getGroups();
 56.2456 +		} finally {
 56.2457 +			if (stmt != null) {
 56.2458 +				try {
 56.2459 +					stmt.close(); // Implicitely closes ResultSets
 56.2460 +				} catch (SQLException ex) {
 56.2461 +					ex.printStackTrace();
 56.2462 +				}
 56.2463 +			}
 56.2464 +		}
 56.2465 +	}
 56.2466 +
 56.2467 +	@Override
 56.2468 +	public List<String> getGroupsForList(String listAddress)
 56.2469 +		throws StorageBackendException
 56.2470 +	{
 56.2471 +		ResultSet rs = null;
 56.2472 +
 56.2473 +		try {
 56.2474 +			this.pstmtGetGroupForList.setString(1, listAddress);
 56.2475 +
 56.2476 +			rs = this.pstmtGetGroupForList.executeQuery();
 56.2477 +			List<String> groups = new ArrayList<String>();
 56.2478 +			while (rs.next()) {
 56.2479 +				String group = rs.getString(1);
 56.2480 +				groups.add(group);
 56.2481 +			}
 56.2482 +			return groups;
 56.2483 +		} catch (SQLException ex) {
 56.2484 +			restartConnection(ex);
 56.2485 +			return getGroupsForList(listAddress);
 56.2486 +		} finally {
 56.2487 +			if (rs != null) {
 56.2488 +				try {
 56.2489 +					rs.close();
 56.2490 +				} catch (SQLException ex) {
 56.2491 +					ex.printStackTrace();
 56.2492 +				}
 56.2493 +			}
 56.2494 +		}
 56.2495 +	}
 56.2496 +
 56.2497 +	/**
 56.2498 +	 * Returns the Group that is identified by the name.
 56.2499 +	 * @param name
 56.2500 +	 * @return
 56.2501 +	 * @throws StorageBackendException
 56.2502 +	 */
 56.2503 +	@Override
 56.2504 +	public Group getGroup(String name)
 56.2505 +		throws StorageBackendException
 56.2506 +	{
 56.2507 +		ResultSet rs = null;
 56.2508 +
 56.2509 +		try {
 56.2510 +			this.pstmtGetGroup0.setString(1, name);
 56.2511 +			rs = this.pstmtGetGroup0.executeQuery();
 56.2512 +
 56.2513 +			if (!rs.next()) {
 56.2514 +				return null;
 56.2515 +			} else {
 56.2516 +				long id = rs.getLong("group_id");
 56.2517 +				int flags = rs.getInt("flags");
 56.2518 +				return new Group(name, id, flags);
 56.2519 +			}
 56.2520 +		} catch (SQLException ex) {
 56.2521 +			restartConnection(ex);
 56.2522 +			return getGroup(name);
 56.2523 +		} finally {
 56.2524 +			if (rs != null) {
 56.2525 +				try {
 56.2526 +					rs.close();
 56.2527 +				} catch (SQLException ex) {
 56.2528 +					ex.printStackTrace();
 56.2529 +				}
 56.2530 +			}
 56.2531 +		}
 56.2532 +	}
 56.2533 +
 56.2534 +	@Override
 56.2535 +	public List<String> getListsForGroup(String group)
 56.2536 +		throws StorageBackendException
 56.2537 +	{
 56.2538 +		ResultSet rs = null;
 56.2539 +		List<String> lists = new ArrayList<String>();
 56.2540 +
 56.2541 +		try {
 56.2542 +			this.pstmtGetListForGroup.setString(1, group);
 56.2543 +			rs = this.pstmtGetListForGroup.executeQuery();
 56.2544 +
 56.2545 +			while (rs.next()) {
 56.2546 +				lists.add(rs.getString(1));
 56.2547 +			}
 56.2548 +			return lists;
 56.2549 +		} catch (SQLException ex) {
 56.2550 +			restartConnection(ex);
 56.2551 +			return getListsForGroup(group);
 56.2552 +		} finally {
 56.2553 +			if (rs != null) {
 56.2554 +				try {
 56.2555 +					rs.close();
 56.2556 +				} catch (SQLException ex) {
 56.2557 +					ex.printStackTrace();
 56.2558 +				}
 56.2559 +			}
 56.2560 +		}
 56.2561 +	}
 56.2562 +
 56.2563 +	private int getMaxArticleIndex(long groupID)
 56.2564 +		throws StorageBackendException
 56.2565 +	{
 56.2566 +		ResultSet rs = null;
 56.2567 +
 56.2568 +		try {
 56.2569 +			this.pstmtGetMaxArticleIndex.setLong(1, groupID);
 56.2570 +			rs = this.pstmtGetMaxArticleIndex.executeQuery();
 56.2571 +
 56.2572 +			int maxIndex = 0;
 56.2573 +			if (rs.next()) {
 56.2574 +				maxIndex = rs.getInt(1);
 56.2575 +			}
 56.2576 +
 56.2577 +			return maxIndex;
 56.2578 +		} catch (SQLException ex) {
 56.2579 +			restartConnection(ex);
 56.2580 +			return getMaxArticleIndex(groupID);
 56.2581 +		} finally {
 56.2582 +			if (rs != null) {
 56.2583 +				try {
 56.2584 +					rs.close();
 56.2585 +				} catch (SQLException ex) {
 56.2586 +					ex.printStackTrace();
 56.2587 +				}
 56.2588 +			}
 56.2589 +		}
 56.2590 +	}
 56.2591 +
 56.2592 +	private int getMaxArticleID()
 56.2593 +		throws StorageBackendException
 56.2594 +	{
 56.2595 +		ResultSet rs = null;
 56.2596 +
 56.2597 +		try {
 56.2598 +			rs = this.pstmtGetMaxArticleID.executeQuery();
 56.2599 +
 56.2600 +			int maxIndex = 0;
 56.2601 +			if (rs.next()) {
 56.2602 +				maxIndex = rs.getInt(1);
 56.2603 +			}
 56.2604 +
 56.2605 +			return maxIndex;
 56.2606 +		} catch (SQLException ex) {
 56.2607 +			restartConnection(ex);
 56.2608 +			return getMaxArticleID();
 56.2609 +		} finally {
 56.2610 +			if (rs != null) {
 56.2611 +				try {
 56.2612 +					rs.close();
 56.2613 +				} catch (SQLException ex) {
 56.2614 +					ex.printStackTrace();
 56.2615 +				}
 56.2616 +			}
 56.2617 +		}
 56.2618 +	}
 56.2619 +
 56.2620 +	@Override
 56.2621 +	public int getLastArticleNumber(Group group)
 56.2622 +		throws StorageBackendException
 56.2623 +	{
 56.2624 +		ResultSet rs = null;
 56.2625 +
 56.2626 +		try {
 56.2627 +			this.pstmtGetLastArticleNumber.setLong(1, group.getInternalID());
 56.2628 +			rs = this.pstmtGetLastArticleNumber.executeQuery();
 56.2629 +			if (rs.next()) {
 56.2630 +				return rs.getInt(1);
 56.2631 +			} else {
 56.2632 +				return 0;
 56.2633 +			}
 56.2634 +		} catch (SQLException ex) {
 56.2635 +			restartConnection(ex);
 56.2636 +			return getLastArticleNumber(group);
 56.2637 +		} finally {
 56.2638 +			if (rs != null) {
 56.2639 +				try {
 56.2640 +					rs.close();
 56.2641 +				} catch (SQLException ex) {
 56.2642 +					ex.printStackTrace();
 56.2643 +				}
 56.2644 +			}
 56.2645 +		}
 56.2646 +	}
 56.2647 +
 56.2648 +	@Override
 56.2649 +	public int getFirstArticleNumber(Group group)
 56.2650 +		throws StorageBackendException
 56.2651 +	{
 56.2652 +		ResultSet rs = null;
 56.2653 +		try {
 56.2654 +			this.pstmtGetFirstArticleNumber.setLong(1, group.getInternalID());
 56.2655 +			rs = this.pstmtGetFirstArticleNumber.executeQuery();
 56.2656 +			if (rs.next()) {
 56.2657 +				return rs.getInt(1);
 56.2658 +			} else {
 56.2659 +				return 0;
 56.2660 +			}
 56.2661 +		} catch (SQLException ex) {
 56.2662 +			restartConnection(ex);
 56.2663 +			return getFirstArticleNumber(group);
 56.2664 +		} finally {
 56.2665 +			if (rs != null) {
 56.2666 +				try {
 56.2667 +					rs.close();
 56.2668 +				} catch (SQLException ex) {
 56.2669 +					ex.printStackTrace();
 56.2670 +				}
 56.2671 +			}
 56.2672 +		}
 56.2673 +	}
 56.2674 +
 56.2675 +	/**
 56.2676 +	 * Returns a group name identified by the given id.
 56.2677 +	 * @param id
 56.2678 +	 * @return
 56.2679 +	 * @throws StorageBackendException
 56.2680 +	 */
 56.2681 +	public String getGroup(int id)
 56.2682 +		throws StorageBackendException
 56.2683 +	{
 56.2684 +		ResultSet rs = null;
 56.2685 +
 56.2686 +		try {
 56.2687 +			this.pstmtGetGroup1.setInt(1, id);
 56.2688 +			rs = this.pstmtGetGroup1.executeQuery();
 56.2689 +
 56.2690 +			if (rs.next()) {
 56.2691 +				return rs.getString(1);
 56.2692 +			} else {
 56.2693 +				return null;
 56.2694 +			}
 56.2695 +		} catch (SQLException ex) {
 56.2696 +			restartConnection(ex);
 56.2697 +			return getGroup(id);
 56.2698 +		} finally {
 56.2699 +			if (rs != null) {
 56.2700 +				try {
 56.2701 +					rs.close();
 56.2702 +				} catch (SQLException ex) {
 56.2703 +					ex.printStackTrace();
 56.2704 +				}
 56.2705 +			}
 56.2706 +		}
 56.2707 +	}
 56.2708 +
 56.2709 +	@Override
 56.2710 +	public double getEventsPerHour(int key, long gid)
 56.2711 +		throws StorageBackendException
 56.2712 +	{
 56.2713 +		String gidquery = "";
 56.2714 +		if (gid >= 0) {
 56.2715 +			gidquery = " AND group_id = " + gid;
 56.2716 +		}
 56.2717 +
 56.2718 +		Statement stmt = null;
 56.2719 +		ResultSet rs = null;
 56.2720 +
 56.2721 +		try {
 56.2722 +			stmt = this.conn.createStatement();
 56.2723 +			rs = stmt.executeQuery("SELECT Count(*) / (Max(event_time) - Min(event_time))"
 56.2724 +				+ " * 1000 * 60 * 60 FROM events WHERE event_key = " + key + gidquery);
 56.2725 +
 56.2726 +			if (rs.next()) {
 56.2727 +				restarts = 0; // reset error count
 56.2728 +				return rs.getDouble(1);
 56.2729 +			} else {
 56.2730 +				return Double.NaN;
 56.2731 +			}
 56.2732 +		} catch (SQLException ex) {
 56.2733 +			restartConnection(ex);
 56.2734 +			return getEventsPerHour(key, gid);
 56.2735 +		} finally {
 56.2736 +			try {
 56.2737 +				if (stmt != null) {
 56.2738 +					stmt.close(); // Implicitely closes the result sets
 56.2739 +				}
 56.2740 +			} catch (SQLException ex) {
 56.2741 +				ex.printStackTrace();
 56.2742 +			}
 56.2743 +		}
 56.2744 +	}
 56.2745 +
 56.2746 +	@Override
 56.2747 +	public String getOldestArticle()
 56.2748 +		throws StorageBackendException
 56.2749 +	{
 56.2750 +		ResultSet rs = null;
 56.2751 +
 56.2752 +		try {
 56.2753 +			rs = this.pstmtGetOldestArticle.executeQuery();
 56.2754 +			if (rs.next()) {
 56.2755 +				return rs.getString(1);
 56.2756 +			} else {
 56.2757 +				return null;
 56.2758 +			}
 56.2759 +		} catch (SQLException ex) {
 56.2760 +			restartConnection(ex);
 56.2761 +			return getOldestArticle();
 56.2762 +		} finally {
 56.2763 +			if (rs != null) {
 56.2764 +				try {
 56.2765 +					rs.close();
 56.2766 +				} catch (SQLException ex) {
 56.2767 +					ex.printStackTrace();
 56.2768 +				}
 56.2769 +			}
 56.2770 +		}
 56.2771 +	}
 56.2772 +
 56.2773 +	@Override
 56.2774 +	public int getPostingsCount(String groupname)
 56.2775 +		throws StorageBackendException
 56.2776 +	{
 56.2777 +		ResultSet rs = null;
 56.2778 +
 56.2779 +		try {
 56.2780 +			this.pstmtGetPostingsCount.setString(1, groupname);
 56.2781 +			rs = this.pstmtGetPostingsCount.executeQuery();
 56.2782 +			if (rs.next()) {
 56.2783 +				return rs.getInt(1);
 56.2784 +			} else {
 56.2785 +				Log.get().warning("Count on postings return nothing!");
 56.2786 +				return 0;
 56.2787 +			}
 56.2788 +		} catch (SQLException ex) {
 56.2789 +			restartConnection(ex);
 56.2790 +			return getPostingsCount(groupname);
 56.2791 +		} finally {
 56.2792 +			if (rs != null) {
 56.2793 +				try {
 56.2794 +					rs.close();
 56.2795 +				} catch (SQLException ex) {
 56.2796 +					ex.printStackTrace();
 56.2797 +				}
 56.2798 +			}
 56.2799 +		}
 56.2800 +	}
 56.2801 +
 56.2802 +	@Override
 56.2803 +	public List<Subscription> getSubscriptions(int feedtype)
 56.2804 +		throws StorageBackendException
 56.2805 +	{
 56.2806 +		ResultSet rs = null;
 56.2807 +
 56.2808 +		try {
 56.2809 +			List<Subscription> subs = new ArrayList<Subscription>();
 56.2810 +			this.pstmtGetSubscriptions.setInt(1, feedtype);
 56.2811 +			rs = this.pstmtGetSubscriptions.executeQuery();
 56.2812 +
 56.2813 +			while (rs.next()) {
 56.2814 +				String host = rs.getString("host");
 56.2815 +				String group = rs.getString("name");
 56.2816 +				int port = rs.getInt("port");
 56.2817 +				subs.add(new Subscription(host, port, feedtype, group));
 56.2818 +			}
 56.2819 +
 56.2820 +			return subs;
 56.2821 +		} catch (SQLException ex) {
 56.2822 +			restartConnection(ex);
 56.2823 +			return getSubscriptions(feedtype);
 56.2824 +		} finally {
 56.2825 +			if (rs != null) {
 56.2826 +				try {
 56.2827 +					rs.close();
 56.2828 +				} catch (SQLException ex) {
 56.2829 +					ex.printStackTrace();
 56.2830 +				}
 56.2831 +			}
 56.2832 +		}
 56.2833 +	}
 56.2834 +
 56.2835 +	/**
 56.2836 +	 * Checks if there is an article with the given messageid in the JDBCDatabase.
 56.2837 +	 * @param name
 56.2838 +	 * @return
 56.2839 +	 * @throws StorageBackendException
 56.2840 +	 */
 56.2841 +	@Override
 56.2842 +	public boolean isArticleExisting(String messageID)
 56.2843 +		throws StorageBackendException
 56.2844 +	{
 56.2845 +		ResultSet rs = null;
 56.2846 +
 56.2847 +		try {
 56.2848 +			this.pstmtIsArticleExisting.setString(1, messageID);
 56.2849 +			rs = this.pstmtIsArticleExisting.executeQuery();
 56.2850 +			return rs.next() && rs.getInt(1) == 1;
 56.2851 +		} catch (SQLException ex) {
 56.2852 +			restartConnection(ex);
 56.2853 +			return isArticleExisting(messageID);
 56.2854 +		} finally {
 56.2855 +			if (rs != null) {
 56.2856 +				try {
 56.2857 +					rs.close();
 56.2858 +				} catch (SQLException ex) {
 56.2859 +					ex.printStackTrace();
 56.2860 +				}
 56.2861 +			}
 56.2862 +		}
 56.2863 +	}
 56.2864 +
 56.2865 +	/**
 56.2866 +	 * Checks if there is a group with the given name in the JDBCDatabase.
 56.2867 +	 * @param name
 56.2868 +	 * @return
 56.2869 +	 * @throws StorageBackendException
 56.2870 +	 */
 56.2871 +	@Override
 56.2872 +	public boolean isGroupExisting(String name)
 56.2873 +		throws StorageBackendException
 56.2874 +	{
 56.2875 +		ResultSet rs = null;
 56.2876 +
 56.2877 +		try {
 56.2878 +			this.pstmtIsGroupExisting.setString(1, name);
 56.2879 +			rs = this.pstmtIsGroupExisting.executeQuery();
 56.2880 +			return rs.next();
 56.2881 +		} catch (SQLException ex) {
 56.2882 +			restartConnection(ex);
 56.2883 +			return isGroupExisting(name);
 56.2884 +		} finally {
 56.2885 +			if (rs != null) {
 56.2886 +				try {
 56.2887 +					rs.close();
 56.2888 +				} catch (SQLException ex) {
 56.2889 +					ex.printStackTrace();
 56.2890 +				}
 56.2891 +			}
 56.2892 +		}
 56.2893 +	}
 56.2894 +
 56.2895 +	@Override
 56.2896 +	public void setConfigValue(String key, String value)
 56.2897 +		throws StorageBackendException
 56.2898 +	{
 56.2899 +		try {
 56.2900 +			conn.setAutoCommit(false);
 56.2901 +			this.pstmtSetConfigValue0.setString(1, key);
 56.2902 +			this.pstmtSetConfigValue0.execute();
 56.2903 +			this.pstmtSetConfigValue1.setString(1, key);
 56.2904 +			this.pstmtSetConfigValue1.setString(2, value);
 56.2905 +			this.pstmtSetConfigValue1.execute();
 56.2906 +			conn.commit();
 56.2907 +			conn.setAutoCommit(true);
 56.2908 +		} catch (SQLException ex) {
 56.2909 +			restartConnection(ex);
 56.2910 +			setConfigValue(key, value);
 56.2911 +		}
 56.2912 +	}
 56.2913 +
 56.2914 +	/**
 56.2915 +	 * Closes the JDBCDatabase connection.
 56.2916 +	 */
 56.2917 +	public void shutdown()
 56.2918 +		throws StorageBackendException
 56.2919 +	{
 56.2920 +		try {
 56.2921 +			if (this.conn != null) {
 56.2922 +				this.conn.close();
 56.2923 +			}
 56.2924 +		} catch (SQLException ex) {
 56.2925 +			throw new StorageBackendException(ex);
 56.2926 +		}
 56.2927 +	}
 56.2928 +
 56.2929 +	@Override
 56.2930 +	public void purgeGroup(Group group)
 56.2931 +		throws StorageBackendException
 56.2932 +	{
 56.2933 +		try {
 56.2934 +			this.pstmtPurgeGroup0.setLong(1, group.getInternalID());
 56.2935 +			this.pstmtPurgeGroup0.executeUpdate();
 56.2936 +
 56.2937 +			this.pstmtPurgeGroup1.setLong(1, group.getInternalID());
 56.2938 +			this.pstmtPurgeGroup1.executeUpdate();
 56.2939 +		} catch (SQLException ex) {
 56.2940 +			restartConnection(ex);
 56.2941 +			purgeGroup(group);
 56.2942 +		}
 56.2943 +	}
 56.2944 +
 56.2945 +	private void restartConnection(SQLException cause)
 56.2946 +		throws StorageBackendException
 56.2947 +	{
 56.2948 +		restarts++;
 56.2949 +		Log.get().severe(Thread.currentThread()
 56.2950 +			+ ": Database connection was closed (restart " + restarts + ").");
 56.2951 +
 56.2952 +		if (restarts >= MAX_RESTARTS) {
 56.2953 +			// Delete the current, probably broken JDBCDatabase instance.
 56.2954 +			// So no one can use the instance any more.
 56.2955 +			JDBCDatabaseProvider.instances.remove(Thread.currentThread());
 56.2956 +
 56.2957 +			// Throw the exception upwards
 56.2958 +			throw new StorageBackendException(cause);
 56.2959 +		}
 56.2960 +
 56.2961 +		try {
 56.2962 +			Thread.sleep(1500L * restarts);
 56.2963 +		} catch (InterruptedException ex) {
 56.2964 +			Log.get().warning("Interrupted: " + ex.getMessage());
 56.2965 +		}
 56.2966 +
 56.2967 +		// Try to properly close the old database connection
 56.2968 +		try {
 56.2969 +			if (this.conn != null) {
 56.2970 +				this.conn.close();
 56.2971 +			}
 56.2972 +		} catch (SQLException ex) {
 56.2973 +			Log.get().warning(ex.getMessage());
 56.2974 +		}
 56.2975 +
 56.2976 +		try {
 56.2977 +			// Try to reinitialize database connection
 56.2978 +			arise();
 56.2979 +		} catch (SQLException ex) {
 56.2980 +			Log.get().warning(ex.getMessage());
 56.2981 +			restartConnection(ex);
 56.2982 +		}
 56.2983 +	}
 56.2984 +
 56.2985 +	@Override
 56.2986 +	public boolean update(Article article)
 56.2987 +		throws StorageBackendException
 56.2988 +	{
 56.2989 +		// DELETE FROM headers WHERE article_id = ?
 56.2990 +
 56.2991 +		// INSERT INTO headers ...
 56.2992 +
 56.2993 +		// SELECT * FROM postings WHERE article_id = ? AND group_id = ?
 56.2994 +		return false;
 56.2995 +	}
 56.2996 +
 56.2997 +	/**
 56.2998 +	 * Writes the flags and the name of the given group to the database.
 56.2999 +	 * @param group
 56.3000 +	 * @throws StorageBackendException
 56.3001 +	 */
 56.3002 +	@Override
 56.3003 +	public boolean update(Group group)
 56.3004 +		throws StorageBackendException
 56.3005 +	{
 56.3006 +		try {
 56.3007 +			this.pstmtUpdateGroup.setInt(1, group.getFlags());
 56.3008 +			this.pstmtUpdateGroup.setString(2, group.getName());
 56.3009 +			this.pstmtUpdateGroup.setLong(3, group.getInternalID());
 56.3010 +			int rs = this.pstmtUpdateGroup.executeUpdate();
 56.3011 +			return rs == 1;
 56.3012 +		} catch (SQLException ex) {
 56.3013 +			restartConnection(ex);
 56.3014 +			return update(group);
 56.3015 +		}
 56.3016 +	}
 56.3017  }
    57.1 --- a/src/org/sonews/storage/impl/JDBCDatabaseProvider.java	Sun Aug 29 17:43:58 2010 +0200
    57.2 +++ b/src/org/sonews/storage/impl/JDBCDatabaseProvider.java	Sun Aug 29 18:17:37 2010 +0200
    57.3 @@ -33,37 +33,29 @@
    57.4  public class JDBCDatabaseProvider implements StorageProvider
    57.5  {
    57.6  
    57.7 -  protected static final Map<Thread, JDBCDatabase> instances
    57.8 -    = new ConcurrentHashMap<Thread, JDBCDatabase>();
    57.9 +	protected static final Map<Thread, JDBCDatabase> instances = new ConcurrentHashMap<Thread, JDBCDatabase>();
   57.10  
   57.11 -  @Override
   57.12 -  public boolean isSupported(String uri)
   57.13 -  {
   57.14 -    throw new UnsupportedOperationException("Not supported yet.");
   57.15 -  }
   57.16 +	@Override
   57.17 +	public boolean isSupported(String uri)
   57.18 +	{
   57.19 +		throw new UnsupportedOperationException("Not supported yet.");
   57.20 +	}
   57.21  
   57.22 -  @Override
   57.23 -  public Storage storage(Thread thread)
   57.24 -    throws StorageBackendException
   57.25 -  {
   57.26 -    try
   57.27 -    {
   57.28 -    if(!instances.containsKey(Thread.currentThread()))
   57.29 -    {
   57.30 -      JDBCDatabase db = new JDBCDatabase();
   57.31 -      db.arise();
   57.32 -      instances.put(Thread.currentThread(), db);
   57.33 -      return db;
   57.34 -    }
   57.35 -    else
   57.36 -    {
   57.37 -      return instances.get(Thread.currentThread());
   57.38 -    }
   57.39 -    }
   57.40 -    catch(SQLException ex)
   57.41 -    {
   57.42 -      throw new StorageBackendException(ex);
   57.43 -    }
   57.44 -  }
   57.45 -
   57.46 +	@Override
   57.47 +	public Storage storage(Thread thread)
   57.48 +		throws StorageBackendException
   57.49 +	{
   57.50 +		try {
   57.51 +			if (!instances.containsKey(Thread.currentThread())) {
   57.52 +				JDBCDatabase db = new JDBCDatabase();
   57.53 +				db.arise();
   57.54 +				instances.put(Thread.currentThread(), db);
   57.55 +				return db;
   57.56 +			} else {
   57.57 +				return instances.get(Thread.currentThread());
   57.58 +			}
   57.59 +		} catch (SQLException ex) {
   57.60 +			throw new StorageBackendException(ex);
   57.61 +		}
   57.62 +	}
   57.63  }
    58.1 --- a/src/org/sonews/util/DatabaseSetup.java	Sun Aug 29 17:43:58 2010 +0200
    58.2 +++ b/src/org/sonews/util/DatabaseSetup.java	Sun Aug 29 18:17:37 2010 +0200
    58.3 @@ -25,7 +25,6 @@
    58.4  import java.sql.Statement;
    58.5  import java.util.HashMap;
    58.6  import java.util.Map;
    58.7 -import org.sonews.config.Config;
    58.8  import org.sonews.util.io.Resource;
    58.9  
   58.10  /**
   58.11 @@ -33,95 +32,85 @@
   58.12   * @author Christian Lins
   58.13   * @since sonews/0.5.0
   58.14   */
   58.15 -public final class DatabaseSetup 
   58.16 +public final class DatabaseSetup
   58.17  {
   58.18  
   58.19 -  private static final Map<String, String> templateMap 
   58.20 -    = new HashMap<String, String>();
   58.21 -  private static final Map<String, StringTemplate> urlMap
   58.22 -    = new HashMap<String, StringTemplate>();
   58.23 -  private static final Map<String, String> driverMap
   58.24 -    = new HashMap<String, String>();
   58.25 -  
   58.26 -  static
   58.27 -  {
   58.28 -    templateMap.put("1", "helpers/database_mysql5_tmpl.sql");
   58.29 -    templateMap.put("2", "helpers/database_postgresql8_tmpl.sql");
   58.30 -    
   58.31 -    urlMap.put("1", new StringTemplate("jdbc:mysql://%HOSTNAME/%DB"));
   58.32 -    urlMap.put("2", new StringTemplate("jdbc:postgresql://%HOSTNAME/%DB"));
   58.33 -    
   58.34 -    driverMap.put("1", "com.mysql.jdbc.Driver");
   58.35 -    driverMap.put("2", "org.postgresql.Driver");
   58.36 -  }
   58.37 -  
   58.38 -  public static void main(String[] args)
   58.39 -    throws Exception
   58.40 -  {
   58.41 -    System.out.println("sonews Database setup helper");
   58.42 -    System.out.println("This program will create a initial database table structure");
   58.43 -    System.out.println("for the sonews Newsserver.");
   58.44 -    System.out.println("You need to create a database and a db user manually before!");
   58.45 -    
   58.46 -    System.out.println("Select DBMS type:");
   58.47 -    System.out.println("[1] MySQL 5.x or higher");
   58.48 -    System.out.println("[2] PostgreSQL 8.x or higher");
   58.49 -    System.out.print("Your choice: ");
   58.50 -    
   58.51 -    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
   58.52 -    String dbmsType = in.readLine();
   58.53 -    String tmplName = templateMap.get(dbmsType);
   58.54 -    if(tmplName == null)
   58.55 -    {
   58.56 -      System.err.println("Invalid choice. Try again you fool!");
   58.57 -      main(args);
   58.58 -      return;
   58.59 -    }
   58.60 -    
   58.61 -    // Load JDBC Driver class
   58.62 -    Class.forName(driverMap.get(dbmsType));
   58.63 -    
   58.64 -    String tmpl = Resource.getAsString(tmplName, true);
   58.65 -    
   58.66 -    System.out.print("Database server hostname (e.g. localhost): ");
   58.67 -    String dbHostname = in.readLine();
   58.68 -    
   58.69 -    System.out.print("Database name: ");
   58.70 -    String dbName = in.readLine();
   58.71 +	private static final Map<String, String> templateMap = new HashMap<String, String>();
   58.72 +	private static final Map<String, StringTemplate> urlMap = new HashMap<String, StringTemplate>();
   58.73 +	private static final Map<String, String> driverMap = new HashMap<String, String>();
   58.74  
   58.75 -    System.out.print("Give name of DB user that can create tables: ");
   58.76 -    String dbUser = in.readLine();
   58.77 +	static {
   58.78 +		templateMap.put("1", "helpers/database_mysql5_tmpl.sql");
   58.79 +		templateMap.put("2", "helpers/database_postgresql8_tmpl.sql");
   58.80  
   58.81 -    System.out.print("Password: ");
   58.82 -    String dbPassword = in.readLine();
   58.83 -    
   58.84 -    String url = urlMap.get(dbmsType)
   58.85 -      .set("HOSTNAME", dbHostname)
   58.86 -      .set("DB", dbName).toString();
   58.87 -    
   58.88 -    Connection conn = 
   58.89 -      DriverManager.getConnection(url, dbUser, dbPassword);
   58.90 -    conn.setAutoCommit(false);
   58.91 -    
   58.92 -    String[] tmplChunks = tmpl.split(";");
   58.93 -    
   58.94 -    for(String chunk : tmplChunks)
   58.95 -    {
   58.96 -      if(chunk.trim().equals(""))
   58.97 -      {
   58.98 -        continue;
   58.99 -      }
  58.100 -      
  58.101 -      Statement stmt = conn.createStatement();
  58.102 -      stmt.execute(chunk);
  58.103 -    }
  58.104 -    
  58.105 -    conn.commit();
  58.106 -    conn.setAutoCommit(true);
  58.107 -    
  58.108 -    // Create config file
  58.109 -    
  58.110 -    System.out.println("Ok");
  58.111 -  }
  58.112 -  
  58.113 +		urlMap.put("1", new StringTemplate("jdbc:mysql://%HOSTNAME/%DB"));
  58.114 +		urlMap.put("2", new StringTemplate("jdbc:postgresql://%HOSTNAME/%DB"));
  58.115 +
  58.116 +		driverMap.put("1", "com.mysql.jdbc.Driver");
  58.117 +		driverMap.put("2", "org.postgresql.Driver");
  58.118 +	}
  58.119 +
  58.120 +	public static void main(String[] args)
  58.121 +		throws Exception
  58.122 +	{
  58.123 +		System.out.println("sonews Database setup helper");
  58.124 +		System.out.println("This program will create a initial database table structure");
  58.125 +		System.out.println("for the sonews Newsserver.");
  58.126 +		System.out.println("You need to create a database and a db user manually before!");
  58.127 +
  58.128 +		System.out.println("Select DBMS type:");
  58.129 +		System.out.println("[1] MySQL 5.x or higher");
  58.130 +		System.out.println("[2] PostgreSQL 8.x or higher");
  58.131 +		System.out.print("Your choice: ");
  58.132 +
  58.133 +		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
  58.134 +		String dbmsType = in.readLine();
  58.135 +		String tmplName = templateMap.get(dbmsType);
  58.136 +		if (tmplName == null) {
  58.137 +			System.err.println("Invalid choice. Try again you fool!");
  58.138 +			main(args);
  58.139 +			return;
  58.140 +		}
  58.141 +
  58.142 +		// Load JDBC Driver class
  58.143 +		Class.forName(driverMap.get(dbmsType));
  58.144 +
  58.145 +		String tmpl = Resource.getAsString(tmplName, true);
  58.146 +
  58.147 +		System.out.print("Database server hostname (e.g. localhost): ");
  58.148 +		String dbHostname = in.readLine();
  58.149 +
  58.150 +		System.out.print("Database name: ");
  58.151 +		String dbName = in.readLine();
  58.152 +
  58.153 +		System.out.print("Give name of DB user that can create tables: ");
  58.154 +		String dbUser = in.readLine();
  58.155 +
  58.156 +		System.out.print("Password: ");
  58.157 +		String dbPassword = in.readLine();
  58.158 +
  58.159 +		String url = urlMap.get(dbmsType).set("HOSTNAME", dbHostname).set("DB", dbName).toString();
  58.160 +
  58.161 +		Connection conn =
  58.162 +			DriverManager.getConnection(url, dbUser, dbPassword);
  58.163 +		conn.setAutoCommit(false);
  58.164 +
  58.165 +		String[] tmplChunks = tmpl.split(";");
  58.166 +
  58.167 +		for (String chunk : tmplChunks) {
  58.168 +			if (chunk.trim().equals("")) {
  58.169 +				continue;
  58.170 +			}
  58.171 +
  58.172 +			Statement stmt = conn.createStatement();
  58.173 +			stmt.execute(chunk);
  58.174 +		}
  58.175 +
  58.176 +		conn.commit();
  58.177 +		conn.setAutoCommit(true);
  58.178 +
  58.179 +		// Create config file
  58.180 +
  58.181 +		System.out.println("Ok");
  58.182 +	}
  58.183  }
    59.1 --- a/src/org/sonews/util/Log.java	Sun Aug 29 17:43:58 2010 +0200
    59.2 +++ b/src/org/sonews/util/Log.java	Sun Aug 29 18:17:37 2010 +0200
    59.3 @@ -33,25 +33,24 @@
    59.4  public class Log extends Logger
    59.5  {
    59.6  
    59.7 -  private static Log instance = new Log();
    59.8 +	private static Log instance = new Log();
    59.9  
   59.10 -  private Log()
   59.11 -  {
   59.12 -    super("org.sonews", null);
   59.13 +	private Log()
   59.14 +	{
   59.15 +		super("org.sonews", null);
   59.16  
   59.17 -    StreamHandler handler = new StreamHandler(System.out, new SimpleFormatter());
   59.18 -    Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
   59.19 -    handler.setLevel(level);
   59.20 -    addHandler(handler);
   59.21 -    setLevel(level);
   59.22 -    LogManager.getLogManager().addLogger(this);
   59.23 -  }
   59.24 +		StreamHandler handler = new StreamHandler(System.out, new SimpleFormatter());
   59.25 +		Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
   59.26 +		handler.setLevel(level);
   59.27 +		addHandler(handler);
   59.28 +		setLevel(level);
   59.29 +		LogManager.getLogManager().addLogger(this);
   59.30 +	}
   59.31  
   59.32 -  public static Logger get()
   59.33 -  {
   59.34 -    Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
   59.35 -    instance.setLevel(level);
   59.36 -    return instance;
   59.37 -  }
   59.38 -
   59.39 +	public static Logger get()
   59.40 +	{
   59.41 +		Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
   59.42 +		instance.setLevel(level);
   59.43 +		return instance;
   59.44 +	}
   59.45  }
    60.1 --- a/src/org/sonews/util/Pair.java	Sun Aug 29 17:43:58 2010 +0200
    60.2 +++ b/src/org/sonews/util/Pair.java	Sun Aug 29 18:17:37 2010 +0200
    60.3 @@ -23,26 +23,25 @@
    60.4   * @author Christian Lins
    60.5   * @since sonews/0.5.0
    60.6   */
    60.7 -public class Pair<T1, T2> 
    60.8 +public class Pair<T1, T2>
    60.9  {
   60.10 - 
   60.11 -  private T1 a;
   60.12 -  private T2 b;
   60.13 -  
   60.14 -  public Pair(T1 a, T2 b)
   60.15 -  {
   60.16 -    this.a = a;
   60.17 -    this.b = b;
   60.18 -  }
   60.19  
   60.20 -  public T1 getA()
   60.21 -  {
   60.22 -    return a;
   60.23 -  }
   60.24 +	private T1 a;
   60.25 +	private T2 b;
   60.26  
   60.27 -  public T2 getB()
   60.28 -  {
   60.29 -    return b;
   60.30 -  } 
   60.31 - 
   60.32 +	public Pair(T1 a, T2 b)
   60.33 +	{
   60.34 +		this.a = a;
   60.35 +		this.b = b;
   60.36 +	}
   60.37 +
   60.38 +	public T1 getA()
   60.39 +	{
   60.40 +		return a;
   60.41 +	}
   60.42 +
   60.43 +	public T2 getB()
   60.44 +	{
   60.45 +		return b;
   60.46 +	}
   60.47  }
    61.1 --- a/src/org/sonews/util/Purger.java	Sun Aug 29 17:43:58 2010 +0200
    61.2 +++ b/src/org/sonews/util/Purger.java	Sun Aug 29 18:17:37 2010 +0200
    61.3 @@ -40,110 +40,91 @@
    61.4  public class Purger extends AbstractDaemon
    61.5  {
    61.6  
    61.7 -  /**
    61.8 -   * Loops through all messages and deletes them if their time
    61.9 -   * has come.
   61.10 -   */
   61.11 -  @Override
   61.12 -  public void run()
   61.13 -  {
   61.14 -    try
   61.15 -    {
   61.16 -      while(isRunning())
   61.17 -      {
   61.18 -        purgeDeleted();
   61.19 -        purgeOutdated();
   61.20 +	/**
   61.21 +	 * Loops through all messages and deletes them if their time
   61.22 +	 * has come.
   61.23 +	 */
   61.24 +	@Override
   61.25 +	public void run()
   61.26 +	{
   61.27 +		try {
   61.28 +			while (isRunning()) {
   61.29 +				purgeDeleted();
   61.30 +				purgeOutdated();
   61.31  
   61.32 -        Thread.sleep(120000); // Sleep for two minutes
   61.33 -      }
   61.34 -    }
   61.35 -    catch(StorageBackendException ex)
   61.36 -    {
   61.37 -      ex.printStackTrace();
   61.38 -    }
   61.39 -    catch(InterruptedException ex)
   61.40 -    {
   61.41 -      Log.get().warning("Purger interrupted: " + ex);
   61.42 -    }
   61.43 -  }
   61.44 +				Thread.sleep(120000); // Sleep for two minutes
   61.45 +			}
   61.46 +		} catch (StorageBackendException ex) {
   61.47 +			ex.printStackTrace();
   61.48 +		} catch (InterruptedException ex) {
   61.49 +			Log.get().warning("Purger interrupted: " + ex);
   61.50 +		}
   61.51 +	}
   61.52  
   61.53 -  private void purgeDeleted()
   61.54 -    throws StorageBackendException
   61.55 -  {
   61.56 -    List<Channel> groups = StorageManager.current().getGroups();
   61.57 -    for(Channel channel : groups)
   61.58 -    {
   61.59 -      if(!(channel instanceof Group))
   61.60 -        continue;
   61.61 -      
   61.62 -      Group group = (Group)channel;
   61.63 -      // Look for groups that are marked as deleted
   61.64 -      if(group.isDeleted())
   61.65 -      {
   61.66 -        List<Long> ids = StorageManager.current().getArticleNumbers(group.getInternalID());
   61.67 -        if(ids.size() == 0)
   61.68 -        {
   61.69 -          StorageManager.current().purgeGroup(group);
   61.70 -          Log.get().info("Group " + group.getName() + " purged.");
   61.71 -        }
   61.72 +	private void purgeDeleted()
   61.73 +		throws StorageBackendException
   61.74 +	{
   61.75 +		List<Channel> groups = StorageManager.current().getGroups();
   61.76 +		for (Channel channel : groups) {
   61.77 +			if (!(channel instanceof Group)) {
   61.78 +				continue;
   61.79 +			}
   61.80  
   61.81 -        for(int n = 0; n < ids.size() && n < 10; n++)
   61.82 -        {
   61.83 -          Article art = StorageManager.current().getArticle(ids.get(n), group.getInternalID());
   61.84 -          StorageManager.current().delete(art.getMessageID());
   61.85 -          Log.get().info("Article " + art.getMessageID() + " purged.");
   61.86 -        }
   61.87 -      }
   61.88 -    }
   61.89 -  }
   61.90 +			Group group = (Group) channel;
   61.91 +			// Look for groups that are marked as deleted
   61.92 +			if (group.isDeleted()) {
   61.93 +				List<Long> ids = StorageManager.current().getArticleNumbers(group.getInternalID());
   61.94 +				if (ids.size() == 0) {
   61.95 +					StorageManager.current().purgeGroup(group);
   61.96 +					Log.get().info("Group " + group.getName() + " purged.");
   61.97 +				}
   61.98  
   61.99 -  private void purgeOutdated()
  61.100 -    throws InterruptedException, StorageBackendException
  61.101 -  {
  61.102 -    long articleMaximum =
  61.103 -      Config.inst().get("sonews.article.maxnum", Long.MAX_VALUE);
  61.104 -    long lifetime =
  61.105 -      Config.inst().get("sonews.article.lifetime", -1);
  61.106 +				for (int n = 0; n < ids.size() && n < 10; n++) {
  61.107 +					Article art = StorageManager.current().getArticle(ids.get(n), group.getInternalID());
  61.108 +					StorageManager.current().delete(art.getMessageID());
  61.109 +					Log.get().info("Article " + art.getMessageID() + " purged.");
  61.110 +				}
  61.111 +			}
  61.112 +		}
  61.113 +	}
  61.114  
  61.115 -    if(lifetime > 0 || articleMaximum < Stats.getInstance().getNumberOfNews())
  61.116 -    {
  61.117 -      Log.get().info("Purging old messages...");
  61.118 -      String mid = StorageManager.current().getOldestArticle();
  61.119 -      if (mid == null) // No articles in the database
  61.120 -      {
  61.121 -        return;
  61.122 -      }
  61.123 +	private void purgeOutdated()
  61.124 +		throws InterruptedException, StorageBackendException
  61.125 +	{
  61.126 +		long articleMaximum =
  61.127 +			Config.inst().get("sonews.article.maxnum", Long.MAX_VALUE);
  61.128 +		long lifetime =
  61.129 +			Config.inst().get("sonews.article.lifetime", -1);
  61.130  
  61.131 -      Article art = StorageManager.current().getArticle(mid);
  61.132 -      long artDate = 0;
  61.133 -      String dateStr = art.getHeader(Headers.DATE)[0];
  61.134 -      try
  61.135 -      {
  61.136 -        artDate = Date.parse(dateStr) / 1000 / 60 / 60 / 24;
  61.137 -      }
  61.138 -      catch (IllegalArgumentException ex)
  61.139 -      {
  61.140 -        Log.get().warning("Could not parse date string: " + dateStr + " " + ex);
  61.141 -      }
  61.142 +		if (lifetime > 0 || articleMaximum < Stats.getInstance().getNumberOfNews()) {
  61.143 +			Log.get().info("Purging old messages...");
  61.144 +			String mid = StorageManager.current().getOldestArticle();
  61.145 +			if (mid == null) // No articles in the database
  61.146 +			{
  61.147 +				return;
  61.148 +			}
  61.149  
  61.150 -      // Should we delete the message because of its age or because the
  61.151 -      // article maximum was reached?
  61.152 -      if (lifetime < 0 || artDate < (new Date().getTime() + lifetime))
  61.153 -      {
  61.154 -        StorageManager.current().delete(mid);
  61.155 -        System.out.println("Deleted: " + mid);
  61.156 -      }
  61.157 -      else
  61.158 -      {
  61.159 -        Thread.sleep(1000 * 60); // Wait 60 seconds
  61.160 -        return;
  61.161 -      }
  61.162 -    }
  61.163 -    else
  61.164 -    {
  61.165 -      Log.get().info("Lifetime purger is disabled");
  61.166 -      Thread.sleep(1000 * 60 * 30); // Wait 30 minutes
  61.167 -    }
  61.168 -  }
  61.169 +			Article art = StorageManager.current().getArticle(mid);
  61.170 +			long artDate = 0;
  61.171 +			String dateStr = art.getHeader(Headers.DATE)[0];
  61.172 +			try {
  61.173 +				artDate = Date.parse(dateStr) / 1000 / 60 / 60 / 24;
  61.174 +			} catch (IllegalArgumentException ex) {
  61.175 +				Log.get().warning("Could not parse date string: " + dateStr + " " + ex);
  61.176 +			}
  61.177  
  61.178 +			// Should we delete the message because of its age or because the
  61.179 +			// article maximum was reached?
  61.180 +			if (lifetime < 0 || artDate < (new Date().getTime() + lifetime)) {
  61.181 +				StorageManager.current().delete(mid);
  61.182 +				System.out.println("Deleted: " + mid);
  61.183 +			} else {
  61.184 +				Thread.sleep(1000 * 60); // Wait 60 seconds
  61.185 +				return;
  61.186 +			}
  61.187 +		} else {
  61.188 +			Log.get().info("Lifetime purger is disabled");
  61.189 +			Thread.sleep(1000 * 60 * 30); // Wait 30 minutes
  61.190 +		}
  61.191 +	}
  61.192  }
    62.1 --- a/src/org/sonews/util/Stats.java	Sun Aug 29 17:43:58 2010 +0200
    62.2 +++ b/src/org/sonews/util/Stats.java	Sun Aug 29 18:17:37 2010 +0200
    62.3 @@ -29,178 +29,157 @@
    62.4   * @author Christian Lins
    62.5   * @since sonews/0.5.0
    62.6   */
    62.7 -public final class Stats 
    62.8 +public final class Stats
    62.9  {
   62.10 -      
   62.11 -  public static final byte CONNECTIONS    = 1;
   62.12 -  public static final byte POSTED_NEWS    = 2;
   62.13 -  public static final byte GATEWAYED_NEWS = 3;
   62.14 -  public static final byte FEEDED_NEWS    = 4;
   62.15 -  public static final byte MLGW_RUNSTART  = 5;
   62.16 -  public static final byte MLGW_RUNEND    = 6;
   62.17  
   62.18 -  private static Stats instance = new Stats();
   62.19 -  
   62.20 -  public static Stats getInstance()
   62.21 -  {
   62.22 -    return Stats.instance;
   62.23 -  }
   62.24 -  
   62.25 -  private Stats() {}
   62.26 -  
   62.27 -  private volatile int connectedClients = 0;
   62.28 +	public static final byte CONNECTIONS = 1;
   62.29 +	public static final byte POSTED_NEWS = 2;
   62.30 +	public static final byte GATEWAYED_NEWS = 3;
   62.31 +	public static final byte FEEDED_NEWS = 4;
   62.32 +	public static final byte MLGW_RUNSTART = 5;
   62.33 +	public static final byte MLGW_RUNEND = 6;
   62.34 +	private static Stats instance = new Stats();
   62.35  
   62.36 -  /**
   62.37 -   * A generic method that writes event data to the storage backend.
   62.38 -   * If event logging is disabled with sonews.eventlog=false this method
   62.39 -   * simply does nothing.
   62.40 -   * @param type
   62.41 -   * @param groupname
   62.42 -   */
   62.43 -  private void addEvent(byte type, String groupname)
   62.44 -  {
   62.45 -    try
   62.46 -    {
   62.47 -      if (Config.inst().get(Config.EVENTLOG, true))
   62.48 -      {
   62.49 +	public static Stats getInstance()
   62.50 +	{
   62.51 +		return Stats.instance;
   62.52 +	}
   62.53  
   62.54 -        Channel group = Channel.getByName(groupname);
   62.55 -        if (group != null)
   62.56 -        {
   62.57 -          StorageManager.current().addEvent(
   62.58 -                  System.currentTimeMillis(), type, group.getInternalID());
   62.59 -        }
   62.60 -      } 
   62.61 -      else
   62.62 -      {
   62.63 -        Log.get().info("Group " + groupname + " does not exist.");
   62.64 -      }
   62.65 -    } 
   62.66 -    catch (StorageBackendException ex)
   62.67 -    {
   62.68 -      ex.printStackTrace();
   62.69 -    }
   62.70 -  }
   62.71 -  
   62.72 -  public void clientConnect()
   62.73 -  {
   62.74 -    this.connectedClients++;
   62.75 -  }
   62.76 -  
   62.77 -  public void clientDisconnect()
   62.78 -  {
   62.79 -    this.connectedClients--;
   62.80 -  }
   62.81 -  
   62.82 -  public int connectedClients()
   62.83 -  {
   62.84 -    return this.connectedClients;
   62.85 -  }
   62.86 -  
   62.87 -  public int getNumberOfGroups()
   62.88 -  {
   62.89 -    try
   62.90 -    {
   62.91 -      return StorageManager.current().countGroups();
   62.92 -    }
   62.93 -    catch(StorageBackendException ex)
   62.94 -    {
   62.95 -      ex.printStackTrace();
   62.96 -      return -1;
   62.97 -    }
   62.98 -  }
   62.99 -  
  62.100 -  public int getNumberOfNews()
  62.101 -  {
  62.102 -    try
  62.103 -    {
  62.104 -      return StorageManager.current().countArticles();
  62.105 -    }
  62.106 -    catch(StorageBackendException ex)
  62.107 -    {
  62.108 -      ex.printStackTrace();
  62.109 -      return -1;
  62.110 -    }
  62.111 -  }
  62.112 -  
  62.113 -  public int getYesterdaysEvents(final byte eventType, final int hour,
  62.114 -    final Channel group)
  62.115 -  {
  62.116 -    // Determine the timestamp values for yesterday and the given hour
  62.117 -    Calendar cal = Calendar.getInstance();
  62.118 -    int year  = cal.get(Calendar.YEAR);
  62.119 -    int month = cal.get(Calendar.MONTH);
  62.120 -    int dayom = cal.get(Calendar.DAY_OF_MONTH) - 1; // Yesterday
  62.121 -    
  62.122 -    cal.set(year, month, dayom, hour, 0, 0);
  62.123 -    long startTimestamp = cal.getTimeInMillis();
  62.124 -    
  62.125 -    cal.set(year, month, dayom, hour + 1, 0, 0);
  62.126 -    long endTimestamp = cal.getTimeInMillis();
  62.127 -    
  62.128 -    try
  62.129 -    {
  62.130 -      return StorageManager.current()
  62.131 -        .getEventsCount(eventType, startTimestamp, endTimestamp, group);
  62.132 -    }
  62.133 -    catch(StorageBackendException ex)
  62.134 -    {
  62.135 -      ex.printStackTrace();
  62.136 -      return -1;
  62.137 -    }
  62.138 -  }
  62.139 -  
  62.140 -  public void mailPosted(String groupname)
  62.141 -  {
  62.142 -    addEvent(POSTED_NEWS, groupname);
  62.143 -  }
  62.144 -  
  62.145 -  public void mailGatewayed(String groupname)
  62.146 -  {
  62.147 -    addEvent(GATEWAYED_NEWS, groupname);
  62.148 -  }
  62.149 -  
  62.150 -  public void mailFeeded(String groupname)
  62.151 -  {
  62.152 -    addEvent(FEEDED_NEWS, groupname);
  62.153 -  }
  62.154 -  
  62.155 -  public void mlgwRunStart()
  62.156 -  {
  62.157 -    addEvent(MLGW_RUNSTART, "control");
  62.158 -  }
  62.159 -  
  62.160 -  public void mlgwRunEnd()
  62.161 -  {
  62.162 -    addEvent(MLGW_RUNEND, "control");
  62.163 -  }
  62.164 -  
  62.165 -  private double perHour(int key, long gid)
  62.166 -  {
  62.167 -    try
  62.168 -    {
  62.169 -      return StorageManager.current().getEventsPerHour(key, gid);
  62.170 -    }
  62.171 -    catch(StorageBackendException ex)
  62.172 -    {
  62.173 -      ex.printStackTrace();
  62.174 -      return -1;
  62.175 -    }
  62.176 -  }
  62.177 -  
  62.178 -  public double postedPerHour(long gid)
  62.179 -  {
  62.180 -    return perHour(POSTED_NEWS, gid);
  62.181 -  }
  62.182 -  
  62.183 -  public double gatewayedPerHour(long gid)
  62.184 -  {
  62.185 -    return perHour(GATEWAYED_NEWS, gid);
  62.186 -  }
  62.187 -  
  62.188 -  public double feededPerHour(long gid)
  62.189 -  {
  62.190 -    return perHour(FEEDED_NEWS, gid);
  62.191 -  }
  62.192 -  
  62.193 +	private Stats()
  62.194 +	{
  62.195 +	}
  62.196 +	private volatile int connectedClients = 0;
  62.197 +
  62.198 +	/**
  62.199 +	 * A generic method that writes event data to the storage backend.
  62.200 +	 * If event logging is disabled with sonews.eventlog=false this method
  62.201 +	 * simply does nothing.
  62.202 +	 * @param type
  62.203 +	 * @param groupname
  62.204 +	 */
  62.205 +	private void addEvent(byte type, String groupname)
  62.206 +	{
  62.207 +		try {
  62.208 +			if (Config.inst().get(Config.EVENTLOG, true)) {
  62.209 +
  62.210 +				Channel group = Channel.getByName(groupname);
  62.211 +				if (group != null) {
  62.212 +					StorageManager.current().addEvent(
  62.213 +						System.currentTimeMillis(), type, group.getInternalID());
  62.214 +				}
  62.215 +			} else {
  62.216 +				Log.get().info("Group " + groupname + " does not exist.");
  62.217 +			}
  62.218 +		} catch (StorageBackendException ex) {
  62.219 +			ex.printStackTrace();
  62.220 +		}
  62.221 +	}
  62.222 +
  62.223 +	public void clientConnect()
  62.224 +	{
  62.225 +		this.connectedClients++;
  62.226 +	}
  62.227 +
  62.228 +	public void clientDisconnect()
  62.229 +	{
  62.230 +		this.connectedClients--;
  62.231 +	}
  62.232 +
  62.233 +	public int connectedClients()
  62.234 +	{
  62.235 +		return this.connectedClients;
  62.236 +	}
  62.237 +
  62.238 +	public int getNumberOfGroups()
  62.239 +	{
  62.240 +		try {
  62.241 +			return StorageManager.current().countGroups();
  62.242 +		} catch (StorageBackendException ex) {
  62.243 +			ex.printStackTrace();
  62.244 +			return -1;
  62.245 +		}
  62.246 +	}
  62.247 +
  62.248 +	public int getNumberOfNews()
  62.249 +	{
  62.250 +		try {
  62.251 +			return StorageManager.current().countArticles();
  62.252 +		} catch (StorageBackendException ex) {
  62.253 +			ex.printStackTrace();
  62.254 +			return -1;
  62.255 +		}
  62.256 +	}
  62.257 +
  62.258 +	public int getYesterdaysEvents(final byte eventType, final int hour,
  62.259 +		final Channel group)
  62.260 +	{
  62.261 +		// Determine the timestamp values for yesterday and the given hour
  62.262 +		Calendar cal = Calendar.getInstance();
  62.263 +		int year = cal.get(Calendar.YEAR);
  62.264 +		int month = cal.get(Calendar.MONTH);
  62.265 +		int dayom = cal.get(Calendar.DAY_OF_MONTH) - 1; // Yesterday
  62.266 +
  62.267 +		cal.set(year, month, dayom, hour, 0, 0);
  62.268 +		long startTimestamp = cal.getTimeInMillis();
  62.269 +
  62.270 +		cal.set(year, month, dayom, hour + 1, 0, 0);
  62.271 +		long endTimestamp = cal.getTimeInMillis();
  62.272 +
  62.273 +		try {
  62.274 +			return StorageManager.current().getEventsCount(eventType, startTimestamp, endTimestamp, group);
  62.275 +		} catch (StorageBackendException ex) {
  62.276 +			ex.printStackTrace();
  62.277 +			return -1;
  62.278 +		}
  62.279 +	}
  62.280 +
  62.281 +	public void mailPosted(String groupname)
  62.282 +	{
  62.283 +		addEvent(POSTED_NEWS, groupname);
  62.284 +	}
  62.285 +
  62.286 +	public void mailGatewayed(String groupname)
  62.287 +	{
  62.288 +		addEvent(GATEWAYED_NEWS, groupname);
  62.289 +	}
  62.290 +
  62.291 +	public void mailFeeded(String groupname)
  62.292 +	{
  62.293 +		addEvent(FEEDED_NEWS, groupname);
  62.294 +	}
  62.295 +
  62.296 +	public void mlgwRunStart()
  62.297 +	{
  62.298 +		addEvent(MLGW_RUNSTART, "control");
  62.299 +	}
  62.300 +
  62.301 +	public void mlgwRunEnd()
  62.302 +	{
  62.303 +		addEvent(MLGW_RUNEND, "control");
  62.304 +	}
  62.305 +
  62.306 +	private double perHour(int key, long gid)
  62.307 +	{
  62.308 +		try {
  62.309 +			return StorageManager.current().getEventsPerHour(key, gid);
  62.310 +		} catch (StorageBackendException ex) {
  62.311 +			ex.printStackTrace();
  62.312 +			return -1;
  62.313 +		}
  62.314 +	}
  62.315 +
  62.316 +	public double postedPerHour(long gid)
  62.317 +	{
  62.318 +		return perHour(POSTED_NEWS, gid);
  62.319 +	}
  62.320 +
  62.321 +	public double gatewayedPerHour(long gid)
  62.322 +	{
  62.323 +		return perHour(GATEWAYED_NEWS, gid);
  62.324 +	}
  62.325 +
  62.326 +	public double feededPerHour(long gid)
  62.327 +	{
  62.328 +		return perHour(FEEDED_NEWS, gid);
  62.329 +	}
  62.330  }
    63.1 --- a/src/org/sonews/util/StringTemplate.java	Sun Aug 29 17:43:58 2010 +0200
    63.2 +++ b/src/org/sonews/util/StringTemplate.java	Sun Aug 29 18:17:37 2010 +0200
    63.3 @@ -26,72 +26,67 @@
    63.4   * @author Christian Lins
    63.5   * @since sonews/0.5.0
    63.6   */
    63.7 -public class StringTemplate 
    63.8 +public class StringTemplate
    63.9  {
   63.10  
   63.11 -  private String              str               = null;
   63.12 -  private String              templateDelimiter = "%";
   63.13 -  private Map<String, String> templateValues    = new HashMap<String, String>();
   63.14 -  
   63.15 -  public StringTemplate(String str, final String templateDelimiter)
   63.16 -  {
   63.17 -    if(str == null || templateDelimiter == null)
   63.18 -    {
   63.19 -      throw new IllegalArgumentException("null arguments not allowed");
   63.20 -    }
   63.21 +	private String str = null;
   63.22 +	private String templateDelimiter = "%";
   63.23 +	private Map<String, String> templateValues = new HashMap<String, String>();
   63.24  
   63.25 -    this.str               = str;
   63.26 -    this.templateDelimiter = templateDelimiter;
   63.27 -  }
   63.28 -  
   63.29 -  public StringTemplate(String str)
   63.30 -  {
   63.31 -    this(str, "%");
   63.32 -  }
   63.33 -  
   63.34 -  public StringTemplate set(String template, String value)
   63.35 -  {
   63.36 -    if(template == null || value == null)
   63.37 -    {
   63.38 -      throw new IllegalArgumentException("null arguments not allowed");
   63.39 -    }
   63.40 -    
   63.41 -    this.templateValues.put(template, value);
   63.42 -    return this;
   63.43 -  }
   63.44 -  
   63.45 -  public StringTemplate set(String template, long value)
   63.46 -  {
   63.47 -    return set(template, Long.toString(value));
   63.48 -  }
   63.49 -  
   63.50 -  public StringTemplate set(String template, double value)
   63.51 -  {
   63.52 -    return set(template, Double.toString(value));
   63.53 -  }
   63.54 -  
   63.55 -  public StringTemplate set(String template, Object obj)
   63.56 -  {
   63.57 -    if(template == null || obj == null)
   63.58 -    {
   63.59 -      throw new IllegalArgumentException("null arguments not allowed");
   63.60 -    }
   63.61 +	public StringTemplate(String str, final String templateDelimiter)
   63.62 +	{
   63.63 +		if (str == null || templateDelimiter == null) {
   63.64 +			throw new IllegalArgumentException("null arguments not allowed");
   63.65 +		}
   63.66  
   63.67 -    return set(template, obj.toString());
   63.68 -  }
   63.69 -  
   63.70 -  @Override
   63.71 -  public String toString()
   63.72 -  {
   63.73 -    String ret = str;
   63.74 +		this.str = str;
   63.75 +		this.templateDelimiter = templateDelimiter;
   63.76 +	}
   63.77  
   63.78 -    for(String key : this.templateValues.keySet())
   63.79 -    {
   63.80 -      String value = this.templateValues.get(key);
   63.81 -      ret = ret.replace(templateDelimiter + key, value);
   63.82 -    }
   63.83 -    
   63.84 -    return ret;
   63.85 -  }
   63.86 +	public StringTemplate(String str)
   63.87 +	{
   63.88 +		this(str, "%");
   63.89 +	}
   63.90  
   63.91 +	public StringTemplate set(String template, String value)
   63.92 +	{
   63.93 +		if (template == null || value == null) {
   63.94 +			throw new IllegalArgumentException("null arguments not allowed");
   63.95 +		}
   63.96 +
   63.97 +		this.templateValues.put(template, value);
   63.98 +		return this;
   63.99 +	}
  63.100 +
  63.101 +	public StringTemplate set(String template, long value)
  63.102 +	{
  63.103 +		return set(template, Long.toString(value));
  63.104 +	}
  63.105 +
  63.106 +	public StringTemplate set(String template, double value)
  63.107 +	{
  63.108 +		return set(template, Double.toString(value));
  63.109 +	}
  63.110 +
  63.111 +	public StringTemplate set(String template, Object obj)
  63.112 +	{
  63.113 +		if (template == null || obj == null) {
  63.114 +			throw new IllegalArgumentException("null arguments not allowed");
  63.115 +		}
  63.116 +
  63.117 +		return set(template, obj.toString());
  63.118 +	}
  63.119 +
  63.120 +	@Override
  63.121 +	public String toString()
  63.122 +	{
  63.123 +		String ret = str;
  63.124 +
  63.125 +		for (String key : this.templateValues.keySet()) {
  63.126 +			String value = this.templateValues.get(key);
  63.127 +			ret = ret.replace(templateDelimiter + key, value);
  63.128 +		}
  63.129 +
  63.130 +		return ret;
  63.131 +	}
  63.132  }
    64.1 --- a/src/org/sonews/util/TimeoutMap.java	Sun Aug 29 17:43:58 2010 +0200
    64.2 +++ b/src/org/sonews/util/TimeoutMap.java	Sun Aug 29 18:17:37 2010 +0200
    64.3 @@ -31,115 +31,99 @@
    64.4   * @author Christian Lins
    64.5   * @since sonews/0.5.0
    64.6   */
    64.7 -public class TimeoutMap<K,V> extends ConcurrentHashMap<K, V>
    64.8 +public class TimeoutMap<K, V> extends ConcurrentHashMap<K, V>
    64.9  {
   64.10 -  
   64.11 -  private static final long serialVersionUID = 453453467700345L;
   64.12  
   64.13 -  private int                    timeout     = 60000; // 60 sec
   64.14 -  private transient Map<K, Long> timeoutMap  = new HashMap<K, Long>();
   64.15 -  
   64.16 -  /**
   64.17 -   * Constructor.
   64.18 -   * @param timeout Timeout in milliseconds
   64.19 -   */
   64.20 -  public TimeoutMap(final int timeout)
   64.21 -  {
   64.22 -    this.timeout = timeout;
   64.23 -  }
   64.24 -  
   64.25 -  /**
   64.26 -   * Uses default timeout (60 sec).
   64.27 -   */
   64.28 -  public TimeoutMap()
   64.29 -  {
   64.30 -  }
   64.31 -  
   64.32 -  /**
   64.33 -   * 
   64.34 -   * @param key
   64.35 -   * @return true if key is still valid.
   64.36 -   */
   64.37 -  protected boolean checkTimeOut(Object key)
   64.38 -  {
   64.39 -    synchronized(this.timeoutMap)
   64.40 -    {
   64.41 -      if(this.timeoutMap.containsKey(key))
   64.42 -      {
   64.43 -        long keytime = this.timeoutMap.get(key);
   64.44 -        if((System.currentTimeMillis() - keytime) < this.timeout)
   64.45 -        {
   64.46 -          return true;
   64.47 -        }
   64.48 -        else
   64.49 -        {
   64.50 -          remove(key);
   64.51 -          return false;
   64.52 -        }
   64.53 -      }
   64.54 -      else
   64.55 -      {
   64.56 -        return false;
   64.57 -      }
   64.58 -    }
   64.59 -  }
   64.60 -  
   64.61 -  @Override
   64.62 -  public boolean containsKey(Object key)
   64.63 -  {
   64.64 -    return checkTimeOut(key);
   64.65 -  }
   64.66 +	private static final long serialVersionUID = 453453467700345L;
   64.67 +	private int timeout = 60000; // 60 sec
   64.68 +	private transient Map<K, Long> timeoutMap = new HashMap<K, Long>();
   64.69  
   64.70 -  @Override
   64.71 -  public synchronized V get(Object key)
   64.72 -  {
   64.73 -    if(checkTimeOut(key))
   64.74 -    {
   64.75 -      return super.get(key);
   64.76 -    }
   64.77 -    else
   64.78 -    {
   64.79 -      return null;
   64.80 -    }
   64.81 -  }
   64.82 +	/**
   64.83 +	 * Constructor.
   64.84 +	 * @param timeout Timeout in milliseconds
   64.85 +	 */
   64.86 +	public TimeoutMap(final int timeout)
   64.87 +	{
   64.88 +		this.timeout = timeout;
   64.89 +	}
   64.90  
   64.91 -  @Override
   64.92 -  public V put(K key, V value)
   64.93 -  {
   64.94 -    synchronized(this.timeoutMap)
   64.95 -    {
   64.96 -      removeStaleKeys();
   64.97 -      this.timeoutMap.put(key, System.currentTimeMillis());
   64.98 -      return super.put(key, value);
   64.99 -    }
  64.100 -  }
  64.101 +	/**
  64.102 +	 * Uses default timeout (60 sec).
  64.103 +	 */
  64.104 +	public TimeoutMap()
  64.105 +	{
  64.106 +	}
  64.107  
  64.108 -  /**
  64.109 -   * @param arg0
  64.110 -   * @return
  64.111 -   */
  64.112 -  @Override
  64.113 -  public V remove(Object arg0)
  64.114 -  {
  64.115 -    synchronized(this.timeoutMap)
  64.116 -    {
  64.117 -      this.timeoutMap.remove(arg0);
  64.118 -      V val = super.remove(arg0);
  64.119 -      return val;
  64.120 -    }
  64.121 -  }
  64.122 +	/**
  64.123 +	 *
  64.124 +	 * @param key
  64.125 +	 * @return true if key is still valid.
  64.126 +	 */
  64.127 +	protected boolean checkTimeOut(Object key)
  64.128 +	{
  64.129 +		synchronized (this.timeoutMap) {
  64.130 +			if (this.timeoutMap.containsKey(key)) {
  64.131 +				long keytime = this.timeoutMap.get(key);
  64.132 +				if ((System.currentTimeMillis() - keytime) < this.timeout) {
  64.133 +					return true;
  64.134 +				} else {
  64.135 +					remove(key);
  64.136 +					return false;
  64.137 +				}
  64.138 +			} else {
  64.139 +				return false;
  64.140 +			}
  64.141 +		}
  64.142 +	}
  64.143  
  64.144 -  protected void removeStaleKeys()
  64.145 -  {
  64.146 -    synchronized(this.timeoutMap)
  64.147 -    {
  64.148 -      Set<Object> keySet = new HashSet<Object>(this.timeoutMap.keySet());
  64.149 -      for(Object key : keySet)
  64.150 -      {
  64.151 -        // The key/value is removed by the checkTimeOut() method if true
  64.152 -        checkTimeOut(key);
  64.153 -      }
  64.154 -    }
  64.155 -  }
  64.156 -  
  64.157 +	@Override
  64.158 +	public boolean containsKey(Object key)
  64.159 +	{
  64.160 +		return checkTimeOut(key);
  64.161 +	}
  64.162 +
  64.163 +	@Override
  64.164 +	public synchronized V get(Object key)
  64.165 +	{
  64.166 +		if (checkTimeOut(key)) {
  64.167 +			return super.get(key);
  64.168 +		} else {
  64.169 +			return null;
  64.170 +		}
  64.171 +	}
  64.172 +
  64.173 +	@Override
  64.174 +	public V put(K key, V value)
  64.175 +	{
  64.176 +		synchronized (this.timeoutMap) {
  64.177 +			removeStaleKeys();
  64.178 +			this.timeoutMap.put(key, System.currentTimeMillis());
  64.179 +			return super.put(key, value);
  64.180 +		}
  64.181 +	}
  64.182 +
  64.183 +	/**
  64.184 +	 * @param arg0
  64.185 +	 * @return
  64.186 +	 */
  64.187 +	@Override
  64.188 +	public V remove(Object arg0)
  64.189 +	{
  64.190 +		synchronized (this.timeoutMap) {
  64.191 +			this.timeoutMap.remove(arg0);
  64.192 +			V val = super.remove(arg0);
  64.193 +			return val;
  64.194 +		}
  64.195 +	}
  64.196 +
  64.197 +	protected void removeStaleKeys()
  64.198 +	{
  64.199 +		synchronized (this.timeoutMap) {
  64.200 +			Set<Object> keySet = new HashSet<Object>(this.timeoutMap.keySet());
  64.201 +			for (Object key : keySet) {
  64.202 +				// The key/value is removed by the checkTimeOut() method if true
  64.203 +				checkTimeOut(key);
  64.204 +			}
  64.205 +		}
  64.206 +	}
  64.207  }
    65.1 --- a/src/org/sonews/util/io/ArticleInputStream.java	Sun Aug 29 17:43:58 2010 +0200
    65.2 +++ b/src/org/sonews/util/io/ArticleInputStream.java	Sun Aug 29 18:17:37 2010 +0200
    65.3 @@ -32,40 +32,36 @@
    65.4  public class ArticleInputStream extends InputStream
    65.5  {
    65.6  
    65.7 -  private byte[] buf;
    65.8 -  private int    pos = 0;
    65.9 -  
   65.10 -  public ArticleInputStream(final Article art)
   65.11 -    throws IOException, UnsupportedEncodingException
   65.12 -  {
   65.13 -    final ByteArrayOutputStream out = new ByteArrayOutputStream();
   65.14 -    out.write(art.getHeaderSource().getBytes("UTF-8"));
   65.15 -    out.write("\r\n\r\n".getBytes());
   65.16 -    out.write(art.getBody()); // Without CRLF
   65.17 -    out.flush();
   65.18 -    this.buf = out.toByteArray();
   65.19 -  }
   65.20 +	private byte[] buf;
   65.21 +	private int pos = 0;
   65.22  
   65.23 -  /**
   65.24 -   * This method reads one byte from the stream.  The <code>pos</code>
   65.25 -   * counter is advanced to the next byte to be read.  The byte read is
   65.26 -   * returned as an int in the range of 0-255.  If the stream position
   65.27 -   * is already at the end of the buffer, no byte is read and a -1 is
   65.28 -   * returned in order to indicate the end of the stream.
   65.29 -   *
   65.30 -   * @return The byte read, or -1 if end of stream
   65.31 -   */
   65.32 -  @Override
   65.33 -  public synchronized int read()
   65.34 -  {
   65.35 -    if(pos < buf.length)
   65.36 -    {
   65.37 -      return ((int)buf[pos++]) & 0xFF;
   65.38 -    }
   65.39 -    else
   65.40 -    {
   65.41 -      return -1;
   65.42 -    }
   65.43 -  }
   65.44 -  
   65.45 +	public ArticleInputStream(final Article art)
   65.46 +		throws IOException, UnsupportedEncodingException
   65.47 +	{
   65.48 +		final ByteArrayOutputStream out = new ByteArrayOutputStream();
   65.49 +		out.write(art.getHeaderSource().getBytes("UTF-8"));
   65.50 +		out.write("\r\n\r\n".getBytes());
   65.51 +		out.write(art.getBody()); // Without CRLF
   65.52 +		out.flush();
   65.53 +		this.buf = out.toByteArray();
   65.54 +	}
   65.55 +
   65.56 +	/**
   65.57 +	 * This method reads one byte from the stream.  The <code>pos</code>
   65.58 +	 * counter is advanced to the next byte to be read.  The byte read is
   65.59 +	 * returned as an int in the range of 0-255.  If the stream position
   65.60 +	 * is already at the end of the buffer, no byte is read and a -1 is
   65.61 +	 * returned in order to indicate the end of the stream.
   65.62 +	 *
   65.63 +	 * @return The byte read, or -1 if end of stream
   65.64 +	 */
   65.65 +	@Override
   65.66 +	public synchronized int read()
   65.67 +	{
   65.68 +		if (pos < buf.length) {
   65.69 +			return ((int) buf[pos++]) & 0xFF;
   65.70 +		} else {
   65.71 +			return -1;
   65.72 +		}
   65.73 +	}
   65.74  }
    66.1 --- a/src/org/sonews/util/io/ArticleReader.java	Sun Aug 29 17:43:58 2010 +0200
    66.2 +++ b/src/org/sonews/util/io/ArticleReader.java	Sun Aug 29 18:17:37 2010 +0200
    66.3 @@ -34,102 +34,87 @@
    66.4   * @author Christian Lins
    66.5   * @since sonews/0.5.0
    66.6   */
    66.7 -public class ArticleReader 
    66.8 +public class ArticleReader
    66.9  {
   66.10  
   66.11 -  private BufferedOutputStream out;
   66.12 -  private BufferedInputStream  in;
   66.13 -  private String               messageID;
   66.14 -  
   66.15 -  public ArticleReader(String host, int port, String messageID)
   66.16 -    throws IOException, UnknownHostException
   66.17 -  {
   66.18 -    this.messageID = messageID;
   66.19 +	private BufferedOutputStream out;
   66.20 +	private BufferedInputStream in;
   66.21 +	private String messageID;
   66.22  
   66.23 -    // Connect to NNTP server
   66.24 -    Socket socket = new Socket(host, port);
   66.25 -    this.out = new BufferedOutputStream(socket.getOutputStream());
   66.26 -    this.in  = new BufferedInputStream(socket.getInputStream());
   66.27 -    String line = readln(this.in);
   66.28 -    if(!line.startsWith("200 "))
   66.29 -    {
   66.30 -      throw new IOException("Invalid hello from server: " + line);
   66.31 -    }
   66.32 -  }
   66.33 -  
   66.34 -  private boolean eofArticle(byte[] buf)
   66.35 -  {
   66.36 -    if(buf.length < 4)
   66.37 -    {
   66.38 -      return false;
   66.39 -    }
   66.40 -    
   66.41 -    int l = buf.length - 1;
   66.42 -    return buf[l-3] == 10 // '*\n'
   66.43 -        && buf[l-2] == '.'                   // '.'
   66.44 -        && buf[l-1] == 13 && buf[l] == 10;  // '\r\n'
   66.45 -  }
   66.46 -  
   66.47 -  public byte[] getArticleData()
   66.48 -    throws IOException, UnsupportedEncodingException
   66.49 -  {
   66.50 -    long maxSize = Config.inst().get(Config.ARTICLE_MAXSIZE, 1024) * 1024L;
   66.51 +	public ArticleReader(String host, int port, String messageID)
   66.52 +		throws IOException, UnknownHostException
   66.53 +	{
   66.54 +		this.messageID = messageID;
   66.55  
   66.56 -    try
   66.57 -    {
   66.58 -      this.out.write(("ARTICLE " + this.messageID + "\r\n").getBytes("UTF-8"));
   66.59 -      this.out.flush();
   66.60 +		// Connect to NNTP server
   66.61 +		Socket socket = new Socket(host, port);
   66.62 +		this.out = new BufferedOutputStream(socket.getOutputStream());
   66.63 +		this.in = new BufferedInputStream(socket.getInputStream());
   66.64 +		String line = readln(this.in);
   66.65 +		if (!line.startsWith("200 ")) {
   66.66 +			throw new IOException("Invalid hello from server: " + line);
   66.67 +		}
   66.68 +	}
   66.69  
   66.70 -      String line = readln(this.in);
   66.71 -      if(line.startsWith("220 "))
   66.72 -      {
   66.73 -        ByteArrayOutputStream buf = new ByteArrayOutputStream();
   66.74 -        
   66.75 -        while(!eofArticle(buf.toByteArray()))
   66.76 -        {
   66.77 -          for(int b = in.read(); b != 10; b = in.read())
   66.78 -          {
   66.79 -            buf.write(b);
   66.80 -          }
   66.81 +	private boolean eofArticle(byte[] buf)
   66.82 +	{
   66.83 +		if (buf.length < 4) {
   66.84 +			return false;
   66.85 +		}
   66.86  
   66.87 -          buf.write(10);
   66.88 -          if(buf.size() > maxSize)
   66.89 -          {
   66.90 -            Log.get().warning("Skipping message that is too large: " + buf.size());
   66.91 -            return null;
   66.92 -          }
   66.93 -        }
   66.94 -        
   66.95 -        return buf.toByteArray();
   66.96 -      }
   66.97 -      else
   66.98 -      {
   66.99 -        Log.get().warning("ArticleReader: " + line);
  66.100 -        return null;
  66.101 -      }
  66.102 -    }
  66.103 -    catch(IOException ex)
  66.104 -    {
  66.105 -      throw ex;
  66.106 -    }
  66.107 -    finally
  66.108 -    {
  66.109 -      this.out.write("QUIT\r\n".getBytes("UTF-8"));
  66.110 -      this.out.flush();
  66.111 -      this.out.close();
  66.112 -    }
  66.113 -  }
  66.114 -  
  66.115 -  private String readln(InputStream in)
  66.116 -    throws IOException
  66.117 -  {
  66.118 -    ByteArrayOutputStream buf = new ByteArrayOutputStream();
  66.119 -    for(int b = in.read(); b != 10 /* \n */; b = in.read())
  66.120 -    {
  66.121 -      buf.write(b);
  66.122 -    }
  66.123 -    
  66.124 -    return new String(buf.toByteArray());
  66.125 -  }
  66.126 +		int l = buf.length - 1;
  66.127 +		return buf[l - 3] == 10 // '*\n'
  66.128 +			&& buf[l - 2] == '.' // '.'
  66.129 +			&& buf[l - 1] == 13 && buf[l] == 10;  // '\r\n'
  66.130 +	}
  66.131  
  66.132 +	public byte[] getArticleData()
  66.133 +		throws IOException, UnsupportedEncodingException
  66.134 +	{
  66.135 +		long maxSize = Config.inst().get(Config.ARTICLE_MAXSIZE, 1024) * 1024L;
  66.136 +
  66.137 +		try {
  66.138 +			this.out.write(("ARTICLE " + this.messageID + "\r\n").getBytes("UTF-8"));
  66.139 +			this.out.flush();
  66.140 +
  66.141 +			String line = readln(this.in);
  66.142 +			if (line.startsWith("220 ")) {
  66.143 +				ByteArrayOutputStream buf = new ByteArrayOutputStream();
  66.144 +
  66.145 +				while (!eofArticle(buf.toByteArray())) {
  66.146 +					for (int b = in.read(); b != 10; b = in.read()) {
  66.147 +						buf.write(b);
  66.148 +					}
  66.149 +
  66.150 +					buf.write(10);
  66.151 +					if (buf.size() > maxSize) {
  66.152 +						Log.get().warning("Skipping message that is too large: " + buf.size());
  66.153 +						return null;
  66.154 +					}
  66.155 +				}
  66.156 +
  66.157 +				return buf.toByteArray();
  66.158 +			} else {
  66.159 +				Log.get().warning("ArticleReader: " + line);
  66.160 +				return null;
  66.161 +			}
  66.162 +		} catch (IOException ex) {
  66.163 +			throw ex;
  66.164 +		} finally {
  66.165 +			this.out.write("QUIT\r\n".getBytes("UTF-8"));
  66.166 +			this.out.flush();
  66.167 +			this.out.close();
  66.168 +		}
  66.169 +	}
  66.170 +
  66.171 +	private String readln(InputStream in)
  66.172 +		throws IOException
  66.173 +	{
  66.174 +		ByteArrayOutputStream buf = new ByteArrayOutputStream();
  66.175 +		for (int b = in.read(); b != 10 /* \n */; b = in.read()) {
  66.176 +			buf.write(b);
  66.177 +		}
  66.178 +
  66.179 +		return new String(buf.toByteArray());
  66.180 +	}
  66.181  }
    67.1 --- a/src/org/sonews/util/io/ArticleWriter.java	Sun Aug 29 17:43:58 2010 +0200
    67.2 +++ b/src/org/sonews/util/io/ArticleWriter.java	Sun Aug 29 18:17:37 2010 +0200
    67.3 @@ -32,102 +32,97 @@
    67.4   * @author Christian Lins
    67.5   * @since sonews/0.5.0
    67.6   */
    67.7 -public class ArticleWriter 
    67.8 +public class ArticleWriter
    67.9  {
   67.10 -  
   67.11 -  private BufferedOutputStream out;
   67.12 -  private BufferedReader       inr;
   67.13  
   67.14 -  public ArticleWriter(String host, int port)
   67.15 -    throws IOException, UnknownHostException
   67.16 -  {
   67.17 -    // Connect to NNTP server
   67.18 -    Socket socket = new Socket(host, port);
   67.19 -    this.out = new BufferedOutputStream(socket.getOutputStream());
   67.20 -    this.inr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
   67.21 -    String line = inr.readLine();
   67.22 -    if(line == null || !line.startsWith("200 "))
   67.23 -    {
   67.24 -      throw new IOException("Invalid hello from server: " + line);
   67.25 -    }
   67.26 -  }
   67.27 -  
   67.28 -  public void close()
   67.29 -    throws IOException, UnsupportedEncodingException
   67.30 -  {
   67.31 -    this.out.write("QUIT\r\n".getBytes("UTF-8"));
   67.32 -    this.out.flush();
   67.33 -  }
   67.34 +	private BufferedOutputStream out;
   67.35 +	private BufferedReader inr;
   67.36  
   67.37 -  protected void finishPOST()
   67.38 -    throws IOException
   67.39 -  {
   67.40 -    this.out.write("\r\n.\r\n".getBytes());
   67.41 -    this.out.flush();
   67.42 -    String line = inr.readLine();
   67.43 -    if(line == null || !line.startsWith("240 ") || !line.startsWith("441 "))
   67.44 -    {
   67.45 -      throw new IOException(line);
   67.46 -    }
   67.47 -  }
   67.48 +	public ArticleWriter(String host, int port)
   67.49 +		throws IOException, UnknownHostException
   67.50 +	{
   67.51 +		// Connect to NNTP server
   67.52 +		Socket socket = new Socket(host, port);
   67.53 +		this.out = new BufferedOutputStream(socket.getOutputStream());
   67.54 +		this.inr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
   67.55 +		String line = inr.readLine();
   67.56 +		if (line == null || !line.startsWith("200 ")) {
   67.57 +			throw new IOException("Invalid hello from server: " + line);
   67.58 +		}
   67.59 +	}
   67.60  
   67.61 -  protected void preparePOST()
   67.62 -    throws IOException
   67.63 -  {
   67.64 -    this.out.write("POST\r\n".getBytes("UTF-8"));
   67.65 -    this.out.flush();
   67.66 +	public void close()
   67.67 +		throws IOException, UnsupportedEncodingException
   67.68 +	{
   67.69 +		this.out.write("QUIT\r\n".getBytes("UTF-8"));
   67.70 +		this.out.flush();
   67.71 +	}
   67.72  
   67.73 -    String line = this.inr.readLine();
   67.74 -    if(line == null || !line.startsWith("340 "))
   67.75 -    {
   67.76 -      throw new IOException(line);
   67.77 -    }
   67.78 -  }
   67.79 +	protected void finishPOST()
   67.80 +		throws IOException
   67.81 +	{
   67.82 +		this.out.write("\r\n.\r\n".getBytes());
   67.83 +		this.out.flush();
   67.84 +		String line = inr.readLine();
   67.85 +		if (line == null || !line.startsWith("240 ") || !line.startsWith("441 ")) {
   67.86 +			throw new IOException(line);
   67.87 +		}
   67.88 +	}
   67.89  
   67.90 -  public void writeArticle(Article article)
   67.91 -    throws IOException, UnsupportedEncodingException
   67.92 -  {
   67.93 -    byte[] buf = new byte[512];
   67.94 -    ArticleInputStream in = new ArticleInputStream(article);
   67.95 +	protected void preparePOST()
   67.96 +		throws IOException
   67.97 +	{
   67.98 +		this.out.write("POST\r\n".getBytes("UTF-8"));
   67.99 +		this.out.flush();
  67.100  
  67.101 -    preparePOST();
  67.102 -    
  67.103 -    int len = in.read(buf);
  67.104 -    while(len != -1)
  67.105 -    {
  67.106 -      writeLine(buf, len);
  67.107 -      len = in.read(buf);
  67.108 -    }
  67.109 +		String line = this.inr.readLine();
  67.110 +		if (line == null || !line.startsWith("340 ")) {
  67.111 +			throw new IOException(line);
  67.112 +		}
  67.113 +	}
  67.114  
  67.115 -    finishPOST();
  67.116 -  }
  67.117 +	public void writeArticle(Article article)
  67.118 +		throws IOException, UnsupportedEncodingException
  67.119 +	{
  67.120 +		byte[] buf = new byte[512];
  67.121 +		ArticleInputStream in = new ArticleInputStream(article);
  67.122  
  67.123 -  /**
  67.124 -   * Writes the raw content of an article to the remote server. This method
  67.125 -   * does no charset conversion/handling of any kind so its the preferred
  67.126 -   * method for sending an article to remote peers.
  67.127 -   * @param rawArticle
  67.128 -   * @throws IOException
  67.129 -   */
  67.130 -  public void writeArticle(byte[] rawArticle)
  67.131 -    throws IOException
  67.132 -  {
  67.133 -    preparePOST();
  67.134 -    writeLine(rawArticle, rawArticle.length);
  67.135 -    finishPOST();
  67.136 -  }
  67.137 +		preparePOST();
  67.138  
  67.139 -  /**
  67.140 -   * Writes the given buffer to the connect remote server.
  67.141 -   * @param buffer
  67.142 -   * @param len
  67.143 -   * @throws IOException
  67.144 -   */
  67.145 -  protected void writeLine(byte[] buffer, int len)
  67.146 -    throws IOException
  67.147 -  {
  67.148 -    this.out.write(buffer, 0, len);
  67.149 -    this.out.flush();
  67.150 -  }
  67.151 +		int len = in.read(buf);
  67.152 +		while (len != -1) {
  67.153 +			writeLine(buf, len);
  67.154 +			len = in.read(buf);
  67.155 +		}
  67.156  
  67.157 +		finishPOST();
  67.158 +	}
  67.159 +
  67.160 +	/**
  67.161 +	 * Writes the raw content of an article to the remote server. This method
  67.162 +	 * does no charset conversion/handling of any kind so its the preferred
  67.163 +	 * method for sending an article to remote peers.
  67.164 +	 * @param rawArticle
  67.165 +	 * @throws IOException
  67.166 +	 */
  67.167 +	public void writeArticle(byte[] rawArticle)
  67.168 +		throws IOException
  67.169 +	{
  67.170 +		preparePOST();
  67.171 +		writeLine(rawArticle, rawArticle.length);
  67.172 +		finishPOST();
  67.173 +	}
  67.174 +
  67.175 +	/**
  67.176 +	 * Writes the given buffer to the connect remote server.
  67.177 +	 * @param buffer
  67.178 +	 * @param len
  67.179 +	 * @throws IOException
  67.180 +	 */
  67.181 +	protected void writeLine(byte[] buffer, int len)
  67.182 +		throws IOException
  67.183 +	{
  67.184 +		this.out.write(buffer, 0, len);
  67.185 +		this.out.flush();
  67.186 +	}
  67.187  }
    68.1 --- a/src/org/sonews/util/io/Resource.java	Sun Aug 29 17:43:58 2010 +0200
    68.2 +++ b/src/org/sonews/util/io/Resource.java	Sun Aug 29 18:17:37 2010 +0200
    68.3 @@ -32,101 +32,89 @@
    68.4   */
    68.5  public final class Resource
    68.6  {
    68.7 -  
    68.8 -  /**
    68.9 -   * Loads a resource and returns it as URL reference.
   68.10 -   * The Resource's classloader is used to load the resource, not
   68.11 -   * the System's ClassLoader so it may be safe to use this method
   68.12 -   * in a sandboxed environment.
   68.13 -   * @return
   68.14 -   */
   68.15 -  public static URL getAsURL(final String name)
   68.16 -  {
   68.17 -    if(name == null)
   68.18 -    {
   68.19 -      return null;
   68.20 -    }
   68.21  
   68.22 -    return Resource.class.getClassLoader().getResource(name);
   68.23 -  }
   68.24 -  
   68.25 -  /**
   68.26 -   * Loads a resource and returns an InputStream to it.
   68.27 -   * @param name
   68.28 -   * @return
   68.29 -   */
   68.30 -  public static InputStream getAsStream(String name)
   68.31 -  {
   68.32 -    try
   68.33 -    {
   68.34 -      URL url = getAsURL(name);
   68.35 -      if(url == null)
   68.36 -      {
   68.37 -        return null;
   68.38 -      }
   68.39 -      else
   68.40 -      {
   68.41 -        return url.openStream();
   68.42 -      }
   68.43 -    }
   68.44 -    catch(IOException e)
   68.45 -    {
   68.46 -      e.printStackTrace();
   68.47 -      return null;
   68.48 -    }
   68.49 -  }
   68.50 +	/**
   68.51 +	 * Loads a resource and returns it as URL reference.
   68.52 +	 * The Resource's classloader is used to load the resource, not
   68.53 +	 * the System's ClassLoader so it may be safe to use this method
   68.54 +	 * in a sandboxed environment.
   68.55 +	 * @return
   68.56 +	 */
   68.57 +	public static URL getAsURL(final String name)
   68.58 +	{
   68.59 +		if (name == null) {
   68.60 +			return null;
   68.61 +		}
   68.62  
   68.63 -  /**
   68.64 -   * Loads a plain text resource.
   68.65 -   * @param withNewline If false all newlines are removed from the 
   68.66 -   * return String
   68.67 -   */
   68.68 -  public static String getAsString(String name, boolean withNewline)
   68.69 -  {
   68.70 -    if(name == null)
   68.71 -      return null;
   68.72 +		return Resource.class.getClassLoader().getResource(name);
   68.73 +	}
   68.74  
   68.75 -    BufferedReader in = null;
   68.76 -    try
   68.77 -    {
   68.78 -      InputStream ins = getAsStream(name);
   68.79 -      if(ins == null)
   68.80 -        return null;
   68.81 +	/**
   68.82 +	 * Loads a resource and returns an InputStream to it.
   68.83 +	 * @param name
   68.84 +	 * @return
   68.85 +	 */
   68.86 +	public static InputStream getAsStream(String name)
   68.87 +	{
   68.88 +		try {
   68.89 +			URL url = getAsURL(name);
   68.90 +			if (url == null) {
   68.91 +				return null;
   68.92 +			} else {
   68.93 +				return url.openStream();
   68.94 +			}
   68.95 +		} catch (IOException e) {
   68.96 +			e.printStackTrace();
   68.97 +			return null;
   68.98 +		}
   68.99 +	}
  68.100  
  68.101 -      in = new BufferedReader(
  68.102 -        new InputStreamReader(ins, Charset.forName("UTF-8")));
  68.103 -      StringBuffer buf = new StringBuffer();
  68.104 +	/**
  68.105 +	 * Loads a plain text resource.
  68.106 +	 * @param withNewline If false all newlines are removed from the
  68.107 +	 * return String
  68.108 +	 */
  68.109 +	public static String getAsString(String name, boolean withNewline)
  68.110 +	{
  68.111 +		if (name == null) {
  68.112 +			return null;
  68.113 +		}
  68.114  
  68.115 -      for(;;)
  68.116 -      {
  68.117 -        String line = in.readLine();
  68.118 -        if(line == null)
  68.119 -          break;
  68.120 +		BufferedReader in = null;
  68.121 +		try {
  68.122 +			InputStream ins = getAsStream(name);
  68.123 +			if (ins == null) {
  68.124 +				return null;
  68.125 +			}
  68.126  
  68.127 -        buf.append(line);
  68.128 -        if(withNewline)
  68.129 -          buf.append('\n');
  68.130 -      }
  68.131 +			in = new BufferedReader(
  68.132 +				new InputStreamReader(ins, Charset.forName("UTF-8")));
  68.133 +			StringBuffer buf = new StringBuffer();
  68.134  
  68.135 -      return buf.toString();
  68.136 -    }
  68.137 -    catch(Exception e)
  68.138 -    {
  68.139 -      e.printStackTrace();
  68.140 -      return null;
  68.141 -    }
  68.142 -    finally
  68.143 -    {
  68.144 -      try
  68.145 -      {
  68.146 -        if(in != null)
  68.147 -          in.close();
  68.148 -      }
  68.149 -      catch(IOException ex)
  68.150 -      {
  68.151 -        ex.printStackTrace();
  68.152 -      }
  68.153 -    }
  68.154 -  }
  68.155 +			for (;;) {
  68.156 +				String line = in.readLine();
  68.157 +				if (line == null) {
  68.158 +					break;
  68.159 +				}
  68.160  
  68.161 +				buf.append(line);
  68.162 +				if (withNewline) {
  68.163 +					buf.append('\n');
  68.164 +				}
  68.165 +			}
  68.166 +
  68.167 +			return buf.toString();
  68.168 +		} catch (Exception e) {
  68.169 +			e.printStackTrace();
  68.170 +			return null;
  68.171 +		} finally {
  68.172 +			try {
  68.173 +				if (in != null) {
  68.174 +					in.close();
  68.175 +				}
  68.176 +			} catch (IOException ex) {
  68.177 +				ex.printStackTrace();
  68.178 +			}
  68.179 +		}
  68.180 +	}
  68.181  }