Moving source files into src/-subdir.
authorcli
Sun Aug 29 17:28:58 2010 +0200 (2010-08-29)
changeset 35ed84c8bdd87b
parent 34 9f0b95aafaa3
child 36 c404a87db5b7
Moving source files into src/-subdir.
org/sonews/Main.java
org/sonews/ShutdownHook.java
org/sonews/acl/AccessControl.java
org/sonews/acl/AuthInfoCommand.java
org/sonews/config/AbstractConfig.java
org/sonews/config/BackendConfig.java
org/sonews/config/CommandLineConfig.java
org/sonews/config/Config.java
org/sonews/config/FileConfig.java
org/sonews/daemon/AbstractDaemon.java
org/sonews/daemon/ChannelLineBuffers.java
org/sonews/daemon/ChannelReader.java
org/sonews/daemon/ChannelWriter.java
org/sonews/daemon/CommandSelector.java
org/sonews/daemon/ConnectionWorker.java
org/sonews/daemon/Connections.java
org/sonews/daemon/LineEncoder.java
org/sonews/daemon/NNTPConnection.java
org/sonews/daemon/NNTPDaemon.java
org/sonews/daemon/command/ArticleCommand.java
org/sonews/daemon/command/CapabilitiesCommand.java
org/sonews/daemon/command/Command.java
org/sonews/daemon/command/GroupCommand.java
org/sonews/daemon/command/HelpCommand.java
org/sonews/daemon/command/HelpfulCommand.java
org/sonews/daemon/command/ListCommand.java
org/sonews/daemon/command/ListGroupCommand.java
org/sonews/daemon/command/ModeReaderCommand.java
org/sonews/daemon/command/NewGroupsCommand.java
org/sonews/daemon/command/NextPrevCommand.java
org/sonews/daemon/command/OverCommand.java
org/sonews/daemon/command/PostCommand.java
org/sonews/daemon/command/PostState.java
org/sonews/daemon/command/QuitCommand.java
org/sonews/daemon/command/StatCommand.java
org/sonews/daemon/command/UnsupportedCommand.java
org/sonews/daemon/command/XDaemonCommand.java
org/sonews/daemon/command/XPatCommand.java
org/sonews/daemon/command/package.html
org/sonews/daemon/package.html
org/sonews/feed/FeedManager.java
org/sonews/feed/PullFeeder.java
org/sonews/feed/PushFeeder.java
org/sonews/feed/Subscription.java
org/sonews/feed/package.html
org/sonews/mlgw/Dispatcher.java
org/sonews/mlgw/MailPoller.java
org/sonews/mlgw/SMTPTransport.java
org/sonews/mlgw/package.html
org/sonews/plugin/Plugin.java
org/sonews/storage/Article.java
org/sonews/storage/ArticleHead.java
org/sonews/storage/Channel.java
org/sonews/storage/Group.java
org/sonews/storage/Headers.java
org/sonews/storage/Storage.java
org/sonews/storage/StorageBackendException.java
org/sonews/storage/StorageManager.java
org/sonews/storage/StorageProvider.java
org/sonews/storage/impl/JDBCDatabase.java
org/sonews/storage/impl/JDBCDatabaseProvider.java
org/sonews/storage/package.html
org/sonews/util/DatabaseSetup.java
org/sonews/util/Log.java
org/sonews/util/Pair.java
org/sonews/util/Purger.java
org/sonews/util/Stats.java
org/sonews/util/StringTemplate.java
org/sonews/util/TimeoutMap.java
org/sonews/util/io/ArticleInputStream.java
org/sonews/util/io/ArticleReader.java
org/sonews/util/io/ArticleWriter.java
org/sonews/util/io/Resource.java
org/sonews/util/io/package.html
org/sonews/util/package.html
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/daemon/command/package.html
src/org/sonews/daemon/package.html
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/feed/package.html
src/org/sonews/mlgw/Dispatcher.java
src/org/sonews/mlgw/MailPoller.java
src/org/sonews/mlgw/SMTPTransport.java
src/org/sonews/mlgw/package.html
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/storage/package.html
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
src/org/sonews/util/io/package.html
src/org/sonews/util/package.html
     1.1 --- a/org/sonews/Main.java	Sun Aug 29 17:04:25 2010 +0200
     1.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.3 @@ -1,198 +0,0 @@
     1.4 -/*
     1.5 - *   SONEWS News Server
     1.6 - *   see AUTHORS for the list of contributors
     1.7 - *
     1.8 - *   This program is free software: you can redistribute it and/or modify
     1.9 - *   it under the terms of the GNU General Public License as published by
    1.10 - *   the Free Software Foundation, either version 3 of the License, or
    1.11 - *   (at your option) any later version.
    1.12 - *
    1.13 - *   This program is distributed in the hope that it will be useful,
    1.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    1.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1.16 - *   GNU General Public License for more details.
    1.17 - *
    1.18 - *   You should have received a copy of the GNU General Public License
    1.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    1.20 - */
    1.21 -
    1.22 -package org.sonews;
    1.23 -
    1.24 -import java.sql.Driver;
    1.25 -import java.sql.DriverManager;
    1.26 -import java.util.Enumeration;
    1.27 -import java.util.Date;
    1.28 -import java.util.logging.Level;
    1.29 -import org.sonews.config.Config;
    1.30 -import org.sonews.daemon.ChannelLineBuffers;
    1.31 -import org.sonews.daemon.CommandSelector;
    1.32 -import org.sonews.daemon.Connections;
    1.33 -import org.sonews.daemon.NNTPDaemon;
    1.34 -import org.sonews.feed.FeedManager;
    1.35 -import org.sonews.mlgw.MailPoller;
    1.36 -import org.sonews.storage.StorageBackendException;
    1.37 -import org.sonews.storage.StorageManager;
    1.38 -import org.sonews.storage.StorageProvider;
    1.39 -import org.sonews.util.Log;
    1.40 -import org.sonews.util.Purger;
    1.41 -import org.sonews.util.io.Resource;
    1.42 -
    1.43 -/**
    1.44 - * Startup class of the daemon.
    1.45 - * @author Christian Lins
    1.46 - * @since sonews/0.5.0
    1.47 - */
    1.48 -public final class Main
    1.49 -{
    1.50 -  
    1.51 -  private Main()
    1.52 -  {
    1.53 -  }
    1.54 -
    1.55 -  /** Version information of the sonews daemon */
    1.56 -  public static final String VERSION = "sonews/1.1.0";
    1.57 -  public static final Date   STARTDATE = new Date();
    1.58 -  
    1.59 -  /**
    1.60 -   * The main entrypoint.
    1.61 -   * @param args
    1.62 -   * @throws Exception
    1.63 -   */
    1.64 -  public static void main(String[] args) throws Exception
    1.65 -  {
    1.66 -    System.out.println(VERSION);
    1.67 -    Thread.currentThread().setName("Mainthread");
    1.68 -
    1.69 -    // Command line arguments
    1.70 -    boolean feed    = false;  // Enable feeding?
    1.71 -    boolean mlgw    = false;  // Enable Mailinglist gateway?
    1.72 -    int     port    = -1;
    1.73 -    
    1.74 -    for(int n = 0; n < args.length; n++)
    1.75 -    {
    1.76 -      if(args[n].equals("-c") || args[n].equals("-config"))
    1.77 -      {
    1.78 -        Config.inst().set(Config.LEVEL_CLI, Config.CONFIGFILE, args[++n]);
    1.79 -        System.out.println("Using config file " + args[n]);
    1.80 -      }
    1.81 -      else if(args[n].equals("-dumpjdbcdriver"))
    1.82 -      {
    1.83 -        System.out.println("Available JDBC drivers:");
    1.84 -        Enumeration<Driver> drvs =  DriverManager.getDrivers();
    1.85 -        while(drvs.hasMoreElements())
    1.86 -        {
    1.87 -          System.out.println(drvs.nextElement());
    1.88 -        }
    1.89 -        return;
    1.90 -      }
    1.91 -      else if(args[n].equals("-feed"))
    1.92 -      {
    1.93 -        feed = true;
    1.94 -      }
    1.95 -      else if(args[n].equals("-h") || args[n].equals("-help"))
    1.96 -      {
    1.97 -        printArguments();
    1.98 -        return;
    1.99 -      }
   1.100 -      else if(args[n].equals("-mlgw"))
   1.101 -      {
   1.102 -        mlgw = true;
   1.103 -      }
   1.104 -      else if(args[n].equals("-p"))
   1.105 -      {
   1.106 -        port = Integer.parseInt(args[++n]);
   1.107 -      }
   1.108 -      else if(args[n].equals("-plugin"))
   1.109 -      {
   1.110 -        System.out.println("Warning: -plugin-storage is not implemented!");
   1.111 -      }
   1.112 -      else if(args[n].equals("-plugin-command"))
   1.113 -      {
   1.114 -        try
   1.115 -        {
   1.116 -          CommandSelector.addCommandHandler(args[++n]);
   1.117 -        }
   1.118 -        catch(Exception ex)
   1.119 -        {
   1.120 -          Log.get().warning("Could not load command plugin: " + args[n]);
   1.121 -          Log.get().log(Level.INFO, "Main.java", ex);
   1.122 -        }
   1.123 -      }
   1.124 -      else if(args[n].equals("-plugin-storage"))
   1.125 -      {
   1.126 -        System.out.println("Warning: -plugin-storage is not implemented!");
   1.127 -      }
   1.128 -      else if(args[n].equals("-v") || args[n].equals("-version"))
   1.129 -      {
   1.130 -        // Simply return as the version info is already printed above
   1.131 -        return;
   1.132 -      }
   1.133 -    }
   1.134 -    
   1.135 -    // Try to load the JDBCDatabase;
   1.136 -    // Do NOT USE BackendConfig or Log classes before this point because they require
   1.137 -    // a working JDBCDatabase connection.
   1.138 -    try
   1.139 -    {
   1.140 -      StorageProvider sprov =
   1.141 -        StorageManager.loadProvider("org.sonews.storage.impl.JDBCDatabaseProvider");
   1.142 -      StorageManager.enableProvider(sprov);
   1.143 -      
   1.144 -      // Make sure some elementary groups are existing
   1.145 -      if(!StorageManager.current().isGroupExisting("control"))
   1.146 -      {
   1.147 -        StorageManager.current().addGroup("control", 0);
   1.148 -        Log.get().info("Group 'control' created.");
   1.149 -      }
   1.150 -    }
   1.151 -    catch(StorageBackendException ex)
   1.152 -    {
   1.153 -      ex.printStackTrace();
   1.154 -      System.err.println("Database initialization failed with " + ex.toString());
   1.155 -      System.err.println("Make sure you have specified the correct database" +
   1.156 -        " settings in sonews.conf!");
   1.157 -      return;
   1.158 -    }
   1.159 -    
   1.160 -    ChannelLineBuffers.allocateDirect();
   1.161 -    
   1.162 -    // Add shutdown hook
   1.163 -    Runtime.getRuntime().addShutdownHook(new ShutdownHook());
   1.164 -    
   1.165 -    // Start the listening daemon
   1.166 -    if(port <= 0)
   1.167 -    {
   1.168 -      port = Config.inst().get(Config.PORT, 119);
   1.169 -    }
   1.170 -    final NNTPDaemon daemon = NNTPDaemon.createInstance(port);
   1.171 -    daemon.start();
   1.172 -    
   1.173 -    // Start Connections purger thread...
   1.174 -    Connections.getInstance().start();
   1.175 -    
   1.176 -    // Start mailinglist gateway...
   1.177 -    if(mlgw)
   1.178 -    {
   1.179 -      new MailPoller().start();
   1.180 -    }
   1.181 -    
   1.182 -    // Start feeds
   1.183 -    if(feed)
   1.184 -    {
   1.185 -      FeedManager.startFeeding();
   1.186 -    }
   1.187 -
   1.188 -    Purger purger = new Purger();
   1.189 -    purger.start();
   1.190 -    
   1.191 -    // Wait for main thread to exit (setDaemon(false))
   1.192 -    daemon.join();
   1.193 -  }
   1.194 -  
   1.195 -  private static void printArguments()
   1.196 -  {
   1.197 -    String usage = Resource.getAsString("helpers/usage", true);
   1.198 -    System.out.println(usage);
   1.199 -  }
   1.200 -
   1.201 -}
     2.1 --- a/org/sonews/ShutdownHook.java	Sun Aug 29 17:04:25 2010 +0200
     2.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.3 @@ -1,84 +0,0 @@
     2.4 -/*
     2.5 - *   SONEWS News Server
     2.6 - *   see AUTHORS for the list of contributors
     2.7 - *
     2.8 - *   This program is free software: you can redistribute it and/or modify
     2.9 - *   it under the terms of the GNU General Public License as published by
    2.10 - *   the Free Software Foundation, either version 3 of the License, or
    2.11 - *   (at your option) any later version.
    2.12 - *
    2.13 - *   This program is distributed in the hope that it will be useful,
    2.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    2.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    2.16 - *   GNU General Public License for more details.
    2.17 - *
    2.18 - *   You should have received a copy of the GNU General Public License
    2.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    2.20 - */
    2.21 -
    2.22 -package org.sonews;
    2.23 -
    2.24 -import java.sql.SQLException;
    2.25 -import java.util.Map;
    2.26 -import org.sonews.daemon.AbstractDaemon;
    2.27 -
    2.28 -/**
    2.29 - * Will force all other threads to shutdown cleanly.
    2.30 - * @author Christian Lins
    2.31 - * @since sonews/0.5.0
    2.32 - */
    2.33 -class ShutdownHook extends Thread
    2.34 -{
    2.35 -
    2.36 -  /**
    2.37 -   * Called when the JVM exits.
    2.38 -   */
    2.39 -  @Override
    2.40 -  public void run()
    2.41 -  {
    2.42 -    System.out.println("sonews: Trying to shutdown all threads...");
    2.43 -
    2.44 -    Map<Thread, StackTraceElement[]> threadsMap = Thread.getAllStackTraces();
    2.45 -    for(Thread thread : threadsMap.keySet())
    2.46 -    {
    2.47 -      // Interrupt the thread if it's a AbstractDaemon
    2.48 -      AbstractDaemon daemon;
    2.49 -      if(thread instanceof AbstractDaemon && thread.isAlive())
    2.50 -      {
    2.51 -        try
    2.52 -        {
    2.53 -          daemon = (AbstractDaemon)thread;
    2.54 -          daemon.shutdownNow();
    2.55 -        }
    2.56 -        catch(SQLException ex)
    2.57 -        {
    2.58 -          System.out.println("sonews: " + ex);
    2.59 -        }
    2.60 -      }
    2.61 -    }
    2.62 -    
    2.63 -    for(Thread thread : threadsMap.keySet())
    2.64 -    {
    2.65 -      AbstractDaemon daemon;
    2.66 -      if(thread instanceof AbstractDaemon && thread.isAlive())
    2.67 -      {
    2.68 -        daemon = (AbstractDaemon)thread;
    2.69 -        System.out.println("sonews: Waiting for " + daemon + " to exit...");
    2.70 -        try
    2.71 -        {
    2.72 -          daemon.join(500);
    2.73 -        }
    2.74 -        catch(InterruptedException ex)
    2.75 -        {
    2.76 -          System.out.println(ex.getLocalizedMessage());
    2.77 -        }
    2.78 -      }
    2.79 -    }
    2.80 -    
    2.81 -    // We have notified all not-sleeping AbstractDaemons of the shutdown;
    2.82 -    // all other threads can be simply purged on VM shutdown
    2.83 -    
    2.84 -    System.out.println("sonews: Clean shutdown.");
    2.85 -  }
    2.86 -  
    2.87 -}
     3.1 --- a/org/sonews/acl/AccessControl.java	Sun Aug 29 17:04:25 2010 +0200
     3.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.3 @@ -1,31 +0,0 @@
     3.4 -/*
     3.5 - *   SONEWS News Server
     3.6 - *   see AUTHORS for the list of contributors
     3.7 - *
     3.8 - *   This program is free software: you can redistribute it and/or modify
     3.9 - *   it under the terms of the GNU General Public License as published by
    3.10 - *   the Free Software Foundation, either version 3 of the License, or
    3.11 - *   (at your option) any later version.
    3.12 - *
    3.13 - *   This program is distributed in the hope that it will be useful,
    3.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    3.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    3.16 - *   GNU General Public License for more details.
    3.17 - *
    3.18 - *   You should have received a copy of the GNU General Public License
    3.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    3.20 - */
    3.21 -
    3.22 -package org.sonews.acl;
    3.23 -
    3.24 -/**
    3.25 - *
    3.26 - * @author Christian Lins
    3.27 - * @since sonews/1.1
    3.28 - */
    3.29 -public interface AccessControl
    3.30 -{
    3.31 -
    3.32 -  boolean hasPermission(String user, char[] secret, String permission);
    3.33 -
    3.34 -}
     4.1 --- a/org/sonews/acl/AuthInfoCommand.java	Sun Aug 29 17:04:25 2010 +0200
     4.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.3 @@ -1,64 +0,0 @@
     4.4 -/*
     4.5 - *   SONEWS News Server
     4.6 - *   see AUTHORS for the list of contributors
     4.7 - *
     4.8 - *   This program is free software: you can redistribute it and/or modify
     4.9 - *   it under the terms of the GNU General Public License as published by
    4.10 - *   the Free Software Foundation, either version 3 of the License, or
    4.11 - *   (at your option) any later version.
    4.12 - *
    4.13 - *   This program is distributed in the hope that it will be useful,
    4.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    4.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    4.16 - *   GNU General Public License for more details.
    4.17 - *
    4.18 - *   You should have received a copy of the GNU General Public License
    4.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    4.20 - */
    4.21 -
    4.22 -package org.sonews.acl;
    4.23 -
    4.24 -import java.io.IOException;
    4.25 -import org.sonews.daemon.NNTPConnection;
    4.26 -import org.sonews.daemon.command.Command;
    4.27 -import org.sonews.storage.StorageBackendException;
    4.28 -
    4.29 -/**
    4.30 - *
    4.31 - * @author Christian Lins
    4.32 - * @since sonews/1.1
    4.33 - */
    4.34 -public class AuthInfoCommand implements Command
    4.35 -{
    4.36 -
    4.37 -  @Override
    4.38 -  public String[] getSupportedCommandStrings()
    4.39 -  {
    4.40 -    throw new UnsupportedOperationException("Not supported yet.");
    4.41 -  }
    4.42 -
    4.43 -  @Override
    4.44 -  public boolean hasFinished()
    4.45 -  {
    4.46 -    throw new UnsupportedOperationException("Not supported yet.");
    4.47 -  }
    4.48 -
    4.49 -  @Override
    4.50 -  public String impliedCapability()
    4.51 -  {
    4.52 -    throw new UnsupportedOperationException("Not supported yet.");
    4.53 -  }
    4.54 -
    4.55 -  @Override
    4.56 -  public boolean isStateful()
    4.57 -  {
    4.58 -    throw new UnsupportedOperationException("Not supported yet.");
    4.59 -  }
    4.60 -
    4.61 -  @Override
    4.62 -  public void processLine(NNTPConnection conn, String line, byte[] rawLine) throws IOException, StorageBackendException
    4.63 -  {
    4.64 -    throw new UnsupportedOperationException("Not supported yet.");
    4.65 -  }
    4.66 -
    4.67 -}
     5.1 --- a/org/sonews/config/AbstractConfig.java	Sun Aug 29 17:04:25 2010 +0200
     5.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.3 @@ -1,57 +0,0 @@
     5.4 -/*
     5.5 - *   SONEWS News Server
     5.6 - *   see AUTHORS for the list of contributors
     5.7 - *
     5.8 - *   This program is free software: you can redistribute it and/or modify
     5.9 - *   it under the terms of the GNU General Public License as published by
    5.10 - *   the Free Software Foundation, either version 3 of the License, or
    5.11 - *   (at your option) any later version.
    5.12 - *
    5.13 - *   This program is distributed in the hope that it will be useful,
    5.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    5.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    5.16 - *   GNU General Public License for more details.
    5.17 - *
    5.18 - *   You should have received a copy of the GNU General Public License
    5.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    5.20 - */
    5.21 -
    5.22 -package org.sonews.config;
    5.23 -
    5.24 -/**
    5.25 - * Base class for Config and BootstrapConfig.
    5.26 - * @author Christian Lins
    5.27 - * @since sonews/0.5.0
    5.28 - */
    5.29 -public abstract class AbstractConfig 
    5.30 -{
    5.31 -  
    5.32 -  public abstract String get(String key, String defVal);
    5.33 -  
    5.34 -  public int get(final String key, final int defVal)
    5.35 -  {
    5.36 -    return Integer.parseInt(
    5.37 -      get(key, Integer.toString(defVal)));
    5.38 -  }
    5.39 -  
    5.40 -  public boolean get(String key, boolean defVal)
    5.41 -  {
    5.42 -    String val = get(key, Boolean.toString(defVal));
    5.43 -    return Boolean.parseBoolean(val);
    5.44 -  }
    5.45 -
    5.46 -  /**
    5.47 -   * Returns a long config value specified via the given key.
    5.48 -   * @param key
    5.49 -   * @param defVal
    5.50 -   * @return
    5.51 -   */
    5.52 -  public long get(String key, long defVal)
    5.53 -  {
    5.54 -    String val = get(key, Long.toString(defVal));
    5.55 -    return Long.parseLong(val);
    5.56 -  }
    5.57 -
    5.58 -  protected abstract void set(String key, String val);
    5.59 -  
    5.60 -}
     6.1 --- a/org/sonews/config/BackendConfig.java	Sun Aug 29 17:04:25 2010 +0200
     6.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.3 @@ -1,115 +0,0 @@
     6.4 -/*
     6.5 - *   SONEWS News Server
     6.6 - *   see AUTHORS for the list of contributors
     6.7 - *
     6.8 - *   This program is free software: you can redistribute it and/or modify
     6.9 - *   it under the terms of the GNU General Public License as published by
    6.10 - *   the Free Software Foundation, either version 3 of the License, or
    6.11 - *   (at your option) any later version.
    6.12 - *
    6.13 - *   This program is distributed in the hope that it will be useful,
    6.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    6.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    6.16 - *   GNU General Public License for more details.
    6.17 - *
    6.18 - *   You should have received a copy of the GNU General Public License
    6.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    6.20 - */
    6.21 -
    6.22 -package org.sonews.config;
    6.23 -
    6.24 -import java.util.logging.Level;
    6.25 -import org.sonews.util.Log;
    6.26 -import org.sonews.storage.StorageBackendException;
    6.27 -import org.sonews.storage.StorageManager;
    6.28 -import org.sonews.util.TimeoutMap;
    6.29 -
    6.30 -/**
    6.31 - * Provides access to the program wide configuration that is stored within
    6.32 - * the server's database.
    6.33 - * @author Christian Lins
    6.34 - * @since sonews/0.5.0
    6.35 - */
    6.36 -class BackendConfig extends AbstractConfig
    6.37 -{
    6.38 -
    6.39 -  private static BackendConfig instance = new BackendConfig();
    6.40 -  
    6.41 -  public static BackendConfig getInstance()
    6.42 -  {
    6.43 -    return instance;
    6.44 -  }
    6.45 -  
    6.46 -  private final TimeoutMap<String, String> values 
    6.47 -    = new TimeoutMap<String, String>();
    6.48 -  
    6.49 -  private BackendConfig()
    6.50 -  {
    6.51 -    super();
    6.52 -  }
    6.53 -  
    6.54 -  /**
    6.55 -   * Returns the config value for the given key or the defaultValue if the
    6.56 -   * key is not found in config.
    6.57 -   * @param key
    6.58 -   * @param defaultValue
    6.59 -   * @return
    6.60 -   */
    6.61 -  @Override
    6.62 -  public String get(String key, String defaultValue)
    6.63 -  {
    6.64 -    try
    6.65 -    {
    6.66 -      String configValue = values.get(key);
    6.67 -      if(configValue == null)
    6.68 -      {
    6.69 -        if(StorageManager.current() == null)
    6.70 -        {
    6.71 -          Log.get().warning("BackendConfig not available, using default.");
    6.72 -          return defaultValue;
    6.73 -        }
    6.74 -
    6.75 -        configValue = StorageManager.current().getConfigValue(key);
    6.76 -        if(configValue == null)
    6.77 -        {
    6.78 -          return defaultValue;
    6.79 -        }
    6.80 -        else
    6.81 -        {
    6.82 -          values.put(key, configValue);
    6.83 -          return configValue;
    6.84 -        }
    6.85 -      }
    6.86 -      else
    6.87 -      {
    6.88 -        return configValue;
    6.89 -      }
    6.90 -    }
    6.91 -    catch(StorageBackendException ex)
    6.92 -    {
    6.93 -      Log.get().log(Level.SEVERE, "Storage backend problem", ex);
    6.94 -      return defaultValue;
    6.95 -    }
    6.96 -  }
    6.97 -  
    6.98 -  /**
    6.99 -   * Sets the config value which is identified by the given key.
   6.100 -   * @param key
   6.101 -   * @param value
   6.102 -   */
   6.103 -  public void set(String key, String value)
   6.104 -  {
   6.105 -    values.put(key, value);
   6.106 -    
   6.107 -    try
   6.108 -    {
   6.109 -      // Write values to database
   6.110 -      StorageManager.current().setConfigValue(key, value);
   6.111 -    }
   6.112 -    catch(StorageBackendException ex)
   6.113 -    {
   6.114 -      ex.printStackTrace();
   6.115 -    }
   6.116 -  }
   6.117 -  
   6.118 -}
     7.1 --- a/org/sonews/config/CommandLineConfig.java	Sun Aug 29 17:04:25 2010 +0200
     7.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.3 @@ -1,64 +0,0 @@
     7.4 -/*
     7.5 - *   SONEWS News Server
     7.6 - *   see AUTHORS for the list of contributors
     7.7 - *
     7.8 - *   This program is free software: you can redistribute it and/or modify
     7.9 - *   it under the terms of the GNU General Public License as published by
    7.10 - *   the Free Software Foundation, either version 3 of the License, or
    7.11 - *   (at your option) any later version.
    7.12 - *
    7.13 - *   This program is distributed in the hope that it will be useful,
    7.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    7.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    7.16 - *   GNU General Public License for more details.
    7.17 - *
    7.18 - *   You should have received a copy of the GNU General Public License
    7.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    7.20 - */
    7.21 -
    7.22 -package org.sonews.config;
    7.23 -
    7.24 -import java.util.Map;
    7.25 -import java.util.HashMap;
    7.26 -
    7.27 -/**
    7.28 - *
    7.29 - * @author Christian Lins
    7.30 - */
    7.31 -class CommandLineConfig extends AbstractConfig
    7.32 -{
    7.33 -
    7.34 -  private static final CommandLineConfig instance = new CommandLineConfig();
    7.35 -
    7.36 -  public static CommandLineConfig getInstance()
    7.37 -  {
    7.38 -    return instance;
    7.39 -  }
    7.40 -
    7.41 -  private final Map<String, String> values = new HashMap<String, String>();
    7.42 -  
    7.43 -  private CommandLineConfig() {}
    7.44 -
    7.45 -  @Override
    7.46 -  public String get(String key, String def)
    7.47 -  {
    7.48 -    synchronized(this.values)
    7.49 -    {
    7.50 -      if(this.values.containsKey(key))
    7.51 -      {
    7.52 -        def = this.values.get(key);
    7.53 -      }
    7.54 -    }
    7.55 -    return def;
    7.56 -  }
    7.57 -
    7.58 -  @Override
    7.59 -  public void set(String key, String val)
    7.60 -  {
    7.61 -    synchronized(this.values)
    7.62 -    {
    7.63 -      this.values.put(key, val);
    7.64 -    }
    7.65 -  }
    7.66 -
    7.67 -}
     8.1 --- a/org/sonews/config/Config.java	Sun Aug 29 17:04:25 2010 +0200
     8.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.3 @@ -1,175 +0,0 @@
     8.4 -/*
     8.5 - *   SONEWS News Server
     8.6 - *   see AUTHORS for the list of contributors
     8.7 - *
     8.8 - *   This program is free software: you can redistribute it and/or modify
     8.9 - *   it under the terms of the GNU General Public License as published by
    8.10 - *   the Free Software Foundation, either version 3 of the License, or
    8.11 - *   (at your option) any later version.
    8.12 - *
    8.13 - *   This program is distributed in the hope that it will be useful,
    8.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    8.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    8.16 - *   GNU General Public License for more details.
    8.17 - *
    8.18 - *   You should have received a copy of the GNU General Public License
    8.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    8.20 - */
    8.21 -
    8.22 -package org.sonews.config;
    8.23 -
    8.24 -/**
    8.25 - * Configuration facade class.
    8.26 - * @author Christian Lins
    8.27 - * @since sonews/1.0
    8.28 - */
    8.29 -public class Config extends AbstractConfig
    8.30 -{
    8.31 -  
    8.32 -  public static final int LEVEL_CLI     = 1;
    8.33 -  public static final int LEVEL_FILE    = 2;
    8.34 -  public static final int LEVEL_BACKEND = 3;
    8.35 -
    8.36 -  public static final String CONFIGFILE = "sonews.configfile";
    8.37 -  
    8.38 -    /** BackendConfig key constant. Value is the maximum article size in kilobytes. */
    8.39 -  public static final String ARTICLE_MAXSIZE   = "sonews.article.maxsize";
    8.40 -
    8.41 -  /** BackendConfig key constant. Value: Amount of news that are feeded per run. */
    8.42 -  public static final String EVENTLOG          = "sonews.eventlog";
    8.43 -  public static final String FEED_NEWSPERRUN   = "sonews.feed.newsperrun";
    8.44 -  public static final String FEED_PULLINTERVAL = "sonews.feed.pullinterval";
    8.45 -  public static final String HOSTNAME          = "sonews.hostname";
    8.46 -  public static final String PORT              = "sonews.port";
    8.47 -  public static final String TIMEOUT           = "sonews.timeout";
    8.48 -  public static final String LOGLEVEL          = "sonews.loglevel";
    8.49 -  public static final String MLPOLL_DELETEUNKNOWN = "sonews.mlpoll.deleteunknown";
    8.50 -  public static final String MLPOLL_HOST       = "sonews.mlpoll.host";
    8.51 -  public static final String MLPOLL_PASSWORD   = "sonews.mlpoll.password";
    8.52 -  public static final String MLPOLL_USER       = "sonews.mlpoll.user";
    8.53 -  public static final String MLSEND_ADDRESS    = "sonews.mlsend.address";
    8.54 -  public static final String MLSEND_RW_FROM    = "sonews.mlsend.rewrite.from";
    8.55 -  public static final String MLSEND_RW_SENDER  = "sonews.mlsend.rewrite.sender";
    8.56 -  public static final String MLSEND_HOST       = "sonews.mlsend.host";
    8.57 -  public static final String MLSEND_PASSWORD   = "sonews.mlsend.password";
    8.58 -  public static final String MLSEND_PORT       = "sonews.mlsend.port";
    8.59 -  public static final String MLSEND_USER       = "sonews.mlsend.user";
    8.60 -  
    8.61 -  /** Key constant. If value is "true" every I/O is written to logfile
    8.62 -   * (which is a lot!)
    8.63 -   */
    8.64 -  public static final String DEBUG              = "sonews.debug";
    8.65 -
    8.66 -  /** Key constant. Value is classname of the JDBC driver */
    8.67 -  public static final String STORAGE_DBMSDRIVER = "sonews.storage.dbmsdriver";
    8.68 -
    8.69 -  /** Key constant. Value is JDBC connect String to the database. */
    8.70 -  public static final String STORAGE_DATABASE   = "sonews.storage.database";
    8.71 -
    8.72 -  /** Key constant. Value is the username for the DBMS. */
    8.73 -  public static final String STORAGE_USER       = "sonews.storage.user";
    8.74 -
    8.75 -  /** Key constant. Value is the password for the DBMS. */
    8.76 -  public static final String STORAGE_PASSWORD   = "sonews.storage.password";
    8.77 -
    8.78 -  /** Key constant. Value is the name of the host which is allowed to use the
    8.79 -   *  XDAEMON command; default: "localhost" */
    8.80 -  public static final String XDAEMON_HOST       = "sonews.xdaemon.host";
    8.81 -
    8.82 -  /** The config key for the filename of the logfile */
    8.83 -  public static final String LOGFILE = "sonews.log";
    8.84 -
    8.85 -  public static final String[] AVAILABLE_KEYS = {
    8.86 -      ARTICLE_MAXSIZE,
    8.87 -      EVENTLOG,
    8.88 -      FEED_NEWSPERRUN,
    8.89 -      FEED_PULLINTERVAL,
    8.90 -      HOSTNAME,
    8.91 -      MLPOLL_DELETEUNKNOWN,
    8.92 -      MLPOLL_HOST,
    8.93 -      MLPOLL_PASSWORD,
    8.94 -      MLPOLL_USER,
    8.95 -      MLSEND_ADDRESS,
    8.96 -      MLSEND_HOST,
    8.97 -      MLSEND_PASSWORD,
    8.98 -      MLSEND_PORT,
    8.99 -      MLSEND_RW_FROM,
   8.100 -      MLSEND_RW_SENDER,
   8.101 -      MLSEND_USER,
   8.102 -      PORT,
   8.103 -      TIMEOUT,
   8.104 -      XDAEMON_HOST
   8.105 -  };
   8.106 -
   8.107 -  private static Config instance = new Config();
   8.108 -  
   8.109 -  public static Config inst()
   8.110 -  {
   8.111 -    return instance;
   8.112 -  }
   8.113 -  
   8.114 -  private Config(){}
   8.115 -
   8.116 -  @Override
   8.117 -  public String get(String key, String def)
   8.118 -  {
   8.119 -    String val = CommandLineConfig.getInstance().get(key, null);
   8.120 -    
   8.121 -    if(val == null)
   8.122 -    {
   8.123 -      val = FileConfig.getInstance().get(key, null);
   8.124 -    }
   8.125 -
   8.126 -    if(val == null)
   8.127 -    {
   8.128 -      val = BackendConfig.getInstance().get(key, def);
   8.129 -    }
   8.130 -
   8.131 -    return val;
   8.132 -  }
   8.133 -
   8.134 -  public String get(int maxLevel, String key, String def)
   8.135 -  {
   8.136 -    String val = CommandLineConfig.getInstance().get(key, null);
   8.137 -
   8.138 -    if(val == null && maxLevel >= LEVEL_FILE)
   8.139 -    {
   8.140 -      val = FileConfig.getInstance().get(key, null);
   8.141 -      if(val == null && maxLevel >= LEVEL_BACKEND)
   8.142 -      {
   8.143 -        val = BackendConfig.getInstance().get(key, def);
   8.144 -      }
   8.145 -    }
   8.146 -
   8.147 -    return val != null ? val : def;
   8.148 -  }
   8.149 -
   8.150 -  @Override
   8.151 -  public void set(String key, String val)
   8.152 -  {
   8.153 -    set(LEVEL_BACKEND, key, val);
   8.154 -  }
   8.155 -
   8.156 -  public void set(int level, String key, String val)
   8.157 -  {
   8.158 -    switch(level)
   8.159 -    {
   8.160 -      case LEVEL_CLI:
   8.161 -      {
   8.162 -        CommandLineConfig.getInstance().set(key, val);
   8.163 -        break;
   8.164 -      }
   8.165 -      case LEVEL_FILE:
   8.166 -      {
   8.167 -        FileConfig.getInstance().set(key, val);
   8.168 -        break;
   8.169 -      }
   8.170 -      case LEVEL_BACKEND:
   8.171 -      {
   8.172 -        BackendConfig.getInstance().set(key, val);
   8.173 -        break;
   8.174 -      }
   8.175 -    }
   8.176 -  }
   8.177 -
   8.178 -}
     9.1 --- a/org/sonews/config/FileConfig.java	Sun Aug 29 17:04:25 2010 +0200
     9.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.3 @@ -1,170 +0,0 @@
     9.4 -/*
     9.5 - *   SONEWS News Server
     9.6 - *   see AUTHORS for the list of contributors
     9.7 - *
     9.8 - *   This program is free software: you can redistribute it and/or modify
     9.9 - *   it under the terms of the GNU General Public License as published by
    9.10 - *   the Free Software Foundation, either version 3 of the License, or
    9.11 - *   (at your option) any later version.
    9.12 - *
    9.13 - *   This program is distributed in the hope that it will be useful,
    9.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    9.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    9.16 - *   GNU General Public License for more details.
    9.17 - *
    9.18 - *   You should have received a copy of the GNU General Public License
    9.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    9.20 - */
    9.21 -
    9.22 -package org.sonews.config;
    9.23 -
    9.24 -import java.io.FileInputStream;
    9.25 -import java.io.FileNotFoundException;
    9.26 -import java.io.FileOutputStream;
    9.27 -import java.io.IOException;
    9.28 -import java.util.Properties;
    9.29 -
    9.30 -/**
    9.31 - * Manages the bootstrap configuration. It MUST contain all config values
    9.32 - * that are needed to establish a database connection.
    9.33 - * For further configuration values use the Config class instead as that class
    9.34 - * stores its values within the database.
    9.35 - * @author Christian Lins
    9.36 - * @since sonews/0.5.0
    9.37 - */
    9.38 -class FileConfig extends AbstractConfig
    9.39 -{
    9.40 -
    9.41 -  private static final Properties defaultConfig = new Properties();
    9.42 -  
    9.43 -  private static FileConfig instance = null;
    9.44 -  
    9.45 -  static
    9.46 -  {
    9.47 -    // Set some default values
    9.48 -    defaultConfig.setProperty(Config.STORAGE_DATABASE, "jdbc:mysql://localhost/sonews");
    9.49 -    defaultConfig.setProperty(Config.STORAGE_DBMSDRIVER, "com.mysql.jdbc.Driver");
    9.50 -    defaultConfig.setProperty(Config.STORAGE_USER, "sonews_user");
    9.51 -    defaultConfig.setProperty(Config.STORAGE_PASSWORD, "mysecret");
    9.52 -    defaultConfig.setProperty(Config.DEBUG, "false");
    9.53 -  }
    9.54 -  
    9.55 -  /**
    9.56 -   * Note: this method is not thread-safe
    9.57 -   * @return A Config instance
    9.58 -   */
    9.59 -  public static synchronized FileConfig getInstance()
    9.60 -  {
    9.61 -    if(instance == null)
    9.62 -    {
    9.63 -      instance = new FileConfig();
    9.64 -    }
    9.65 -    return instance;
    9.66 -  }
    9.67 -
    9.68 -  // Every config instance is initialized with the default values.
    9.69 -  private final Properties settings = (Properties)defaultConfig.clone();
    9.70 -
    9.71 -  /**
    9.72 -   * Config is a singelton class with only one instance at time.
    9.73 -   * So the constructor is private to prevent the creation of more
    9.74 -   * then one Config instance.
    9.75 -   * @see Config.getInstance() to retrieve an instance of Config
    9.76 -   */
    9.77 -  private FileConfig()
    9.78 -  {
    9.79 -    try
    9.80 -    {
    9.81 -      // Load settings from file
    9.82 -      load();
    9.83 -    }
    9.84 -    catch(IOException ex)
    9.85 -    {
    9.86 -      ex.printStackTrace();
    9.87 -    }
    9.88 -  }
    9.89 -
    9.90 -  /**
    9.91 -   * Loads the configuration from the config file. By default this is done
    9.92 -   * by the (private) constructor but it can be useful to reload the config
    9.93 -   * by invoking this method.
    9.94 -   * @throws IOException
    9.95 -   */
    9.96 -  public void load() 
    9.97 -    throws IOException
    9.98 -  {
    9.99 -    FileInputStream in = null;
   9.100 -    
   9.101 -    try
   9.102 -    {
   9.103 -      in = new FileInputStream(
   9.104 -        Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
   9.105 -      settings.load(in);
   9.106 -    }
   9.107 -    catch (FileNotFoundException e)
   9.108 -    {
   9.109 -      // MUST NOT use Log otherwise endless loop
   9.110 -      System.err.println(e.getMessage());
   9.111 -      save();
   9.112 -    }
   9.113 -    finally
   9.114 -    {
   9.115 -      if(in != null)
   9.116 -        in.close();
   9.117 -    }
   9.118 -  }
   9.119 -
   9.120 -  /**
   9.121 -   * Saves this Config to the config file. By default this is done
   9.122 -   * at program end.
   9.123 -   * @throws FileNotFoundException
   9.124 -   * @throws IOException
   9.125 -   */
   9.126 -  public void save() throws FileNotFoundException, IOException
   9.127 -  {
   9.128 -    FileOutputStream out = null;
   9.129 -    try
   9.130 -    {
   9.131 -      out = new FileOutputStream(
   9.132 -        Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
   9.133 -      settings.store(out, "SONEWS Config File");
   9.134 -      out.flush();
   9.135 -    }
   9.136 -    catch(IOException ex)
   9.137 -    {
   9.138 -      throw ex;
   9.139 -    }
   9.140 -    finally
   9.141 -    {
   9.142 -      if(out != null)
   9.143 -        out.close();
   9.144 -    }
   9.145 -  }
   9.146 -  
   9.147 -  /**
   9.148 -   * Returns the value that is stored within this config
   9.149 -   * identified by the given key. If the key cannot be found
   9.150 -   * the default value is returned.
   9.151 -   * @param key Key to identify the value.
   9.152 -   * @param def The default value that is returned if the key
   9.153 -   * is not found in this Config.
   9.154 -   * @return
   9.155 -   */
   9.156 -  @Override
   9.157 -  public String get(String key, String def)
   9.158 -  {
   9.159 -    return settings.getProperty(key, def);
   9.160 -  }
   9.161 -
   9.162 -  /**
   9.163 -   * Sets the value for a given key.
   9.164 -   * @param key
   9.165 -   * @param value
   9.166 -   */
   9.167 -  @Override
   9.168 -  public void set(final String key, final String value)
   9.169 -  {
   9.170 -    settings.setProperty(key, value);
   9.171 -  }
   9.172 -
   9.173 -}
    10.1 --- a/org/sonews/daemon/AbstractDaemon.java	Sun Aug 29 17:04:25 2010 +0200
    10.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.3 @@ -1,101 +0,0 @@
    10.4 -/*
    10.5 - *   SONEWS News Server
    10.6 - *   see AUTHORS for the list of contributors
    10.7 - *
    10.8 - *   This program is free software: you can redistribute it and/or modify
    10.9 - *   it under the terms of the GNU General Public License as published by
   10.10 - *   the Free Software Foundation, either version 3 of the License, or
   10.11 - *   (at your option) any later version.
   10.12 - *
   10.13 - *   This program is distributed in the hope that it will be useful,
   10.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   10.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   10.16 - *   GNU General Public License for more details.
   10.17 - *
   10.18 - *   You should have received a copy of the GNU General Public License
   10.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   10.20 - */
   10.21 -
   10.22 -package org.sonews.daemon;
   10.23 -
   10.24 -import java.sql.SQLException;
   10.25 -import org.sonews.storage.StorageManager;
   10.26 -import org.sonews.util.Log;
   10.27 -
   10.28 -/**
   10.29 - * Base class of all sonews threads.
   10.30 - * Instances of this class will be automatically registered at the ShutdownHook
   10.31 - * to be cleanly exited when the server is forced to exit.
   10.32 - * @author Christian Lins
   10.33 - * @since sonews/0.5.0
   10.34 - */
   10.35 -public abstract class AbstractDaemon extends Thread
   10.36 -{
   10.37 -
   10.38 -  /** This variable is write synchronized through setRunning */
   10.39 -  private boolean isRunning = false;
   10.40 -
   10.41 -  /**
   10.42 -   * Protected constructor. Will be called by derived classes.
   10.43 -   */
   10.44 -  protected AbstractDaemon()
   10.45 -  {
   10.46 -    setDaemon(true); // VM will exit when all threads are daemons
   10.47 -    setName(getClass().getSimpleName());
   10.48 -  }
   10.49 -  
   10.50 -  /**
   10.51 -   * @return true if shutdown() was not yet called.
   10.52 -   */
   10.53 -  public boolean isRunning()
   10.54 -  {
   10.55 -    synchronized(this)
   10.56 -    {
   10.57 -      return this.isRunning;
   10.58 -    }
   10.59 -  }
   10.60 -  
   10.61 -  /**
   10.62 -   * Marks this thread to exit soon. Closes the associated JDBCDatabase connection
   10.63 -   * if available.
   10.64 -   * @throws java.sql.SQLException
   10.65 -   */
   10.66 -  public void shutdownNow()
   10.67 -    throws SQLException
   10.68 -  {
   10.69 -    synchronized(this)
   10.70 -    {
   10.71 -      this.isRunning = false;
   10.72 -      StorageManager.disableProvider();
   10.73 -    }
   10.74 -  }
   10.75 -  
   10.76 -  /**
   10.77 -   * Calls shutdownNow() but catches SQLExceptions if occurring.
   10.78 -   */
   10.79 -  public void shutdown()
   10.80 -  {
   10.81 -    try
   10.82 -    {
   10.83 -      shutdownNow();
   10.84 -    }
   10.85 -    catch(SQLException ex)
   10.86 -    {
   10.87 -      Log.get().warning(ex.toString());
   10.88 -    }
   10.89 -  }
   10.90 -  
   10.91 -  /**
   10.92 -   * Starts this daemon.
   10.93 -   */
   10.94 -  @Override
   10.95 -  public void start()
   10.96 -  {
   10.97 -    synchronized(this)
   10.98 -    {
   10.99 -      this.isRunning = true;
  10.100 -    }
  10.101 -    super.start();
  10.102 -  }
  10.103 -  
  10.104 -}
    11.1 --- a/org/sonews/daemon/ChannelLineBuffers.java	Sun Aug 29 17:04:25 2010 +0200
    11.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.3 @@ -1,283 +0,0 @@
    11.4 -/*
    11.5 - *   SONEWS News Server
    11.6 - *   see AUTHORS for the list of contributors
    11.7 - *
    11.8 - *   This program is free software: you can redistribute it and/or modify
    11.9 - *   it under the terms of the GNU General Public License as published by
   11.10 - *   the Free Software Foundation, either version 3 of the License, or
   11.11 - *   (at your option) any later version.
   11.12 - *
   11.13 - *   This program is distributed in the hope that it will be useful,
   11.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   11.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   11.16 - *   GNU General Public License for more details.
   11.17 - *
   11.18 - *   You should have received a copy of the GNU General Public License
   11.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   11.20 - */
   11.21 -
   11.22 -package org.sonews.daemon;
   11.23 -
   11.24 -import java.nio.ByteBuffer;
   11.25 -import java.nio.channels.ClosedChannelException;
   11.26 -import java.util.ArrayList;
   11.27 -import java.util.List;
   11.28 -
   11.29 -/**
   11.30 - * Class holding ByteBuffers for SocketChannels/NNTPConnection.
   11.31 - * Due to the complex nature of AIO/NIO we must properly handle the line 
   11.32 - * buffers for the input and output of the SocketChannels.
   11.33 - * @author Christian Lins
   11.34 - * @since sonews/0.5.0
   11.35 - */
   11.36 -public class ChannelLineBuffers 
   11.37 -{
   11.38 -  
   11.39 -  /**
   11.40 -   * Size of one small buffer; 
   11.41 -   * per default this is 512 bytes to fit one standard line.
   11.42 -   */
   11.43 -  public static final int BUFFER_SIZE = 512;
   11.44 -  
   11.45 -  private static int maxCachedBuffers = 2048; // Cached buffers maximum
   11.46 -  
   11.47 -  private static final List<ByteBuffer> freeSmallBuffers
   11.48 -    = new ArrayList<ByteBuffer>(maxCachedBuffers);
   11.49 -  
   11.50 -  /**
   11.51 -   * Allocates a predefined number of direct ByteBuffers (allocated via
   11.52 -   * ByteBuffer.allocateDirect()). This method is Thread-safe, but should only
   11.53 -   * called at startup.
   11.54 -   */
   11.55 -  public static void allocateDirect()
   11.56 -  {
   11.57 -    synchronized(freeSmallBuffers)
   11.58 -    {
   11.59 -      for(int n = 0; n < maxCachedBuffers; n++)
   11.60 -      {
   11.61 -        ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
   11.62 -        freeSmallBuffers.add(buffer);
   11.63 -      }
   11.64 -    }
   11.65 -  }
   11.66 -  
   11.67 -  private ByteBuffer       inputBuffer   = newLineBuffer();
   11.68 -  private List<ByteBuffer> outputBuffers = new ArrayList<ByteBuffer>();
   11.69 -  
   11.70 -  /**
   11.71 -   * Add the given ByteBuffer to the list of buffers to be send to the client.
   11.72 -   * This method is Thread-safe.
   11.73 -   * @param buffer
   11.74 -   * @throws java.nio.channels.ClosedChannelException If the client channel was
   11.75 -   * already closed.
   11.76 -   */
   11.77 -  public void addOutputBuffer(ByteBuffer buffer)
   11.78 -    throws ClosedChannelException
   11.79 -  {
   11.80 -    if(outputBuffers == null)
   11.81 -    {
   11.82 -      throw new ClosedChannelException();
   11.83 -    }
   11.84 -    
   11.85 -    synchronized(outputBuffers)
   11.86 -    {
   11.87 -      outputBuffers.add(buffer);
   11.88 -    }
   11.89 -  }
   11.90 -  
   11.91 -  /**
   11.92 -   * Currently a channel has only one input buffer. This *may* be a bottleneck
   11.93 -   * and should investigated in the future.
   11.94 -   * @param channel
   11.95 -   * @return The input buffer associated with given channel.
   11.96 -   */
   11.97 -  public ByteBuffer getInputBuffer()
   11.98 -  {
   11.99 -    return inputBuffer;
  11.100 -  }
  11.101 -  
  11.102 -  /**
  11.103 -   * Returns the current output buffer for writing(!) to SocketChannel.
  11.104 -   * @param channel
  11.105 -   * @return The next input buffer that contains unprocessed data or null
  11.106 -   * if the connection was closed or there are no more unprocessed buffers.
  11.107 -   */
  11.108 -  public ByteBuffer getOutputBuffer()
  11.109 -  {
  11.110 -    synchronized(outputBuffers)
  11.111 -    {
  11.112 -      if(outputBuffers == null || outputBuffers.isEmpty())
  11.113 -      {
  11.114 -        return null;
  11.115 -      }
  11.116 -      else
  11.117 -      {
  11.118 -        ByteBuffer buffer = outputBuffers.get(0);
  11.119 -        if(buffer.remaining() == 0)
  11.120 -        {
  11.121 -          outputBuffers.remove(0);
  11.122 -          // Add old buffers to the list of free buffers
  11.123 -          recycleBuffer(buffer);
  11.124 -          buffer = getOutputBuffer();
  11.125 -        }
  11.126 -        return buffer;
  11.127 -      }
  11.128 -    }
  11.129 -  }
  11.130 -
  11.131 -  /**
  11.132 -   * @return false if there are output buffers pending to be written to the
  11.133 -   * client.
  11.134 -   */
  11.135 -  boolean isOutputBufferEmpty()
  11.136 -  {
  11.137 -    synchronized(outputBuffers)
  11.138 -    {
  11.139 -      return outputBuffers.isEmpty();
  11.140 -    }
  11.141 -  }
  11.142 -  
  11.143 -  /**
  11.144 -   * Goes through the input buffer of the given channel and searches
  11.145 -   * for next line terminator. If a '\n' is found, the bytes up to the
  11.146 -   * line terminator are returned as array of bytes (the line terminator
  11.147 -   * is omitted). If none is found the method returns null.
  11.148 -   * @param channel
  11.149 -   * @return A ByteBuffer wrapping the line.
  11.150 -   */
  11.151 -  ByteBuffer nextInputLine()
  11.152 -  {
  11.153 -    if(inputBuffer == null)
  11.154 -    {
  11.155 -      return null;
  11.156 -    }
  11.157 -    
  11.158 -    synchronized(inputBuffer)
  11.159 -    {
  11.160 -      ByteBuffer buffer = inputBuffer;
  11.161 -
  11.162 -      // Mark the current write position
  11.163 -      int mark = buffer.position();
  11.164 -
  11.165 -      // Set position to 0 and limit to current position
  11.166 -      buffer.flip();
  11.167 -
  11.168 -      ByteBuffer lineBuffer = newLineBuffer();
  11.169 -
  11.170 -      while (buffer.position() < buffer.limit())
  11.171 -      {
  11.172 -        byte b = buffer.get();
  11.173 -        if (b == 10) // '\n'
  11.174 -        {
  11.175 -          // The bytes between the buffer's current position and its limit, 
  11.176 -          // if any, are copied to the beginning of the buffer. That is, the 
  11.177 -          // byte at index p = position() is copied to index zero, the byte at 
  11.178 -          // index p + 1 is copied to index one, and so forth until the byte 
  11.179 -          // at index limit() - 1 is copied to index n = limit() - 1 - p. 
  11.180 -          // The buffer's position is then set to n+1 and its limit is set to 
  11.181 -          // its capacity.
  11.182 -          buffer.compact();
  11.183 -
  11.184 -          lineBuffer.flip(); // limit to position, position to 0
  11.185 -          return lineBuffer;
  11.186 -        }
  11.187 -        else
  11.188 -        {
  11.189 -          lineBuffer.put(b);
  11.190 -        }
  11.191 -      }
  11.192 -
  11.193 -      buffer.limit(BUFFER_SIZE);
  11.194 -      buffer.position(mark);
  11.195 -
  11.196 -      if(buffer.hasRemaining())
  11.197 -      {
  11.198 -        return null;
  11.199 -      }
  11.200 -      else
  11.201 -      {
  11.202 -        // In the first 512 was no newline found, so the input is not standard
  11.203 -        // compliant. We return the current buffer as new line and add a space
  11.204 -        // to the beginning of the next line which corrects some overlong header
  11.205 -        // lines.
  11.206 -        inputBuffer = newLineBuffer();
  11.207 -        inputBuffer.put((byte)' ');
  11.208 -        buffer.flip();
  11.209 -        return buffer;
  11.210 -      }
  11.211 -    }
  11.212 -  }
  11.213 -  
  11.214 -  /**
  11.215 -   * Returns a at least 512 bytes long ByteBuffer ready for usage.
  11.216 -   * The method first try to reuse an already allocated (cached) buffer but
  11.217 -   * if that fails returns a newly allocated direct buffer.
  11.218 -   * Use recycleBuffer() method when you do not longer use the allocated buffer.
  11.219 -   */
  11.220 -  static ByteBuffer newLineBuffer()
  11.221 -  {
  11.222 -    ByteBuffer buf = null;
  11.223 -    synchronized(freeSmallBuffers)
  11.224 -    {
  11.225 -      if(!freeSmallBuffers.isEmpty())
  11.226 -      {
  11.227 -        buf = freeSmallBuffers.remove(0);
  11.228 -      }
  11.229 -    }
  11.230 -      
  11.231 -    if(buf == null)
  11.232 -    {
  11.233 -      // Allocate a non-direct buffer
  11.234 -      buf = ByteBuffer.allocate(BUFFER_SIZE);
  11.235 -    }
  11.236 -    
  11.237 -    assert buf.position() == 0;
  11.238 -    assert buf.limit() >= BUFFER_SIZE;
  11.239 -    
  11.240 -    return buf;
  11.241 -  }
  11.242 -  
  11.243 -  /**
  11.244 -   * Adds the given buffer to the list of free buffers if it is a valuable
  11.245 -   * direct allocated buffer.
  11.246 -   * @param buffer
  11.247 -   */
  11.248 -  public static void recycleBuffer(ByteBuffer buffer)
  11.249 -  {
  11.250 -    assert buffer != null;
  11.251 -
  11.252 -    if(buffer.isDirect())
  11.253 -    {
  11.254 -      assert buffer.capacity() >= BUFFER_SIZE;
  11.255 -      
  11.256 -      // Add old buffers to the list of free buffers
  11.257 -      synchronized(freeSmallBuffers)
  11.258 -      {
  11.259 -        buffer.clear(); // Set position to 0 and limit to capacity
  11.260 -        freeSmallBuffers.add(buffer);
  11.261 -      }
  11.262 -    } // if(buffer.isDirect())
  11.263 -  }
  11.264 -  
  11.265 -  /**
  11.266 -   * Recycles all buffers of this ChannelLineBuffers object.
  11.267 -   */
  11.268 -  public void recycleBuffers()
  11.269 -  {
  11.270 -    synchronized(inputBuffer)
  11.271 -    {
  11.272 -      recycleBuffer(inputBuffer);
  11.273 -      this.inputBuffer = null;
  11.274 -    }
  11.275 -    
  11.276 -    synchronized(outputBuffers)
  11.277 -    {
  11.278 -      for(ByteBuffer buf : outputBuffers)
  11.279 -      {
  11.280 -        recycleBuffer(buf);
  11.281 -      }
  11.282 -      outputBuffers = null;
  11.283 -    }
  11.284 -  }
  11.285 -  
  11.286 -}
    12.1 --- a/org/sonews/daemon/ChannelReader.java	Sun Aug 29 17:04:25 2010 +0200
    12.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.3 @@ -1,202 +0,0 @@
    12.4 -/*
    12.5 - *   SONEWS News Server
    12.6 - *   see AUTHORS for the list of contributors
    12.7 - *
    12.8 - *   This program is free software: you can redistribute it and/or modify
    12.9 - *   it under the terms of the GNU General Public License as published by
   12.10 - *   the Free Software Foundation, either version 3 of the License, or
   12.11 - *   (at your option) any later version.
   12.12 - *
   12.13 - *   This program is distributed in the hope that it will be useful,
   12.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   12.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   12.16 - *   GNU General Public License for more details.
   12.17 - *
   12.18 - *   You should have received a copy of the GNU General Public License
   12.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   12.20 - */
   12.21 -
   12.22 -package org.sonews.daemon;
   12.23 -
   12.24 -import java.io.IOException;
   12.25 -import java.nio.ByteBuffer;
   12.26 -import java.nio.channels.CancelledKeyException;
   12.27 -import java.nio.channels.SelectionKey;
   12.28 -import java.nio.channels.Selector;
   12.29 -import java.nio.channels.SocketChannel;
   12.30 -import java.util.Iterator;
   12.31 -import java.util.Set;
   12.32 -import java.util.logging.Level;
   12.33 -import org.sonews.util.Log;
   12.34 -
   12.35 -/**
   12.36 - * A Thread task listening for OP_READ events from SocketChannels.
   12.37 - * @author Christian Lins
   12.38 - * @since sonews/0.5.0
   12.39 - */
   12.40 -class ChannelReader extends AbstractDaemon
   12.41 -{
   12.42 -
   12.43 -  private static ChannelReader instance = new ChannelReader();
   12.44 -
   12.45 -  /**
   12.46 -   * @return Active ChannelReader instance.
   12.47 -   */
   12.48 -  public static ChannelReader getInstance()
   12.49 -  {
   12.50 -    return instance;
   12.51 -  }
   12.52 -  
   12.53 -  private Selector selector = null;
   12.54 -  
   12.55 -  protected ChannelReader()
   12.56 -  {
   12.57 -  }
   12.58 -  
   12.59 -  /**
   12.60 -   * Sets the selector which is used by this reader to determine the channel
   12.61 -   * to read from.
   12.62 -   * @param selector
   12.63 -   */
   12.64 -  public void setSelector(final Selector selector)
   12.65 -  {
   12.66 -    this.selector = selector;
   12.67 -  }
   12.68 -  
   12.69 -  /**
   12.70 -   * Run loop. Blocks until some data is available in a channel.
   12.71 -   */
   12.72 -  @Override
   12.73 -  public void run()
   12.74 -  {
   12.75 -    assert selector != null;
   12.76 -
   12.77 -    while(isRunning())
   12.78 -    {
   12.79 -      try
   12.80 -      {
   12.81 -        // select() blocks until some SelectableChannels are ready for
   12.82 -        // processing. There is no need to lock the selector as we have only
   12.83 -        // one thread per selector.
   12.84 -        selector.select();
   12.85 -
   12.86 -        // Get list of selection keys with pending events.
   12.87 -        // Note: the selected key set is not thread-safe
   12.88 -        SocketChannel channel = null;
   12.89 -        NNTPConnection conn = null;
   12.90 -        final Set<SelectionKey> selKeys = selector.selectedKeys();
   12.91 -        SelectionKey selKey = null;
   12.92 -
   12.93 -        synchronized (selKeys)
   12.94 -        {
   12.95 -          Iterator it = selKeys.iterator();
   12.96 -
   12.97 -          // Process the first pending event
   12.98 -          while (it.hasNext())
   12.99 -          {
  12.100 -            selKey = (SelectionKey) it.next();
  12.101 -            channel = (SocketChannel) selKey.channel();
  12.102 -            conn = Connections.getInstance().get(channel);
  12.103 -
  12.104 -            // Because we cannot lock the selKey as that would cause a deadlock
  12.105 -            // we lock the connection. To preserve the order of the received
  12.106 -            // byte blocks a selection key for a connection that has pending
  12.107 -            // read events is skipped.
  12.108 -            if (conn == null || conn.tryReadLock())
  12.109 -            {
  12.110 -              // Remove from set to indicate that it's being processed
  12.111 -              it.remove();
  12.112 -              if (conn != null)
  12.113 -              {
  12.114 -                break; // End while loop
  12.115 -              }
  12.116 -            }
  12.117 -            else
  12.118 -            {
  12.119 -              selKey = null;
  12.120 -              channel = null;
  12.121 -              conn = null;
  12.122 -            }
  12.123 -          }
  12.124 -        }
  12.125 -
  12.126 -        // Do not lock the selKeys while processing because this causes
  12.127 -        // a deadlock in sun.nio.ch.SelectorImpl.lockAndDoSelect()
  12.128 -        if (selKey != null && channel != null && conn != null)
  12.129 -        {
  12.130 -          processSelectionKey(conn, channel, selKey);
  12.131 -          conn.unlockReadLock();
  12.132 -        }
  12.133 -
  12.134 -      }
  12.135 -      catch(CancelledKeyException ex)
  12.136 -      {
  12.137 -        Log.get().warning("ChannelReader.run(): " + ex);
  12.138 -        Log.get().log(Level.INFO, "", ex);
  12.139 -      }
  12.140 -      catch(Exception ex)
  12.141 -      {
  12.142 -        ex.printStackTrace();
  12.143 -      }
  12.144 -      
  12.145 -      // Eventually wait for a register operation
  12.146 -      synchronized (NNTPDaemon.RegisterGate)
  12.147 -      {
  12.148 -      // Do nothing; FindBugs may warn about an empty synchronized 
  12.149 -      // statement, but we cannot use a wait()/notify() mechanism here.
  12.150 -      // If we used something like RegisterGate.wait() we block here
  12.151 -      // until the NNTPDaemon calls notify(). But the daemon only
  12.152 -      // calls notify() if itself is NOT blocked in the listening socket.
  12.153 -      }
  12.154 -    } // while(isRunning())
  12.155 -  }
  12.156 -  
  12.157 -  private void processSelectionKey(final NNTPConnection connection,
  12.158 -    final SocketChannel socketChannel, final SelectionKey selKey)
  12.159 -    throws InterruptedException, IOException
  12.160 -  {
  12.161 -    assert selKey != null;
  12.162 -    assert selKey.isReadable();
  12.163 -    
  12.164 -    // Some bytes are available for reading
  12.165 -    if(selKey.isValid())
  12.166 -    {   
  12.167 -      // Lock the channel
  12.168 -      //synchronized(socketChannel)
  12.169 -      {
  12.170 -        // Read the data into the appropriate buffer
  12.171 -        ByteBuffer buf = connection.getInputBuffer();
  12.172 -        int read = -1;
  12.173 -        try 
  12.174 -        {
  12.175 -          read = socketChannel.read(buf);
  12.176 -        }
  12.177 -        catch(IOException ex)
  12.178 -        {
  12.179 -          // The connection was probably closed by the remote host
  12.180 -          // in a non-clean fashion
  12.181 -          Log.get().info("ChannelReader.processSelectionKey(): " + ex);
  12.182 -        }
  12.183 -        catch(Exception ex) 
  12.184 -        {
  12.185 -          Log.get().warning("ChannelReader.processSelectionKey(): " + ex);
  12.186 -        }
  12.187 -        
  12.188 -        if(read == -1) // End of stream
  12.189 -        {
  12.190 -          selKey.cancel();
  12.191 -        }
  12.192 -        else if(read > 0) // If some data was read
  12.193 -        {
  12.194 -          ConnectionWorker.addChannel(socketChannel);
  12.195 -        }
  12.196 -      }
  12.197 -    }
  12.198 -    else
  12.199 -    {
  12.200 -      // Should not happen
  12.201 -      Log.get().severe("Should not happen: " + selKey.toString());
  12.202 -    }
  12.203 -  }
  12.204 -  
  12.205 -}
    13.1 --- a/org/sonews/daemon/ChannelWriter.java	Sun Aug 29 17:04:25 2010 +0200
    13.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.3 @@ -1,210 +0,0 @@
    13.4 -/*
    13.5 - *   SONEWS News Server
    13.6 - *   see AUTHORS for the list of contributors
    13.7 - *
    13.8 - *   This program is free software: you can redistribute it and/or modify
    13.9 - *   it under the terms of the GNU General Public License as published by
   13.10 - *   the Free Software Foundation, either version 3 of the License, or
   13.11 - *   (at your option) any later version.
   13.12 - *
   13.13 - *   This program is distributed in the hope that it will be useful,
   13.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   13.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   13.16 - *   GNU General Public License for more details.
   13.17 - *
   13.18 - *   You should have received a copy of the GNU General Public License
   13.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   13.20 - */
   13.21 -
   13.22 -package org.sonews.daemon;
   13.23 -
   13.24 -import org.sonews.util.Log;
   13.25 -import java.io.IOException;
   13.26 -import java.nio.ByteBuffer;
   13.27 -import java.nio.channels.CancelledKeyException;
   13.28 -import java.nio.channels.SelectionKey;
   13.29 -import java.nio.channels.Selector;
   13.30 -import java.nio.channels.SocketChannel;
   13.31 -import java.util.Iterator;
   13.32 -
   13.33 -/**
   13.34 - * A Thread task that processes OP_WRITE events for SocketChannels.
   13.35 - * @author Christian Lins
   13.36 - * @since sonews/0.5.0
   13.37 - */
   13.38 -class ChannelWriter extends AbstractDaemon
   13.39 -{
   13.40 -
   13.41 -  private static ChannelWriter instance = new ChannelWriter();
   13.42 -
   13.43 -  /**
   13.44 -   * @return Returns the active ChannelWriter instance.
   13.45 -   */
   13.46 -  public static ChannelWriter getInstance()
   13.47 -  {
   13.48 -    return instance;
   13.49 -  }
   13.50 -  
   13.51 -  private Selector selector = null;
   13.52 -  
   13.53 -  protected ChannelWriter()
   13.54 -  {
   13.55 -  }
   13.56 -  
   13.57 -  /**
   13.58 -   * @return Selector associated with this instance.
   13.59 -   */
   13.60 -  public Selector getSelector()
   13.61 -  {
   13.62 -    return this.selector;
   13.63 -  }
   13.64 -  
   13.65 -  /**
   13.66 -   * Sets the selector that is used by this ChannelWriter.
   13.67 -   * @param selector
   13.68 -   */
   13.69 -  public void setSelector(final Selector selector)
   13.70 -  {
   13.71 -    this.selector = selector;
   13.72 -  }
   13.73 -  
   13.74 -  /**
   13.75 -   * Run loop.
   13.76 -   */
   13.77 -  @Override
   13.78 -  public void run()
   13.79 -  {
   13.80 -    assert selector != null;
   13.81 -
   13.82 -    while(isRunning())
   13.83 -    {
   13.84 -      try
   13.85 -      {
   13.86 -        SelectionKey   selKey        = null;
   13.87 -        SocketChannel  socketChannel = null;
   13.88 -        NNTPConnection connection    = null;
   13.89 -
   13.90 -        // select() blocks until some SelectableChannels are ready for
   13.91 -        // processing. There is no need to synchronize the selector as we
   13.92 -        // have only one thread per selector.
   13.93 -        selector.select(); // The return value of select can be ignored
   13.94 -
   13.95 -        // Get list of selection keys with pending OP_WRITE events.
   13.96 -        // The keySET is not thread-safe whereas the keys itself are.
   13.97 -        Iterator it = selector.selectedKeys().iterator();
   13.98 -
   13.99 -        while (it.hasNext())
  13.100 -        {
  13.101 -          // We remove the first event from the set and store it for
  13.102 -          // later processing.
  13.103 -          selKey = (SelectionKey) it.next();
  13.104 -          socketChannel = (SocketChannel) selKey.channel();
  13.105 -          connection = Connections.getInstance().get(socketChannel);
  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 -
  13.131 -            // Cancel write events for this channel
  13.132 -            selKey.cancel();
  13.133 -            connection.shutdownInput();
  13.134 -            connection.shutdownOutput();
  13.135 -          }
  13.136 -        }
  13.137 -        
  13.138 -        // Eventually wait for a register operation
  13.139 -        synchronized(NNTPDaemon.RegisterGate) { /* do nothing */ }
  13.140 -      }
  13.141 -      catch(CancelledKeyException ex)
  13.142 -      {
  13.143 -        Log.get().info("ChannelWriter.run(): " + ex);
  13.144 -      }
  13.145 -      catch(Exception ex)
  13.146 -      {
  13.147 -        ex.printStackTrace();
  13.148 -      }
  13.149 -    } // while(isRunning())
  13.150 -  }
  13.151 -  
  13.152 -  private void processSelectionKey(final NNTPConnection connection,
  13.153 -    final SocketChannel socketChannel, final SelectionKey selKey)
  13.154 -    throws InterruptedException, IOException
  13.155 -  {
  13.156 -    assert connection != null;
  13.157 -    assert socketChannel != null;
  13.158 -    assert selKey != null;
  13.159 -    assert selKey.isWritable();
  13.160 -
  13.161 -    // SocketChannel is ready for writing
  13.162 -    if(selKey.isValid())
  13.163 -    {
  13.164 -      // Lock the socket channel
  13.165 -      synchronized(socketChannel)
  13.166 -      {
  13.167 -        // Get next output buffer
  13.168 -        ByteBuffer buf = connection.getOutputBuffer();
  13.169 -        if(buf == null)
  13.170 -        {
  13.171 -          // Currently we have nothing to write, so we stop the writeable
  13.172 -          // events until we have something to write to the socket channel
  13.173 -          //selKey.cancel();
  13.174 -          selKey.interestOps(0);
  13.175 -          // Update activity timestamp to prevent too early disconnects
  13.176 -          // on slow client connections
  13.177 -          connection.setLastActivity(System.currentTimeMillis());
  13.178 -          return;
  13.179 -        }
  13.180 - 
  13.181 -        while(buf != null) // There is data to be send
  13.182 -        {
  13.183 -          // Write buffer to socket channel; this method does not block
  13.184 -          if(socketChannel.write(buf) <= 0)
  13.185 -          {
  13.186 -            // Perhaps there is data to be written, but the SocketChannel's
  13.187 -            // buffer is full, so we stop writing to until the next event.
  13.188 -            break;
  13.189 -          }
  13.190 -          else
  13.191 -          {
  13.192 -            // Retrieve next buffer if available; method may return the same
  13.193 -            // buffer instance if it still have some bytes remaining
  13.194 -            buf = connection.getOutputBuffer();
  13.195 -          }
  13.196 -        }
  13.197 -      }
  13.198 -    }
  13.199 -    else
  13.200 -    {
  13.201 -      Log.get().warning("Invalid OP_WRITE key: " + selKey);
  13.202 -
  13.203 -      if(socketChannel.socket().isClosed())
  13.204 -      {
  13.205 -        connection.shutdownInput();
  13.206 -        connection.shutdownOutput();
  13.207 -        socketChannel.close();
  13.208 -        Log.get().info("Connection closed.");
  13.209 -      }
  13.210 -    }
  13.211 -  }
  13.212 -  
  13.213 -}
    14.1 --- a/org/sonews/daemon/CommandSelector.java	Sun Aug 29 17:04:25 2010 +0200
    14.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.3 @@ -1,141 +0,0 @@
    14.4 -/*
    14.5 - *   SONEWS News Server
    14.6 - *   see AUTHORS for the list of contributors
    14.7 - *
    14.8 - *   This program is free software: you can redistribute it and/or modify
    14.9 - *   it under the terms of the GNU General Public License as published by
   14.10 - *   the Free Software Foundation, either version 3 of the License, or
   14.11 - *   (at your option) any later version.
   14.12 - *
   14.13 - *   This program is distributed in the hope that it will be useful,
   14.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   14.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14.16 - *   GNU General Public License for more details.
   14.17 - *
   14.18 - *   You should have received a copy of the GNU General Public License
   14.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   14.20 - */
   14.21 -
   14.22 -package org.sonews.daemon;
   14.23 -
   14.24 -import java.util.HashMap;
   14.25 -import java.util.Map;
   14.26 -import java.util.Set;
   14.27 -import java.util.concurrent.ConcurrentHashMap;
   14.28 -import org.sonews.daemon.command.Command;
   14.29 -import org.sonews.daemon.command.UnsupportedCommand;
   14.30 -import org.sonews.util.Log;
   14.31 -import org.sonews.util.io.Resource;
   14.32 -
   14.33 -/**
   14.34 - * Selects the correct command processing class.
   14.35 - * @author Christian Lins
   14.36 - * @since sonews/1.0
   14.37 - */
   14.38 -public class CommandSelector
   14.39 -{
   14.40 -
   14.41 -  private static Map<Thread, CommandSelector> instances
   14.42 -    = new ConcurrentHashMap<Thread, CommandSelector>();
   14.43 -  private static Map<String, Class<?>> commandClassesMapping
   14.44 -    = new ConcurrentHashMap<String, Class<?>>();
   14.45 -
   14.46 -  static
   14.47 -  {
   14.48 -    String[] classes = Resource.getAsString("helpers/commands.list", true).split("\n");
   14.49 -    for(String className : classes)
   14.50 -    {
   14.51 -      if(className.charAt(0) == '#')
   14.52 -      {
   14.53 -        // Skip comments
   14.54 -        continue;
   14.55 -      }
   14.56 -
   14.57 -      try
   14.58 -      {
   14.59 -        addCommandHandler(className);
   14.60 -      }
   14.61 -      catch(ClassNotFoundException ex)
   14.62 -      {
   14.63 -        Log.get().warning("Could not load command class: " + ex);
   14.64 -      }
   14.65 -      catch(InstantiationException ex)
   14.66 -      {
   14.67 -        Log.get().severe("Could not instantiate command class: " + ex);
   14.68 -      }
   14.69 -      catch(IllegalAccessException ex)
   14.70 -      {
   14.71 -        Log.get().severe("Could not access command class: " + ex);
   14.72 -      }
   14.73 -    }
   14.74 -  }
   14.75 -
   14.76 -  public static void addCommandHandler(String className)
   14.77 -    throws ClassNotFoundException, InstantiationException, IllegalAccessException
   14.78 -  {
   14.79 -    Class<?> clazz = Class.forName(className);
   14.80 -    Command cmd = (Command)clazz.newInstance();
   14.81 -    String[] cmdStrs = cmd.getSupportedCommandStrings();
   14.82 -    for (String cmdStr : cmdStrs)
   14.83 -    {
   14.84 -      commandClassesMapping.put(cmdStr, clazz);
   14.85 -    }
   14.86 -  }
   14.87 -
   14.88 -  public static Set<String> getCommandNames()
   14.89 -  {
   14.90 -    return commandClassesMapping.keySet();
   14.91 -  }
   14.92 -
   14.93 -  public static CommandSelector getInstance()
   14.94 -  {
   14.95 -    CommandSelector csel = instances.get(Thread.currentThread());
   14.96 -    if(csel == null)
   14.97 -    {
   14.98 -      csel = new CommandSelector();
   14.99 -      instances.put(Thread.currentThread(), csel);
  14.100 -    }
  14.101 -    return csel;
  14.102 -  }
  14.103 -
  14.104 -  private Map<String, Command> commandMapping = new HashMap<String, Command>();
  14.105 -  private Command              unsupportedCmd = new UnsupportedCommand();
  14.106 -
  14.107 -  private CommandSelector()
  14.108 -  {}
  14.109 -
  14.110 -  public Command get(String commandName)
  14.111 -  {
  14.112 -    try
  14.113 -    {
  14.114 -      commandName = commandName.toUpperCase();
  14.115 -      Command cmd = this.commandMapping.get(commandName);
  14.116 -
  14.117 -      if(cmd == null)
  14.118 -      {
  14.119 -        Class<?> clazz = commandClassesMapping.get(commandName);
  14.120 -        if(clazz == null)
  14.121 -        {
  14.122 -          cmd = this.unsupportedCmd;
  14.123 -        }
  14.124 -        else
  14.125 -        {
  14.126 -          cmd = (Command)clazz.newInstance();
  14.127 -          this.commandMapping.put(commandName, cmd);
  14.128 -        }
  14.129 -      }
  14.130 -      else if(cmd.isStateful())
  14.131 -      {
  14.132 -        cmd = cmd.getClass().newInstance();
  14.133 -      }
  14.134 -
  14.135 -      return cmd;
  14.136 -    }
  14.137 -    catch(Exception ex)
  14.138 -    {
  14.139 -      ex.printStackTrace();
  14.140 -      return this.unsupportedCmd;
  14.141 -    }
  14.142 -  }
  14.143 -
  14.144 -}
    15.1 --- a/org/sonews/daemon/ConnectionWorker.java	Sun Aug 29 17:04:25 2010 +0200
    15.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.3 @@ -1,102 +0,0 @@
    15.4 -/*
    15.5 - *   SONEWS News Server
    15.6 - *   see AUTHORS for the list of contributors
    15.7 - *
    15.8 - *   This program is free software: you can redistribute it and/or modify
    15.9 - *   it under the terms of the GNU General Public License as published by
   15.10 - *   the Free Software Foundation, either version 3 of the License, or
   15.11 - *   (at your option) any later version.
   15.12 - *
   15.13 - *   This program is distributed in the hope that it will be useful,
   15.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   15.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   15.16 - *   GNU General Public License for more details.
   15.17 - *
   15.18 - *   You should have received a copy of the GNU General Public License
   15.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   15.20 - */
   15.21 -
   15.22 -package org.sonews.daemon;
   15.23 -
   15.24 -import org.sonews.util.Log;
   15.25 -import java.nio.ByteBuffer;
   15.26 -import java.nio.channels.SocketChannel;
   15.27 -import java.util.concurrent.ArrayBlockingQueue;
   15.28 -
   15.29 -/**
   15.30 - * Does most of the work: parsing input, talking to client and Database.
   15.31 - * @author Christian Lins
   15.32 - * @since sonews/0.5.0
   15.33 - */
   15.34 -class ConnectionWorker extends AbstractDaemon
   15.35 -{
   15.36 -
   15.37 -  // 256 pending events should be enough
   15.38 -  private static ArrayBlockingQueue<SocketChannel> pendingChannels
   15.39 -    = new ArrayBlockingQueue<SocketChannel>(256, true);
   15.40 -  
   15.41 -  /**
   15.42 -   * Registers the given channel for further event processing.
   15.43 -   * @param channel
   15.44 -   */
   15.45 -  public static void addChannel(SocketChannel channel)
   15.46 -    throws InterruptedException
   15.47 -  {
   15.48 -    pendingChannels.put(channel);
   15.49 -  }
   15.50 -  
   15.51 -  /**
   15.52 -   * Processing loop.
   15.53 -   */
   15.54 -  @Override
   15.55 -  public void run()
   15.56 -  {
   15.57 -    while(isRunning())
   15.58 -    {
   15.59 -      try
   15.60 -      {
   15.61 -        // Retrieve and remove if available, otherwise wait.
   15.62 -        SocketChannel channel = pendingChannels.take();
   15.63 -
   15.64 -        if(channel != null)
   15.65 -        {
   15.66 -          // Connections.getInstance().get() MAY return null
   15.67 -          NNTPConnection conn = Connections.getInstance().get(channel);
   15.68 -          
   15.69 -          // Try to lock the connection object
   15.70 -          if(conn != null && conn.tryReadLock())
   15.71 -          {
   15.72 -            ByteBuffer buf = conn.getBuffers().nextInputLine();
   15.73 -            while(buf != null) // Complete line was received
   15.74 -            {
   15.75 -              final byte[] line = new byte[buf.limit()];
   15.76 -              buf.get(line);
   15.77 -              ChannelLineBuffers.recycleBuffer(buf);
   15.78 -              
   15.79 -              // Here is the actual work done
   15.80 -              conn.lineReceived(line);
   15.81 -
   15.82 -              // Read next line as we could have already received the next line
   15.83 -              buf = conn.getBuffers().nextInputLine();
   15.84 -            }
   15.85 -            conn.unlockReadLock();
   15.86 -          }
   15.87 -          else
   15.88 -          {
   15.89 -            addChannel(channel);
   15.90 -          }
   15.91 -        }
   15.92 -      }
   15.93 -      catch(InterruptedException ex)
   15.94 -      {
   15.95 -        Log.get().info("ConnectionWorker interrupted: " + ex);
   15.96 -      }
   15.97 -      catch(Exception ex)
   15.98 -      {
   15.99 -        Log.get().severe("Exception in ConnectionWorker: " + ex);
  15.100 -        ex.printStackTrace();
  15.101 -      }
  15.102 -    } // end while(isRunning())
  15.103 -  }
  15.104 -  
  15.105 -}
    16.1 --- a/org/sonews/daemon/Connections.java	Sun Aug 29 17:04:25 2010 +0200
    16.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.3 @@ -1,181 +0,0 @@
    16.4 -/*
    16.5 - *   SONEWS News Server
    16.6 - *   see AUTHORS for the list of contributors
    16.7 - *
    16.8 - *   This program is free software: you can redistribute it and/or modify
    16.9 - *   it under the terms of the GNU General Public License as published by
   16.10 - *   the Free Software Foundation, either version 3 of the License, or
   16.11 - *   (at your option) any later version.
   16.12 - *
   16.13 - *   This program is distributed in the hope that it will be useful,
   16.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   16.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   16.16 - *   GNU General Public License for more details.
   16.17 - *
   16.18 - *   You should have received a copy of the GNU General Public License
   16.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   16.20 - */
   16.21 -
   16.22 -package org.sonews.daemon;
   16.23 -
   16.24 -import org.sonews.config.Config;
   16.25 -import org.sonews.util.Log;
   16.26 -import org.sonews.util.Stats;
   16.27 -import java.io.IOException;
   16.28 -import java.net.InetSocketAddress;
   16.29 -import java.net.Socket;
   16.30 -import java.nio.channels.SocketChannel;
   16.31 -import java.util.ArrayList;
   16.32 -import java.util.HashMap;
   16.33 -import java.util.List;
   16.34 -import java.util.ListIterator;
   16.35 -import java.util.Map;
   16.36 -
   16.37 -/**
   16.38 - * Daemon thread collecting all NNTPConnection instances. The thread
   16.39 - * checks periodically if there are stale/timed out connections and
   16.40 - * removes and purges them properly.
   16.41 - * @author Christian Lins
   16.42 - * @since sonews/0.5.0
   16.43 - */
   16.44 -public final class Connections extends AbstractDaemon
   16.45 -{
   16.46 -
   16.47 -  private static final Connections instance = new Connections();
   16.48 -  
   16.49 -  /**
   16.50 -   * @return Active Connections instance.
   16.51 -   */
   16.52 -  public static Connections getInstance()
   16.53 -  {
   16.54 -    return Connections.instance;
   16.55 -  }
   16.56 -  
   16.57 -  private final List<NNTPConnection> connections 
   16.58 -    = new ArrayList<NNTPConnection>();
   16.59 -  private final Map<SocketChannel, NNTPConnection> connByChannel 
   16.60 -    = new HashMap<SocketChannel, NNTPConnection>();
   16.61 -  
   16.62 -  private Connections()
   16.63 -  {
   16.64 -    setName("Connections");
   16.65 -  }
   16.66 -  
   16.67 -  /**
   16.68 -   * Adds the given NNTPConnection to the Connections management.
   16.69 -   * @param conn
   16.70 -   * @see org.sonews.daemon.NNTPConnection
   16.71 -   */
   16.72 -  public void add(final NNTPConnection conn)
   16.73 -  {
   16.74 -    synchronized(this.connections)
   16.75 -    {
   16.76 -      this.connections.add(conn);
   16.77 -      this.connByChannel.put(conn.getSocketChannel(), conn);
   16.78 -    }
   16.79 -  }
   16.80 -  
   16.81 -  /**
   16.82 -   * @param channel
   16.83 -   * @return NNTPConnection instance that is associated with the given
   16.84 -   * SocketChannel.
   16.85 -   */
   16.86 -  public NNTPConnection get(final SocketChannel channel)
   16.87 -  {
   16.88 -    synchronized(this.connections)
   16.89 -    {
   16.90 -      return this.connByChannel.get(channel);
   16.91 -    }
   16.92 -  }
   16.93 -
   16.94 -  int getConnectionCount(String remote)
   16.95 -  {
   16.96 -    int cnt = 0;
   16.97 -    synchronized(this.connections)
   16.98 -    {
   16.99 -      for(NNTPConnection conn : this.connections)
  16.100 -      {
  16.101 -        assert conn != null;
  16.102 -        assert conn.getSocketChannel() != null;
  16.103 -
  16.104 -        Socket socket = conn.getSocketChannel().socket();
  16.105 -        if(socket != null)
  16.106 -        {
  16.107 -          InetSocketAddress sockAddr = (InetSocketAddress)socket.getRemoteSocketAddress();
  16.108 -          if(sockAddr != null)
  16.109 -          {
  16.110 -            if(sockAddr.getHostName().equals(remote))
  16.111 -            {
  16.112 -              cnt++;
  16.113 -            }
  16.114 -          }
  16.115 -        } // if(socket != null)
  16.116 -      }
  16.117 -    }
  16.118 -    return cnt;
  16.119 -  }
  16.120 -  
  16.121 -  /**
  16.122 -   * Run loops. Checks periodically for timed out connections and purged them
  16.123 -   * from the lists.
  16.124 -   */
  16.125 -  @Override
  16.126 -  public void run()
  16.127 -  {
  16.128 -    while(isRunning())
  16.129 -    {
  16.130 -      int timeoutMillis = 1000 * Config.inst().get(Config.TIMEOUT, 180);
  16.131 -      
  16.132 -      synchronized (this.connections)
  16.133 -      {
  16.134 -        final ListIterator<NNTPConnection> iter = this.connections.listIterator();
  16.135 -        NNTPConnection conn;
  16.136 -
  16.137 -        while (iter.hasNext())
  16.138 -        {
  16.139 -          conn = iter.next();
  16.140 -          if((System.currentTimeMillis() - conn.getLastActivity()) > timeoutMillis
  16.141 -              && conn.getBuffers().isOutputBufferEmpty())
  16.142 -          {
  16.143 -            // A connection timeout has occurred so purge the connection
  16.144 -            iter.remove();
  16.145 -
  16.146 -            // Close and remove the channel
  16.147 -            SocketChannel channel = conn.getSocketChannel();
  16.148 -            connByChannel.remove(channel);
  16.149 -            
  16.150 -            try
  16.151 -            {
  16.152 -              assert channel != null;
  16.153 -              assert channel.socket() != null;
  16.154 -      
  16.155 -              // Close the channel; implicitely cancels all selectionkeys
  16.156 -              channel.close();
  16.157 -              Log.get().info("Disconnected: " + channel.socket().getRemoteSocketAddress() +
  16.158 -                " (timeout)");
  16.159 -            }
  16.160 -            catch(IOException ex)
  16.161 -            {
  16.162 -              Log.get().warning("Connections.run(): " + ex);
  16.163 -            }
  16.164 -
  16.165 -            // Recycle the used buffers
  16.166 -            conn.getBuffers().recycleBuffers();
  16.167 -            
  16.168 -            Stats.getInstance().clientDisconnect();
  16.169 -          }
  16.170 -        }
  16.171 -      }
  16.172 -
  16.173 -      try
  16.174 -      {
  16.175 -        Thread.sleep(10000); // Sleep ten seconds
  16.176 -      }
  16.177 -      catch(InterruptedException ex)
  16.178 -      {
  16.179 -        Log.get().warning("Connections Thread was interrupted: " + ex.getMessage());
  16.180 -      }
  16.181 -    }
  16.182 -  }
  16.183 -  
  16.184 -}
    17.1 --- a/org/sonews/daemon/LineEncoder.java	Sun Aug 29 17:04:25 2010 +0200
    17.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.3 @@ -1,80 +0,0 @@
    17.4 -/*
    17.5 - *   SONEWS News Server
    17.6 - *   see AUTHORS for the list of contributors
    17.7 - *
    17.8 - *   This program is free software: you can redistribute it and/or modify
    17.9 - *   it under the terms of the GNU General Public License as published by
   17.10 - *   the Free Software Foundation, either version 3 of the License, or
   17.11 - *   (at your option) any later version.
   17.12 - *
   17.13 - *   This program is distributed in the hope that it will be useful,
   17.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   17.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   17.16 - *   GNU General Public License for more details.
   17.17 - *
   17.18 - *   You should have received a copy of the GNU General Public License
   17.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   17.20 - */
   17.21 -
   17.22 -package org.sonews.daemon;
   17.23 -
   17.24 -import java.nio.ByteBuffer;
   17.25 -import java.nio.CharBuffer;
   17.26 -import java.nio.channels.ClosedChannelException;
   17.27 -import java.nio.charset.Charset;
   17.28 -import java.nio.charset.CharsetEncoder;
   17.29 -import java.nio.charset.CoderResult;
   17.30 -
   17.31 -/**
   17.32 - * Encodes a line to buffers using the correct charset.
   17.33 - * @author Christian Lins
   17.34 - * @since sonews/0.5.0
   17.35 - */
   17.36 -class LineEncoder
   17.37 -{
   17.38 -
   17.39 -  private CharBuffer    characters;
   17.40 -  private Charset       charset;
   17.41 -  
   17.42 -  /**
   17.43 -   * Constructs new LineEncoder.
   17.44 -   * @param characters
   17.45 -   * @param charset
   17.46 -   */
   17.47 -  public LineEncoder(CharBuffer characters, Charset charset)
   17.48 -  {
   17.49 -    this.characters = characters;
   17.50 -    this.charset    = charset;
   17.51 -  }
   17.52 -  
   17.53 -  /**
   17.54 -   * Encodes the characters of this instance to the given ChannelLineBuffers
   17.55 -   * using the Charset of this instance.
   17.56 -   * @param buffer
   17.57 -   * @throws java.nio.channels.ClosedChannelException
   17.58 -   */
   17.59 -  public void encode(ChannelLineBuffers buffer)
   17.60 -    throws ClosedChannelException
   17.61 -  {
   17.62 -    CharsetEncoder encoder = charset.newEncoder();
   17.63 -    while (characters.hasRemaining())
   17.64 -    {
   17.65 -      ByteBuffer buf = ChannelLineBuffers.newLineBuffer();
   17.66 -      assert buf.position() == 0;
   17.67 -      assert buf.capacity() >= 512;
   17.68 -
   17.69 -      CoderResult res = encoder.encode(characters, buf, true);
   17.70 -
   17.71 -      // Set limit to current position and current position to 0;
   17.72 -      // means make ready for read from buffer
   17.73 -      buf.flip();
   17.74 -      buffer.addOutputBuffer(buf);
   17.75 -
   17.76 -      if (res.isUnderflow()) // All input processed
   17.77 -      {
   17.78 -        break;
   17.79 -      }
   17.80 -    }
   17.81 -  }
   17.82 -  
   17.83 -}
    18.1 --- a/org/sonews/daemon/NNTPConnection.java	Sun Aug 29 17:04:25 2010 +0200
    18.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.3 @@ -1,428 +0,0 @@
    18.4 -/*
    18.5 - *   SONEWS News Server
    18.6 - *   see AUTHORS for the list of contributors
    18.7 - *
    18.8 - *   This program is free software: you can redistribute it and/or modify
    18.9 - *   it under the terms of the GNU General Public License as published by
   18.10 - *   the Free Software Foundation, either version 3 of the License, or
   18.11 - *   (at your option) any later version.
   18.12 - *
   18.13 - *   This program is distributed in the hope that it will be useful,
   18.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   18.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18.16 - *   GNU General Public License for more details.
   18.17 - *
   18.18 - *   You should have received a copy of the GNU General Public License
   18.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   18.20 - */
   18.21 -
   18.22 -package org.sonews.daemon;
   18.23 -
   18.24 -import java.io.IOException;
   18.25 -import java.net.InetSocketAddress;
   18.26 -import java.net.SocketException;
   18.27 -import java.nio.ByteBuffer;
   18.28 -import java.nio.CharBuffer;
   18.29 -import java.nio.channels.ClosedChannelException;
   18.30 -import java.nio.channels.SelectionKey;
   18.31 -import java.nio.channels.SocketChannel;
   18.32 -import java.nio.charset.Charset;
   18.33 -import java.util.Arrays;
   18.34 -import java.util.Timer;
   18.35 -import java.util.TimerTask;
   18.36 -import org.sonews.daemon.command.Command;
   18.37 -import org.sonews.storage.Article;
   18.38 -import org.sonews.storage.Channel;
   18.39 -import org.sonews.storage.StorageBackendException;
   18.40 -import org.sonews.util.Log;
   18.41 -import org.sonews.util.Stats;
   18.42 -
   18.43 -/**
   18.44 - * For every SocketChannel (so TCP/IP connection) there is an instance of
   18.45 - * this class.
   18.46 - * @author Christian Lins
   18.47 - * @since sonews/0.5.0
   18.48 - */
   18.49 -public final class NNTPConnection
   18.50 -{
   18.51 -
   18.52 -  public static final String NEWLINE            = "\r\n";    // RFC defines this as newline
   18.53 -  public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
   18.54 -  
   18.55 -  private static final Timer cancelTimer = new Timer(true); // Thread-safe? True for run as daemon
   18.56 -  
   18.57 -  /** SocketChannel is generally thread-safe */
   18.58 -  private SocketChannel   channel        = null;
   18.59 -  private Charset         charset        = Charset.forName("UTF-8");
   18.60 -  private Command         command        = null;
   18.61 -  private Article         currentArticle = null;
   18.62 -  private Channel         currentGroup   = null;
   18.63 -  private volatile long   lastActivity   = System.currentTimeMillis();
   18.64 -  private ChannelLineBuffers lineBuffers = new ChannelLineBuffers();
   18.65 -  private int             readLock       = 0;
   18.66 -  private final Object    readLockGate   = new Object();
   18.67 -  private SelectionKey    writeSelKey    = null;
   18.68 -  
   18.69 -  public NNTPConnection(final SocketChannel channel)
   18.70 -    throws IOException
   18.71 -  {
   18.72 -    if(channel == null)
   18.73 -    {
   18.74 -      throw new IllegalArgumentException("channel is null");
   18.75 -    }
   18.76 -
   18.77 -    this.channel = channel;
   18.78 -    Stats.getInstance().clientConnect();
   18.79 -  }
   18.80 -  
   18.81 -  /**
   18.82 -   * Tries to get the read lock for this NNTPConnection. This method is Thread-
   18.83 -   * safe and returns true of the read lock was successfully set. If the lock
   18.84 -   * is still hold by another Thread the method returns false.
   18.85 -   */
   18.86 -  boolean tryReadLock()
   18.87 -  {
   18.88 -    // As synchronizing simple types may cause deadlocks,
   18.89 -    // we use a gate object.
   18.90 -    synchronized(readLockGate)
   18.91 -    {
   18.92 -      if(readLock != 0)
   18.93 -      {
   18.94 -        return false;
   18.95 -      }
   18.96 -      else
   18.97 -      {
   18.98 -        readLock = Thread.currentThread().hashCode();
   18.99 -        return true;
  18.100 -      }
  18.101 -    }
  18.102 -  }
  18.103 -  
  18.104 -  /**
  18.105 -   * Releases the read lock in a Thread-safe way.
  18.106 -   * @throws IllegalMonitorStateException if a Thread not holding the lock
  18.107 -   * tries to release it.
  18.108 -   */
  18.109 -  void unlockReadLock()
  18.110 -  {
  18.111 -    synchronized(readLockGate)
  18.112 -    {
  18.113 -      if(readLock == Thread.currentThread().hashCode())
  18.114 -      {
  18.115 -        readLock = 0;
  18.116 -      }
  18.117 -      else
  18.118 -      {
  18.119 -        throw new IllegalMonitorStateException();
  18.120 -      }
  18.121 -    }
  18.122 -  }
  18.123 -  
  18.124 -  /**
  18.125 -   * @return Current input buffer of this NNTPConnection instance.
  18.126 -   */
  18.127 -  public ByteBuffer getInputBuffer()
  18.128 -  {
  18.129 -    return this.lineBuffers.getInputBuffer();
  18.130 -  }
  18.131 -  
  18.132 -  /**
  18.133 -   * @return Output buffer of this NNTPConnection which has at least one byte
  18.134 -   * free storage.
  18.135 -   */
  18.136 -  public ByteBuffer getOutputBuffer()
  18.137 -  {
  18.138 -    return this.lineBuffers.getOutputBuffer();
  18.139 -  }
  18.140 -  
  18.141 -  /**
  18.142 -   * @return ChannelLineBuffers instance associated with this NNTPConnection.
  18.143 -   */
  18.144 -  public ChannelLineBuffers getBuffers()
  18.145 -  {
  18.146 -    return this.lineBuffers;
  18.147 -  }
  18.148 -  
  18.149 -  /**
  18.150 -   * @return true if this connection comes from a local remote address.
  18.151 -   */
  18.152 -  public boolean isLocalConnection()
  18.153 -  {
  18.154 -    return ((InetSocketAddress)this.channel.socket().getRemoteSocketAddress())
  18.155 -      .getHostName().equalsIgnoreCase("localhost");
  18.156 -  }
  18.157 -
  18.158 -  void setWriteSelectionKey(SelectionKey selKey)
  18.159 -  {
  18.160 -    this.writeSelKey = selKey;
  18.161 -  }
  18.162 -
  18.163 -  public void shutdownInput()
  18.164 -  {
  18.165 -    try
  18.166 -    {
  18.167 -      // Closes the input line of the channel's socket, so no new data
  18.168 -      // will be received and a timeout can be triggered.
  18.169 -      this.channel.socket().shutdownInput();
  18.170 -    }
  18.171 -    catch(IOException ex)
  18.172 -    {
  18.173 -      Log.get().warning("Exception in NNTPConnection.shutdownInput(): " + ex);
  18.174 -    }
  18.175 -  }
  18.176 -  
  18.177 -  public void shutdownOutput()
  18.178 -  {
  18.179 -    cancelTimer.schedule(new TimerTask() 
  18.180 -    {
  18.181 -      @Override
  18.182 -      public void run()
  18.183 -      {
  18.184 -        try
  18.185 -        {
  18.186 -          // Closes the output line of the channel's socket.
  18.187 -          channel.socket().shutdownOutput();
  18.188 -          channel.close();
  18.189 -        }
  18.190 -        catch(SocketException ex)
  18.191 -        {
  18.192 -          // Socket was already disconnected
  18.193 -          Log.get().info("NNTPConnection.shutdownOutput(): " + ex);
  18.194 -        }
  18.195 -        catch(Exception ex)
  18.196 -        {
  18.197 -          Log.get().warning("NNTPConnection.shutdownOutput(): " + ex);
  18.198 -        }
  18.199 -      }
  18.200 -    }, 3000);
  18.201 -  }
  18.202 -  
  18.203 -  public SocketChannel getSocketChannel()
  18.204 -  {
  18.205 -    return this.channel;
  18.206 -  }
  18.207 -  
  18.208 -  public Article getCurrentArticle()
  18.209 -  {
  18.210 -    return this.currentArticle;
  18.211 -  }
  18.212 -  
  18.213 -  public Charset getCurrentCharset()
  18.214 -  {
  18.215 -    return this.charset;
  18.216 -  }
  18.217 -
  18.218 -  /**
  18.219 -   * @return The currently selected communication channel (not SocketChannel)
  18.220 -   */
  18.221 -  public Channel getCurrentChannel()
  18.222 -  {
  18.223 -    return this.currentGroup;
  18.224 -  }
  18.225 -  
  18.226 -  public void setCurrentArticle(final Article article)
  18.227 -  {
  18.228 -    this.currentArticle = article;
  18.229 -  }
  18.230 -  
  18.231 -  public void setCurrentGroup(final Channel group)
  18.232 -  {
  18.233 -    this.currentGroup = group;
  18.234 -  }
  18.235 -  
  18.236 -  public long getLastActivity()
  18.237 -  {
  18.238 -    return this.lastActivity;
  18.239 -  }
  18.240 -  
  18.241 -  /**
  18.242 -   * Due to the readLockGate there is no need to synchronize this method.
  18.243 -   * @param raw
  18.244 -   * @throws IllegalArgumentException if raw is null.
  18.245 -   * @throws IllegalStateException if calling thread does not own the readLock.
  18.246 -   */
  18.247 -  void lineReceived(byte[] raw)
  18.248 -  {
  18.249 -    if(raw == null)
  18.250 -    {
  18.251 -      throw new IllegalArgumentException("raw is null");
  18.252 -    }
  18.253 -    
  18.254 -    if(readLock == 0 || readLock != Thread.currentThread().hashCode())
  18.255 -    {
  18.256 -      throw new IllegalStateException("readLock not properly set");
  18.257 -    }
  18.258 -
  18.259 -    this.lastActivity = System.currentTimeMillis();
  18.260 -    
  18.261 -    String line = new String(raw, this.charset);
  18.262 -    
  18.263 -    // There might be a trailing \r, but trim() is a bad idea
  18.264 -    // as it removes also leading spaces from long header lines.
  18.265 -    if(line.endsWith("\r"))
  18.266 -    {
  18.267 -      line = line.substring(0, line.length() - 1);
  18.268 -      raw  = Arrays.copyOf(raw, raw.length - 1);
  18.269 -    }
  18.270 -    
  18.271 -    Log.get().fine("<< " + line);
  18.272 -    
  18.273 -    if(command == null)
  18.274 -    {
  18.275 -      command = parseCommandLine(line);
  18.276 -      assert command != null;
  18.277 -    }
  18.278 -
  18.279 -    try
  18.280 -    {
  18.281 -      // The command object will process the line we just received
  18.282 -      try
  18.283 -      {
  18.284 -        command.processLine(this, line, raw);
  18.285 -      }
  18.286 -      catch(StorageBackendException ex)
  18.287 -      {
  18.288 -        Log.get().info("Retry command processing after StorageBackendException");
  18.289 -
  18.290 -        // Try it a second time, so that the backend has time to recover
  18.291 -        command.processLine(this, line, raw);
  18.292 -      }
  18.293 -    }
  18.294 -    catch(ClosedChannelException ex0)
  18.295 -    {
  18.296 -      try
  18.297 -      {
  18.298 -        Log.get().info("Connection to " + channel.socket().getRemoteSocketAddress()
  18.299 -            + " closed: " + ex0);
  18.300 -      }
  18.301 -      catch(Exception ex0a)
  18.302 -      {
  18.303 -        ex0a.printStackTrace();
  18.304 -      }
  18.305 -    }
  18.306 -    catch(Exception ex1) // This will catch a second StorageBackendException
  18.307 -    {
  18.308 -      try
  18.309 -      {
  18.310 -        command = null;
  18.311 -        ex1.printStackTrace();
  18.312 -        println("500 Internal server error");
  18.313 -      }
  18.314 -      catch(Exception ex2)
  18.315 -      {
  18.316 -        ex2.printStackTrace();
  18.317 -      }
  18.318 -    }
  18.319 -
  18.320 -    if(command == null || command.hasFinished())
  18.321 -    {
  18.322 -      command = null;
  18.323 -      charset = Charset.forName("UTF-8"); // Reset to default
  18.324 -    }
  18.325 -  }
  18.326 -  
  18.327 -  /**
  18.328 -   * This method determines the fitting command processing class.
  18.329 -   * @param line
  18.330 -   * @return
  18.331 -   */
  18.332 -  private Command parseCommandLine(String line)
  18.333 -  {
  18.334 -    String cmdStr = line.split(" ")[0];
  18.335 -    return CommandSelector.getInstance().get(cmdStr);
  18.336 -  }
  18.337 -  
  18.338 -  /**
  18.339 -   * Puts the given line into the output buffer, adds a newline character
  18.340 -   * and returns. The method returns immediately and does not block until
  18.341 -   * the line was sent. If line is longer than 510 octets it is split up in
  18.342 -   * several lines. Each line is terminated by \r\n (NNTPConnection.NEWLINE).
  18.343 -   * @param line
  18.344 -   */
  18.345 -  public void println(final CharSequence line, final Charset charset)
  18.346 -    throws IOException
  18.347 -  {    
  18.348 -    writeToChannel(CharBuffer.wrap(line), charset, line);
  18.349 -    writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
  18.350 -  }
  18.351 -
  18.352 -  /**
  18.353 -   * Writes the given raw lines to the output buffers and finishes with
  18.354 -   * a newline character (\r\n).
  18.355 -   * @param rawLines
  18.356 -   */
  18.357 -  public void println(final byte[] rawLines)
  18.358 -    throws IOException
  18.359 -  {
  18.360 -    this.lineBuffers.addOutputBuffer(ByteBuffer.wrap(rawLines));
  18.361 -    writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
  18.362 -  }
  18.363 -  
  18.364 -  /**
  18.365 -   * Encodes the given CharBuffer using the given Charset to a bunch of
  18.366 -   * ByteBuffers (each 512 bytes large) and enqueues them for writing at the
  18.367 -   * connected SocketChannel.
  18.368 -   * @throws java.io.IOException
  18.369 -   */
  18.370 -  private void writeToChannel(CharBuffer characters, final Charset charset,
  18.371 -    CharSequence debugLine)
  18.372 -    throws IOException
  18.373 -  {
  18.374 -    if(!charset.canEncode())
  18.375 -    {
  18.376 -      Log.get().severe("FATAL: Charset " + charset + " cannot encode!");
  18.377 -      return;
  18.378 -    }
  18.379 -    
  18.380 -    // Write characters to output buffers
  18.381 -    LineEncoder lenc = new LineEncoder(characters, charset);
  18.382 -    lenc.encode(lineBuffers);
  18.383 -    
  18.384 -    enableWriteEvents(debugLine);
  18.385 -  }
  18.386 -
  18.387 -  private void enableWriteEvents(CharSequence debugLine)
  18.388 -  {
  18.389 -    // Enable OP_WRITE events so that the buffers are processed
  18.390 -    try
  18.391 -    {
  18.392 -      this.writeSelKey.interestOps(SelectionKey.OP_WRITE);
  18.393 -      ChannelWriter.getInstance().getSelector().wakeup();
  18.394 -    }
  18.395 -    catch(Exception ex) // CancelledKeyException and ChannelCloseException
  18.396 -    {
  18.397 -      Log.get().warning("NNTPConnection.writeToChannel(): " + ex);
  18.398 -      return;
  18.399 -    }
  18.400 -
  18.401 -    // Update last activity timestamp
  18.402 -    this.lastActivity = System.currentTimeMillis();
  18.403 -    if(debugLine != null)
  18.404 -    {
  18.405 -      Log.get().fine(">> " + debugLine);
  18.406 -    }
  18.407 -  }
  18.408 -  
  18.409 -  public void println(final CharSequence line)
  18.410 -    throws IOException
  18.411 -  {
  18.412 -    println(line, charset);
  18.413 -  }
  18.414 -  
  18.415 -  public void print(final String line)
  18.416 -    throws IOException
  18.417 -  {
  18.418 -    writeToChannel(CharBuffer.wrap(line), charset, line);
  18.419 -  }
  18.420 -  
  18.421 -  public void setCurrentCharset(final Charset charset)
  18.422 -  {
  18.423 -    this.charset = charset;
  18.424 -  }
  18.425 -
  18.426 -  void setLastActivity(long timestamp)
  18.427 -  {
  18.428 -    this.lastActivity = timestamp;
  18.429 -  }
  18.430 -  
  18.431 -}
    19.1 --- a/org/sonews/daemon/NNTPDaemon.java	Sun Aug 29 17:04:25 2010 +0200
    19.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.3 @@ -1,197 +0,0 @@
    19.4 -/*
    19.5 - *   SONEWS News Server
    19.6 - *   see AUTHORS for the list of contributors
    19.7 - *
    19.8 - *   This program is free software: you can redistribute it and/or modify
    19.9 - *   it under the terms of the GNU General Public License as published by
   19.10 - *   the Free Software Foundation, either version 3 of the License, or
   19.11 - *   (at your option) any later version.
   19.12 - *
   19.13 - *   This program is distributed in the hope that it will be useful,
   19.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   19.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   19.16 - *   GNU General Public License for more details.
   19.17 - *
   19.18 - *   You should have received a copy of the GNU General Public License
   19.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   19.20 - */
   19.21 -
   19.22 -package org.sonews.daemon;
   19.23 -
   19.24 -import org.sonews.config.Config;
   19.25 -import org.sonews.Main;
   19.26 -import org.sonews.util.Log;
   19.27 -import java.io.IOException;
   19.28 -import java.net.BindException;
   19.29 -import java.net.InetSocketAddress;
   19.30 -import java.net.ServerSocket;
   19.31 -import java.nio.channels.CancelledKeyException;
   19.32 -import java.nio.channels.ClosedChannelException;
   19.33 -import java.nio.channels.SelectionKey;
   19.34 -import java.nio.channels.Selector;
   19.35 -import java.nio.channels.ServerSocketChannel;
   19.36 -import java.nio.channels.SocketChannel;
   19.37 -
   19.38 -/**
   19.39 - * NNTP daemon using SelectableChannels.
   19.40 - * @author Christian Lins
   19.41 - * @since sonews/0.5.0
   19.42 - */
   19.43 -public final class NNTPDaemon extends AbstractDaemon
   19.44 -{
   19.45 -
   19.46 -  public static final Object RegisterGate = new Object();
   19.47 -  
   19.48 -  private static NNTPDaemon instance = null;
   19.49 -  
   19.50 -  public static synchronized NNTPDaemon createInstance(int port)
   19.51 -  {
   19.52 -    if(instance == null)
   19.53 -    {
   19.54 -      instance = new NNTPDaemon(port);
   19.55 -      return instance;
   19.56 -    }
   19.57 -    else
   19.58 -    {
   19.59 -      throw new RuntimeException("NNTPDaemon.createInstance() called twice");
   19.60 -    }
   19.61 -  }
   19.62 -  
   19.63 -  private int port;
   19.64 -  
   19.65 -  private NNTPDaemon(final int port)
   19.66 -  {
   19.67 -    Log.get().info("Server listening on port " + port);
   19.68 -    this.port = port;
   19.69 -  }
   19.70 -
   19.71 -  @Override
   19.72 -  public void run()
   19.73 -  {
   19.74 -    try
   19.75 -    {
   19.76 -      // Create a Selector that handles the SocketChannel multiplexing
   19.77 -      final Selector readSelector  = Selector.open();
   19.78 -      final Selector writeSelector = Selector.open();
   19.79 -      
   19.80 -      // Start working threads
   19.81 -      final int workerThreads = Runtime.getRuntime().availableProcessors() * 4;
   19.82 -      ConnectionWorker[] cworkers = new ConnectionWorker[workerThreads];
   19.83 -      for(int n = 0; n < workerThreads; n++)
   19.84 -      {
   19.85 -        cworkers[n] = new ConnectionWorker();
   19.86 -        cworkers[n].start();
   19.87 -      }
   19.88 -      
   19.89 -      ChannelWriter.getInstance().setSelector(writeSelector);
   19.90 -      ChannelReader.getInstance().setSelector(readSelector);
   19.91 -      ChannelWriter.getInstance().start();
   19.92 -      ChannelReader.getInstance().start();
   19.93 -      
   19.94 -      final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
   19.95 -      serverSocketChannel.configureBlocking(true);  // Set to blocking mode
   19.96 -      
   19.97 -      // Configure ServerSocket; bind to socket...
   19.98 -      final ServerSocket serverSocket = serverSocketChannel.socket();
   19.99 -      serverSocket.bind(new InetSocketAddress(this.port));
  19.100 -      
  19.101 -      while(isRunning())
  19.102 -      {
  19.103 -        SocketChannel socketChannel;
  19.104 -        
  19.105 -        try
  19.106 -        {
  19.107 -          // As we set the server socket channel to blocking mode the accept()
  19.108 -          // method will block.
  19.109 -          socketChannel = serverSocketChannel.accept();
  19.110 -          socketChannel.configureBlocking(false);
  19.111 -          assert socketChannel.isConnected();
  19.112 -          assert socketChannel.finishConnect();
  19.113 -        }
  19.114 -        catch(IOException ex)
  19.115 -        {
  19.116 -          // Under heavy load an IOException "Too many open files may
  19.117 -          // be thrown. It most cases we should slow down the connection
  19.118 -          // accepting, to give the worker threads some time to process work.
  19.119 -          Log.get().severe("IOException while accepting connection: " + ex.getMessage());
  19.120 -          Log.get().info("Connection accepting sleeping for seconds...");
  19.121 -          Thread.sleep(5000); // 5 seconds
  19.122 -          continue;
  19.123 -        }
  19.124 -        
  19.125 -        final NNTPConnection conn;
  19.126 -        try
  19.127 -        {
  19.128 -          conn = new NNTPConnection(socketChannel);
  19.129 -          Connections.getInstance().add(conn);
  19.130 -        }
  19.131 -        catch(IOException ex)
  19.132 -        {
  19.133 -          Log.get().warning(ex.toString());
  19.134 -          socketChannel.close();
  19.135 -          continue;
  19.136 -        }
  19.137 -        
  19.138 -        try
  19.139 -        {
  19.140 -          SelectionKey selKeyWrite =
  19.141 -            registerSelector(writeSelector, socketChannel, SelectionKey.OP_WRITE);
  19.142 -          registerSelector(readSelector, socketChannel, SelectionKey.OP_READ);
  19.143 -          
  19.144 -          Log.get().info("Connected: " + socketChannel.socket().getRemoteSocketAddress());
  19.145 -
  19.146 -          // Set write selection key and send hello to client
  19.147 -          conn.setWriteSelectionKey(selKeyWrite);
  19.148 -          conn.println("200 " + Config.inst().get(Config.HOSTNAME, "localhost")
  19.149 -              + " " + Main.VERSION + " news server ready - (posting ok).");
  19.150 -        }
  19.151 -        catch(CancelledKeyException cke)
  19.152 -        {
  19.153 -          Log.get().warning("CancelledKeyException " + cke.getMessage() + " was thrown: "
  19.154 -            + socketChannel.socket());
  19.155 -        }
  19.156 -        catch(ClosedChannelException cce)
  19.157 -        {
  19.158 -          Log.get().warning("ClosedChannelException " + cce.getMessage() + " was thrown: "
  19.159 -            + socketChannel.socket());
  19.160 -        }
  19.161 -      }
  19.162 -    }
  19.163 -    catch(BindException ex)
  19.164 -    {
  19.165 -      // Could not bind to socket; this is a fatal problem; so perform shutdown
  19.166 -      ex.printStackTrace();
  19.167 -      System.exit(1);
  19.168 -    }
  19.169 -    catch(IOException ex)
  19.170 -    {
  19.171 -      ex.printStackTrace();
  19.172 -    }
  19.173 -    catch(Exception ex)
  19.174 -    {
  19.175 -      ex.printStackTrace();
  19.176 -    }
  19.177 -  }
  19.178 -  
  19.179 -  public static SelectionKey registerSelector(final Selector selector,
  19.180 -    final SocketChannel channel, final int op)
  19.181 -    throws CancelledKeyException, ClosedChannelException
  19.182 -  {
  19.183 -    // Register the selector at the channel, so that it will be notified
  19.184 -    // on the socket's events
  19.185 -    synchronized(RegisterGate)
  19.186 -    {
  19.187 -      // Wakeup the currently blocking reader/writer thread; we have locked
  19.188 -      // the RegisterGate to prevent the awakened thread to block again
  19.189 -      selector.wakeup();
  19.190 -      
  19.191 -      // Lock the selector to prevent the waiting worker threads going into
  19.192 -      // selector.select() which would block the selector.
  19.193 -      synchronized (selector)
  19.194 -      {
  19.195 -        return channel.register(selector, op, null);
  19.196 -      }
  19.197 -    }
  19.198 -  }
  19.199 -  
  19.200 -}
    20.1 --- a/org/sonews/daemon/command/ArticleCommand.java	Sun Aug 29 17:04:25 2010 +0200
    20.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.3 @@ -1,174 +0,0 @@
    20.4 -/*
    20.5 - *   SONEWS News Server
    20.6 - *   see AUTHORS for the list of contributors
    20.7 - *
    20.8 - *   This program is free software: you can redistribute it and/or modify
    20.9 - *   it under the terms of the GNU General Public License as published by
   20.10 - *   the Free Software Foundation, either version 3 of the License, or
   20.11 - *   (at your option) any later version.
   20.12 - *
   20.13 - *   This program is distributed in the hope that it will be useful,
   20.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   20.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   20.16 - *   GNU General Public License for more details.
   20.17 - *
   20.18 - *   You should have received a copy of the GNU General Public License
   20.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   20.20 - */
   20.21 -
   20.22 -package org.sonews.daemon.command;
   20.23 -
   20.24 -import java.io.IOException;
   20.25 -import org.sonews.storage.Article;
   20.26 -import org.sonews.daemon.NNTPConnection;
   20.27 -import org.sonews.storage.Channel;
   20.28 -import org.sonews.storage.StorageBackendException;
   20.29 -
   20.30 -/**
   20.31 - * Class handling the ARTICLE, BODY and HEAD commands.
   20.32 - * @author Christian Lins
   20.33 - * @author Dennis Schwerdel
   20.34 - * @since n3tpd/0.1
   20.35 - */
   20.36 -public class ArticleCommand implements Command
   20.37 -{
   20.38 -
   20.39 -  @Override
   20.40 -  public String[] getSupportedCommandStrings()
   20.41 -  {
   20.42 -    return new String[] {"ARTICLE", "BODY", "HEAD"};
   20.43 -  }
   20.44 -
   20.45 -  @Override
   20.46 -  public boolean hasFinished()
   20.47 -  {
   20.48 -    return true;
   20.49 -  }
   20.50 -
   20.51 -  @Override
   20.52 -  public String impliedCapability()
   20.53 -  {
   20.54 -    return null;
   20.55 -  }
   20.56 -
   20.57 -  @Override
   20.58 -  public boolean isStateful()
   20.59 -  {
   20.60 -    return false;
   20.61 -  }
   20.62 -
   20.63 -  // TODO: Refactor this method to reduce its complexity!
   20.64 -  @Override
   20.65 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   20.66 -    throws IOException
   20.67 -  {
   20.68 -    final String[] command = line.split(" ");
   20.69 -    
   20.70 -    Article article  = null;
   20.71 -    long    artIndex = -1;
   20.72 -    if (command.length == 1)
   20.73 -    {
   20.74 -      article = conn.getCurrentArticle();
   20.75 -      if (article == null)
   20.76 -      {
   20.77 -        conn.println("420 no current article has been selected");
   20.78 -        return;
   20.79 -      }
   20.80 -    }
   20.81 -    else if (command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN))
   20.82 -    {
   20.83 -      // Message-ID
   20.84 -      article = Article.getByMessageID(command[1]);
   20.85 -      if (article == null)
   20.86 -      {
   20.87 -        conn.println("430 no such article found");
   20.88 -        return;
   20.89 -      }
   20.90 -    }
   20.91 -    else
   20.92 -    {
   20.93 -      // Message Number
   20.94 -      try
   20.95 -      {
   20.96 -        Channel currentGroup = conn.getCurrentChannel();
   20.97 -        if(currentGroup == null)
   20.98 -        {
   20.99 -          conn.println("400 no group selected");
  20.100 -          return;
  20.101 -        }
  20.102 -        
  20.103 -        artIndex = Long.parseLong(command[1]);
  20.104 -        article  = currentGroup.getArticle(artIndex);
  20.105 -      }
  20.106 -      catch(NumberFormatException ex)
  20.107 -      {
  20.108 -        ex.printStackTrace();
  20.109 -      }
  20.110 -      catch(StorageBackendException ex)
  20.111 -      {
  20.112 -        ex.printStackTrace();
  20.113 -      }
  20.114 -
  20.115 -      if (article == null)
  20.116 -      {
  20.117 -        conn.println("423 no such article number in this group");
  20.118 -        return;
  20.119 -      }
  20.120 -      conn.setCurrentArticle(article);
  20.121 -    }
  20.122 -
  20.123 -    if(command[0].equalsIgnoreCase("ARTICLE"))
  20.124 -    {
  20.125 -      conn.println("220 " + artIndex + " " + article.getMessageID()
  20.126 -          + " article retrieved - head and body follow");
  20.127 -      conn.println(article.getHeaderSource());
  20.128 -      conn.println("");
  20.129 -      conn.println(article.getBody());
  20.130 -      conn.println(".");
  20.131 -    }
  20.132 -    else if(command[0].equalsIgnoreCase("BODY"))
  20.133 -    {
  20.134 -      conn.println("222 " + artIndex + " " + article.getMessageID() + " body");
  20.135 -      conn.println(article.getBody());
  20.136 -      conn.println(".");
  20.137 -    }
  20.138 -    
  20.139 -    /*
  20.140 -     * HEAD: This command is mandatory.
  20.141 -     *
  20.142 -     * Syntax
  20.143 -     *    HEAD message-id
  20.144 -     *    HEAD number
  20.145 -     *    HEAD
  20.146 -     *
  20.147 -     * Responses
  20.148 -     *
  20.149 -     * First form (message-id specified)
  20.150 -     *  221 0|n message-id    Headers follow (multi-line)
  20.151 -     *  430                   No article with that message-id
  20.152 -     *
  20.153 -     * Second form (article number specified)
  20.154 -     *  221 n message-id      Headers follow (multi-line)
  20.155 -     *  412                   No newsgroup selected
  20.156 -     *  423                   No article with that number
  20.157 -     *
  20.158 -     * Third form (current article number used)
  20.159 -     *  221 n message-id      Headers follow (multi-line)
  20.160 -     *  412                   No newsgroup selected
  20.161 -     *  420                   Current article number is invalid
  20.162 -     *
  20.163 -     * Parameters
  20.164 -     *  number        Requested article number
  20.165 -     *  n             Returned article number
  20.166 -     *  message-id    Article message-id
  20.167 -     */
  20.168 -    else if(command[0].equalsIgnoreCase("HEAD"))
  20.169 -    {
  20.170 -      conn.println("221 " + artIndex + " " + article.getMessageID()
  20.171 -          + " Headers follow (multi-line)");
  20.172 -      conn.println(article.getHeaderSource());
  20.173 -      conn.println(".");
  20.174 -    }
  20.175 -  }  
  20.176 -  
  20.177 -}
    21.1 --- a/org/sonews/daemon/command/CapabilitiesCommand.java	Sun Aug 29 17:04:25 2010 +0200
    21.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.3 @@ -1,93 +0,0 @@
    21.4 -/*
    21.5 - *   SONEWS News Server
    21.6 - *   see AUTHORS for the list of contributors
    21.7 - *
    21.8 - *   This program is free software: you can redistribute it and/or modify
    21.9 - *   it under the terms of the GNU General Public License as published by
   21.10 - *   the Free Software Foundation, either version 3 of the License, or
   21.11 - *   (at your option) any later version.
   21.12 - *
   21.13 - *   This program is distributed in the hope that it will be useful,
   21.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   21.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   21.16 - *   GNU General Public License for more details.
   21.17 - *
   21.18 - *   You should have received a copy of the GNU General Public License
   21.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   21.20 - */
   21.21 -
   21.22 -package org.sonews.daemon.command;
   21.23 -
   21.24 -import java.io.IOException;
   21.25 -import org.sonews.daemon.NNTPConnection;
   21.26 -
   21.27 -/**
   21.28 - * <pre>
   21.29 - *  The CAPABILITIES command allows a client to determine the
   21.30 - *  capabilities of the server at any given time.
   21.31 - *
   21.32 - *  This command MAY be issued at any time; the server MUST NOT require
   21.33 - *  it to be issued in order to make use of any capability. The response
   21.34 - *  generated by this command MAY change during a session because of
   21.35 - *  other state information (which, in turn, may be changed by the
   21.36 - *  effects of other commands or by external events).  An NNTP client is
   21.37 - *  only able to get the current and correct information concerning
   21.38 - *  available capabilities at any point during a session by issuing a
   21.39 - *  CAPABILITIES command at that point of that session and processing the
   21.40 - *  response.
   21.41 - * </pre>
   21.42 - * @author Christian Lins
   21.43 - * @since sonews/0.5.0
   21.44 - */
   21.45 -public class CapabilitiesCommand implements Command
   21.46 -{
   21.47 -
   21.48 -  static final String[] CAPABILITIES = new String[]
   21.49 -    {
   21.50 -      "VERSION 2", // MUST be the first one; VERSION 2 refers to RFC3977
   21.51 -      "READER",    // Server implements commands for reading
   21.52 -      "POST",      // Server implements POST command
   21.53 -      "OVER"       // Server implements OVER command
   21.54 -    };
   21.55 -
   21.56 -  @Override
   21.57 -  public String[] getSupportedCommandStrings()
   21.58 -  {
   21.59 -    return new String[] {"CAPABILITIES"};
   21.60 -  }
   21.61 -
   21.62 -  /**
   21.63 -   * First called after one call to processLine().
   21.64 -   * @return
   21.65 -   */
   21.66 -  @Override
   21.67 -  public boolean hasFinished()
   21.68 -  {
   21.69 -    return true;
   21.70 -  }
   21.71 -
   21.72 -  @Override
   21.73 -  public String impliedCapability()
   21.74 -  {
   21.75 -    return null;
   21.76 -  }
   21.77 -  
   21.78 -  @Override
   21.79 -  public boolean isStateful()
   21.80 -  {
   21.81 -    return false;
   21.82 -  }
   21.83 -
   21.84 -  @Override
   21.85 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   21.86 -    throws IOException
   21.87 -  {
   21.88 -    conn.println("101 Capabilities list:");
   21.89 -    for(String cap : CAPABILITIES)
   21.90 -    {
   21.91 -      conn.println(cap);
   21.92 -    }
   21.93 -    conn.println(".");
   21.94 -  }
   21.95 -
   21.96 -}
    22.1 --- a/org/sonews/daemon/command/Command.java	Sun Aug 29 17:04:25 2010 +0200
    22.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    22.3 @@ -1,51 +0,0 @@
    22.4 -/*
    22.5 - *   SONEWS News Server
    22.6 - *   see AUTHORS for the list of contributors
    22.7 - *
    22.8 - *   This program is free software: you can redistribute it and/or modify
    22.9 - *   it under the terms of the GNU General Public License as published by
   22.10 - *   the Free Software Foundation, either version 3 of the License, or
   22.11 - *   (at your option) any later version.
   22.12 - *
   22.13 - *   This program is distributed in the hope that it will be useful,
   22.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   22.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   22.16 - *   GNU General Public License for more details.
   22.17 - *
   22.18 - *   You should have received a copy of the GNU General Public License
   22.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   22.20 - */
   22.21 -
   22.22 -package org.sonews.daemon.command;
   22.23 -
   22.24 -import java.io.IOException;
   22.25 -import org.sonews.daemon.NNTPConnection;
   22.26 -import org.sonews.storage.StorageBackendException;
   22.27 -
   22.28 -/**
   22.29 - * Interface for pluggable NNTP commands handling classes.
   22.30 - * @author Christian Lins
   22.31 - * @since sonews/0.6.0
   22.32 - */
   22.33 -public interface Command
   22.34 -{
   22.35 -
   22.36 -  /**
   22.37 -   * @return true if this instance can be reused.
   22.38 -   */
   22.39 -  boolean hasFinished();
   22.40 -
   22.41 -  /**
   22.42 -   * Returns capability string that is implied by this command class.
   22.43 -   * MAY return null if the command is required by the NNTP standard.
   22.44 -   */
   22.45 -  String impliedCapability();
   22.46 -
   22.47 -  boolean isStateful();
   22.48 -
   22.49 -  String[] getSupportedCommandStrings();
   22.50 -
   22.51 -  void processLine(NNTPConnection conn, String line, byte[] rawLine)
   22.52 -    throws IOException, StorageBackendException;
   22.53 -
   22.54 -}
    23.1 --- a/org/sonews/daemon/command/GroupCommand.java	Sun Aug 29 17:04:25 2010 +0200
    23.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    23.3 @@ -1,102 +0,0 @@
    23.4 -/*
    23.5 - *   SONEWS News Server
    23.6 - *   see AUTHORS for the list of contributors
    23.7 - *
    23.8 - *   This program is free software: you can redistribute it and/or modify
    23.9 - *   it under the terms of the GNU General Public License as published by
   23.10 - *   the Free Software Foundation, either version 3 of the License, or
   23.11 - *   (at your option) any later version.
   23.12 - *
   23.13 - *   This program is distributed in the hope that it will be useful,
   23.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   23.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   23.16 - *   GNU General Public License for more details.
   23.17 - *
   23.18 - *   You should have received a copy of the GNU General Public License
   23.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   23.20 - */
   23.21 -
   23.22 -package org.sonews.daemon.command;
   23.23 -
   23.24 -import java.io.IOException;
   23.25 -import org.sonews.daemon.NNTPConnection;
   23.26 -import org.sonews.storage.Channel;
   23.27 -import org.sonews.storage.StorageBackendException;
   23.28 -
   23.29 -/**
   23.30 - * Class handling the GROUP command.
   23.31 - * <pre>
   23.32 - *  Syntax
   23.33 - *    GROUP group
   23.34 - *
   23.35 - *  Responses
   23.36 - *    211 number low high group     Group successfully selected
   23.37 - *    411                           No such newsgroup
   23.38 - *
   23.39 - *  Parameters
   23.40 - *    group     Name of newsgroup
   23.41 - *    number    Estimated number of articles in the group
   23.42 - *    low       Reported low water mark
   23.43 - *    high      Reported high water mark
   23.44 - * </pre>
   23.45 - * (from RFC 3977)
   23.46 - * 
   23.47 - * @author Christian Lins
   23.48 - * @author Dennis Schwerdel
   23.49 - * @since n3tpd/0.1
   23.50 - */
   23.51 -public class GroupCommand implements Command
   23.52 -{
   23.53 -
   23.54 -  @Override
   23.55 -  public String[] getSupportedCommandStrings()
   23.56 -  {
   23.57 -    return new String[]{"GROUP"};
   23.58 -  }
   23.59 -
   23.60 -  @Override
   23.61 -  public boolean hasFinished()
   23.62 -  {
   23.63 -    return true;
   23.64 -  }
   23.65 -
   23.66 -  @Override
   23.67 -  public String impliedCapability()
   23.68 -  {
   23.69 -    return null;
   23.70 -  }
   23.71 -
   23.72 -  @Override
   23.73 -  public boolean isStateful()
   23.74 -  {
   23.75 -    return true;
   23.76 -  }
   23.77 -  
   23.78 -  @Override
   23.79 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   23.80 -    throws IOException, StorageBackendException
   23.81 -  {
   23.82 -    final String[] command = line.split(" ");
   23.83 -
   23.84 -    Channel group;
   23.85 -    if(command.length >= 2)
   23.86 -    {
   23.87 -      group = Channel.getByName(command[1]);
   23.88 -      if(group == null || group.isDeleted())
   23.89 -      {
   23.90 -        conn.println("411 no such news group");
   23.91 -      }
   23.92 -      else
   23.93 -      {
   23.94 -        conn.setCurrentGroup(group);
   23.95 -        conn.println("211 " + group.getPostingsCount() + " " + group.getFirstArticleNumber()
   23.96 -          + " " + group.getLastArticleNumber() + " " + group.getName() + " group selected");
   23.97 -      }
   23.98 -    }
   23.99 -    else
  23.100 -    {
  23.101 -      conn.println("500 no group name given");
  23.102 -    }
  23.103 -  }
  23.104 -
  23.105 -}
    24.1 --- a/org/sonews/daemon/command/HelpCommand.java	Sun Aug 29 17:04:25 2010 +0200
    24.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    24.3 @@ -1,100 +0,0 @@
    24.4 -/*
    24.5 - *   SONEWS News Server
    24.6 - *   see AUTHORS for the list of contributors
    24.7 - *
    24.8 - *   This program is free software: you can redistribute it and/or modify
    24.9 - *   it under the terms of the GNU General Public License as published by
   24.10 - *   the Free Software Foundation, either version 3 of the License, or
   24.11 - *   (at your option) any later version.
   24.12 - *
   24.13 - *   This program is distributed in the hope that it will be useful,
   24.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   24.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   24.16 - *   GNU General Public License for more details.
   24.17 - *
   24.18 - *   You should have received a copy of the GNU General Public License
   24.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   24.20 - */
   24.21 -
   24.22 -package org.sonews.daemon.command;
   24.23 -
   24.24 -import java.io.IOException;
   24.25 -import java.util.Set;
   24.26 -import org.sonews.daemon.CommandSelector;
   24.27 -import org.sonews.daemon.NNTPConnection;
   24.28 -import org.sonews.util.io.Resource;
   24.29 -
   24.30 -/**
   24.31 - * This command provides a short summary of the commands that are
   24.32 - * understood by this implementation of the server. The help text will
   24.33 - * be presented as a multi-line data block following the 100 response
   24.34 - * code (taken from RFC).
   24.35 - * @author Christian Lins
   24.36 - * @since sonews/0.5.0
   24.37 - */
   24.38 -public class HelpCommand implements Command
   24.39 -{
   24.40 -
   24.41 -  @Override
   24.42 -  public boolean hasFinished()
   24.43 -  {
   24.44 -    return true;
   24.45 -  }
   24.46 -
   24.47 -  @Override
   24.48 -  public String impliedCapability()
   24.49 -  {
   24.50 -    return null;
   24.51 -  }
   24.52 -
   24.53 -  @Override
   24.54 -  public boolean isStateful()
   24.55 -  {
   24.56 -    return true;
   24.57 -  }
   24.58 -
   24.59 -  @Override
   24.60 -  public String[] getSupportedCommandStrings()
   24.61 -  {
   24.62 -    return new String[]{"HELP"};
   24.63 -  }
   24.64 -  
   24.65 -  @Override
   24.66 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   24.67 -    throws IOException
   24.68 -  {
   24.69 -    final String[] command = line.split(" ");
   24.70 -    conn.println("100 help text follows");
   24.71 -
   24.72 -    if(line.length() <= 1)
   24.73 -    {
   24.74 -      final String[] help = Resource
   24.75 -        .getAsString("helpers/helptext", true).split("\n");
   24.76 -      for(String hstr : help)
   24.77 -      {
   24.78 -        conn.println(hstr);
   24.79 -      }
   24.80 -
   24.81 -      Set<String> commandNames = CommandSelector.getCommandNames();
   24.82 -      for(String cmdName : commandNames)
   24.83 -      {
   24.84 -        conn.println(cmdName);
   24.85 -      }
   24.86 -    }
   24.87 -    else
   24.88 -    {
   24.89 -      Command cmd = CommandSelector.getInstance().get(command[1]);
   24.90 -      if(cmd instanceof HelpfulCommand)
   24.91 -      {
   24.92 -        conn.println(((HelpfulCommand)cmd).getHelpString());
   24.93 -      }
   24.94 -      else
   24.95 -      {
   24.96 -        conn.println("No further help information available.");
   24.97 -      }
   24.98 -    }
   24.99 -    
  24.100 -    conn.println(".");
  24.101 -  }
  24.102 -  
  24.103 -}
    25.1 --- a/org/sonews/daemon/command/HelpfulCommand.java	Sun Aug 29 17:04:25 2010 +0200
    25.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    25.3 @@ -1,35 +0,0 @@
    25.4 -/*
    25.5 - *   SONEWS News Server
    25.6 - *   see AUTHORS for the list of contributors
    25.7 - *
    25.8 - *   This program is free software: you can redistribute it and/or modify
    25.9 - *   it under the terms of the GNU General Public License as published by
   25.10 - *   the Free Software Foundation, either version 3 of the License, or
   25.11 - *   (at your option) any later version.
   25.12 - *
   25.13 - *   This program is distributed in the hope that it will be useful,
   25.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   25.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   25.16 - *   GNU General Public License for more details.
   25.17 - *
   25.18 - *   You should have received a copy of the GNU General Public License
   25.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   25.20 - */
   25.21 -
   25.22 -package org.sonews.daemon.command;
   25.23 -
   25.24 -/**
   25.25 - *
   25.26 - * @since sonews/1.1
   25.27 - * @author Christian Lins
   25.28 - */
   25.29 -public interface HelpfulCommand extends Command
   25.30 -{
   25.31 -
   25.32 -  /**
   25.33 -   * @return A short description of this command, that is
   25.34 -   * used within the output of the HELP command.
   25.35 -   */
   25.36 -  String getHelpString();
   25.37 -
   25.38 -}
    26.1 --- a/org/sonews/daemon/command/ListCommand.java	Sun Aug 29 17:04:25 2010 +0200
    26.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    26.3 @@ -1,153 +0,0 @@
    26.4 -/*
    26.5 - *   SONEWS News Server
    26.6 - *   see AUTHORS for the list of contributors
    26.7 - *
    26.8 - *   This program is free software: you can redistribute it and/or modify
    26.9 - *   it under the terms of the GNU General Public License as published by
   26.10 - *   the Free Software Foundation, either version 3 of the License, or
   26.11 - *   (at your option) any later version.
   26.12 - *
   26.13 - *   This program is distributed in the hope that it will be useful,
   26.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   26.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   26.16 - *   GNU General Public License for more details.
   26.17 - *
   26.18 - *   You should have received a copy of the GNU General Public License
   26.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   26.20 - */
   26.21 -
   26.22 -package org.sonews.daemon.command;
   26.23 -
   26.24 -import java.io.IOException;
   26.25 -import java.util.List;
   26.26 -import java.util.regex.Matcher;
   26.27 -import java.util.regex.Pattern;
   26.28 -import java.util.regex.PatternSyntaxException;
   26.29 -import org.sonews.daemon.NNTPConnection;
   26.30 -import org.sonews.storage.Channel;
   26.31 -import org.sonews.storage.StorageBackendException;
   26.32 -import org.sonews.util.Log;
   26.33 -
   26.34 -/**
   26.35 - * Class handling the LIST command.
   26.36 - * @author Christian Lins
   26.37 - * @author Dennis Schwerdel
   26.38 - * @since n3tpd/0.1
   26.39 - */
   26.40 -public class ListCommand implements Command
   26.41 -{
   26.42 -
   26.43 -  @Override
   26.44 -  public String[] getSupportedCommandStrings()
   26.45 -  {
   26.46 -    return new String[]{"LIST"};
   26.47 -  }
   26.48 -
   26.49 -  @Override
   26.50 -  public boolean hasFinished()
   26.51 -  {
   26.52 -    return true;
   26.53 -  }
   26.54 -
   26.55 -  @Override
   26.56 -  public String impliedCapability()
   26.57 -  {
   26.58 -    return null;
   26.59 -  }
   26.60 -
   26.61 -  @Override
   26.62 -  public boolean isStateful()
   26.63 -  {
   26.64 -    return false;
   26.65 -  }
   26.66 -  
   26.67 -  @Override
   26.68 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   26.69 -    throws IOException, StorageBackendException
   26.70 -  {
   26.71 -    final String[] command = line.split(" ");
   26.72 -    
   26.73 -    if(command.length >= 2)
   26.74 -    {
   26.75 -      if(command[1].equalsIgnoreCase("OVERVIEW.FMT"))
   26.76 -      {
   26.77 -        conn.println("215 information follows");
   26.78 -        conn.println("Subject:\nFrom:\nDate:\nMessage-ID:\nReferences:\nBytes:\nLines:\nXref");
   26.79 -        conn.println(".");
   26.80 -      }
   26.81 -      else if(command[1].equalsIgnoreCase("NEWSGROUPS"))
   26.82 -      {
   26.83 -        conn.println("215 information follows");
   26.84 -        final List<Channel> list = Channel.getAll();
   26.85 -        for (Channel g : list)
   26.86 -        {
   26.87 -          conn.println(g.getName() + "\t" + "-");
   26.88 -        }
   26.89 -        conn.println(".");
   26.90 -      }
   26.91 -      else if(command[1].equalsIgnoreCase("SUBSCRIPTIONS"))
   26.92 -      {
   26.93 -        conn.println("215 information follows");
   26.94 -        conn.println(".");
   26.95 -      }
   26.96 -      else if(command[1].equalsIgnoreCase("EXTENSIONS"))
   26.97 -      {
   26.98 -        conn.println("202 Supported NNTP extensions.");
   26.99 -        conn.println("LISTGROUP");
  26.100 -        conn.println("XDAEMON");
  26.101 -        conn.println("XPAT");
  26.102 -        conn.println(".");
  26.103 -      }
  26.104 -      else if(command[1].equalsIgnoreCase("ACTIVE"))
  26.105 -      {
  26.106 -        String  pattern  = command.length == 2
  26.107 -          ? null : command[2].replace("*", "\\w*");
  26.108 -        printGroupInfo(conn, pattern);
  26.109 -      }
  26.110 -      else
  26.111 -      {
  26.112 -        conn.println("500 unknown argument to LIST command");
  26.113 -      }
  26.114 -    }
  26.115 -    else
  26.116 -    {
  26.117 -      printGroupInfo(conn, null);
  26.118 -    }
  26.119 -  }
  26.120 -
  26.121 -  private void printGroupInfo(NNTPConnection conn, String pattern)
  26.122 -    throws IOException, StorageBackendException
  26.123 -  {
  26.124 -    final List<Channel> groups = Channel.getAll();
  26.125 -    if(groups != null)
  26.126 -    {
  26.127 -      conn.println("215 list of newsgroups follows");
  26.128 -      for(Channel g : groups)
  26.129 -      {
  26.130 -        try
  26.131 -        {
  26.132 -          Matcher matcher = pattern == null ?
  26.133 -            null : Pattern.compile(pattern).matcher(g.getName());
  26.134 -          if(!g.isDeleted() &&
  26.135 -            (matcher == null || matcher.find()))
  26.136 -          {
  26.137 -            String writeable = g.isWriteable() ? " y" : " n";
  26.138 -            // Indeed first the higher article number then the lower
  26.139 -            conn.println(g.getName() + " " + g.getLastArticleNumber() + " "
  26.140 -              + g.getFirstArticleNumber() + writeable);
  26.141 -          }
  26.142 -        }
  26.143 -        catch(PatternSyntaxException ex)
  26.144 -        {
  26.145 -          Log.get().info(ex.toString());
  26.146 -        }
  26.147 -      }
  26.148 -      conn.println(".");
  26.149 -    }
  26.150 -    else
  26.151 -    {
  26.152 -      conn.println("500 server backend malfunction");
  26.153 -    }
  26.154 -  }
  26.155 -
  26.156 -}
    27.1 --- a/org/sonews/daemon/command/ListGroupCommand.java	Sun Aug 29 17:04:25 2010 +0200
    27.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    27.3 @@ -1,94 +0,0 @@
    27.4 -/*
    27.5 - *   SONEWS News Server
    27.6 - *   see AUTHORS for the list of contributors
    27.7 - *
    27.8 - *   This program is free software: you can redistribute it and/or modify
    27.9 - *   it under the terms of the GNU General Public License as published by
   27.10 - *   the Free Software Foundation, either version 3 of the License, or
   27.11 - *   (at your option) any later version.
   27.12 - *
   27.13 - *   This program is distributed in the hope that it will be useful,
   27.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   27.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   27.16 - *   GNU General Public License for more details.
   27.17 - *
   27.18 - *   You should have received a copy of the GNU General Public License
   27.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   27.20 - */
   27.21 -
   27.22 -package org.sonews.daemon.command;
   27.23 -
   27.24 -import java.io.IOException;
   27.25 -import java.util.List;
   27.26 -import org.sonews.daemon.NNTPConnection;
   27.27 -import org.sonews.storage.Channel;
   27.28 -import org.sonews.storage.StorageBackendException;
   27.29 -
   27.30 -/**
   27.31 - * Class handling the LISTGROUP command.
   27.32 - * @author Christian Lins
   27.33 - * @author Dennis Schwerdel
   27.34 - * @since n3tpd/0.1
   27.35 - */
   27.36 -public class ListGroupCommand implements Command
   27.37 -{
   27.38 -
   27.39 -  @Override
   27.40 -  public String[] getSupportedCommandStrings()
   27.41 -  {
   27.42 -    return new String[]{"LISTGROUP"};
   27.43 -  }
   27.44 -
   27.45 -  @Override
   27.46 -  public boolean hasFinished()
   27.47 -  {
   27.48 -    return true;
   27.49 -  }
   27.50 -
   27.51 -  @Override
   27.52 -  public String impliedCapability()
   27.53 -  {
   27.54 -    return null;
   27.55 -  }
   27.56 -
   27.57 -  @Override
   27.58 -  public boolean isStateful()
   27.59 -  {
   27.60 -    return false;
   27.61 -  }
   27.62 -
   27.63 -  @Override
   27.64 -  public void processLine(NNTPConnection conn, final String commandName, byte[] raw)
   27.65 -    throws IOException, StorageBackendException
   27.66 -  {
   27.67 -    final String[] command = commandName.split(" ");
   27.68 -
   27.69 -    Channel group;
   27.70 -    if(command.length >= 2)
   27.71 -    {
   27.72 -      group = Channel.getByName(command[1]);
   27.73 -    }
   27.74 -    else
   27.75 -    {
   27.76 -      group = conn.getCurrentChannel();
   27.77 -    }
   27.78 -
   27.79 -    if (group == null)
   27.80 -    {
   27.81 -      conn.println("412 no group selected; use GROUP <group> command");
   27.82 -      return;
   27.83 -    }
   27.84 -
   27.85 -    List<Long> ids = group.getArticleNumbers();
   27.86 -    conn.println("211 " + ids.size() + " " +
   27.87 -      group.getFirstArticleNumber() + " " + 
   27.88 -      group.getLastArticleNumber() + " list of article numbers follow");
   27.89 -    for(long id : ids)
   27.90 -    {
   27.91 -      // One index number per line
   27.92 -      conn.println(Long.toString(id));
   27.93 -    }
   27.94 -    conn.println(".");
   27.95 -  }
   27.96 -
   27.97 -}
    28.1 --- a/org/sonews/daemon/command/ModeReaderCommand.java	Sun Aug 29 17:04:25 2010 +0200
    28.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    28.3 @@ -1,72 +0,0 @@
    28.4 -/*
    28.5 - *   SONEWS News Server
    28.6 - *   see AUTHORS for the list of contributors
    28.7 - *
    28.8 - *   This program is free software: you can redistribute it and/or modify
    28.9 - *   it under the terms of the GNU General Public License as published by
   28.10 - *   the Free Software Foundation, either version 3 of the License, or
   28.11 - *   (at your option) any later version.
   28.12 - *
   28.13 - *   This program is distributed in the hope that it will be useful,
   28.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   28.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   28.16 - *   GNU General Public License for more details.
   28.17 - *
   28.18 - *   You should have received a copy of the GNU General Public License
   28.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   28.20 - */
   28.21 -
   28.22 -package org.sonews.daemon.command;
   28.23 -
   28.24 -import java.io.IOException;
   28.25 -import org.sonews.daemon.NNTPConnection;
   28.26 -import org.sonews.storage.StorageBackendException;
   28.27 -
   28.28 -/**
   28.29 - * Class handling the MODE READER command. This command actually does nothing
   28.30 - * but returning a success status code.
   28.31 - * @author Christian Lins
   28.32 - * @since sonews/0.5.0
   28.33 - */
   28.34 -public class ModeReaderCommand implements Command
   28.35 -{
   28.36 -  
   28.37 -  @Override
   28.38 -  public String[] getSupportedCommandStrings()
   28.39 -  {
   28.40 -    return new String[]{"MODE"};
   28.41 -  }
   28.42 -
   28.43 -  @Override
   28.44 -  public boolean hasFinished()
   28.45 -  {
   28.46 -    return true;
   28.47 -  }
   28.48 -
   28.49 -  @Override
   28.50 -  public String impliedCapability()
   28.51 -  {
   28.52 -    return null;
   28.53 -  }
   28.54 -
   28.55 -  @Override
   28.56 -  public boolean isStateful()
   28.57 -  {
   28.58 -    return false;
   28.59 -  }
   28.60 -
   28.61 -  @Override
   28.62 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   28.63 -    throws IOException, StorageBackendException
   28.64 -  {
   28.65 -    if(line.equalsIgnoreCase("MODE READER"))
   28.66 -    {
   28.67 -      conn.println("200 hello you can post");
   28.68 -    }
   28.69 -    else
   28.70 -    {
   28.71 -      conn.println("500 I do not know this mode command");
   28.72 -    }
   28.73 -  }
   28.74 -
   28.75 -}
    29.1 --- a/org/sonews/daemon/command/NewGroupsCommand.java	Sun Aug 29 17:04:25 2010 +0200
    29.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    29.3 @@ -1,78 +0,0 @@
    29.4 -/*
    29.5 - *   SONEWS News Server
    29.6 - *   see AUTHORS for the list of contributors
    29.7 - *
    29.8 - *   This program is free software: you can redistribute it and/or modify
    29.9 - *   it under the terms of the GNU General Public License as published by
   29.10 - *   the Free Software Foundation, either version 3 of the License, or
   29.11 - *   (at your option) any later version.
   29.12 - *
   29.13 - *   This program is distributed in the hope that it will be useful,
   29.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   29.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   29.16 - *   GNU General Public License for more details.
   29.17 - *
   29.18 - *   You should have received a copy of the GNU General Public License
   29.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   29.20 - */
   29.21 -
   29.22 -package org.sonews.daemon.command;
   29.23 -
   29.24 -import java.io.IOException;
   29.25 -import org.sonews.daemon.NNTPConnection;
   29.26 -import org.sonews.storage.StorageBackendException;
   29.27 -
   29.28 -/**
   29.29 - * Class handling the NEWGROUPS command.
   29.30 - * @author Christian Lins
   29.31 - * @author Dennis Schwerdel
   29.32 - * @since n3tpd/0.1
   29.33 - */
   29.34 -public class NewGroupsCommand implements Command
   29.35 -{
   29.36 -
   29.37 -  @Override
   29.38 -  public String[] getSupportedCommandStrings()
   29.39 -  {
   29.40 -    return new String[]{"NEWGROUPS"};
   29.41 -  }
   29.42 -
   29.43 -  @Override
   29.44 -  public boolean hasFinished()
   29.45 -  {
   29.46 -    return true;
   29.47 -  }
   29.48 -
   29.49 -  @Override
   29.50 -  public String impliedCapability()
   29.51 -  {
   29.52 -    return null;
   29.53 -  }
   29.54 -
   29.55 -  @Override
   29.56 -  public boolean isStateful()
   29.57 -  {
   29.58 -    return false;
   29.59 -  }
   29.60 -
   29.61 -  @Override
   29.62 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   29.63 -    throws IOException, StorageBackendException
   29.64 -  {
   29.65 -    final String[] command = line.split(" ");
   29.66 -
   29.67 -    if(command.length == 3)
   29.68 -    {
   29.69 -      conn.println("231 list of new newsgroups follows");
   29.70 -
   29.71 -      // Currently we do not store a group's creation date;
   29.72 -      // so we return an empty list which is a valid response
   29.73 -      conn.println(".");
   29.74 -    }
   29.75 -    else
   29.76 -    {
   29.77 -      conn.println("500 invalid command usage");
   29.78 -    }
   29.79 -  }
   29.80 -
   29.81 -}
    30.1 --- a/org/sonews/daemon/command/NextPrevCommand.java	Sun Aug 29 17:04:25 2010 +0200
    30.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    30.3 @@ -1,116 +0,0 @@
    30.4 -/*
    30.5 - *   SONEWS News Server
    30.6 - *   see AUTHORS for the list of contributors
    30.7 - *
    30.8 - *   This program is free software: you can redistribute it and/or modify
    30.9 - *   it under the terms of the GNU General Public License as published by
   30.10 - *   the Free Software Foundation, either version 3 of the License, or
   30.11 - *   (at your option) any later version.
   30.12 - *
   30.13 - *   This program is distributed in the hope that it will be useful,
   30.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   30.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   30.16 - *   GNU General Public License for more details.
   30.17 - *
   30.18 - *   You should have received a copy of the GNU General Public License
   30.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   30.20 - */
   30.21 -
   30.22 -package org.sonews.daemon.command;
   30.23 -
   30.24 -import java.io.IOException;
   30.25 -import org.sonews.daemon.NNTPConnection;
   30.26 -import org.sonews.storage.Article;
   30.27 -import org.sonews.storage.Channel;
   30.28 -import org.sonews.storage.StorageBackendException;
   30.29 -
   30.30 -/**
   30.31 - * Class handling the NEXT and LAST command.
   30.32 - * @author Christian Lins
   30.33 - * @author Dennis Schwerdel
   30.34 - * @since n3tpd/0.1
   30.35 - */
   30.36 -public class NextPrevCommand implements Command
   30.37 -{
   30.38 -
   30.39 -  @Override
   30.40 -  public String[] getSupportedCommandStrings()
   30.41 -  {
   30.42 -    return new String[]{"NEXT", "PREV"};
   30.43 -  }
   30.44 -
   30.45 -  @Override
   30.46 -  public boolean hasFinished()
   30.47 -  {
   30.48 -    return true;
   30.49 -  }
   30.50 -
   30.51 -  @Override
   30.52 -  public String impliedCapability()
   30.53 -  {
   30.54 -    return null;
   30.55 -  }
   30.56 -
   30.57 -  @Override
   30.58 -  public boolean isStateful()
   30.59 -  {
   30.60 -    return false;
   30.61 -  }
   30.62 -
   30.63 -  @Override
   30.64 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   30.65 -    throws IOException, StorageBackendException
   30.66 -  {
   30.67 -    final Article currA = conn.getCurrentArticle();
   30.68 -    final Channel currG = conn.getCurrentChannel();
   30.69 -    
   30.70 -    if (currA == null)
   30.71 -    {
   30.72 -      conn.println("420 no current article has been selected");
   30.73 -      return;
   30.74 -    }
   30.75 -    
   30.76 -    if (currG == null)
   30.77 -    {
   30.78 -      conn.println("412 no newsgroup selected");
   30.79 -      return;
   30.80 -    }
   30.81 -    
   30.82 -    final String[] command = line.split(" ");
   30.83 -
   30.84 -    if(command[0].equalsIgnoreCase("NEXT"))
   30.85 -    {
   30.86 -      selectNewArticle(conn, currA, currG, 1);
   30.87 -    }
   30.88 -    else if(command[0].equalsIgnoreCase("PREV"))
   30.89 -    {
   30.90 -      selectNewArticle(conn, currA, currG, -1);
   30.91 -    }
   30.92 -    else
   30.93 -    {
   30.94 -      conn.println("500 internal server error");
   30.95 -    }
   30.96 -  }
   30.97 -  
   30.98 -  private void selectNewArticle(NNTPConnection conn, Article article, Channel grp,
   30.99 -    final int delta)
  30.100 -    throws IOException, StorageBackendException
  30.101 -  {
  30.102 -    assert article != null;
  30.103 -
  30.104 -    article = grp.getArticle(grp.getIndexOf(article) + delta);
  30.105 -
  30.106 -    if(article == null)
  30.107 -    {
  30.108 -      conn.println("421 no next article in this group");
  30.109 -    }
  30.110 -    else
  30.111 -    {
  30.112 -      conn.setCurrentArticle(article);
  30.113 -      conn.println("223 " + conn.getCurrentChannel().getIndexOf(article)
  30.114 -                    + " " + article.getMessageID()
  30.115 -                    + " article retrieved - request text separately");
  30.116 -    }
  30.117 -  }
  30.118 -
  30.119 -}
    31.1 --- a/org/sonews/daemon/command/OverCommand.java	Sun Aug 29 17:04:25 2010 +0200
    31.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    31.3 @@ -1,294 +0,0 @@
    31.4 -/*
    31.5 - *   SONEWS News Server
    31.6 - *   see AUTHORS for the list of contributors
    31.7 - *
    31.8 - *   This program is free software: you can redistribute it and/or modify
    31.9 - *   it under the terms of the GNU General Public License as published by
   31.10 - *   the Free Software Foundation, either version 3 of the License, or
   31.11 - *   (at your option) any later version.
   31.12 - *
   31.13 - *   This program is distributed in the hope that it will be useful,
   31.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   31.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   31.16 - *   GNU General Public License for more details.
   31.17 - *
   31.18 - *   You should have received a copy of the GNU General Public License
   31.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   31.20 - */
   31.21 -
   31.22 -package org.sonews.daemon.command;
   31.23 -
   31.24 -import java.io.IOException;
   31.25 -import java.util.List;
   31.26 -import org.sonews.util.Log;
   31.27 -import org.sonews.daemon.NNTPConnection;
   31.28 -import org.sonews.storage.Article;
   31.29 -import org.sonews.storage.ArticleHead;
   31.30 -import org.sonews.storage.Headers;
   31.31 -import org.sonews.storage.StorageBackendException;
   31.32 -import org.sonews.util.Pair;
   31.33 -
   31.34 -/**
   31.35 - * Class handling the OVER/XOVER command.
   31.36 - * 
   31.37 - * Description of the XOVER command:
   31.38 - * <pre>
   31.39 - * XOVER [range]
   31.40 - *
   31.41 - * The XOVER command returns information from the overview
   31.42 - * database for the article(s) specified.
   31.43 - *
   31.44 - * The optional range argument may be any of the following:
   31.45 - *              an article number
   31.46 - *              an article number followed by a dash to indicate
   31.47 - *                 all following
   31.48 - *              an article number followed by a dash followed by
   31.49 - *                 another article number
   31.50 - *
   31.51 - * If no argument is specified, then information from the
   31.52 - * current article is displayed. Successful responses start
   31.53 - * with a 224 response followed by the overview information
   31.54 - * for all matched messages. Once the output is complete, a
   31.55 - * period is sent on a line by itself. If no argument is
   31.56 - * specified, the information for the current article is
   31.57 - * returned.  A news group must have been selected earlier,
   31.58 - * else a 412 error response is returned. If no articles are
   31.59 - * in the range specified, a 420 error response is returned
   31.60 - * by the server. A 502 response will be returned if the
   31.61 - * client only has permission to transfer articles.
   31.62 - *
   31.63 - * Each line of output will be formatted with the article number,
   31.64 - * followed by each of the headers in the overview database or the
   31.65 - * article itself (when the data is not available in the overview
   31.66 - * database) for that article separated by a tab character.  The
   31.67 - * sequence of fields must be in this order: subject, author,
   31.68 - * date, message-id, references, byte count, and line count. Other
   31.69 - * optional fields may follow line count. Other optional fields may
   31.70 - * follow line count. These fields are specified by examining the
   31.71 - * response to the LIST OVERVIEW.FMT command. Where no data exists,
   31.72 - * a null field must be provided (i.e. the output will have two tab
   31.73 - * characters adjacent to each other). Servers should not output
   31.74 - * fields for articles that have been removed since the XOVER database
   31.75 - * was created.
   31.76 - *
   31.77 - * The LIST OVERVIEW.FMT command should be implemented if XOVER
   31.78 - * is implemented. A client can use LIST OVERVIEW.FMT to determine
   31.79 - * what optional fields  and in which order all fields will be
   31.80 - * supplied by the XOVER command. 
   31.81 - *
   31.82 - * Note that any tab and end-of-line characters in any header
   31.83 - * data that is returned will be converted to a space character.
   31.84 - *
   31.85 - * Responses:
   31.86 - *
   31.87 - *   224 Overview information follows
   31.88 - *   412 No news group current selected
   31.89 - *   420 No article(s) selected
   31.90 - *   502 no permission
   31.91 - *
   31.92 - * OVER defines additional responses:
   31.93 - *
   31.94 - *  First form (message-id specified)
   31.95 - *    224    Overview information follows (multi-line)
   31.96 - *    430    No article with that message-id
   31.97 - *
   31.98 - *  Second form (range specified)
   31.99 - *    224    Overview information follows (multi-line)
  31.100 - *    412    No newsgroup selected
  31.101 - *    423    No articles in that range
  31.102 - *
  31.103 - *  Third form (current article number used)
  31.104 - *    224    Overview information follows (multi-line)
  31.105 - *    412    No newsgroup selected
  31.106 - *    420    Current article number is invalid
  31.107 - *
  31.108 - * </pre>
  31.109 - * @author Christian Lins
  31.110 - * @since sonews/0.5.0
  31.111 - */
  31.112 -public class OverCommand implements Command
  31.113 -{
  31.114 -
  31.115 -  public static final int MAX_LINES_PER_DBREQUEST = 200;
  31.116 -
  31.117 -  @Override
  31.118 -  public String[] getSupportedCommandStrings()
  31.119 -  {
  31.120 -    return new String[]{"OVER", "XOVER"};
  31.121 -  }
  31.122 -
  31.123 -  @Override
  31.124 -  public boolean hasFinished()
  31.125 -  {
  31.126 -    return true;
  31.127 -  }
  31.128 -
  31.129 -  @Override
  31.130 -  public String impliedCapability()
  31.131 -  {
  31.132 -    return null;
  31.133 -  }
  31.134 -
  31.135 -  @Override
  31.136 -  public boolean isStateful()
  31.137 -  {
  31.138 -    return false;
  31.139 -  }
  31.140 -
  31.141 -  @Override
  31.142 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
  31.143 -    throws IOException, StorageBackendException
  31.144 -  {
  31.145 -    if(conn.getCurrentChannel() == null)
  31.146 -    {
  31.147 -      conn.println("412 no newsgroup selected");
  31.148 -    }
  31.149 -    else
  31.150 -    {
  31.151 -      String[] command = line.split(" ");
  31.152 -
  31.153 -      // If no parameter was specified, show information about
  31.154 -      // the currently selected article(s)
  31.155 -      if(command.length == 1)
  31.156 -      {
  31.157 -        final Article art = conn.getCurrentArticle();
  31.158 -        if(art == null)
  31.159 -        {
  31.160 -          conn.println("420 no article(s) selected");
  31.161 -          return;
  31.162 -        }
  31.163 -
  31.164 -        conn.println(buildOverview(art, -1));
  31.165 -      }
  31.166 -      // otherwise print information about the specified range
  31.167 -      else
  31.168 -      {
  31.169 -        long artStart;
  31.170 -        long artEnd   = conn.getCurrentChannel().getLastArticleNumber();
  31.171 -        String[] nums = command[1].split("-");
  31.172 -        if(nums.length >= 1)
  31.173 -        {
  31.174 -          try
  31.175 -          {
  31.176 -            artStart = Integer.parseInt(nums[0]);
  31.177 -          }
  31.178 -          catch(NumberFormatException e) 
  31.179 -          {
  31.180 -            Log.get().info(e.getMessage());
  31.181 -            artStart = Integer.parseInt(command[1]);
  31.182 -          }
  31.183 -        }
  31.184 -        else
  31.185 -        {
  31.186 -          artStart = conn.getCurrentChannel().getFirstArticleNumber();
  31.187 -        }
  31.188 -
  31.189 -        if(nums.length >=2)
  31.190 -        {
  31.191 -          try
  31.192 -          {
  31.193 -            artEnd = Integer.parseInt(nums[1]);
  31.194 -          }
  31.195 -          catch(NumberFormatException e) 
  31.196 -          {
  31.197 -            e.printStackTrace();
  31.198 -          }
  31.199 -        }
  31.200 -
  31.201 -        if(artStart > artEnd)
  31.202 -        {
  31.203 -          if(command[0].equalsIgnoreCase("OVER"))
  31.204 -          {
  31.205 -            conn.println("423 no articles in that range");
  31.206 -          }
  31.207 -          else
  31.208 -          {
  31.209 -            conn.println("224 (empty) overview information follows:");
  31.210 -            conn.println(".");
  31.211 -          }
  31.212 -        }
  31.213 -        else
  31.214 -        {
  31.215 -          for(long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST)
  31.216 -          {
  31.217 -            long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
  31.218 -            List<Pair<Long, ArticleHead>> articleHeads = conn.getCurrentChannel()
  31.219 -              .getArticleHeads(n, nEnd);
  31.220 -            if(articleHeads.isEmpty() && n == artStart
  31.221 -              && command[0].equalsIgnoreCase("OVER"))
  31.222 -            {
  31.223 -              // This reply is only valid for OVER, not for XOVER command
  31.224 -              conn.println("423 no articles in that range");
  31.225 -              return;
  31.226 -            }
  31.227 -            else if(n == artStart)
  31.228 -            {
  31.229 -              // XOVER replies this although there is no data available
  31.230 -              conn.println("224 overview information follows");
  31.231 -            }
  31.232 -
  31.233 -            for(Pair<Long, ArticleHead> article : articleHeads)
  31.234 -            {
  31.235 -              String overview = buildOverview(article.getB(), article.getA());
  31.236 -              conn.println(overview);
  31.237 -            }
  31.238 -          } // for
  31.239 -          conn.println(".");
  31.240 -        }
  31.241 -      }
  31.242 -    }
  31.243 -  }
  31.244 -  
  31.245 -  private String buildOverview(ArticleHead art, long nr)
  31.246 -  {
  31.247 -    StringBuilder overview = new StringBuilder();
  31.248 -    overview.append(nr);
  31.249 -    overview.append('\t');
  31.250 -
  31.251 -    String subject = art.getHeader(Headers.SUBJECT)[0];
  31.252 -    if("".equals(subject))
  31.253 -    {
  31.254 -      subject = "<empty>";
  31.255 -    }
  31.256 -    overview.append(escapeString(subject));
  31.257 -    overview.append('\t');
  31.258 -
  31.259 -    overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
  31.260 -    overview.append('\t');
  31.261 -    overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
  31.262 -    overview.append('\t');
  31.263 -    overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
  31.264 -    overview.append('\t');
  31.265 -    overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
  31.266 -    overview.append('\t');
  31.267 -
  31.268 -    String bytes = art.getHeader(Headers.BYTES)[0];
  31.269 -    if("".equals(bytes))
  31.270 -    {
  31.271 -      bytes = "0";
  31.272 -    }
  31.273 -    overview.append(escapeString(bytes));
  31.274 -    overview.append('\t');
  31.275 -
  31.276 -    String lines = art.getHeader(Headers.LINES)[0];
  31.277 -    if("".equals(lines))
  31.278 -    {
  31.279 -      lines = "0";
  31.280 -    }
  31.281 -    overview.append(escapeString(lines));
  31.282 -    overview.append('\t');
  31.283 -    overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
  31.284 -
  31.285 -    // Remove trailing tabs if some data is empty
  31.286 -    return overview.toString().trim();
  31.287 -  }
  31.288 -  
  31.289 -  private String escapeString(String str)
  31.290 -  {
  31.291 -    String nstr = str.replace("\r", "");
  31.292 -    nstr = nstr.replace('\n', ' ');
  31.293 -    nstr = nstr.replace('\t', ' ');
  31.294 -    return nstr.trim();
  31.295 -  }
  31.296 -  
  31.297 -}
    32.1 --- a/org/sonews/daemon/command/PostCommand.java	Sun Aug 29 17:04:25 2010 +0200
    32.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    32.3 @@ -1,332 +0,0 @@
    32.4 -/*
    32.5 - *   SONEWS News Server
    32.6 - *   see AUTHORS for the list of contributors
    32.7 - *
    32.8 - *   This program is free software: you can redistribute it and/or modify
    32.9 - *   it under the terms of the GNU General Public License as published by
   32.10 - *   the Free Software Foundation, either version 3 of the License, or
   32.11 - *   (at your option) any later version.
   32.12 - *
   32.13 - *   This program is distributed in the hope that it will be useful,
   32.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   32.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   32.16 - *   GNU General Public License for more details.
   32.17 - *
   32.18 - *   You should have received a copy of the GNU General Public License
   32.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   32.20 - */
   32.21 -
   32.22 -package org.sonews.daemon.command;
   32.23 -
   32.24 -import java.io.IOException;
   32.25 -import java.io.ByteArrayInputStream;
   32.26 -import java.io.ByteArrayOutputStream;
   32.27 -import java.sql.SQLException;
   32.28 -import java.util.Arrays;
   32.29 -import javax.mail.MessagingException;
   32.30 -import javax.mail.internet.AddressException;
   32.31 -import javax.mail.internet.InternetHeaders;
   32.32 -import org.sonews.config.Config;
   32.33 -import org.sonews.util.Log;
   32.34 -import org.sonews.mlgw.Dispatcher;
   32.35 -import org.sonews.storage.Article;
   32.36 -import org.sonews.storage.Group;
   32.37 -import org.sonews.daemon.NNTPConnection;
   32.38 -import org.sonews.storage.Headers;
   32.39 -import org.sonews.storage.StorageBackendException;
   32.40 -import org.sonews.storage.StorageManager;
   32.41 -import org.sonews.feed.FeedManager;
   32.42 -import org.sonews.util.Stats;
   32.43 -
   32.44 -/**
   32.45 - * Implementation of the POST command. This command requires multiple lines
   32.46 - * from the client, so the handling of asynchronous reading is a little tricky
   32.47 - * to handle.
   32.48 - * @author Christian Lins
   32.49 - * @since sonews/0.5.0
   32.50 - */
   32.51 -public class PostCommand implements Command
   32.52 -{
   32.53 -  
   32.54 -  private final Article article   = new Article();
   32.55 -  private int           lineCount = 0;
   32.56 -  private long          bodySize  = 0;
   32.57 -  private InternetHeaders headers = null;
   32.58 -  private long          maxBodySize  = 
   32.59 -    Config.inst().get(Config.ARTICLE_MAXSIZE, 128) * 1024L; // Size in bytes
   32.60 -  private PostState     state     = PostState.WaitForLineOne;
   32.61 -  private final ByteArrayOutputStream bufBody   = new ByteArrayOutputStream();
   32.62 -  private final StringBuilder         strHead   = new StringBuilder();
   32.63 -
   32.64 -  @Override
   32.65 -  public String[] getSupportedCommandStrings()
   32.66 -  {
   32.67 -    return new String[]{"POST"};
   32.68 -  }
   32.69 -
   32.70 -  @Override
   32.71 -  public boolean hasFinished()
   32.72 -  {
   32.73 -    return this.state == PostState.Finished;
   32.74 -  }
   32.75 -
   32.76 -  @Override
   32.77 -  public String impliedCapability()
   32.78 -  {
   32.79 -    return null;
   32.80 -  }
   32.81 -
   32.82 -  @Override
   32.83 -  public boolean isStateful()
   32.84 -  {
   32.85 -    return true;
   32.86 -  }
   32.87 -
   32.88 -  /**
   32.89 -   * Process the given line String. line.trim() was called by NNTPConnection.
   32.90 -   * @param line
   32.91 -   * @throws java.io.IOException
   32.92 -   * @throws java.sql.SQLException
   32.93 -   */
   32.94 -  @Override // TODO: Refactor this method to reduce complexity!
   32.95 -  public void processLine(NNTPConnection conn, String line, byte[] raw)
   32.96 -    throws IOException, StorageBackendException
   32.97 -  {
   32.98 -    switch(state)
   32.99 -    {
  32.100 -      case WaitForLineOne:
  32.101 -      {
  32.102 -        if(line.equalsIgnoreCase("POST"))
  32.103 -        {
  32.104 -          conn.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
  32.105 -          state = PostState.ReadingHeaders;
  32.106 -        }
  32.107 -        else
  32.108 -        {
  32.109 -          conn.println("500 invalid command usage");
  32.110 -        }
  32.111 -        break;
  32.112 -      }
  32.113 -      case ReadingHeaders:
  32.114 -      {
  32.115 -        strHead.append(line);
  32.116 -        strHead.append(NNTPConnection.NEWLINE);
  32.117 -        
  32.118 -        if("".equals(line) || ".".equals(line))
  32.119 -        {
  32.120 -          // we finally met the blank line
  32.121 -          // separating headers from body
  32.122 -          
  32.123 -          try
  32.124 -          {
  32.125 -            // Parse the header using the InternetHeader class from JavaMail API
  32.126 -            headers = new InternetHeaders(
  32.127 -              new ByteArrayInputStream(strHead.toString().trim()
  32.128 -                .getBytes(conn.getCurrentCharset())));
  32.129 -
  32.130 -            // add the header entries for the article
  32.131 -            article.setHeaders(headers);
  32.132 -          }
  32.133 -          catch (MessagingException e)
  32.134 -          {
  32.135 -            e.printStackTrace();
  32.136 -            conn.println("500 posting failed - invalid header");
  32.137 -            state = PostState.Finished;
  32.138 -            break;
  32.139 -          }
  32.140 -
  32.141 -          // Change charset for reading body; 
  32.142 -          // for multipart messages UTF-8 is returned
  32.143 -          //conn.setCurrentCharset(article.getBodyCharset());
  32.144 -          
  32.145 -          state = PostState.ReadingBody;
  32.146 -          
  32.147 -          if(".".equals(line))
  32.148 -          {
  32.149 -            // Post an article without body
  32.150 -            postArticle(conn, article);
  32.151 -            state = PostState.Finished;
  32.152 -          }
  32.153 -        }
  32.154 -        break;
  32.155 -      }
  32.156 -      case ReadingBody:
  32.157 -      {
  32.158 -        if(".".equals(line))
  32.159 -        {    
  32.160 -          // Set some headers needed for Over command
  32.161 -          headers.setHeader(Headers.LINES, Integer.toString(lineCount));
  32.162 -          headers.setHeader(Headers.BYTES, Long.toString(bodySize));
  32.163 -
  32.164 -          byte[] body = bufBody.toByteArray();
  32.165 -          if(body.length >= 2)
  32.166 -          {
  32.167 -            // Remove trailing CRLF
  32.168 -            body = Arrays.copyOf(body, body.length - 2);
  32.169 -          }
  32.170 -          article.setBody(body); // set the article body
  32.171 -          
  32.172 -          postArticle(conn, article);
  32.173 -          state = PostState.Finished;
  32.174 -        }
  32.175 -        else
  32.176 -        {
  32.177 -          bodySize += line.length() + 1;
  32.178 -          lineCount++;
  32.179 -          
  32.180 -          // Add line to body buffer
  32.181 -          bufBody.write(raw, 0, raw.length);
  32.182 -          bufBody.write(NNTPConnection.NEWLINE.getBytes());
  32.183 -          
  32.184 -          if(bodySize > maxBodySize)
  32.185 -          {
  32.186 -            conn.println("500 article is too long");
  32.187 -            state = PostState.Finished;
  32.188 -            break;
  32.189 -          }
  32.190 -        }
  32.191 -        break;
  32.192 -      }
  32.193 -      default:
  32.194 -      {
  32.195 -        // Should never happen
  32.196 -        Log.get().severe("PostCommand::processLine(): already finished...");
  32.197 -      }
  32.198 -    }
  32.199 -  }
  32.200 -  
  32.201 -  /**
  32.202 -   * Article is a control message and needs special handling.
  32.203 -   * @param article
  32.204 -   */
  32.205 -  private void controlMessage(NNTPConnection conn, Article article)
  32.206 -    throws IOException
  32.207 -  {
  32.208 -    String[] ctrl = article.getHeader(Headers.CONTROL)[0].split(" ");
  32.209 -    if(ctrl.length == 2) // "cancel <mid>"
  32.210 -    {
  32.211 -      try
  32.212 -      {
  32.213 -        StorageManager.current().delete(ctrl[1]);
  32.214 -        
  32.215 -        // Move cancel message to "control" group
  32.216 -        article.setHeader(Headers.NEWSGROUPS, "control");
  32.217 -        StorageManager.current().addArticle(article);
  32.218 -        conn.println("240 article cancelled");
  32.219 -      }
  32.220 -      catch(StorageBackendException ex)
  32.221 -      {
  32.222 -        Log.get().severe(ex.toString());
  32.223 -        conn.println("500 internal server error");
  32.224 -      }
  32.225 -    }
  32.226 -    else
  32.227 -    {
  32.228 -      conn.println("441 unknown control header");
  32.229 -    }
  32.230 -  }
  32.231 -  
  32.232 -  private void supersedeMessage(NNTPConnection conn, Article article)
  32.233 -    throws IOException
  32.234 -  {
  32.235 -    try
  32.236 -    {
  32.237 -      String oldMsg = article.getHeader(Headers.SUPERSEDES)[0];
  32.238 -      StorageManager.current().delete(oldMsg);
  32.239 -      StorageManager.current().addArticle(article);
  32.240 -      conn.println("240 article replaced");
  32.241 -    }
  32.242 -    catch(StorageBackendException ex)
  32.243 -    {
  32.244 -      Log.get().severe(ex.toString());
  32.245 -      conn.println("500 internal server error");
  32.246 -    }
  32.247 -  }
  32.248 -  
  32.249 -  private void postArticle(NNTPConnection conn, Article article)
  32.250 -    throws IOException
  32.251 -  {
  32.252 -    if(article.getHeader(Headers.CONTROL)[0].length() > 0)
  32.253 -    {
  32.254 -      controlMessage(conn, article);
  32.255 -    }
  32.256 -    else if(article.getHeader(Headers.SUPERSEDES)[0].length() > 0)
  32.257 -    {
  32.258 -      supersedeMessage(conn, article);
  32.259 -    }
  32.260 -    else // Post the article regularily
  32.261 -    {
  32.262 -      // Circle check; note that Path can already contain the hostname here
  32.263 -      String host = Config.inst().get(Config.HOSTNAME, "localhost");
  32.264 -      if(article.getHeader(Headers.PATH)[0].indexOf(host + "!", 1) > 0)
  32.265 -      {
  32.266 -        Log.get().info(article.getMessageID() + " skipped for host " + host);
  32.267 -        conn.println("441 I know this article already");
  32.268 -        return;
  32.269 -      }
  32.270 -
  32.271 -      // Try to create the article in the database or post it to
  32.272 -      // appropriate mailing list
  32.273 -      try
  32.274 -      {
  32.275 -        boolean success = false;
  32.276 -        String[] groupnames = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
  32.277 -        for(String groupname : groupnames)
  32.278 -        {          
  32.279 -          Group group = StorageManager.current().getGroup(groupname);
  32.280 -          if(group != null && !group.isDeleted())
  32.281 -          {
  32.282 -            if(group.isMailingList() && !conn.isLocalConnection())
  32.283 -            {
  32.284 -              // Send to mailing list; the Dispatcher writes 
  32.285 -              // statistics to database
  32.286 -              Dispatcher.toList(article, group.getName());
  32.287 -              success = true;
  32.288 -            }
  32.289 -            else
  32.290 -            {
  32.291 -              // Store in database
  32.292 -              if(!StorageManager.current().isArticleExisting(article.getMessageID()))
  32.293 -              {
  32.294 -                StorageManager.current().addArticle(article);
  32.295 -
  32.296 -                // Log this posting to statistics
  32.297 -                Stats.getInstance().mailPosted(
  32.298 -                  article.getHeader(Headers.NEWSGROUPS)[0]);
  32.299 -              }
  32.300 -              success = true;
  32.301 -            }
  32.302 -          }
  32.303 -        } // end for
  32.304 -
  32.305 -        if(success)
  32.306 -        {
  32.307 -          conn.println("240 article posted ok");
  32.308 -          FeedManager.queueForPush(article);
  32.309 -        }
  32.310 -        else
  32.311 -        {
  32.312 -          conn.println("441 newsgroup not found");
  32.313 -        }
  32.314 -      }
  32.315 -      catch(AddressException ex)
  32.316 -      {
  32.317 -        Log.get().warning(ex.getMessage());
  32.318 -        conn.println("441 invalid sender address");
  32.319 -      }
  32.320 -      catch(MessagingException ex)
  32.321 -      {
  32.322 -        // A MessageException is thrown when the sender email address is
  32.323 -        // invalid or something is wrong with the SMTP server.
  32.324 -        System.err.println(ex.getLocalizedMessage());
  32.325 -        conn.println("441 " + ex.getClass().getCanonicalName() + ": " + ex.getLocalizedMessage());
  32.326 -      }
  32.327 -      catch(StorageBackendException ex)
  32.328 -      {
  32.329 -        ex.printStackTrace();
  32.330 -        conn.println("500 internal server error");
  32.331 -      }
  32.332 -    }
  32.333 -  }
  32.334 -
  32.335 -}
    33.1 --- a/org/sonews/daemon/command/PostState.java	Sun Aug 29 17:04:25 2010 +0200
    33.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    33.3 @@ -1,29 +0,0 @@
    33.4 -/*
    33.5 - *   SONEWS News Server
    33.6 - *   see AUTHORS for the list of contributors
    33.7 - *
    33.8 - *   This program is free software: you can redistribute it and/or modify
    33.9 - *   it under the terms of the GNU General Public License as published by
   33.10 - *   the Free Software Foundation, either version 3 of the License, or
   33.11 - *   (at your option) any later version.
   33.12 - *
   33.13 - *   This program is distributed in the hope that it will be useful,
   33.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   33.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   33.16 - *   GNU General Public License for more details.
   33.17 - *
   33.18 - *   You should have received a copy of the GNU General Public License
   33.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   33.20 - */
   33.21 -
   33.22 -package org.sonews.daemon.command;
   33.23 -
   33.24 -/**
   33.25 - * States of the POST command's finite state machine.
   33.26 - * @author Christian Lins
   33.27 - * @since sonews/0.5.0
   33.28 - */
   33.29 -enum PostState
   33.30 -{
   33.31 -  WaitForLineOne, ReadingHeaders, ReadingBody, Finished
   33.32 -}
    34.1 --- a/org/sonews/daemon/command/QuitCommand.java	Sun Aug 29 17:04:25 2010 +0200
    34.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    34.3 @@ -1,67 +0,0 @@
    34.4 -/*
    34.5 - *   SONEWS News Server
    34.6 - *   see AUTHORS for the list of contributors
    34.7 - *
    34.8 - *   This program is free software: you can redistribute it and/or modify
    34.9 - *   it under the terms of the GNU General Public License as published by
   34.10 - *   the Free Software Foundation, either version 3 of the License, or
   34.11 - *   (at your option) any later version.
   34.12 - *
   34.13 - *   This program is distributed in the hope that it will be useful,
   34.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   34.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   34.16 - *   GNU General Public License for more details.
   34.17 - *
   34.18 - *   You should have received a copy of the GNU General Public License
   34.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   34.20 - */
   34.21 -
   34.22 -package org.sonews.daemon.command;
   34.23 -
   34.24 -import java.io.IOException;
   34.25 -import org.sonews.daemon.NNTPConnection;
   34.26 -import org.sonews.storage.StorageBackendException;
   34.27 -
   34.28 -/**
   34.29 - * Implementation of the QUIT command; client wants to shutdown the connection.
   34.30 - * @author Christian Lins
   34.31 - * @since sonews/0.5.0
   34.32 - */
   34.33 -public class QuitCommand implements Command
   34.34 -{
   34.35 -
   34.36 -  @Override
   34.37 -  public String[] getSupportedCommandStrings()
   34.38 -  {
   34.39 -    return new String[]{"QUIT"};
   34.40 -  }
   34.41 -  
   34.42 -  @Override
   34.43 -  public boolean hasFinished()
   34.44 -  {
   34.45 -    return true;
   34.46 -  }
   34.47 -
   34.48 -  @Override
   34.49 -  public String impliedCapability()
   34.50 -  {
   34.51 -    return null;
   34.52 -  }
   34.53 -
   34.54 -  @Override
   34.55 -  public boolean isStateful()
   34.56 -  {
   34.57 -    return false;
   34.58 -  }
   34.59 -
   34.60 -  @Override
   34.61 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   34.62 -    throws IOException, StorageBackendException
   34.63 -  {    
   34.64 -    conn.println("205 cya");
   34.65 -    
   34.66 -    conn.shutdownInput();
   34.67 -    conn.shutdownOutput();
   34.68 -  }
   34.69 -
   34.70 -}
    35.1 --- a/org/sonews/daemon/command/StatCommand.java	Sun Aug 29 17:04:25 2010 +0200
    35.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    35.3 @@ -1,114 +0,0 @@
    35.4 -/*
    35.5 - *   SONEWS News Server
    35.6 - *   see AUTHORS for the list of contributors
    35.7 - *
    35.8 - *   This program is free software: you can redistribute it and/or modify
    35.9 - *   it under the terms of the GNU General Public License as published by
   35.10 - *   the Free Software Foundation, either version 3 of the License, or
   35.11 - *   (at your option) any later version.
   35.12 - *
   35.13 - *   This program is distributed in the hope that it will be useful,
   35.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   35.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   35.16 - *   GNU General Public License for more details.
   35.17 - *
   35.18 - *   You should have received a copy of the GNU General Public License
   35.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   35.20 - */
   35.21 -
   35.22 -package org.sonews.daemon.command;
   35.23 -
   35.24 -import java.io.IOException;
   35.25 -import org.sonews.storage.Article;
   35.26 -import org.sonews.daemon.NNTPConnection;
   35.27 -import org.sonews.storage.StorageBackendException;
   35.28 -
   35.29 -/**
   35.30 - * Implementation of the STAT command.
   35.31 - * @author Christian Lins
   35.32 - * @since sonews/0.5.0
   35.33 - */
   35.34 -public class StatCommand implements Command
   35.35 -{
   35.36 -
   35.37 -  @Override
   35.38 -  public String[] getSupportedCommandStrings()
   35.39 -  {
   35.40 -    return new String[]{"STAT"};
   35.41 -  }
   35.42 -
   35.43 -  @Override
   35.44 -  public boolean hasFinished()
   35.45 -  {
   35.46 -    return true;
   35.47 -  }
   35.48 -
   35.49 -  @Override
   35.50 -  public String impliedCapability()
   35.51 -  {
   35.52 -    return null;
   35.53 -  }
   35.54 -
   35.55 -  @Override
   35.56 -  public boolean isStateful()
   35.57 -  {
   35.58 -    return false;
   35.59 -  }
   35.60 -
   35.61 -  // TODO: Method has various exit points => Refactor!
   35.62 -  @Override
   35.63 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   35.64 -    throws IOException, StorageBackendException
   35.65 -  {
   35.66 -    final String[] command = line.split(" ");
   35.67 -
   35.68 -    Article article = null;
   35.69 -    if(command.length == 1)
   35.70 -    {
   35.71 -      article = conn.getCurrentArticle();
   35.72 -      if(article == null)
   35.73 -      {
   35.74 -        conn.println("420 no current article has been selected");
   35.75 -        return;
   35.76 -      }
   35.77 -    }
   35.78 -    else if(command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN))
   35.79 -    {
   35.80 -      // Message-ID
   35.81 -      article = Article.getByMessageID(command[1]);
   35.82 -      if (article == null)
   35.83 -      {
   35.84 -        conn.println("430 no such article found");
   35.85 -        return;
   35.86 -      }
   35.87 -    }
   35.88 -    else
   35.89 -    {
   35.90 -      // Message Number
   35.91 -      try
   35.92 -      {
   35.93 -        long aid = Long.parseLong(command[1]);
   35.94 -        article = conn.getCurrentChannel().getArticle(aid);
   35.95 -      }
   35.96 -      catch(NumberFormatException ex)
   35.97 -      {
   35.98 -        ex.printStackTrace();
   35.99 -      }
  35.100 -      catch(StorageBackendException ex)
  35.101 -      {
  35.102 -        ex.printStackTrace();
  35.103 -      }
  35.104 -      if (article == null)
  35.105 -      {
  35.106 -        conn.println("423 no such article number in this group");
  35.107 -        return;
  35.108 -      }
  35.109 -      conn.setCurrentArticle(article);
  35.110 -    }
  35.111 -    
  35.112 -    conn.println("223 " + conn.getCurrentChannel().getIndexOf(article) + " "
  35.113 -      + article.getMessageID()
  35.114 -      + " article retrieved - request text separately");
  35.115 -  }
  35.116 -  
  35.117 -}
    36.1 --- a/org/sonews/daemon/command/UnsupportedCommand.java	Sun Aug 29 17:04:25 2010 +0200
    36.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    36.3 @@ -1,67 +0,0 @@
    36.4 -/*
    36.5 - *   SONEWS News Server
    36.6 - *   see AUTHORS for the list of contributors
    36.7 - *
    36.8 - *   This program is free software: you can redistribute it and/or modify
    36.9 - *   it under the terms of the GNU General Public License as published by
   36.10 - *   the Free Software Foundation, either version 3 of the License, or
   36.11 - *   (at your option) any later version.
   36.12 - *
   36.13 - *   This program is distributed in the hope that it will be useful,
   36.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   36.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   36.16 - *   GNU General Public License for more details.
   36.17 - *
   36.18 - *   You should have received a copy of the GNU General Public License
   36.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   36.20 - */
   36.21 -
   36.22 -package org.sonews.daemon.command;
   36.23 -
   36.24 -import java.io.IOException;
   36.25 -import org.sonews.daemon.NNTPConnection;
   36.26 -
   36.27 -/**
   36.28 - * A default "Unsupported Command". Simply returns error code 500 and a
   36.29 - * "command not supported" message.
   36.30 - * @author Christian Lins
   36.31 - * @since sonews/0.5.0
   36.32 - */
   36.33 -public class UnsupportedCommand implements Command
   36.34 -{
   36.35 -  
   36.36 -  /**
   36.37 -   * @return Always returns null.
   36.38 -   */
   36.39 -  @Override
   36.40 -  public String[] getSupportedCommandStrings()
   36.41 -  {
   36.42 -    return null;
   36.43 -  }
   36.44 -
   36.45 -  @Override
   36.46 -  public boolean hasFinished()
   36.47 -  {
   36.48 -    return true;
   36.49 -  }
   36.50 -
   36.51 -  @Override
   36.52 -  public String impliedCapability()
   36.53 -  {
   36.54 -    return null;
   36.55 -  }
   36.56 -
   36.57 -  @Override
   36.58 -  public boolean isStateful()
   36.59 -  {
   36.60 -    return false;
   36.61 -  }
   36.62 -
   36.63 -  @Override
   36.64 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   36.65 -    throws IOException
   36.66 -  {
   36.67 -    conn.println("500 command not supported");
   36.68 -  }
   36.69 -  
   36.70 -}
    37.1 --- a/org/sonews/daemon/command/XDaemonCommand.java	Sun Aug 29 17:04:25 2010 +0200
    37.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    37.3 @@ -1,270 +0,0 @@
    37.4 -/*
    37.5 - *   SONEWS News Server
    37.6 - *   see AUTHORS for the list of contributors
    37.7 - *
    37.8 - *   This program is free software: you can redistribute it and/or modify
    37.9 - *   it under the terms of the GNU General Public License as published by
   37.10 - *   the Free Software Foundation, either version 3 of the License, or
   37.11 - *   (at your option) any later version.
   37.12 - *
   37.13 - *   This program is distributed in the hope that it will be useful,
   37.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   37.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   37.16 - *   GNU General Public License for more details.
   37.17 - *
   37.18 - *   You should have received a copy of the GNU General Public License
   37.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   37.20 - */
   37.21 -
   37.22 -package org.sonews.daemon.command;
   37.23 -
   37.24 -import java.io.IOException;
   37.25 -import java.net.InetSocketAddress;
   37.26 -import java.util.List;
   37.27 -import org.sonews.config.Config;
   37.28 -import org.sonews.daemon.NNTPConnection;
   37.29 -import org.sonews.storage.StorageBackendException;
   37.30 -import org.sonews.storage.StorageManager;
   37.31 -import org.sonews.feed.FeedManager;
   37.32 -import org.sonews.feed.Subscription;
   37.33 -import org.sonews.storage.Channel;
   37.34 -import org.sonews.storage.Group;
   37.35 -import org.sonews.util.Stats;
   37.36 -
   37.37 -/**
   37.38 - * The XDAEMON command allows a client to get/set properties of the
   37.39 - * running server daemon. Only locally connected clients are allowed to
   37.40 - * use this command.
   37.41 - * The restriction to localhost connection can be suppressed by overriding
   37.42 - * the sonews.xdaemon.host bootstrap config property.
   37.43 - * @author Christian Lins
   37.44 - * @since sonews/0.5.0
   37.45 - */
   37.46 -public class XDaemonCommand implements Command
   37.47 -{
   37.48 -
   37.49 -  @Override
   37.50 -  public String[] getSupportedCommandStrings()
   37.51 -  {
   37.52 -    return new String[]{"XDAEMON"};
   37.53 -  }
   37.54 -
   37.55 -  @Override
   37.56 -  public boolean hasFinished()
   37.57 -  {
   37.58 -    return true;
   37.59 -  }
   37.60 -
   37.61 -  @Override
   37.62 -  public String impliedCapability()
   37.63 -  {
   37.64 -    return null;
   37.65 -  }
   37.66 -
   37.67 -  @Override
   37.68 -  public boolean isStateful()
   37.69 -  {
   37.70 -    return false;
   37.71 -  }
   37.72 -
   37.73 -  private void channelAdd(String[] commands, NNTPConnection conn)
   37.74 -    throws IOException, StorageBackendException
   37.75 -  {
   37.76 -    String groupName = commands[2];
   37.77 -    if(StorageManager.current().isGroupExisting(groupName))
   37.78 -    {
   37.79 -      conn.println("400 group " + groupName + " already existing!");
   37.80 -    }
   37.81 -    else
   37.82 -    {
   37.83 -      StorageManager.current().addGroup(groupName, Integer.parseInt(commands[3]));
   37.84 -      conn.println("200 group " + groupName + " created");
   37.85 -    }
   37.86 -  }
   37.87 -
   37.88 -  // TODO: Refactor this method to reduce complexity!
   37.89 -  @Override
   37.90 -  public void processLine(NNTPConnection conn, String line, byte[] raw)
   37.91 -    throws IOException, StorageBackendException
   37.92 -  {
   37.93 -    InetSocketAddress addr = (InetSocketAddress)conn.getSocketChannel().socket()
   37.94 -      .getRemoteSocketAddress();
   37.95 -    if(addr.getHostName().equals(
   37.96 -      Config.inst().get(Config.XDAEMON_HOST, "localhost")))
   37.97 -    {
   37.98 -      String[] commands = line.split(" ", 4);
   37.99 -      if(commands.length == 3 && commands[1].equalsIgnoreCase("LIST"))
  37.100 -      {
  37.101 -        if(commands[2].equalsIgnoreCase("CONFIGKEYS"))
  37.102 -        {
  37.103 -          conn.println("100 list of available config keys follows");
  37.104 -          for(String key : Config.AVAILABLE_KEYS)
  37.105 -          {
  37.106 -            conn.println(key);
  37.107 -          }
  37.108 -          conn.println(".");
  37.109 -        }
  37.110 -        else if(commands[2].equalsIgnoreCase("PEERINGRULES"))
  37.111 -        {
  37.112 -          List<Subscription> pull = 
  37.113 -            StorageManager.current().getSubscriptions(FeedManager.TYPE_PULL);
  37.114 -          List<Subscription> push =
  37.115 -            StorageManager.current().getSubscriptions(FeedManager.TYPE_PUSH);
  37.116 -          conn.println("100 list of peering rules follows");
  37.117 -          for(Subscription sub : pull)
  37.118 -          {
  37.119 -            conn.println("PULL " + sub.getHost() + ":" + sub.getPort()
  37.120 -              + " " + sub.getGroup());
  37.121 -          }
  37.122 -          for(Subscription sub : push)
  37.123 -          {
  37.124 -            conn.println("PUSH " + sub.getHost() + ":" + sub.getPort()
  37.125 -              + " " + sub.getGroup());
  37.126 -          }
  37.127 -          conn.println(".");
  37.128 -        }
  37.129 -        else
  37.130 -        {
  37.131 -          conn.println("401 unknown sub command");
  37.132 -        }
  37.133 -      }
  37.134 -      else if(commands.length == 3 && commands[1].equalsIgnoreCase("DELETE"))
  37.135 -      {
  37.136 -        StorageManager.current().delete(commands[2]);
  37.137 -        conn.println("200 article " + commands[2] + " deleted");
  37.138 -      }
  37.139 -      else if(commands.length == 4 && commands[1].equalsIgnoreCase("GROUPADD"))
  37.140 -      {
  37.141 -        channelAdd(commands, conn);
  37.142 -      }
  37.143 -      else if(commands.length == 3 && commands[1].equalsIgnoreCase("GROUPDEL"))
  37.144 -      {
  37.145 -        Group group = StorageManager.current().getGroup(commands[2]);
  37.146 -        if(group == null)
  37.147 -        {
  37.148 -          conn.println("400 group not found");
  37.149 -        }
  37.150 -        else
  37.151 -        {
  37.152 -          group.setFlag(Group.DELETED);
  37.153 -          group.update();
  37.154 -          conn.println("200 group " + commands[2] + " marked as deleted");
  37.155 -        }
  37.156 -      }
  37.157 -      else if(commands.length == 4 && commands[1].equalsIgnoreCase("SET"))
  37.158 -      {
  37.159 -        String key = commands[2];
  37.160 -        String val = commands[3];
  37.161 -        Config.inst().set(key, val);
  37.162 -        conn.println("200 new config value set");
  37.163 -      }
  37.164 -      else if(commands.length == 3 && commands[1].equalsIgnoreCase("GET"))
  37.165 -      {
  37.166 -        String key = commands[2];
  37.167 -        String val = Config.inst().get(key, null);
  37.168 -        if(val != null)
  37.169 -        {
  37.170 -          conn.println("100 config value for " + key + " follows");
  37.171 -          conn.println(val);
  37.172 -          conn.println(".");
  37.173 -        }
  37.174 -        else
  37.175 -        {
  37.176 -          conn.println("400 config value not set");
  37.177 -        }
  37.178 -      }
  37.179 -      else if(commands.length >= 3 && commands[1].equalsIgnoreCase("LOG"))
  37.180 -      {
  37.181 -        Group group = null;
  37.182 -        if(commands.length > 3)
  37.183 -        {
  37.184 -          group = (Group)Channel.getByName(commands[3]);
  37.185 -        }
  37.186 -
  37.187 -        if(commands[2].equalsIgnoreCase("CONNECTED_CLIENTS"))
  37.188 -        {
  37.189 -          conn.println("100 number of connections follow");
  37.190 -          conn.println(Integer.toString(Stats.getInstance().connectedClients()));
  37.191 -          conn.println(".");
  37.192 -        }
  37.193 -        else if(commands[2].equalsIgnoreCase("POSTED_NEWS"))
  37.194 -        {
  37.195 -          conn.println("100 hourly numbers of posted news yesterday");
  37.196 -          for(int n = 0; n < 24; n++)
  37.197 -          {
  37.198 -            conn.println(n + " " + Stats.getInstance()
  37.199 -              .getYesterdaysEvents(Stats.POSTED_NEWS, n, group));
  37.200 -          }
  37.201 -          conn.println(".");
  37.202 -        }
  37.203 -        else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS"))
  37.204 -        {
  37.205 -          conn.println("100 hourly numbers of gatewayed news yesterday");
  37.206 -          for(int n = 0; n < 24; n++)
  37.207 -          {
  37.208 -            conn.println(n + " " + Stats.getInstance()
  37.209 -              .getYesterdaysEvents(Stats.GATEWAYED_NEWS, n, group));
  37.210 -          }
  37.211 -          conn.println(".");
  37.212 -        }
  37.213 -        else if(commands[2].equalsIgnoreCase("TRANSMITTED_NEWS"))
  37.214 -        {
  37.215 -          conn.println("100 hourly numbers of news transmitted to peers yesterday");
  37.216 -          for(int n = 0; n < 24; n++)
  37.217 -          {
  37.218 -            conn.println(n + " " + Stats.getInstance()
  37.219 -              .getYesterdaysEvents(Stats.FEEDED_NEWS, n, group));
  37.220 -          }
  37.221 -          conn.println(".");
  37.222 -        }
  37.223 -        else if(commands[2].equalsIgnoreCase("HOSTED_NEWS"))
  37.224 -        {
  37.225 -          conn.println("100 number of overall hosted news");
  37.226 -          conn.println(Integer.toString(Stats.getInstance().getNumberOfNews()));
  37.227 -          conn.println(".");
  37.228 -        }
  37.229 -        else if(commands[2].equalsIgnoreCase("HOSTED_GROUPS"))
  37.230 -        {
  37.231 -          conn.println("100 number of hosted groups");
  37.232 -          conn.println(Integer.toString(Stats.getInstance().getNumberOfGroups()));
  37.233 -          conn.println(".");
  37.234 -        }
  37.235 -        else if(commands[2].equalsIgnoreCase("POSTED_NEWS_PER_HOUR"))
  37.236 -        {
  37.237 -          conn.println("100 posted news per hour");
  37.238 -          conn.println(Double.toString(Stats.getInstance().postedPerHour(-1)));
  37.239 -          conn.println(".");
  37.240 -        }
  37.241 -        else if(commands[2].equalsIgnoreCase("FEEDED_NEWS_PER_HOUR"))
  37.242 -        {
  37.243 -          conn.println("100 feeded news per hour");
  37.244 -          conn.println(Double.toString(Stats.getInstance().feededPerHour(-1)));
  37.245 -          conn.println(".");
  37.246 -        }
  37.247 -        else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS_PER_HOUR"))
  37.248 -        {
  37.249 -          conn.println("100 gatewayed news per hour");
  37.250 -          conn.println(Double.toString(Stats.getInstance().gatewayedPerHour(-1)));
  37.251 -          conn.println(".");
  37.252 -        }
  37.253 -        else
  37.254 -        {
  37.255 -          conn.println("401 unknown sub command");
  37.256 -        }
  37.257 -      }
  37.258 -      else if(commands.length >= 3 && commands[1].equalsIgnoreCase("PLUGIN"))
  37.259 -      {
  37.260 -        
  37.261 -      }
  37.262 -      else
  37.263 -      {
  37.264 -        conn.println("400 invalid command usage");
  37.265 -      }
  37.266 -    }
  37.267 -    else
  37.268 -    {
  37.269 -      conn.println("501 not allowed");
  37.270 -    }
  37.271 -  }
  37.272 -  
  37.273 -}
    38.1 --- a/org/sonews/daemon/command/XPatCommand.java	Sun Aug 29 17:04:25 2010 +0200
    38.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    38.3 @@ -1,170 +0,0 @@
    38.4 -/*
    38.5 - *   SONEWS News Server
    38.6 - *   see AUTHORS for the list of contributors
    38.7 - *
    38.8 - *   This program is free software: you can redistribute it and/or modify
    38.9 - *   it under the terms of the GNU General Public License as published by
   38.10 - *   the Free Software Foundation, either version 3 of the License, or
   38.11 - *   (at your option) any later version.
   38.12 - *
   38.13 - *   This program is distributed in the hope that it will be useful,
   38.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   38.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   38.16 - *   GNU General Public License for more details.
   38.17 - *
   38.18 - *   You should have received a copy of the GNU General Public License
   38.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   38.20 - */
   38.21 -
   38.22 -package org.sonews.daemon.command;
   38.23 -
   38.24 -import java.io.IOException;
   38.25 -import java.util.List;
   38.26 -import java.util.Locale;
   38.27 -import java.util.regex.PatternSyntaxException;
   38.28 -import org.sonews.daemon.NNTPConnection;
   38.29 -import org.sonews.storage.StorageBackendException;
   38.30 -import org.sonews.storage.StorageManager;
   38.31 -import org.sonews.util.Pair;
   38.32 -
   38.33 -/**
   38.34 - * <pre>
   38.35 - *   XPAT header range|<message-id> pat [pat...]
   38.36 - *
   38.37 - *   The XPAT command is used to retrieve specific headers from
   38.38 - *   specific articles, based on pattern matching on the contents of
   38.39 - *   the header. This command was first available in INN.
   38.40 - *
   38.41 - *   The required header parameter is the name of a header line (e.g.
   38.42 - *   "subject") in a news group article. See RFC-1036 for a list
   38.43 - *   of valid header lines. The required range argument may be
   38.44 - *   any of the following:
   38.45 - *               an article number
   38.46 - *               an article number followed by a dash to indicate
   38.47 - *                  all following
   38.48 - *               an article number followed by a dash followed by
   38.49 - *                  another article number
   38.50 - *
   38.51 - *   The required message-id argument indicates a specific
   38.52 - *   article. The range and message-id arguments are mutually
   38.53 - *   exclusive. At least one pattern in wildmat must be specified
   38.54 - *   as well. If there are additional arguments the are joined
   38.55 - *   together separated by a single space to form one complete
   38.56 - *   pattern. Successful responses start with a 221 response
   38.57 - *   followed by a the headers from all messages in which the
   38.58 - *   pattern matched the contents of the specified header line. This
   38.59 - *   includes an empty list. Once the output is complete, a period
   38.60 - *   is sent on a line by itself. If the optional argument is a
   38.61 - *   message-id and no such article exists, the 430 error response
   38.62 - *   is returned. A 502 response will be returned if the client only
   38.63 - *   has permission to transfer articles.
   38.64 - *
   38.65 - *   Responses
   38.66 - *
   38.67 - *       221 Header follows
   38.68 - *       430 no such article
   38.69 - *       502 no permission
   38.70 - *
   38.71 - *   Response Data:
   38.72 - *
   38.73 - *       art_nr fitting_header_value
   38.74 - * 
   38.75 - * </pre>
   38.76 - * [Source:"draft-ietf-nntp-imp-02.txt"] [Copyright: 1998 S. Barber]
   38.77 - * 
   38.78 - * @author Christian Lins
   38.79 - * @since sonews/0.5.0
   38.80 - */
   38.81 -public class XPatCommand implements Command
   38.82 -{
   38.83 -
   38.84 -  @Override
   38.85 -  public String[] getSupportedCommandStrings()
   38.86 -  {
   38.87 -    return new String[]{"XPAT"};
   38.88 -  }
   38.89 -  
   38.90 -  @Override
   38.91 -  public boolean hasFinished()
   38.92 -  {
   38.93 -    return true;
   38.94 -  }
   38.95 -
   38.96 -  @Override
   38.97 -  public String impliedCapability()
   38.98 -  {
   38.99 -    return null;
  38.100 -  }
  38.101 -
  38.102 -  @Override
  38.103 -  public boolean isStateful()
  38.104 -  {
  38.105 -    return false;
  38.106 -  }
  38.107 -
  38.108 -  @Override
  38.109 -  public void processLine(NNTPConnection conn, final String line, byte[] raw)
  38.110 -    throws IOException, StorageBackendException
  38.111 -  {
  38.112 -    if(conn.getCurrentChannel() == null)
  38.113 -    {
  38.114 -      conn.println("430 no group selected");
  38.115 -      return;
  38.116 -    }
  38.117 -
  38.118 -    String[] command = line.split("\\p{Space}+");
  38.119 -
  38.120 -    // There may be multiple patterns and Thunderbird produces
  38.121 -    // additional spaces between range and pattern
  38.122 -    if(command.length >= 4)
  38.123 -    {
  38.124 -      String header  = command[1].toLowerCase(Locale.US);
  38.125 -      String range   = command[2];
  38.126 -      String pattern = command[3];
  38.127 -
  38.128 -      long start = -1;
  38.129 -      long end   = -1;
  38.130 -      if(range.contains("-"))
  38.131 -      {
  38.132 -        String[] rsplit = range.split("-", 2);
  38.133 -        start = Long.parseLong(rsplit[0]);
  38.134 -        if(rsplit[1].length() > 0)
  38.135 -        {
  38.136 -          end = Long.parseLong(rsplit[1]);
  38.137 -        }
  38.138 -      }
  38.139 -      else // TODO: Handle Message-IDs
  38.140 -      {
  38.141 -        start = Long.parseLong(range);
  38.142 -      }
  38.143 -
  38.144 -      try
  38.145 -      {
  38.146 -        List<Pair<Long, String>> heads = StorageManager.current().
  38.147 -          getArticleHeaders(conn.getCurrentChannel(), start, end, header, pattern);
  38.148 -        
  38.149 -        conn.println("221 header follows");
  38.150 -        for(Pair<Long, String> head : heads)
  38.151 -        {
  38.152 -          conn.println(head.getA() + " " + head.getB());
  38.153 -        }
  38.154 -        conn.println(".");
  38.155 -      }
  38.156 -      catch(PatternSyntaxException ex)
  38.157 -      {
  38.158 -        ex.printStackTrace();
  38.159 -        conn.println("500 invalid pattern syntax");
  38.160 -      }
  38.161 -      catch(StorageBackendException ex)
  38.162 -      {
  38.163 -        ex.printStackTrace();
  38.164 -        conn.println("500 internal server error");
  38.165 -      }
  38.166 -    }
  38.167 -    else
  38.168 -    {
  38.169 -      conn.println("430 invalid command usage");
  38.170 -    }
  38.171 -  }
  38.172 -
  38.173 -}
    39.1 --- a/org/sonews/daemon/command/package.html	Sun Aug 29 17:04:25 2010 +0200
    39.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    39.3 @@ -1,1 +0,0 @@
    39.4 -Contains a class for every NNTP command.
    39.5 \ No newline at end of file
    40.1 --- a/org/sonews/daemon/package.html	Sun Aug 29 17:04:25 2010 +0200
    40.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    40.3 @@ -1,1 +0,0 @@
    40.4 -Contains basic classes of the daemon.
    40.5 \ No newline at end of file
    41.1 --- a/org/sonews/feed/FeedManager.java	Sun Aug 29 17:04:25 2010 +0200
    41.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    41.3 @@ -1,54 +0,0 @@
    41.4 -/*
    41.5 - *   SONEWS News Server
    41.6 - *   see AUTHORS for the list of contributors
    41.7 - *
    41.8 - *   This program is free software: you can redistribute it and/or modify
    41.9 - *   it under the terms of the GNU General Public License as published by
   41.10 - *   the Free Software Foundation, either version 3 of the License, or
   41.11 - *   (at your option) any later version.
   41.12 - *
   41.13 - *   This program is distributed in the hope that it will be useful,
   41.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   41.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   41.16 - *   GNU General Public License for more details.
   41.17 - *
   41.18 - *   You should have received a copy of the GNU General Public License
   41.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   41.20 - */
   41.21 -
   41.22 -package org.sonews.feed;
   41.23 -
   41.24 -import org.sonews.storage.Article;
   41.25 -
   41.26 -/**
   41.27 - * Controlls push and pull feeder.
   41.28 - * @author Christian Lins
   41.29 - * @since sonews/0.5.0
   41.30 - */
   41.31 -public final class FeedManager 
   41.32 -{
   41.33 -
   41.34 -  public static final int TYPE_PULL = 0;
   41.35 -  public static final int TYPE_PUSH = 1;
   41.36 -  
   41.37 -  private static PullFeeder pullFeeder = new PullFeeder();
   41.38 -  private static PushFeeder pushFeeder = new PushFeeder();
   41.39 -  
   41.40 -  /**
   41.41 -   * Reads the peer subscriptions from database and starts the appropriate
   41.42 -   * PullFeeder or PushFeeder.
   41.43 -   */
   41.44 -  public static synchronized void startFeeding()
   41.45 -  {
   41.46 -    pullFeeder.start();
   41.47 -    pushFeeder.start();
   41.48 -  }
   41.49 -  
   41.50 -  public static void queueForPush(Article article)
   41.51 -  {
   41.52 -    pushFeeder.queueForPush(article);
   41.53 -  }
   41.54 -  
   41.55 -  private FeedManager() {}
   41.56 -  
   41.57 -}
    42.1 --- a/org/sonews/feed/PullFeeder.java	Sun Aug 29 17:04:25 2010 +0200
    42.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    42.3 @@ -1,276 +0,0 @@
    42.4 -/*
    42.5 - *   SONEWS News Server
    42.6 - *   see AUTHORS for the list of contributors
    42.7 - *
    42.8 - *   This program is free software: you can redistribute it and/or modify
    42.9 - *   it under the terms of the GNU General Public License as published by
   42.10 - *   the Free Software Foundation, either version 3 of the License, or
   42.11 - *   (at your option) any later version.
   42.12 - *
   42.13 - *   This program is distributed in the hope that it will be useful,
   42.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   42.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   42.16 - *   GNU General Public License for more details.
   42.17 - *
   42.18 - *   You should have received a copy of the GNU General Public License
   42.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   42.20 - */
   42.21 -
   42.22 -package org.sonews.feed;
   42.23 -
   42.24 -import java.io.BufferedReader;
   42.25 -import java.io.IOException;
   42.26 -import java.io.InputStreamReader;
   42.27 -import java.io.PrintWriter;
   42.28 -import java.net.Socket;
   42.29 -import java.net.SocketException;
   42.30 -import java.net.UnknownHostException;
   42.31 -import java.util.ArrayList;
   42.32 -import java.util.HashMap;
   42.33 -import java.util.HashSet;
   42.34 -import java.util.List;
   42.35 -import java.util.Map;
   42.36 -import java.util.Set;
   42.37 -import java.util.logging.Level;
   42.38 -import org.sonews.config.Config;
   42.39 -import org.sonews.daemon.AbstractDaemon;
   42.40 -import org.sonews.util.Log;
   42.41 -import org.sonews.storage.StorageBackendException;
   42.42 -import org.sonews.storage.StorageManager;
   42.43 -import org.sonews.util.Stats;
   42.44 -import org.sonews.util.io.ArticleReader;
   42.45 -import org.sonews.util.io.ArticleWriter;
   42.46 -
   42.47 -/**
   42.48 - * The PullFeeder class regularily checks another Newsserver for new
   42.49 - * messages.
   42.50 - * @author Christian Lins
   42.51 - * @since sonews/0.5.0
   42.52 - */
   42.53 -class PullFeeder extends AbstractDaemon
   42.54 -{
   42.55 -  
   42.56 -  private Map<Subscription, Integer> highMarks = new HashMap<Subscription, Integer>();
   42.57 -  private BufferedReader             in;
   42.58 -  private PrintWriter                out;
   42.59 -  private Set<Subscription>          subscriptions = new HashSet<Subscription>();
   42.60 -  
   42.61 -  private void addSubscription(final Subscription sub)
   42.62 -  {
   42.63 -    subscriptions.add(sub);
   42.64 -
   42.65 -    if(!highMarks.containsKey(sub))
   42.66 -    {
   42.67 -      // Set a initial highMark
   42.68 -      this.highMarks.put(sub, 0);
   42.69 -    }
   42.70 -  }
   42.71 -  
   42.72 -  /**
   42.73 -   * Changes to the given group and returns its high mark.
   42.74 -   * @param groupName
   42.75 -   * @return
   42.76 -   */
   42.77 -  private int changeGroup(String groupName)
   42.78 -    throws IOException
   42.79 -  {
   42.80 -    this.out.print("GROUP " + groupName + "\r\n");
   42.81 -    this.out.flush();
   42.82 -    
   42.83 -    String line = this.in.readLine();
   42.84 -    if(line.startsWith("211 "))
   42.85 -    {
   42.86 -      int highmark = Integer.parseInt(line.split(" ")[3]);
   42.87 -      return highmark;
   42.88 -    }
   42.89 -    else
   42.90 -    {
   42.91 -      throw new IOException("GROUP " + groupName + " returned: " + line);
   42.92 -    }
   42.93 -  }
   42.94 -  
   42.95 -  private void connectTo(final String host, final int port)
   42.96 -    throws IOException, UnknownHostException
   42.97 -  {
   42.98 -    Socket socket = new Socket(host, port);
   42.99 -    this.out = new PrintWriter(socket.getOutputStream());
  42.100 -    this.in  = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  42.101 -
  42.102 -    String line = in.readLine();
  42.103 -    if(!(line.charAt(0) == '2')) // Could be 200 or 2xx if posting is not allowed
  42.104 -    {
  42.105 -      throw new IOException(line);
  42.106 -    }
  42.107 -
  42.108 -    // Send MODE READER to peer, some newsservers are friendlier then
  42.109 -    this.out.println("MODE READER\r\n");
  42.110 -    this.out.flush();
  42.111 -    line = this.in.readLine();
  42.112 -  }
  42.113 -  
  42.114 -  private void disconnect()
  42.115 -    throws IOException
  42.116 -  {
  42.117 -    this.out.print("QUIT\r\n");
  42.118 -    this.out.flush();
  42.119 -    this.out.close();
  42.120 -    this.in.close();
  42.121 -    
  42.122 -    this.out = null;
  42.123 -    this.in  = null;
  42.124 -  }
  42.125 -  
  42.126 -  /**
  42.127 -   * Uses the OVER or XOVER command to get a list of message overviews that
  42.128 -   * may be unknown to this feeder and are about to be peered.
  42.129 -   * @param start
  42.130 -   * @param end
  42.131 -   * @return A list of message ids with potentially interesting messages.
  42.132 -   */
  42.133 -  private List<String> over(int start, int end)
  42.134 -    throws IOException
  42.135 -  {
  42.136 -    this.out.print("OVER " + start + "-" + end + "\r\n");
  42.137 -    this.out.flush();
  42.138 -    
  42.139 -    String line = this.in.readLine();
  42.140 -    if(line.startsWith("500 ")) // OVER not supported
  42.141 -    {
  42.142 -      this.out.print("XOVER " + start + "-" + end + "\r\n");
  42.143 -      this.out.flush();
  42.144 -      
  42.145 -      line = this.in.readLine();
  42.146 -    }
  42.147 -    
  42.148 -    if(line.startsWith("224 "))
  42.149 -    {
  42.150 -      List<String> messages = new ArrayList<String>();
  42.151 -      line = this.in.readLine();
  42.152 -      while(!".".equals(line))
  42.153 -      {
  42.154 -        String mid = line.split("\t")[4]; // 5th should be the Message-ID
  42.155 -        messages.add(mid);
  42.156 -        line = this.in.readLine();
  42.157 -      }
  42.158 -      return messages;
  42.159 -    }
  42.160 -    else
  42.161 -    {
  42.162 -      throw new IOException("Server return for OVER/XOVER: " + line);
  42.163 -    }
  42.164 -  }
  42.165 -  
  42.166 -  @Override
  42.167 -  public void run()
  42.168 -  {
  42.169 -    while(isRunning())
  42.170 -    {
  42.171 -      int pullInterval = 1000 * 
  42.172 -        Config.inst().get(Config.FEED_PULLINTERVAL, 3600);
  42.173 -      String host = "localhost";
  42.174 -      int    port = 119;
  42.175 -      
  42.176 -      Log.get().info("Start PullFeeder run...");
  42.177 -
  42.178 -      try
  42.179 -      {
  42.180 -        this.subscriptions.clear();
  42.181 -        List<Subscription> subsPull = StorageManager.current()
  42.182 -          .getSubscriptions(FeedManager.TYPE_PULL);
  42.183 -        for(Subscription sub : subsPull)
  42.184 -        {
  42.185 -          addSubscription(sub);
  42.186 -        }
  42.187 -      }
  42.188 -      catch(StorageBackendException ex)
  42.189 -      {
  42.190 -        Log.get().log(Level.SEVERE, host, ex);
  42.191 -      }
  42.192 -
  42.193 -      try
  42.194 -      {
  42.195 -        for(Subscription sub : this.subscriptions)
  42.196 -        {
  42.197 -          host = sub.getHost();
  42.198 -          port = sub.getPort();
  42.199 -
  42.200 -          try
  42.201 -          {
  42.202 -            Log.get().info("Feeding " + sub.getGroup() + " from " + sub.getHost());
  42.203 -            try
  42.204 -            {
  42.205 -              connectTo(host, port);
  42.206 -            }
  42.207 -            catch(SocketException ex)
  42.208 -            {
  42.209 -              Log.get().info("Skipping " + sub.getHost() + ": " + ex);
  42.210 -              continue;
  42.211 -            }
  42.212 -            
  42.213 -            int oldMark = this.highMarks.get(sub);
  42.214 -            int newMark = changeGroup(sub.getGroup());
  42.215 -            
  42.216 -            if(oldMark != newMark)
  42.217 -            {
  42.218 -              List<String> messageIDs = over(oldMark, newMark);
  42.219 -
  42.220 -              for(String messageID : messageIDs)
  42.221 -              {
  42.222 -                if(!StorageManager.current().isArticleExisting(messageID))
  42.223 -                {
  42.224 -                  try
  42.225 -                  {
  42.226 -                    // Post the message via common socket connection
  42.227 -                    ArticleReader aread =
  42.228 -                      new ArticleReader(sub.getHost(), sub.getPort(), messageID);
  42.229 -                    byte[] abuf = aread.getArticleData();
  42.230 -                    if(abuf == null)
  42.231 -                    {
  42.232 -                      Log.get().warning("Could not feed " + messageID
  42.233 -                        + " from " + sub.getHost());
  42.234 -                    }
  42.235 -                    else
  42.236 -                    {
  42.237 -                      Log.get().info("Feeding " + messageID);
  42.238 -                      ArticleWriter awrite = new ArticleWriter(
  42.239 -                        "localhost", Config.inst().get(Config.PORT, 119));
  42.240 -                      awrite.writeArticle(abuf);
  42.241 -                      awrite.close();
  42.242 -                    }
  42.243 -                    Stats.getInstance().mailFeeded(sub.getGroup());
  42.244 -                  }
  42.245 -                  catch(IOException ex)
  42.246 -                  {
  42.247 -                    // There may be a temporary network failure
  42.248 -                    ex.printStackTrace();
  42.249 -                    Log.get().warning("Skipping mail " + messageID + " due to exception.");
  42.250 -                  }
  42.251 -                }
  42.252 -              } // for(;;)
  42.253 -              this.highMarks.put(sub, newMark);
  42.254 -            }
  42.255 -            
  42.256 -            disconnect();
  42.257 -          }
  42.258 -          catch(StorageBackendException ex)
  42.259 -          {
  42.260 -            ex.printStackTrace();
  42.261 -          }
  42.262 -          catch(IOException ex)
  42.263 -          {
  42.264 -            ex.printStackTrace();
  42.265 -            Log.get().severe("PullFeeder run stopped due to exception.");
  42.266 -          }
  42.267 -        } // for(Subscription sub : subscriptions)
  42.268 -        
  42.269 -        Log.get().info("PullFeeder run ended. Waiting " + pullInterval / 1000 + "s");
  42.270 -        Thread.sleep(pullInterval);
  42.271 -      }
  42.272 -      catch(InterruptedException ex)
  42.273 -      {
  42.274 -        Log.get().warning(ex.getMessage());
  42.275 -      }
  42.276 -    }
  42.277 -  }
  42.278 -  
  42.279 -}
    43.1 --- a/org/sonews/feed/PushFeeder.java	Sun Aug 29 17:04:25 2010 +0200
    43.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    43.3 @@ -1,118 +0,0 @@
    43.4 -/*
    43.5 - *   SONEWS News Server
    43.6 - *   see AUTHORS for the list of contributors
    43.7 - *
    43.8 - *   This program is free software: you can redistribute it and/or modify
    43.9 - *   it under the terms of the GNU General Public License as published by
   43.10 - *   the Free Software Foundation, either version 3 of the License, or
   43.11 - *   (at your option) any later version.
   43.12 - *
   43.13 - *   This program is distributed in the hope that it will be useful,
   43.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   43.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   43.16 - *   GNU General Public License for more details.
   43.17 - *
   43.18 - *   You should have received a copy of the GNU General Public License
   43.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   43.20 - */
   43.21 -
   43.22 -package org.sonews.feed;
   43.23 -
   43.24 -import java.io.IOException;
   43.25 -import java.util.List;
   43.26 -import java.util.concurrent.ConcurrentLinkedQueue;
   43.27 -import org.sonews.daemon.AbstractDaemon;
   43.28 -import org.sonews.storage.Article;
   43.29 -import org.sonews.storage.Headers;
   43.30 -import org.sonews.storage.StorageBackendException;
   43.31 -import org.sonews.storage.StorageManager;
   43.32 -import org.sonews.util.Log;
   43.33 -import org.sonews.util.io.ArticleWriter;
   43.34 -
   43.35 -/**
   43.36 - * Pushes new articles to remote newsservers. This feeder sleeps until a new
   43.37 - * message is posted to the sonews instance.
   43.38 - * @author Christian Lins
   43.39 - * @since sonews/0.5.0
   43.40 - */
   43.41 -class PushFeeder extends AbstractDaemon
   43.42 -{
   43.43 -  
   43.44 -  private ConcurrentLinkedQueue<Article> articleQueue = 
   43.45 -    new ConcurrentLinkedQueue<Article>();
   43.46 -  
   43.47 -  @Override
   43.48 -  public void run()
   43.49 -  {
   43.50 -    while(isRunning())
   43.51 -    {
   43.52 -      try
   43.53 -      {
   43.54 -        synchronized(this)
   43.55 -        {
   43.56 -          this.wait();
   43.57 -        }
   43.58 -        
   43.59 -        List<Subscription> subscriptions = StorageManager.current()
   43.60 -          .getSubscriptions(FeedManager.TYPE_PUSH);
   43.61 -
   43.62 -        Article  article = this.articleQueue.poll();
   43.63 -        String[] groups  = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
   43.64 -        Log.get().info("PushFeed: " + article.getMessageID());
   43.65 -        for(Subscription sub : subscriptions)
   43.66 -        {
   43.67 -          // Circle check
   43.68 -          if(article.getHeader(Headers.PATH)[0].contains(sub.getHost()))
   43.69 -          {
   43.70 -            Log.get().info(article.getMessageID() + " skipped for host "
   43.71 -              + sub.getHost());
   43.72 -            continue;
   43.73 -          }
   43.74 -
   43.75 -          try
   43.76 -          {
   43.77 -            for(String group : groups)
   43.78 -            {
   43.79 -              if(sub.getGroup().equals(group))
   43.80 -              {
   43.81 -                // Delete headers that may cause problems
   43.82 -                article.removeHeader(Headers.NNTP_POSTING_DATE);
   43.83 -                article.removeHeader(Headers.NNTP_POSTING_HOST);
   43.84 -                article.removeHeader(Headers.X_COMPLAINTS_TO);
   43.85 -                article.removeHeader(Headers.X_TRACE);
   43.86 -                article.removeHeader(Headers.XREF);
   43.87 -                
   43.88 -                // POST the message to remote server
   43.89 -                ArticleWriter awriter = new ArticleWriter(sub.getHost(), sub.getPort());
   43.90 -                awriter.writeArticle(article);
   43.91 -                break;
   43.92 -              }
   43.93 -            }
   43.94 -          }
   43.95 -          catch(IOException ex)
   43.96 -          {
   43.97 -            Log.get().warning(ex.toString());
   43.98 -          }
   43.99 -        }
  43.100 -      }
  43.101 -      catch(StorageBackendException ex)
  43.102 -      {
  43.103 -        Log.get().severe(ex.toString());
  43.104 -      }
  43.105 -      catch(InterruptedException ex)
  43.106 -      {
  43.107 -        Log.get().warning("PushFeeder interrupted: " + ex);
  43.108 -      }
  43.109 -    }
  43.110 -  }
  43.111 -  
  43.112 -  public void queueForPush(Article article)
  43.113 -  {
  43.114 -    this.articleQueue.add(article);
  43.115 -    synchronized(this)
  43.116 -    {
  43.117 -      this.notifyAll();
  43.118 -    }
  43.119 -  }
  43.120 -  
  43.121 -}
    44.1 --- a/org/sonews/feed/Subscription.java	Sun Aug 29 17:04:25 2010 +0200
    44.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    44.3 @@ -1,84 +0,0 @@
    44.4 -/*
    44.5 - *   SONEWS News Server
    44.6 - *   see AUTHORS for the list of contributors
    44.7 - *
    44.8 - *   This program is free software: you can redistribute it and/or modify
    44.9 - *   it under the terms of the GNU General Public License as published by
   44.10 - *   the Free Software Foundation, either version 3 of the License, or
   44.11 - *   (at your option) any later version.
   44.12 - *
   44.13 - *   This program is distributed in the hope that it will be useful,
   44.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   44.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   44.16 - *   GNU General Public License for more details.
   44.17 - *
   44.18 - *   You should have received a copy of the GNU General Public License
   44.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   44.20 - */
   44.21 -
   44.22 -package org.sonews.feed;
   44.23 -
   44.24 -/**
   44.25 - * For every group that is synchronized with or from a remote newsserver 
   44.26 - * a Subscription instance exists.
   44.27 - * @author Christian Lins
   44.28 - * @since sonews/0.5.0
   44.29 - */
   44.30 -public class Subscription 
   44.31 -{
   44.32 -
   44.33 -  private String host;
   44.34 -  private int    port;
   44.35 -  private int    feedtype;
   44.36 -  private String group;
   44.37 -  
   44.38 -  public Subscription(String host, int port, int feedtype, String group)
   44.39 -  {
   44.40 -    this.host     = host;
   44.41 -    this.port     = port;
   44.42 -    this.feedtype = feedtype;
   44.43 -    this.group    = group;
   44.44 -  }
   44.45 -
   44.46 -  @Override
   44.47 -  public boolean equals(Object obj)
   44.48 -  {
   44.49 -    if(obj instanceof Subscription)
   44.50 -    {
   44.51 -      Subscription sub = (Subscription)obj;
   44.52 -      return sub.host.equals(host) && sub.group.equals(group) 
   44.53 -        && sub.port == port && sub.feedtype == feedtype;
   44.54 -    }
   44.55 -    else
   44.56 -    {
   44.57 -      return false;
   44.58 -    }
   44.59 -  }
   44.60 -
   44.61 -  @Override
   44.62 -  public int hashCode()
   44.63 -  {
   44.64 -    return host.hashCode() + port + feedtype + group.hashCode();
   44.65 -  }
   44.66 -
   44.67 -  public int getFeedtype()
   44.68 -  {
   44.69 -    return feedtype;
   44.70 -  }
   44.71 -
   44.72 -  public String getGroup()
   44.73 -  {
   44.74 -    return group;
   44.75 -  }
   44.76 -
   44.77 -  public String getHost()
   44.78 -  {
   44.79 -    return host;
   44.80 -  }
   44.81 -
   44.82 -  public int getPort()
   44.83 -  {
   44.84 -    return port;
   44.85 -  }
   44.86 -  
   44.87 -}
    45.1 --- a/org/sonews/feed/package.html	Sun Aug 29 17:04:25 2010 +0200
    45.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    45.3 @@ -1,2 +0,0 @@
    45.4 -Contains classes for the peering functionality, e.g. pulling and pushing
    45.5 -mails from and to remote newsservers.
    45.6 \ No newline at end of file
    46.1 --- a/org/sonews/mlgw/Dispatcher.java	Sun Aug 29 17:04:25 2010 +0200
    46.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    46.3 @@ -1,301 +0,0 @@
    46.4 -/*
    46.5 - *   SONEWS News Server
    46.6 - *   see AUTHORS for the list of contributors
    46.7 - *
    46.8 - *   This program is free software: you can redistribute it and/or modify
    46.9 - *   it under the terms of the GNU General Public License as published by
   46.10 - *   the Free Software Foundation, either version 3 of the License, or
   46.11 - *   (at your option) any later version.
   46.12 - *
   46.13 - *   This program is distributed in the hope that it will be useful,
   46.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   46.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   46.16 - *   GNU General Public License for more details.
   46.17 - *
   46.18 - *   You should have received a copy of the GNU General Public License
   46.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   46.20 - */
   46.21 -
   46.22 -package org.sonews.mlgw;
   46.23 -
   46.24 -import java.io.IOException;
   46.25 -import java.util.ArrayList;
   46.26 -import java.util.List;
   46.27 -import java.util.regex.Matcher;
   46.28 -import java.util.regex.Pattern;
   46.29 -import javax.mail.Address;
   46.30 -import javax.mail.Authenticator;
   46.31 -import javax.mail.Message;
   46.32 -import javax.mail.MessagingException;
   46.33 -import javax.mail.PasswordAuthentication;
   46.34 -import javax.mail.internet.InternetAddress;
   46.35 -import org.sonews.config.Config;
   46.36 -import org.sonews.storage.Article;
   46.37 -import org.sonews.storage.Group;
   46.38 -import org.sonews.storage.Headers;
   46.39 -import org.sonews.storage.StorageBackendException;
   46.40 -import org.sonews.storage.StorageManager;
   46.41 -import org.sonews.util.Log;
   46.42 -import org.sonews.util.Stats;
   46.43 -
   46.44 -/**
   46.45 - * Dispatches messages from mailing list to newsserver or vice versa.
   46.46 - * @author Christian Lins
   46.47 - * @since sonews/0.5.0
   46.48 - */
   46.49 -public class Dispatcher 
   46.50 -{
   46.51 -
   46.52 -  static class PasswordAuthenticator extends Authenticator
   46.53 -  {
   46.54 -    
   46.55 -    @Override
   46.56 -    public PasswordAuthentication getPasswordAuthentication()
   46.57 -    {
   46.58 -      final String username = 
   46.59 -        Config.inst().get(Config.MLSEND_USER, "user");
   46.60 -      final String password = 
   46.61 -        Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
   46.62 -
   46.63 -      return new PasswordAuthentication(username, password);
   46.64 -    }
   46.65 -    
   46.66 -  }
   46.67 -
   46.68 -  /**
   46.69 -   * Chunks out the email address of the full List-Post header field.
   46.70 -   * @param listPostValue
   46.71 -   * @return The matching email address or null
   46.72 -   */
   46.73 -  private static String chunkListPost(String listPostValue)
   46.74 -  {
   46.75 -    // listPostValue is of form "<mailto:dev@openoffice.org>"
   46.76 -    Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
   46.77 -    Matcher mailMatcher = mailPattern.matcher(listPostValue);
   46.78 -    if(mailMatcher.find())
   46.79 -    {
   46.80 -      return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
   46.81 -    }
   46.82 -    else
   46.83 -    {
   46.84 -      return null;
   46.85 -    }
   46.86 -  }
   46.87 -
   46.88 -  /**
   46.89 -   * This method inspects the header of the given message, trying
   46.90 -   * to find the most appropriate recipient.
   46.91 -   * @param msg
   46.92 -   * @param fallback If this is false only List-Post and X-List-Post headers
   46.93 -   *                 are examined.
   46.94 -   * @return null or fitting group name for the given message.
   46.95 -   */
   46.96 -  private static List<String> getGroupFor(final Message msg, final boolean fallback)
   46.97 -    throws MessagingException, StorageBackendException
   46.98 -  {
   46.99 -    List<String> groups = null;
  46.100 -
  46.101 -    // Is there a List-Post header?
  46.102 -    String[]        listPost = msg.getHeader(Headers.LIST_POST);
  46.103 -    InternetAddress listPostAddr;
  46.104 -
  46.105 -    if(listPost == null || listPost.length == 0 || "".equals(listPost[0]))
  46.106 -    {
  46.107 -      // Is there a X-List-Post header?
  46.108 -      listPost = msg.getHeader(Headers.X_LIST_POST);
  46.109 -    }
  46.110 -
  46.111 -    if(listPost != null && listPost.length > 0 
  46.112 -      && !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null)
  46.113 -    {
  46.114 -      // listPost[0] is of form "<mailto:dev@openoffice.org>"
  46.115 -      listPost[0]  = chunkListPost(listPost[0]);
  46.116 -      listPostAddr = new InternetAddress(listPost[0], false);
  46.117 -      groups = StorageManager.current().getGroupsForList(listPostAddr.getAddress());
  46.118 -    }
  46.119 -    else if(fallback)
  46.120 -    {
  46.121 -      Log.get().info("Using fallback recipient discovery for: " + msg.getSubject());
  46.122 -      groups = new ArrayList<String>();
  46.123 -      // Fallback to TO/CC/BCC addresses
  46.124 -      Address[] to = msg.getAllRecipients();
  46.125 -      for(Address toa : to) // Address can have '<' '>' around
  46.126 -      {
  46.127 -        if(toa instanceof InternetAddress)
  46.128 -        {
  46.129 -          List<String> g = StorageManager.current()
  46.130 -            .getGroupsForList(((InternetAddress)toa).getAddress());
  46.131 -          groups.addAll(g);
  46.132 -        }
  46.133 -      }
  46.134 -    }
  46.135 -    
  46.136 -    return groups;
  46.137 -  }
  46.138 -  
  46.139 -  /**
  46.140 -   * Posts a message that was received from a mailing list to the 
  46.141 -   * appropriate newsgroup.
  46.142 -   * If the message already exists in the storage, this message checks
  46.143 -   * if it must be posted in an additional group. This can happen for
  46.144 -   * crosspostings in different mailing lists.
  46.145 -   * @param msg
  46.146 -   */
  46.147 -  public static boolean toGroup(final Message msg)
  46.148 -  {
  46.149 -    try
  46.150 -    {
  46.151 -      // Create new Article object
  46.152 -      Article article = new Article(msg);
  46.153 -      boolean posted  = false;
  46.154 -
  46.155 -      // Check if this mail is already existing the storage
  46.156 -      boolean updateReq = 
  46.157 -        StorageManager.current().isArticleExisting(article.getMessageID());
  46.158 -
  46.159 -      List<String> newsgroups = getGroupFor(msg, !updateReq);
  46.160 -      List<String> oldgroups  = new ArrayList<String>();
  46.161 -      if(updateReq)
  46.162 -      {
  46.163 -        // Check for duplicate entries of the same group
  46.164 -        Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
  46.165 -        List<Group> oldGroups = oldArticle.getGroups();
  46.166 -        for(Group oldGroup : oldGroups)
  46.167 -        {
  46.168 -          if(!newsgroups.contains(oldGroup.getName()))
  46.169 -          {
  46.170 -            oldgroups.add(oldGroup.getName());
  46.171 -          }
  46.172 -        }
  46.173 -      }
  46.174 -
  46.175 -      if(newsgroups.size() > 0)
  46.176 -      {
  46.177 -        newsgroups.addAll(oldgroups);
  46.178 -        StringBuilder groups = new StringBuilder();
  46.179 -        for(int n = 0; n < newsgroups.size(); n++)
  46.180 -        {
  46.181 -          groups.append(newsgroups.get(n));
  46.182 -          if (n + 1 != newsgroups.size())
  46.183 -          {
  46.184 -            groups.append(',');
  46.185 -          }
  46.186 -        }
  46.187 -        Log.get().info("Posting to group " + groups.toString());
  46.188 -
  46.189 -        article.setGroup(groups.toString());
  46.190 -        //article.removeHeader(Headers.REPLY_TO);
  46.191 -        //article.removeHeader(Headers.TO);
  46.192 -
  46.193 -        // Write article to database
  46.194 -        if(updateReq)
  46.195 -        {
  46.196 -          Log.get().info("Updating " + article.getMessageID()
  46.197 -            + " with additional groups");
  46.198 -          StorageManager.current().delete(article.getMessageID());
  46.199 -          StorageManager.current().addArticle(article);
  46.200 -        }
  46.201 -        else
  46.202 -        {
  46.203 -          Log.get().info("Gatewaying " + article.getMessageID() + " to "
  46.204 -            + article.getHeader(Headers.NEWSGROUPS)[0]);
  46.205 -          StorageManager.current().addArticle(article);
  46.206 -          Stats.getInstance().mailGatewayed(
  46.207 -            article.getHeader(Headers.NEWSGROUPS)[0]);
  46.208 -        }
  46.209 -        posted = true;
  46.210 -      }
  46.211 -      else
  46.212 -      {
  46.213 -        StringBuilder buf = new StringBuilder();
  46.214 -        for (Address toa : msg.getAllRecipients())
  46.215 -        {
  46.216 -          buf.append(' ');
  46.217 -          buf.append(toa.toString());
  46.218 -        }
  46.219 -        buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
  46.220 -        Log.get().warning("No group for" + buf.toString());
  46.221 -      }
  46.222 -      return posted;
  46.223 -    }
  46.224 -    catch(Exception ex)
  46.225 -    {
  46.226 -      ex.printStackTrace();
  46.227 -      return false;
  46.228 -    }
  46.229 -  }
  46.230 -  
  46.231 -  /**
  46.232 -   * Mails a message received through NNTP to the appropriate mailing list.
  46.233 -   * This method MAY be called several times by PostCommand for the same
  46.234 -   * article.
  46.235 -   */
  46.236 -  public static void toList(Article article, String group)
  46.237 -    throws IOException, MessagingException, StorageBackendException
  46.238 -  {
  46.239 -    // Get mailing lists for the group of this article
  46.240 -    List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
  46.241 -
  46.242 -    if(rcptAddresses == null || rcptAddresses.size() == 0)
  46.243 -    {
  46.244 -      Log.get().warning("No ML-address for " + group + " found.");
  46.245 -      return;
  46.246 -    }
  46.247 -
  46.248 -    for(String rcptAddress : rcptAddresses)
  46.249 -    {
  46.250 -      // Compose message and send it via given SMTP-Host
  46.251 -      String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
  46.252 -      int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
  46.253 -      String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
  46.254 -      String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
  46.255 -      String smtpFrom = Config.inst().get(
  46.256 -        Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
  46.257 -
  46.258 -      // TODO: Make Article cloneable()
  46.259 -      article.getMessageID(); // Make sure an ID is existing
  46.260 -      article.removeHeader(Headers.NEWSGROUPS);
  46.261 -      article.removeHeader(Headers.PATH);
  46.262 -      article.removeHeader(Headers.LINES);
  46.263 -      article.removeHeader(Headers.BYTES);
  46.264 -
  46.265 -      article.setHeader("To", rcptAddress);
  46.266 -      //article.setHeader("Reply-To", listAddress);
  46.267 -
  46.268 -      if (Config.inst().get(Config.MLSEND_RW_SENDER, false))
  46.269 -      {
  46.270 -        rewriteSenderAddress(article); // Set the SENDER address
  46.271 -      }
  46.272 -
  46.273 -      SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
  46.274 -      smtpTransport.send(article, smtpFrom, rcptAddress);
  46.275 -      smtpTransport.close();
  46.276 -
  46.277 -      Stats.getInstance().mailGatewayed(group);
  46.278 -      Log.get().info("MLGateway: Mail " + article.getHeader("Subject")[0]
  46.279 -        + " was delivered to " + rcptAddress + ".");
  46.280 -    }
  46.281 -  }
  46.282 -  
  46.283 -  /**
  46.284 -   * Sets the SENDER header of the given MimeMessage. This might be necessary
  46.285 -   * for moderated groups that does not allow the "normal" FROM sender.
  46.286 -   * @param msg
  46.287 -   * @throws javax.mail.MessagingException
  46.288 -   */
  46.289 -  private static void rewriteSenderAddress(Article msg)
  46.290 -    throws MessagingException
  46.291 -  {
  46.292 -    String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
  46.293 -
  46.294 -    if(mlAddress != null)
  46.295 -    {
  46.296 -      msg.setHeader(Headers.SENDER, mlAddress);
  46.297 -    }
  46.298 -    else
  46.299 -    {
  46.300 -      throw new MessagingException("Cannot rewrite SENDER header!");
  46.301 -    }
  46.302 -  }
  46.303 -  
  46.304 -}
    47.1 --- a/org/sonews/mlgw/MailPoller.java	Sun Aug 29 17:04:25 2010 +0200
    47.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    47.3 @@ -1,151 +0,0 @@
    47.4 -/*
    47.5 - *   SONEWS News Server
    47.6 - *   see AUTHORS for the list of contributors
    47.7 - *
    47.8 - *   This program is free software: you can redistribute it and/or modify
    47.9 - *   it under the terms of the GNU General Public License as published by
   47.10 - *   the Free Software Foundation, either version 3 of the License, or
   47.11 - *   (at your option) any later version.
   47.12 - *
   47.13 - *   This program is distributed in the hope that it will be useful,
   47.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   47.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   47.16 - *   GNU General Public License for more details.
   47.17 - *
   47.18 - *   You should have received a copy of the GNU General Public License
   47.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   47.20 - */
   47.21 -
   47.22 -package org.sonews.mlgw;
   47.23 -
   47.24 -import java.util.Properties;
   47.25 -import javax.mail.AuthenticationFailedException;
   47.26 -import javax.mail.Authenticator;
   47.27 -import javax.mail.Flags.Flag;
   47.28 -import javax.mail.Folder;
   47.29 -import javax.mail.Message;
   47.30 -import javax.mail.MessagingException;
   47.31 -import javax.mail.NoSuchProviderException;
   47.32 -import javax.mail.PasswordAuthentication;
   47.33 -import javax.mail.Session;
   47.34 -import javax.mail.Store;
   47.35 -import org.sonews.config.Config;
   47.36 -import org.sonews.daemon.AbstractDaemon;
   47.37 -import org.sonews.util.Log;
   47.38 -import org.sonews.util.Stats;
   47.39 -
   47.40 -/**
   47.41 - * Daemon polling for new mails in a POP3 account to be delivered to newsgroups.
   47.42 - * @author Christian Lins
   47.43 - * @since sonews/0.5.0
   47.44 - */
   47.45 -public class MailPoller extends AbstractDaemon
   47.46 -{
   47.47 -
   47.48 -  static class PasswordAuthenticator extends Authenticator
   47.49 -  {
   47.50 -    
   47.51 -    @Override
   47.52 -    public PasswordAuthentication getPasswordAuthentication()
   47.53 -    {
   47.54 -      final String username = 
   47.55 -        Config.inst().get(Config.MLPOLL_USER, "user");
   47.56 -      final String password = 
   47.57 -        Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
   47.58 -
   47.59 -      return new PasswordAuthentication(username, password);
   47.60 -    }
   47.61 -    
   47.62 -  }
   47.63 -  
   47.64 -  @Override
   47.65 -  public void run()
   47.66 -  {
   47.67 -    Log.get().info("Starting Mailinglist Poller...");
   47.68 -    int errors = 0;
   47.69 -    while(isRunning())
   47.70 -    {
   47.71 -      try
   47.72 -      {
   47.73 -        // Wait some time between runs. At the beginning has advantages,
   47.74 -        // because the wait is not skipped if an exception occurs.
   47.75 -        Thread.sleep(60000 * (errors + 1)); // one minute * errors
   47.76 -        
   47.77 -        final String host     = 
   47.78 -          Config.inst().get(Config.MLPOLL_HOST, "samplehost");
   47.79 -        final String username = 
   47.80 -          Config.inst().get(Config.MLPOLL_USER, "user");
   47.81 -        final String password = 
   47.82 -          Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
   47.83 -        
   47.84 -        Stats.getInstance().mlgwRunStart();
   47.85 -        
   47.86 -        // Create empty properties
   47.87 -        Properties props = System.getProperties();
   47.88 -        props.put("mail.pop3.host", host);
   47.89 -        props.put("mail.mime.address.strict", "false");
   47.90 -
   47.91 -        // Get session
   47.92 -        Session session = Session.getInstance(props);
   47.93 -
   47.94 -        // Get the store
   47.95 -        Store store = session.getStore("pop3");
   47.96 -        store.connect(host, 110, username, password);
   47.97 -
   47.98 -        // Get folder
   47.99 -        Folder folder = store.getFolder("INBOX");
  47.100 -        folder.open(Folder.READ_WRITE);
  47.101 -
  47.102 -        // Get directory
  47.103 -        Message[] messages = folder.getMessages();
  47.104 -
  47.105 -        // Dispatch messages and delete it afterwards on the inbox
  47.106 -        for(Message message : messages)
  47.107 -        {
  47.108 -          if(Dispatcher.toGroup(message)
  47.109 -            || Config.inst().get(Config.MLPOLL_DELETEUNKNOWN, false))
  47.110 -          {
  47.111 -            // Delete the message
  47.112 -            message.setFlag(Flag.DELETED, true);
  47.113 -          }
  47.114 -        }
  47.115 -
  47.116 -        // Close connection 
  47.117 -        folder.close(true); // true to expunge deleted messages
  47.118 -        store.close();
  47.119 -        errors = 0;
  47.120 -        
  47.121 -        Stats.getInstance().mlgwRunEnd();
  47.122 -      }
  47.123 -      catch(NoSuchProviderException ex)
  47.124 -      {
  47.125 -        Log.get().severe(ex.toString());
  47.126 -        shutdown();
  47.127 -      }
  47.128 -      catch(AuthenticationFailedException ex)
  47.129 -      {
  47.130 -        // AuthentificationFailedException may be thrown if credentials are
  47.131 -        // bad or if the Mailbox is in use (locked).
  47.132 -        ex.printStackTrace();
  47.133 -        errors = errors < 5 ? errors + 1 : errors;
  47.134 -      }
  47.135 -      catch(InterruptedException ex)
  47.136 -      {
  47.137 -        System.out.println("sonews: " + this + " returns: " + ex);
  47.138 -        return;
  47.139 -      }
  47.140 -      catch(MessagingException ex)
  47.141 -      {
  47.142 -        ex.printStackTrace();
  47.143 -        errors = errors < 5 ? errors + 1 : errors;
  47.144 -      }
  47.145 -      catch(Exception ex)
  47.146 -      {
  47.147 -        ex.printStackTrace();
  47.148 -        errors = errors < 5 ? errors + 1 : errors;
  47.149 -      }
  47.150 -    }
  47.151 -    Log.get().severe("MailPoller exited.");
  47.152 -  }
  47.153 -  
  47.154 -}
    48.1 --- a/org/sonews/mlgw/SMTPTransport.java	Sun Aug 29 17:04:25 2010 +0200
    48.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    48.3 @@ -1,133 +0,0 @@
    48.4 -/*
    48.5 - *   SONEWS News Server
    48.6 - *   see AUTHORS for the list of contributors
    48.7 - *
    48.8 - *   This program is free software: you can redistribute it and/or modify
    48.9 - *   it under the terms of the GNU General Public License as published by
   48.10 - *   the Free Software Foundation, either version 3 of the License, or
   48.11 - *   (at your option) any later version.
   48.12 - *
   48.13 - *   This program is distributed in the hope that it will be useful,
   48.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   48.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   48.16 - *   GNU General Public License for more details.
   48.17 - *
   48.18 - *   You should have received a copy of the GNU General Public License
   48.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   48.20 - */
   48.21 -
   48.22 -package org.sonews.mlgw;
   48.23 -
   48.24 -import java.io.BufferedOutputStream;
   48.25 -import java.io.BufferedReader;
   48.26 -import java.io.IOException;
   48.27 -import java.io.InputStreamReader;
   48.28 -import java.net.Socket;
   48.29 -import java.net.UnknownHostException;
   48.30 -import org.sonews.config.Config;
   48.31 -import org.sonews.storage.Article;
   48.32 -import org.sonews.util.io.ArticleInputStream;
   48.33 -
   48.34 -/**
   48.35 - * Connects to a SMTP server and sends a given Article to it.
   48.36 - * @author Christian Lins
   48.37 - * @since sonews/1.0
   48.38 - */
   48.39 -class SMTPTransport
   48.40 -{
   48.41 -
   48.42 -  protected BufferedReader       in;
   48.43 -  protected BufferedOutputStream out;
   48.44 -  protected Socket               socket;
   48.45 -
   48.46 -  public SMTPTransport(String host, int port)
   48.47 -    throws IOException, UnknownHostException
   48.48 -  {
   48.49 -    socket = new Socket(host, port);
   48.50 -    this.in  = new BufferedReader(new InputStreamReader(socket.getInputStream()));
   48.51 -    this.out = new BufferedOutputStream(socket.getOutputStream());
   48.52 -
   48.53 -    // Read helo from server
   48.54 -    String line = this.in.readLine();
   48.55 -    if(line == null || !line.startsWith("220 "))
   48.56 -    {
   48.57 -      throw new IOException("Invalid helo from server: " + line);
   48.58 -    }
   48.59 -
   48.60 -    // Send HELO to server
   48.61 -    this.out.write(
   48.62 -      ("HELO " + Config.inst().get(Config.HOSTNAME, "localhost") + "\r\n").getBytes("UTF-8"));
   48.63 -    this.out.flush();
   48.64 -    line = this.in.readLine();
   48.65 -    if(line == null || !line.startsWith("250 "))
   48.66 -    {
   48.67 -      throw new IOException("Unexpected reply: " + line);
   48.68 -    }
   48.69 -  }
   48.70 -
   48.71 -  public SMTPTransport(String host)
   48.72 -    throws IOException
   48.73 -  {
   48.74 -    this(host, 25);
   48.75 -  }
   48.76 -
   48.77 -  public void close()
   48.78 -    throws IOException
   48.79 -  {
   48.80 -    this.out.write("QUIT".getBytes("UTF-8"));
   48.81 -    this.out.flush();
   48.82 -    this.in.readLine();
   48.83 -
   48.84 -    this.socket.close();
   48.85 -  }
   48.86 -
   48.87 -  public void send(Article article, String mailFrom, String rcptTo)
   48.88 -    throws IOException
   48.89 -  {
   48.90 -    assert(article != null);
   48.91 -    assert(mailFrom != null);
   48.92 -    assert(rcptTo != null);
   48.93 -
   48.94 -    this.out.write(("MAIL FROM: " + mailFrom).getBytes("UTF-8"));
   48.95 -    this.out.flush();
   48.96 -    String line = this.in.readLine();
   48.97 -    if(line == null || !line.startsWith("250 "))
   48.98 -    {
   48.99 -      throw new IOException("Unexpected reply: " + line);
  48.100 -    }
  48.101 -
  48.102 -    this.out.write(("RCPT TO: " + rcptTo).getBytes("UTF-8"));
  48.103 -    this.out.flush();
  48.104 -    line  = this.in.readLine();
  48.105 -    if(line == null || !line.startsWith("250 "))
  48.106 -    {
  48.107 -      throw new IOException("Unexpected reply: " + line);
  48.108 -    }
  48.109 -
  48.110 -    this.out.write("DATA".getBytes("UTF-8"));
  48.111 -    this.out.flush();
  48.112 -    line = this.in.readLine();
  48.113 -    if(line == null || !line.startsWith("354 "))
  48.114 -    {
  48.115 -      throw new IOException("Unexpected reply: " + line);
  48.116 -    }
  48.117 -
  48.118 -    ArticleInputStream   artStream = new ArticleInputStream(article);
  48.119 -    for(int b = artStream.read(); b >= 0; b = artStream.read())
  48.120 -    {
  48.121 -      this.out.write(b);
  48.122 -    }
  48.123 -
  48.124 -    // Flush the binary stream; important because otherwise the output
  48.125 -    // will be mixed with the PrintWriter.
  48.126 -    this.out.flush();
  48.127 -    this.out.write("\r\n.\r\n".getBytes("UTF-8"));
  48.128 -    this.out.flush();
  48.129 -    line = this.in.readLine();
  48.130 -    if(line == null || !line.startsWith("250 "))
  48.131 -    {
  48.132 -      throw new IOException("Unexpected reply: " + line);
  48.133 -    }
  48.134 -  }
  48.135 -
  48.136 -}
    49.1 --- a/org/sonews/mlgw/package.html	Sun Aug 29 17:04:25 2010 +0200
    49.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    49.3 @@ -1,1 +0,0 @@
    49.4 -Contains classes of the Mailinglist Gateway.
    49.5 \ No newline at end of file
    50.1 --- a/org/sonews/plugin/Plugin.java	Sun Aug 29 17:04:25 2010 +0200
    50.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    50.3 @@ -1,42 +0,0 @@
    50.4 -/*
    50.5 - *   SONEWS News Server
    50.6 - *   see AUTHORS for the list of contributors
    50.7 - *
    50.8 - *   This program is free software: you can redistribute it and/or modify
    50.9 - *   it under the terms of the GNU General Public License as published by
   50.10 - *   the Free Software Foundation, either version 3 of the License, or
   50.11 - *   (at your option) any later version.
   50.12 - *
   50.13 - *   This program is distributed in the hope that it will be useful,
   50.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   50.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   50.16 - *   GNU General Public License for more details.
   50.17 - *
   50.18 - *   You should have received a copy of the GNU General Public License
   50.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   50.20 - */
   50.21 -
   50.22 -package org.sonews.plugin;
   50.23 -
   50.24 -/**
   50.25 - * A generic Plugin for sonews. Implementing classes do not really add new
   50.26 - * functionality to sonews but can use this interface as convenient procedure
   50.27 - * for installing functionality plugins, e.g. Command-Plugins or Storage-Plugins.
   50.28 - * @author Christian Lins
   50.29 - * @since sonews/1.1
   50.30 - */
   50.31 -public interface Plugin
   50.32 -{
   50.33 -
   50.34 -  /**
   50.35 -   * Called when the Plugin is loaded by sonews. This method can be used
   50.36 -   * by implementing classes to install additional or required plugins.
   50.37 -   */
   50.38 -  void load();
   50.39 -
   50.40 -  /**
   50.41 -   * Called when the Plugin is unloaded by sonews.
   50.42 -   */
   50.43 -  void unload();
   50.44 -
   50.45 -}
    51.1 --- a/org/sonews/storage/Article.java	Sun Aug 29 17:04:25 2010 +0200
    51.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    51.3 @@ -1,253 +0,0 @@
    51.4 -/*
    51.5 - *   SONEWS News Server
    51.6 - *   see AUTHORS for the list of contributors
    51.7 - *
    51.8 - *   This program is free software: you can redistribute it and/or modify
    51.9 - *   it under the terms of the GNU General Public License as published by
   51.10 - *   the Free Software Foundation, either version 3 of the License, or
   51.11 - *   (at your option) any later version.
   51.12 - *
   51.13 - *   This program is distributed in the hope that it will be useful,
   51.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   51.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   51.16 - *   GNU General Public License for more details.
   51.17 - *
   51.18 - *   You should have received a copy of the GNU General Public License
   51.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   51.20 - */
   51.21 -
   51.22 -package org.sonews.storage;
   51.23 -
   51.24 -import java.io.ByteArrayInputStream;
   51.25 -import java.io.ByteArrayOutputStream;
   51.26 -import java.io.IOException;
   51.27 -import java.security.MessageDigest;
   51.28 -import java.security.NoSuchAlgorithmException;
   51.29 -import java.util.UUID;
   51.30 -import java.util.ArrayList;
   51.31 -import java.util.Enumeration;
   51.32 -import java.util.List;
   51.33 -import javax.mail.Header;
   51.34 -import javax.mail.Message;
   51.35 -import javax.mail.MessagingException;
   51.36 -import javax.mail.internet.InternetHeaders;
   51.37 -import org.sonews.config.Config;
   51.38 -
   51.39 -/**
   51.40 - * Represents a newsgroup article.
   51.41 - * @author Christian Lins
   51.42 - * @author Denis Schwerdel
   51.43 - * @since n3tpd/0.1
   51.44 - */
   51.45 -public class Article extends ArticleHead
   51.46 -{
   51.47 -  
   51.48 -  /**
   51.49 -   * Loads the Article identified by the given ID from the JDBCDatabase.
   51.50 -   * @param messageID
   51.51 -   * @return null if Article is not found or if an error occurred.
   51.52 -   */
   51.53 -  public static Article getByMessageID(final String messageID)
   51.54 -  {
   51.55 -    try
   51.56 -    {
   51.57 -      return StorageManager.current().getArticle(messageID);
   51.58 -    }
   51.59 -    catch(StorageBackendException ex)
   51.60 -    {
   51.61 -      ex.printStackTrace();
   51.62 -      return null;
   51.63 -    }
   51.64 -  }
   51.65 -  
   51.66 -  private byte[] body       = new byte[0];
   51.67 -  
   51.68 -  /**
   51.69 -   * Default constructor.
   51.70 -   */
   51.71 -  public Article()
   51.72 -  {
   51.73 -  }
   51.74 -  
   51.75 -  /**
   51.76 -   * Creates a new Article object using the date from the given
   51.77 -   * raw data.
   51.78 -   */
   51.79 -  public Article(String headers, byte[] body)
   51.80 -  {
   51.81 -    try
   51.82 -    {
   51.83 -      this.body  = body;
   51.84 -
   51.85 -      // Parse the header
   51.86 -      this.headers = new InternetHeaders(
   51.87 -        new ByteArrayInputStream(headers.getBytes()));
   51.88 -      
   51.89 -      this.headerSrc = headers;
   51.90 -    }
   51.91 -    catch(MessagingException ex)
   51.92 -    {
   51.93 -      ex.printStackTrace();
   51.94 -    }
   51.95 -  }
   51.96 -
   51.97 -  /**
   51.98 -   * Creates an Article instance using the data from the javax.mail.Message
   51.99 -   * object. This constructor is called by the Mailinglist gateway.
  51.100 -   * @see javax.mail.Message
  51.101 -   * @param msg
  51.102 -   * @throws IOException
  51.103 -   * @throws MessagingException
  51.104 -   */
  51.105 -  public Article(final Message msg)
  51.106 -    throws IOException, MessagingException
  51.107 -  {
  51.108 -    this.headers = new InternetHeaders();
  51.109 -
  51.110 -    for(Enumeration e = msg.getAllHeaders() ; e.hasMoreElements();) 
  51.111 -    {
  51.112 -      final Header header = (Header)e.nextElement();
  51.113 -      this.headers.addHeader(header.getName(), header.getValue());
  51.114 -    }
  51.115 -
  51.116 -	// Reads the raw byte body using Message.writeTo(OutputStream out)
  51.117 -	this.body = readContent(msg);
  51.118 -    
  51.119 -    // Validate headers
  51.120 -    validateHeaders();
  51.121 -  }
  51.122 -
  51.123 -  /**
  51.124 -   * Reads from the given Message into a byte array.
  51.125 -   * @param in
  51.126 -   * @return
  51.127 -   * @throws IOException
  51.128 -   */
  51.129 -  private byte[] readContent(Message in)
  51.130 -    throws IOException, MessagingException
  51.131 -  {
  51.132 -    ByteArrayOutputStream out = new ByteArrayOutputStream();
  51.133 -    in.writeTo(out);
  51.134 -    return out.toByteArray();
  51.135 -  }
  51.136 -
  51.137 -  /**
  51.138 -   * Removes the header identified by the given key.
  51.139 -   * @param headerKey
  51.140 -   */
  51.141 -  public void removeHeader(final String headerKey)
  51.142 -  {
  51.143 -    this.headers.removeHeader(headerKey);
  51.144 -    this.headerSrc = null;
  51.145 -  }
  51.146 -
  51.147 -  /**
  51.148 -   * Generates a message id for this article and sets it into
  51.149 -   * the header object. You have to update the JDBCDatabase manually to make this
  51.150 -   * change persistent.
  51.151 -   * Note: a Message-ID should never be changed and only generated once.
  51.152 -   */
  51.153 -  private String generateMessageID()
  51.154 -  {
  51.155 -    String randomString;
  51.156 -    MessageDigest md5;
  51.157 -    try
  51.158 -    {
  51.159 -      md5 = MessageDigest.getInstance("MD5");
  51.160 -      md5.reset();
  51.161 -      md5.update(getBody());
  51.162 -      md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
  51.163 -      md5.update(getHeader(Headers.FROM)[0].getBytes());
  51.164 -      byte[] result = md5.digest();
  51.165 -      StringBuffer hexString = new StringBuffer();
  51.166 -      for (int i = 0; i < result.length; i++)
  51.167 -      {
  51.168 -        hexString.append(Integer.toHexString(0xFF & result[i]));
  51.169 -      }
  51.170 -      randomString = hexString.toString();
  51.171 -    }
  51.172 -    catch (NoSuchAlgorithmException e)
  51.173 -    {
  51.174 -      e.printStackTrace();
  51.175 -      randomString = UUID.randomUUID().toString();
  51.176 -    }
  51.177 -    String msgID = "<" + randomString + "@"
  51.178 -        + Config.inst().get(Config.HOSTNAME, "localhost") + ">";
  51.179 -    
  51.180 -    this.headers.setHeader(Headers.MESSAGE_ID, msgID);
  51.181 -    
  51.182 -    return msgID;
  51.183 -  }
  51.184 -
  51.185 -  /**
  51.186 -   * Returns the body string.
  51.187 -   */
  51.188 -  public byte[] getBody()
  51.189 -  {
  51.190 -    return body;
  51.191 -  }
  51.192 -  
  51.193 -  /**
  51.194 -   * @return Numerical IDs of the newsgroups this Article belongs to.
  51.195 -   */
  51.196 -  public List<Group> getGroups()
  51.197 -  {
  51.198 -    String[]         groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
  51.199 -    ArrayList<Group> groups     = new ArrayList<Group>();
  51.200 -
  51.201 -    try
  51.202 -    {
  51.203 -      for(String newsgroup : groupnames)
  51.204 -      {
  51.205 -        newsgroup = newsgroup.trim();
  51.206 -        Group group = StorageManager.current().getGroup(newsgroup);
  51.207 -        if(group != null &&         // If the server does not provide the group, ignore it
  51.208 -          !groups.contains(group))  // Yes, there may be duplicates
  51.209 -        {
  51.210 -          groups.add(group);
  51.211 -        }
  51.212 -      }
  51.213 -    }
  51.214 -    catch(StorageBackendException ex)
  51.215 -    {
  51.216 -      ex.printStackTrace();
  51.217 -      return null;
  51.218 -    }
  51.219 -    return groups;
  51.220 -  }
  51.221 -
  51.222 -  public void setBody(byte[] body)
  51.223 -  {
  51.224 -    this.body = body;
  51.225 -  }
  51.226 -  
  51.227 -  /**
  51.228 -   * 
  51.229 -   * @param groupname Name(s) of newsgroups
  51.230 -   */
  51.231 -  public void setGroup(String groupname)
  51.232 -  {
  51.233 -    this.headers.setHeader(Headers.NEWSGROUPS, groupname);
  51.234 -  }
  51.235 -
  51.236 -  /**
  51.237 -   * Returns the Message-ID of this Article. If the appropriate header
  51.238 -   * is empty, a new Message-ID is created.
  51.239 -   * @return Message-ID of this Article.
  51.240 -   */
  51.241 -  public String getMessageID()
  51.242 -  {
  51.243 -    String[] msgID = getHeader(Headers.MESSAGE_ID);
  51.244 -    return msgID[0].equals("") ? generateMessageID() : msgID[0];
  51.245 -  }
  51.246 -  
  51.247 -  /**
  51.248 -   * @return String containing the Message-ID.
  51.249 -   */
  51.250 -  @Override
  51.251 -  public String toString()
  51.252 -  {
  51.253 -    return getMessageID();
  51.254 -  }
  51.255 -
  51.256 -}
    52.1 --- a/org/sonews/storage/ArticleHead.java	Sun Aug 29 17:04:25 2010 +0200
    52.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    52.3 @@ -1,161 +0,0 @@
    52.4 -/*
    52.5 - *   SONEWS News Server
    52.6 - *   see AUTHORS for the list of contributors
    52.7 - *
    52.8 - *   This program is free software: you can redistribute it and/or modify
    52.9 - *   it under the terms of the GNU General Public License as published by
   52.10 - *   the Free Software Foundation, either version 3 of the License, or
   52.11 - *   (at your option) any later version.
   52.12 - *
   52.13 - *   This program is distributed in the hope that it will be useful,
   52.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   52.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   52.16 - *   GNU General Public License for more details.
   52.17 - *
   52.18 - *   You should have received a copy of the GNU General Public License
   52.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   52.20 - */
   52.21 -
   52.22 -package org.sonews.storage;
   52.23 -
   52.24 -import java.io.ByteArrayInputStream;
   52.25 -import java.util.Enumeration;
   52.26 -import javax.mail.Header;
   52.27 -import javax.mail.MessagingException;
   52.28 -import javax.mail.internet.InternetHeaders;
   52.29 -import javax.mail.internet.MimeUtility;
   52.30 -import org.sonews.config.Config;
   52.31 -
   52.32 -/**
   52.33 - * An article with no body only headers.
   52.34 - * @author Christian Lins
   52.35 - * @since sonews/0.5.0
   52.36 - */
   52.37 -public class ArticleHead 
   52.38 -{
   52.39 -
   52.40 -  protected InternetHeaders headers   = null;
   52.41 -  protected String          headerSrc = null;
   52.42 -  
   52.43 -  protected ArticleHead()
   52.44 -  {
   52.45 -  }
   52.46 -  
   52.47 -  public ArticleHead(String headers)
   52.48 -  {
   52.49 -    try
   52.50 -    {
   52.51 -      // Parse the header
   52.52 -      this.headers = new InternetHeaders(
   52.53 -          new ByteArrayInputStream(headers.getBytes()));
   52.54 -    }
   52.55 -    catch(MessagingException ex)
   52.56 -    {
   52.57 -      ex.printStackTrace();
   52.58 -    }
   52.59 -  }
   52.60 -  
   52.61 -  /**
   52.62 -   * Returns the header field with given name.
   52.63 -   * @param name Name of the header field(s).
   52.64 -   * @param returnNull If set to true, this method will return null instead
   52.65 -   *                   of an empty array if there is no header field found.
   52.66 -   * @return Header values or empty string.
   52.67 -   */
   52.68 -  public String[] getHeader(String name, boolean returnNull)
   52.69 -  {
   52.70 -    String[] ret = this.headers.getHeader(name);
   52.71 -    if(ret == null && !returnNull)
   52.72 -    {
   52.73 -      ret = new String[]{""};
   52.74 -    }
   52.75 -    return ret;
   52.76 -  }
   52.77 -
   52.78 -  public String[] getHeader(String name)
   52.79 -  {
   52.80 -    return getHeader(name, false);
   52.81 -  }
   52.82 -  
   52.83 -  /**
   52.84 -   * Sets the header value identified through the header name.
   52.85 -   * @param name
   52.86 -   * @param value
   52.87 -   */
   52.88 -  public void setHeader(String name, String value)
   52.89 -  {
   52.90 -    this.headers.setHeader(name, value);
   52.91 -    this.headerSrc = null;
   52.92 -  }
   52.93 -
   52.94 -    public Enumeration getAllHeaders()
   52.95 -  {
   52.96 -    return this.headers.getAllHeaders();
   52.97 -  }
   52.98 -
   52.99 -  /**
  52.100 -   * @return Header source code of this Article.
  52.101 -   */
  52.102 -  public String getHeaderSource()
  52.103 -  {
  52.104 -    if(this.headerSrc != null)
  52.105 -    {
  52.106 -      return this.headerSrc;
  52.107 -    }
  52.108 -
  52.109 -    StringBuffer buf = new StringBuffer();
  52.110 -
  52.111 -    for(Enumeration en = this.headers.getAllHeaders(); en.hasMoreElements();)
  52.112 -    {
  52.113 -      Header entry = (Header)en.nextElement();
  52.114 -
  52.115 -      String value = entry.getValue().replaceAll("[\r\n]", " ");
  52.116 -      buf.append(entry.getName());
  52.117 -      buf.append(": ");
  52.118 -      buf.append(MimeUtility.fold(entry.getName().length() + 2, value));
  52.119 -
  52.120 -      if(en.hasMoreElements())
  52.121 -      {
  52.122 -        buf.append("\r\n");
  52.123 -      }
  52.124 -    }
  52.125 -
  52.126 -    this.headerSrc = buf.toString();
  52.127 -    return this.headerSrc;
  52.128 -  }
  52.129 -
  52.130 -  /**
  52.131 -   * Sets the headers of this Article. If headers contain no
  52.132 -   * Message-Id a new one is created.
  52.133 -   * @param headers
  52.134 -   */
  52.135 -  public void setHeaders(InternetHeaders headers)
  52.136 -  {
  52.137 -    this.headers   = headers;
  52.138 -    this.headerSrc = null;
  52.139 -    validateHeaders();
  52.140 -  }
  52.141 -
  52.142 -  /**
  52.143 -   * Checks some headers for their validity and generates an
  52.144 -   * appropriate Path-header for this host if not yet existing.
  52.145 -   * This method is called by some Article constructors and the
  52.146 -   * method setHeaders().
  52.147 -   * @return true if something on the headers was changed.
  52.148 -   */
  52.149 -  protected void validateHeaders()
  52.150 -  {
  52.151 -    // Check for valid Path-header
  52.152 -    final String path = getHeader(Headers.PATH)[0];
  52.153 -    final String host = Config.inst().get(Config.HOSTNAME, "localhost");
  52.154 -    if(!path.startsWith(host))
  52.155 -    {
  52.156 -      StringBuffer pathBuf = new StringBuffer();
  52.157 -      pathBuf.append(host);
  52.158 -      pathBuf.append('!');
  52.159 -      pathBuf.append(path);
  52.160 -      this.headers.setHeader(Headers.PATH, pathBuf.toString());
  52.161 -    }
  52.162 -  }
  52.163 -  
  52.164 -}
    53.1 --- a/org/sonews/storage/Channel.java	Sun Aug 29 17:04:25 2010 +0200
    53.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    53.3 @@ -1,111 +0,0 @@
    53.4 -/*
    53.5 - *   SONEWS News Server
    53.6 - *   see AUTHORS for the list of contributors
    53.7 - *
    53.8 - *   This program is free software: you can redistribute it and/or modify
    53.9 - *   it under the terms of the GNU General Public License as published by
   53.10 - *   the Free Software Foundation, either version 3 of the License, or
   53.11 - *   (at your option) any later version.
   53.12 - *
   53.13 - *   This program is distributed in the hope that it will be useful,
   53.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   53.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   53.16 - *   GNU General Public License for more details.
   53.17 - *
   53.18 - *   You should have received a copy of the GNU General Public License
   53.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   53.20 - */
   53.21 -
   53.22 -package org.sonews.storage;
   53.23 -
   53.24 -import java.util.ArrayList;
   53.25 -import java.util.List;
   53.26 -import org.sonews.util.Pair;
   53.27 -
   53.28 -/**
   53.29 - * A logical communication Channel is the a generic structural element for sets
   53.30 - * of messages; e.g. a Newsgroup for a set of Articles.
   53.31 - * A Channel can either be a real set of messages or an aggregated set of
   53.32 - * several subsets.
   53.33 - * @author Christian Lins
   53.34 - * @since sonews/1.0
   53.35 - */
   53.36 -public abstract class Channel
   53.37 -{
   53.38 -
   53.39 -  /**
   53.40 -   * If this flag is set the Group is no real newsgroup but a mailing list
   53.41 -   * mirror. In that case every posting and receiving mails must go through
   53.42 -   * the mailing list gateway.
   53.43 -   */
   53.44 -  public static final int MAILINGLIST = 0x1;
   53.45 -
   53.46 -  /**
   53.47 -   * If this flag is set the Group is marked as readonly and the posting
   53.48 -   * is prohibited. This can be useful for groups that are synced only in
   53.49 -   * one direction.
   53.50 -   */
   53.51 -  public static final int READONLY    = 0x2;
   53.52 -
   53.53 -  /**
   53.54 -   * If this flag is set the Group is marked as deleted and must not occur
   53.55 -   * in any output. The deletion is done lazily by a low priority daemon.
   53.56 -   */
   53.57 -  public static final int DELETED     = 0x80;
   53.58 -
   53.59 -  public static List<Channel> getAll()
   53.60 -  {
   53.61 -    List<Channel> all = new ArrayList<Channel>();
   53.62 -
   53.63 -    /*List<Channel> agroups = AggregatedGroup.getAll();
   53.64 -    if(agroups != null)
   53.65 -    {
   53.66 -      all.addAll(agroups);
   53.67 -    }*/
   53.68 -
   53.69 -    List<Channel> groups = Group.getAll();
   53.70 -    if(groups != null)
   53.71 -    {
   53.72 -      all.addAll(groups);
   53.73 -    }
   53.74 -
   53.75 -    return all;
   53.76 -  }
   53.77 -
   53.78 -  public static Channel getByName(String name)
   53.79 -    throws StorageBackendException
   53.80 -  {
   53.81 -    return StorageManager.current().getGroup(name);
   53.82 -  }
   53.83 -
   53.84 -  public abstract Article getArticle(long idx)
   53.85 -    throws StorageBackendException;
   53.86 -
   53.87 -  public abstract List<Pair<Long, ArticleHead>> getArticleHeads(
   53.88 -    final long first, final long last)
   53.89 -    throws StorageBackendException;
   53.90 -
   53.91 -  public abstract List<Long> getArticleNumbers()
   53.92 -    throws StorageBackendException;
   53.93 -
   53.94 -  public abstract long getFirstArticleNumber()
   53.95 -    throws StorageBackendException;
   53.96 -
   53.97 -  public abstract long getIndexOf(Article art)
   53.98 -    throws StorageBackendException;
   53.99 -
  53.100 -  public abstract long getInternalID();
  53.101 -
  53.102 -  public abstract long getLastArticleNumber()
  53.103 -    throws StorageBackendException;
  53.104 -
  53.105 -  public abstract String getName();
  53.106 -  
  53.107 -  public abstract long getPostingsCount()
  53.108 -    throws StorageBackendException;
  53.109 -
  53.110 -  public abstract boolean isDeleted();
  53.111 -
  53.112 -  public abstract boolean isWriteable();
  53.113 -
  53.114 -}
    54.1 --- a/org/sonews/storage/Group.java	Sun Aug 29 17:04:25 2010 +0200
    54.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    54.3 @@ -1,184 +0,0 @@
    54.4 -/*
    54.5 - *   SONEWS News Server
    54.6 - *   see AUTHORS for the list of contributors
    54.7 - *
    54.8 - *   This program is free software: you can redistribute it and/or modify
    54.9 - *   it under the terms of the GNU General Public License as published by
   54.10 - *   the Free Software Foundation, either version 3 of the License, or
   54.11 - *   (at your option) any later version.
   54.12 - *
   54.13 - *   This program is distributed in the hope that it will be useful,
   54.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   54.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   54.16 - *   GNU General Public License for more details.
   54.17 - *
   54.18 - *   You should have received a copy of the GNU General Public License
   54.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   54.20 - */
   54.21 -
   54.22 -package org.sonews.storage;
   54.23 -
   54.24 -import java.sql.SQLException;
   54.25 -import java.util.List;
   54.26 -import org.sonews.util.Log;
   54.27 -import org.sonews.util.Pair;
   54.28 -
   54.29 -/**
   54.30 - * Represents a logical Group within this newsserver.
   54.31 - * @author Christian Lins
   54.32 - * @since sonews/0.5.0
   54.33 - */
   54.34 -// TODO: This class should not be public!
   54.35 -public class Group extends Channel
   54.36 -{
   54.37 -  
   54.38 -  private long   id     = 0;
   54.39 -  private int    flags  = -1;
   54.40 -  private String name   = null;
   54.41 -
   54.42 -  /**
   54.43 -   * @return List of all groups this server handles.
   54.44 -   */
   54.45 -  public static List<Channel> getAll()
   54.46 -  {
   54.47 -    try
   54.48 -    {
   54.49 -      return StorageManager.current().getGroups();
   54.50 -    }
   54.51 -    catch(StorageBackendException ex)
   54.52 -    {
   54.53 -      Log.get().severe(ex.getMessage());
   54.54 -      return null;
   54.55 -    }
   54.56 -  }
   54.57 -  
   54.58 -  /**
   54.59 -   * @param name
   54.60 -   * @param id
   54.61 -   */
   54.62 -  public Group(final String name, final long id, final int flags)
   54.63 -  {
   54.64 -    this.id    = id;
   54.65 -    this.flags = flags;
   54.66 -    this.name  = name;
   54.67 -  }
   54.68 -
   54.69 -  @Override
   54.70 -  public boolean equals(Object obj)
   54.71 -  {
   54.72 -    if(obj instanceof Group)
   54.73 -    {
   54.74 -      return ((Group)obj).id == this.id;
   54.75 -    }
   54.76 -    else
   54.77 -    {
   54.78 -      return false;
   54.79 -    }
   54.80 -  }
   54.81 -
   54.82 -  public Article getArticle(long idx)
   54.83 -    throws StorageBackendException
   54.84 -  {
   54.85 -    return StorageManager.current().getArticle(idx, this.id);
   54.86 -  }
   54.87 -
   54.88 -  public List<Pair<Long, ArticleHead>> getArticleHeads(final long first, final long last)
   54.89 -    throws StorageBackendException
   54.90 -  {
   54.91 -    return StorageManager.current().getArticleHeads(this, first, last);
   54.92 -  }
   54.93 -  
   54.94 -  public List<Long> getArticleNumbers()
   54.95 -    throws StorageBackendException
   54.96 -  {
   54.97 -    return StorageManager.current().getArticleNumbers(id);
   54.98 -  }
   54.99 -
  54.100 -  public long getFirstArticleNumber()
  54.101 -    throws StorageBackendException
  54.102 -  {
  54.103 -    return StorageManager.current().getFirstArticleNumber(this);
  54.104 -  }
  54.105 -
  54.106 -  public int getFlags()
  54.107 -  {
  54.108 -    return this.flags;
  54.109 -  }
  54.110 -
  54.111 -  public long getIndexOf(Article art)
  54.112 -    throws StorageBackendException
  54.113 -  {
  54.114 -    return StorageManager.current().getArticleIndex(art, this);
  54.115 -  }
  54.116 -
  54.117 -  /**
  54.118 -   * Returns the group id.
  54.119 -   */
  54.120 -  public long getInternalID()
  54.121 -  {
  54.122 -    assert id > 0;
  54.123 -
  54.124 -    return id;
  54.125 -  }
  54.126 -
  54.127 -  public boolean isDeleted()
  54.128 -  {
  54.129 -    return (this.flags & DELETED) != 0;
  54.130 -  }
  54.131 -
  54.132 -  public boolean isMailingList()
  54.133 -  {
  54.134 -    return (this.flags & MAILINGLIST) != 0;
  54.135 -  }
  54.136 -
  54.137 -  public boolean isWriteable()
  54.138 -  {
  54.139 -    return true;
  54.140 -  }
  54.141 -
  54.142 -  public long getLastArticleNumber()
  54.143 -    throws StorageBackendException
  54.144 -  {
  54.145 -    return StorageManager.current().getLastArticleNumber(this);
  54.146 -  }
  54.147 -
  54.148 -  public String getName()
  54.149 -  {
  54.150 -    return name;
  54.151 -  }
  54.152 -
  54.153 -  /**
  54.154 -   * Performs this.flags |= flag to set a specified flag and updates the data
  54.155 -   * in the JDBCDatabase.
  54.156 -   * @param flag
  54.157 -   */
  54.158 -  public void setFlag(final int flag)
  54.159 -  {
  54.160 -    this.flags |= flag;
  54.161 -  }
  54.162 -
  54.163 -  public void setName(final String name)
  54.164 -  {
  54.165 -    this.name = name;
  54.166 -  }
  54.167 -
  54.168 -  /**
  54.169 -   * @return Number of posted articles in this group.
  54.170 -   * @throws java.sql.SQLException
  54.171 -   */
  54.172 -  public long getPostingsCount()
  54.173 -    throws StorageBackendException
  54.174 -  {
  54.175 -    return StorageManager.current().getPostingsCount(this.name);
  54.176 -  }
  54.177 -
  54.178 -  /**
  54.179 -   * Updates flags and name in the backend.
  54.180 -   */
  54.181 -  public void update()
  54.182 -    throws StorageBackendException
  54.183 -  {
  54.184 -    StorageManager.current().update(this);
  54.185 -  }
  54.186 -
  54.187 -}
    55.1 --- a/org/sonews/storage/Headers.java	Sun Aug 29 17:04:25 2010 +0200
    55.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    55.3 @@ -1,56 +0,0 @@
    55.4 -/*
    55.5 - *   SONEWS News Server
    55.6 - *   see AUTHORS for the list of contributors
    55.7 - *
    55.8 - *   This program is free software: you can redistribute it and/or modify
    55.9 - *   it under the terms of the GNU General Public License as published by
   55.10 - *   the Free Software Foundation, either version 3 of the License, or
   55.11 - *   (at your option) any later version.
   55.12 - *
   55.13 - *   This program is distributed in the hope that it will be useful,
   55.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   55.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   55.16 - *   GNU General Public License for more details.
   55.17 - *
   55.18 - *   You should have received a copy of the GNU General Public License
   55.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   55.20 - */
   55.21 -
   55.22 -package org.sonews.storage;
   55.23 -
   55.24 -/**
   55.25 - * Contains header constants. These header keys are no way complete but all
   55.26 - * headers that are relevant for sonews.
   55.27 - * @author Christian Lins
   55.28 - * @since sonews/0.5.0
   55.29 - */
   55.30 -public final class Headers
   55.31 -{
   55.32 -
   55.33 -  public static final String BYTES             = "bytes";
   55.34 -  public static final String CONTENT_TYPE      = "content-type";
   55.35 -  public static final String CONTROL           = "control";
   55.36 -  public static final String DATE              = "date";
   55.37 -  public static final String FROM              = "from";
   55.38 -  public static final String LINES             = "lines";
   55.39 -  public static final String LIST_POST         = "list-post";
   55.40 -  public static final String MESSAGE_ID        = "message-id";
   55.41 -  public static final String NEWSGROUPS        = "newsgroups";
   55.42 -  public static final String NNTP_POSTING_DATE = "nntp-posting-date";
   55.43 -  public static final String NNTP_POSTING_HOST = "nntp-posting-host";
   55.44 -  public static final String PATH              = "path";
   55.45 -  public static final String REFERENCES        = "references";
   55.46 -  public static final String REPLY_TO          = "reply-to";
   55.47 -  public static final String SENDER            = "sender";
   55.48 -  public static final String SUBJECT           = "subject";
   55.49 -  public static final String SUPERSEDES        = "subersedes";
   55.50 -  public static final String TO                = "to";
   55.51 -  public static final String X_COMPLAINTS_TO   = "x-complaints-to";
   55.52 -  public static final String X_LIST_POST       = "x-list-post";
   55.53 -  public static final String X_TRACE           = "x-trace";
   55.54 -  public static final String XREF              = "xref";
   55.55 -
   55.56 -  private Headers()
   55.57 -  {}
   55.58 -
   55.59 -}
    56.1 --- a/org/sonews/storage/Storage.java	Sun Aug 29 17:04:25 2010 +0200
    56.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    56.3 @@ -1,150 +0,0 @@
    56.4 -/*
    56.5 - *   SONEWS News Server
    56.6 - *   see AUTHORS for the list of contributors
    56.7 - *
    56.8 - *   This program is free software: you can redistribute it and/or modify
    56.9 - *   it under the terms of the GNU General Public License as published by
   56.10 - *   the Free Software Foundation, either version 3 of the License, or
   56.11 - *   (at your option) any later version.
   56.12 - *
   56.13 - *   This program is distributed in the hope that it will be useful,
   56.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   56.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   56.16 - *   GNU General Public License for more details.
   56.17 - *
   56.18 - *   You should have received a copy of the GNU General Public License
   56.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   56.20 - */
   56.21 -
   56.22 -package org.sonews.storage;
   56.23 -
   56.24 -import java.util.List;
   56.25 -import org.sonews.feed.Subscription;
   56.26 -import org.sonews.util.Pair;
   56.27 -
   56.28 -/**
   56.29 - * A generic storage backend interface.
   56.30 - * @author Christian Lins
   56.31 - * @since sonews/1.0
   56.32 - */
   56.33 -public interface Storage
   56.34 -{
   56.35 -
   56.36 -  /**
   56.37 -   * Stores the given Article in the storage.
   56.38 -   * @param art
   56.39 -   * @throws StorageBackendException
   56.40 -   */
   56.41 -  void addArticle(Article art)
   56.42 -    throws StorageBackendException;
   56.43 -
   56.44 -  void addEvent(long timestamp, int type, long groupID)
   56.45 -    throws StorageBackendException;
   56.46 -
   56.47 -  void addGroup(String groupname, int flags)
   56.48 -    throws StorageBackendException;
   56.49 -
   56.50 -  int countArticles()
   56.51 -    throws StorageBackendException;
   56.52 -
   56.53 -  int countGroups()
   56.54 -    throws StorageBackendException;
   56.55 -
   56.56 -  void delete(String messageID)
   56.57 -    throws StorageBackendException;
   56.58 -
   56.59 -  Article getArticle(String messageID)
   56.60 -    throws StorageBackendException;
   56.61 -
   56.62 -  Article getArticle(long articleIndex, long groupID)
   56.63 -    throws StorageBackendException;
   56.64 -
   56.65 -  List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first, long last)
   56.66 -    throws StorageBackendException;
   56.67 -
   56.68 -  List<Pair<Long, String>> getArticleHeaders(Channel channel, long start, long end,
   56.69 -    String header, String pattern)
   56.70 -    throws StorageBackendException;
   56.71 -
   56.72 -  long getArticleIndex(Article art, Group group)
   56.73 -    throws StorageBackendException;
   56.74 -
   56.75 -  List<Long> getArticleNumbers(long groupID)
   56.76 -    throws StorageBackendException;
   56.77 -
   56.78 -  String getConfigValue(String key)
   56.79 -    throws StorageBackendException;
   56.80 -
   56.81 -  int getEventsCount(int eventType, long startTimestamp, long endTimestamp,
   56.82 -    Channel channel)
   56.83 -    throws StorageBackendException;
   56.84 -
   56.85 -  double getEventsPerHour(int key, long gid)
   56.86 -    throws StorageBackendException;
   56.87 -
   56.88 -  int getFirstArticleNumber(Group group)
   56.89 -    throws StorageBackendException;
   56.90 -
   56.91 -  Group getGroup(String name)
   56.92 -    throws StorageBackendException;
   56.93 -
   56.94 -  List<Channel> getGroups()
   56.95 -    throws StorageBackendException;
   56.96 -
   56.97 -  /**
   56.98 -   * Retrieves the collection of groupnames that are associated with the
   56.99 -   * given list address.
  56.100 -   * @param inetaddress
  56.101 -   * @return
  56.102 -   * @throws StorageBackendException
  56.103 -   */
  56.104 -  List<String> getGroupsForList(String listAddress)
  56.105 -    throws StorageBackendException;
  56.106 -
  56.107 -  int getLastArticleNumber(Group group)
  56.108 -    throws StorageBackendException;
  56.109 -
  56.110 -  /**
  56.111 -   * Returns a list of email addresses that are related to the given
  56.112 -   * groupname. In most cases the list may contain only one entry.
  56.113 -   * @param groupname
  56.114 -   * @return
  56.115 -   * @throws StorageBackendException
  56.116 -   */
  56.117 -  List<String> getListsForGroup(String groupname)
  56.118 -    throws StorageBackendException;
  56.119 -
  56.120 -  String getOldestArticle()
  56.121 -    throws StorageBackendException;
  56.122 -
  56.123 -  int getPostingsCount(String groupname)
  56.124 -    throws StorageBackendException;
  56.125 -
  56.126 -  List<Subscription> getSubscriptions(int type)
  56.127 -    throws StorageBackendException;
  56.128 -
  56.129 -  boolean isArticleExisting(String messageID)
  56.130 -    throws StorageBackendException;
  56.131 -
  56.132 -  boolean isGroupExisting(String groupname)
  56.133 -    throws StorageBackendException;
  56.134 -
  56.135 -  void purgeGroup(Group group)
  56.136 -    throws StorageBackendException;
  56.137 -
  56.138 -  void setConfigValue(String key, String value)
  56.139 -    throws StorageBackendException;
  56.140 -
  56.141 -  /**
  56.142 -   * Updates headers and channel references of the given article.
  56.143 -   * @param article
  56.144 -   * @return
  56.145 -   * @throws StorageBackendException
  56.146 -   */
  56.147 -  boolean update(Article article)
  56.148 -    throws StorageBackendException;
  56.149 -
  56.150 -  boolean update(Group group)
  56.151 -    throws StorageBackendException;
  56.152 -
  56.153 -}
    57.1 --- a/org/sonews/storage/StorageBackendException.java	Sun Aug 29 17:04:25 2010 +0200
    57.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    57.3 @@ -1,39 +0,0 @@
    57.4 -/*
    57.5 - *   SONEWS News Server
    57.6 - *   see AUTHORS for the list of contributors
    57.7 - *
    57.8 - *   This program is free software: you can redistribute it and/or modify
    57.9 - *   it under the terms of the GNU General Public License as published by
   57.10 - *   the Free Software Foundation, either version 3 of the License, or
   57.11 - *   (at your option) any later version.
   57.12 - *
   57.13 - *   This program is distributed in the hope that it will be useful,
   57.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   57.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   57.16 - *   GNU General Public License for more details.
   57.17 - *
   57.18 - *   You should have received a copy of the GNU General Public License
   57.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   57.20 - */
   57.21 -
   57.22 -package org.sonews.storage;
   57.23 -
   57.24 -/**
   57.25 - *
   57.26 - * @author Christian Lins
   57.27 - * @since sonews/1.0
   57.28 - */
   57.29 -public class StorageBackendException extends Exception
   57.30 -{
   57.31 -
   57.32 -  public StorageBackendException(Throwable cause)
   57.33 -  {
   57.34 -    super(cause);
   57.35 -  }
   57.36 -
   57.37 -  public StorageBackendException(String msg)
   57.38 -  {
   57.39 -    super(msg);
   57.40 -  }
   57.41 -
   57.42 -}
    58.1 --- a/org/sonews/storage/StorageManager.java	Sun Aug 29 17:04:25 2010 +0200
    58.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    58.3 @@ -1,89 +0,0 @@
    58.4 -/*
    58.5 - *   SONEWS News Server
    58.6 - *   see AUTHORS for the list of contributors
    58.7 - *
    58.8 - *   This program is free software: you can redistribute it and/or modify
    58.9 - *   it under the terms of the GNU General Public License as published by
   58.10 - *   the Free Software Foundation, either version 3 of the License, or
   58.11 - *   (at your option) any later version.
   58.12 - *
   58.13 - *   This program is distributed in the hope that it will be useful,
   58.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   58.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   58.16 - *   GNU General Public License for more details.
   58.17 - *
   58.18 - *   You should have received a copy of the GNU General Public License
   58.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   58.20 - */
   58.21 -
   58.22 -package org.sonews.storage;
   58.23 -
   58.24 -/**
   58.25 - *
   58.26 - * @author Christian Lins
   58.27 - * @since sonews/1.0
   58.28 - */
   58.29 -public final class StorageManager
   58.30 -{
   58.31 -
   58.32 -  private static StorageProvider provider;
   58.33 -
   58.34 -  public static Storage current()
   58.35 -    throws StorageBackendException
   58.36 -  {
   58.37 -    synchronized(StorageManager.class)
   58.38 -    {
   58.39 -      if(provider == null)
   58.40 -      {
   58.41 -        return null;
   58.42 -      }
   58.43 -      else
   58.44 -      {
   58.45 -        return provider.storage(Thread.currentThread());
   58.46 -      }
   58.47 -    }
   58.48 -  }
   58.49 -
   58.50 -  public static StorageProvider loadProvider(String pluginClassName)
   58.51 -  {
   58.52 -    try
   58.53 -    {
   58.54 -      Class<?> clazz = Class.forName(pluginClassName);
   58.55 -      Object   inst  = clazz.newInstance();
   58.56 -      return (StorageProvider)inst;
   58.57 -    }
   58.58 -    catch(Exception ex)
   58.59 -    {
   58.60 -      System.err.println(ex);
   58.61 -      return null;
   58.62 -    }
   58.63 -  }
   58.64 -
   58.65 -  /**
   58.66 -   * Sets the current storage provider.
   58.67 -   * @param provider
   58.68 -   */
   58.69 -  public static void enableProvider(StorageProvider provider)
   58.70 -  {
   58.71 -    synchronized(StorageManager.class)
   58.72 -    {
   58.73 -      if(StorageManager.provider != null)
   58.74 -      {
   58.75 -        disableProvider();
   58.76 -      }
   58.77 -      StorageManager.provider = provider;
   58.78 -    }
   58.79 -  }
   58.80 -
   58.81 -  /**
   58.82 -   * Disables the current provider.
   58.83 -   */
   58.84 -  public static void disableProvider()
   58.85 -  {
   58.86 -    synchronized(StorageManager.class)
   58.87 -    {
   58.88 -      provider = null;
   58.89 -    }
   58.90 -  }
   58.91 -
   58.92 -}
    59.1 --- a/org/sonews/storage/StorageProvider.java	Sun Aug 29 17:04:25 2010 +0200
    59.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    59.3 @@ -1,40 +0,0 @@
    59.4 -/*
    59.5 - *   SONEWS News Server
    59.6 - *   see AUTHORS for the list of contributors
    59.7 - *
    59.8 - *   This program is free software: you can redistribute it and/or modify
    59.9 - *   it under the terms of the GNU General Public License as published by
   59.10 - *   the Free Software Foundation, either version 3 of the License, or
   59.11 - *   (at your option) any later version.
   59.12 - *
   59.13 - *   This program is distributed in the hope that it will be useful,
   59.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   59.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   59.16 - *   GNU General Public License for more details.
   59.17 - *
   59.18 - *   You should have received a copy of the GNU General Public License
   59.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   59.20 - */
   59.21 -
   59.22 -package org.sonews.storage;
   59.23 -
   59.24 -/**
   59.25 - * Provides access to storage backend instances.
   59.26 - * @author Christian Lins
   59.27 - * @since sonews/1.0
   59.28 - */
   59.29 -public interface StorageProvider
   59.30 -{
   59.31 -
   59.32 -  public boolean isSupported(String uri);
   59.33 -
   59.34 -  /**
   59.35 -   * This method returns the reference to the associated storage.
   59.36 -   * The reference MAY be unique for each thread. In any case it MUST be
   59.37 -   * thread-safe to use this method.
   59.38 -   * @return The reference to the associated Storage.
   59.39 -   */
   59.40 -  public Storage storage(Thread thread)
   59.41 -    throws StorageBackendException;
   59.42 -
   59.43 -}
    60.1 --- a/org/sonews/storage/impl/JDBCDatabase.java	Sun Aug 29 17:04:25 2010 +0200
    60.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    60.3 @@ -1,1782 +0,0 @@
    60.4 -/*
    60.5 - *   SONEWS News Server
    60.6 - *   see AUTHORS for the list of contributors
    60.7 - *
    60.8 - *   This program is free software: you can redistribute it and/or modify
    60.9 - *   it under the terms of the GNU General Public License as published by
   60.10 - *   the Free Software Foundation, either version 3 of the License, or
   60.11 - *   (at your option) any later version.
   60.12 - *
   60.13 - *   This program is distributed in the hope that it will be useful,
   60.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   60.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   60.16 - *   GNU General Public License for more details.
   60.17 - *
   60.18 - *   You should have received a copy of the GNU General Public License
   60.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   60.20 - */
   60.21 -
   60.22 -package org.sonews.storage.impl;
   60.23 -
   60.24 -import java.sql.Connection;
   60.25 -import java.sql.DriverManager;
   60.26 -import java.sql.ResultSet;
   60.27 -import java.sql.SQLException;
   60.28 -import java.sql.Statement;
   60.29 -import java.sql.PreparedStatement;
   60.30 -import java.util.ArrayList;
   60.31 -import java.util.Enumeration;
   60.32 -import java.util.List;
   60.33 -import java.util.regex.Matcher;
   60.34 -import java.util.regex.Pattern;
   60.35 -import java.util.regex.PatternSyntaxException;
   60.36 -import javax.mail.Header;
   60.37 -import javax.mail.internet.MimeUtility;
   60.38 -import org.sonews.config.Config;
   60.39 -import org.sonews.util.Log;
   60.40 -import org.sonews.feed.Subscription;
   60.41 -import org.sonews.storage.Article;
   60.42 -import org.sonews.storage.ArticleHead;
   60.43 -import org.sonews.storage.Channel;
   60.44 -import org.sonews.storage.Group;
   60.45 -import org.sonews.storage.Storage;
   60.46 -import org.sonews.storage.StorageBackendException;
   60.47 -import org.sonews.util.Pair;
   60.48 -
   60.49 -/**
   60.50 - * JDBCDatabase facade class.
   60.51 - * @author Christian Lins
   60.52 - * @since sonews/0.5.0
   60.53 - */
   60.54 -// TODO: Refactor this class to reduce size (e.g. ArticleDatabase GroupDatabase)
   60.55 -public class JDBCDatabase implements Storage
   60.56 -{
   60.57 -
   60.58 -  public static final int MAX_RESTARTS = 2;
   60.59 -  
   60.60 -  private Connection        conn = null;
   60.61 -  private PreparedStatement pstmtAddArticle1 = null;
   60.62 -  private PreparedStatement pstmtAddArticle2 = null;
   60.63 -  private PreparedStatement pstmtAddArticle3 = null;
   60.64 -  private PreparedStatement pstmtAddArticle4 = null;
   60.65 -  private PreparedStatement pstmtAddGroup0   = null;
   60.66 -  private PreparedStatement pstmtAddEvent = null;
   60.67 -  private PreparedStatement pstmtCountArticles = null;
   60.68 -  private PreparedStatement pstmtCountGroups   = null;
   60.69 -  private PreparedStatement pstmtDeleteArticle0 = null;
   60.70 -  private PreparedStatement pstmtDeleteArticle1 = null;
   60.71 -  private PreparedStatement pstmtDeleteArticle2 = null;
   60.72 -  private PreparedStatement pstmtDeleteArticle3 = null;
   60.73 -  private PreparedStatement pstmtGetArticle0 = null;
   60.74 -  private PreparedStatement pstmtGetArticle1 = null;
   60.75 -  private PreparedStatement pstmtGetArticleHeaders0 = null;
   60.76 -  private PreparedStatement pstmtGetArticleHeaders1 = null;
   60.77 -  private PreparedStatement pstmtGetArticleHeads = null;
   60.78 -  private PreparedStatement pstmtGetArticleIDs   = null;
   60.79 -  private PreparedStatement pstmtGetArticleIndex    = null;
   60.80 -  private PreparedStatement pstmtGetConfigValue = null;
   60.81 -  private PreparedStatement pstmtGetEventsCount0 = null;
   60.82 -  private PreparedStatement pstmtGetEventsCount1 = null;
   60.83 -  private PreparedStatement pstmtGetGroupForList = null;
   60.84 -  private PreparedStatement pstmtGetGroup0     = null;
   60.85 -  private PreparedStatement pstmtGetGroup1     = null;
   60.86 -  private PreparedStatement pstmtGetFirstArticleNumber = null;
   60.87 -  private PreparedStatement pstmtGetListForGroup       = null;
   60.88 -  private PreparedStatement pstmtGetLastArticleNumber  = null;
   60.89 -  private PreparedStatement pstmtGetMaxArticleID       = null;
   60.90 -  private PreparedStatement pstmtGetMaxArticleIndex    = null;
   60.91 -  private PreparedStatement pstmtGetOldestArticle      = null;
   60.92 -  private PreparedStatement pstmtGetPostingsCount      = null;
   60.93 -  private PreparedStatement pstmtGetSubscriptions  = null;
   60.94 -  private PreparedStatement pstmtIsArticleExisting = null;
   60.95 -  private PreparedStatement pstmtIsGroupExisting = null;
   60.96 -  private PreparedStatement pstmtPurgeGroup0     = null;
   60.97 -  private PreparedStatement pstmtPurgeGroup1     = null;
   60.98 -  private PreparedStatement pstmtSetConfigValue0 = null;
   60.99 -  private PreparedStatement pstmtSetConfigValue1 = null;
  60.100 -  private PreparedStatement pstmtUpdateGroup     = null;
  60.101 -  
  60.102 -  /** How many times the database connection was reinitialized */
  60.103 -  private int restarts = 0;
  60.104 -  
  60.105 -  /**
  60.106 -   * Rises the database: reconnect and recreate all prepared statements.
  60.107 -   * @throws java.lang.SQLException
  60.108 -   */
  60.109 -  protected void arise()
  60.110 -    throws SQLException
  60.111 -  {
  60.112 -    try
  60.113 -    {
  60.114 -      // Load database driver
  60.115 -      Class.forName(
  60.116 -        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DBMSDRIVER, "java.lang.Object"));
  60.117 -
  60.118 -      // Establish database connection
  60.119 -      this.conn = DriverManager.getConnection(
  60.120 -        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DATABASE, "<not specified>"),
  60.121 -        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_USER, "root"),
  60.122 -        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_PASSWORD, ""));
  60.123 -
  60.124 -      this.conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
  60.125 -      if(this.conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE)
  60.126 -      {
  60.127 -        Log.get().warning("Database is NOT fully serializable!");
  60.128 -      }
  60.129 -
  60.130 -      // Prepare statements for method addArticle()
  60.131 -      this.pstmtAddArticle1 = conn.prepareStatement(
  60.132 -        "INSERT INTO articles (article_id, body) VALUES(?, ?)");
  60.133 -      this.pstmtAddArticle2 = conn.prepareStatement(
  60.134 -        "INSERT INTO headers (article_id, header_key, header_value, header_index) " +
  60.135 -        "VALUES (?, ?, ?, ?)");
  60.136 -      this.pstmtAddArticle3 = conn.prepareStatement(
  60.137 -        "INSERT INTO postings (group_id, article_id, article_index)" +
  60.138 -        "VALUES (?, ?, ?)");
  60.139 -      this.pstmtAddArticle4 = conn.prepareStatement(
  60.140 -        "INSERT INTO article_ids (article_id, message_id) VALUES (?, ?)");
  60.141 -
  60.142 -      // Prepare statement for method addStatValue()
  60.143 -      this.pstmtAddEvent = conn.prepareStatement(
  60.144 -        "INSERT INTO events VALUES (?, ?, ?)");
  60.145 -     
  60.146 -      // Prepare statement for method addGroup()
  60.147 -      this.pstmtAddGroup0 = conn.prepareStatement(
  60.148 -        "INSERT INTO groups (name, flags) VALUES (?, ?)");
  60.149 -      
  60.150 -      // Prepare statement for method countArticles()
  60.151 -      this.pstmtCountArticles = conn.prepareStatement(
  60.152 -        "SELECT Count(article_id) FROM article_ids");
  60.153 -      
  60.154 -      // Prepare statement for method countGroups()
  60.155 -      this.pstmtCountGroups = conn.prepareStatement(
  60.156 -        "SELECT Count(group_id) FROM groups WHERE " +
  60.157 -        "flags & " + Channel.DELETED + " = 0");
  60.158 -      
  60.159 -      // Prepare statements for method delete(article)
  60.160 -      this.pstmtDeleteArticle0 = conn.prepareStatement(
  60.161 -        "DELETE FROM articles WHERE article_id = " +
  60.162 -        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
  60.163 -      this.pstmtDeleteArticle1 = conn.prepareStatement(
  60.164 -        "DELETE FROM headers WHERE article_id = " +
  60.165 -        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
  60.166 -      this.pstmtDeleteArticle2 = conn.prepareStatement(
  60.167 -        "DELETE FROM postings WHERE article_id = " +
  60.168 -        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
  60.169 -      this.pstmtDeleteArticle3 = conn.prepareStatement(
  60.170 -        "DELETE FROM article_ids WHERE message_id = ?");
  60.171 -
  60.172 -      // Prepare statements for methods getArticle()
  60.173 -      this.pstmtGetArticle0 = conn.prepareStatement(
  60.174 -        "SELECT * FROM articles  WHERE article_id = " +
  60.175 -        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
  60.176 -      this.pstmtGetArticle1 = conn.prepareStatement(
  60.177 -        "SELECT * FROM articles WHERE article_id = " +
  60.178 -        "(SELECT article_id FROM postings WHERE " +
  60.179 -        "article_index = ? AND group_id = ?)");
  60.180 -      
  60.181 -      // Prepare statement for method getArticleHeaders()
  60.182 -      this.pstmtGetArticleHeaders0 = conn.prepareStatement(
  60.183 -        "SELECT header_key, header_value FROM headers WHERE article_id = ? " +
  60.184 -        "ORDER BY header_index ASC");
  60.185 -
  60.186 -      // Prepare statement for method getArticleHeaders(regular expr pattern)
  60.187 -      this.pstmtGetArticleHeaders1 = conn.prepareStatement(
  60.188 -        "SELECT p.article_index, h.header_value FROM headers h " +
  60.189 -          "INNER JOIN postings p ON h.article_id = p.article_id " +
  60.190 -          "INNER JOIN groups g ON p.group_id = g.group_id " +
  60.191 -            "WHERE g.name          =  ? AND " +
  60.192 -                  "h.header_key    =  ? AND " +
  60.193 -                  "p.article_index >= ? " +
  60.194 -        "ORDER BY p.article_index ASC");
  60.195 -
  60.196 -      this.pstmtGetArticleIDs = conn.prepareStatement(
  60.197 -        "SELECT article_index FROM postings WHERE group_id = ?");
  60.198 -      
  60.199 -      // Prepare statement for method getArticleIndex
  60.200 -      this.pstmtGetArticleIndex = conn.prepareStatement(
  60.201 -              "SELECT article_index FROM postings WHERE " +
  60.202 -              "article_id = (SELECT article_id FROM article_ids " +
  60.203 -              "WHERE message_id = ?) " +
  60.204 -              " AND group_id = ?");
  60.205 -
  60.206 -      // Prepare statements for method getArticleHeads()
  60.207 -      this.pstmtGetArticleHeads = conn.prepareStatement(
  60.208 -        "SELECT article_id, article_index FROM postings WHERE " +
  60.209 -        "postings.group_id = ? AND article_index >= ? AND " +
  60.210 -        "article_index <= ?");
  60.211 -
  60.212 -      // Prepare statements for method getConfigValue()
  60.213 -      this.pstmtGetConfigValue = conn.prepareStatement(
  60.214 -        "SELECT config_value FROM config WHERE config_key = ?");
  60.215 -
  60.216 -      // Prepare statements for method getEventsCount()
  60.217 -      this.pstmtGetEventsCount0 = conn.prepareStatement(
  60.218 -        "SELECT Count(*) FROM events WHERE event_key = ? AND " +
  60.219 -        "event_time >= ? AND event_time < ?");
  60.220 -
  60.221 -      this.pstmtGetEventsCount1 = conn.prepareStatement(
  60.222 -        "SELECT Count(*) FROM events WHERE event_key = ? AND " +
  60.223 -        "event_time >= ? AND event_time < ? AND group_id = ?");
  60.224 -      
  60.225 -      // Prepare statement for method getGroupForList()
  60.226 -      this.pstmtGetGroupForList = conn.prepareStatement(
  60.227 -        "SELECT name FROM groups INNER JOIN groups2list " +
  60.228 -        "ON groups.group_id = groups2list.group_id " +
  60.229 -        "WHERE groups2list.listaddress = ?");
  60.230 -
  60.231 -      // Prepare statement for method getGroup()
  60.232 -      this.pstmtGetGroup0 = conn.prepareStatement(
  60.233 -        "SELECT group_id, flags FROM groups WHERE Name = ?");
  60.234 -      this.pstmtGetGroup1 = conn.prepareStatement(
  60.235 -        "SELECT name FROM groups WHERE group_id = ?");
  60.236 -
  60.237 -      // Prepare statement for method getLastArticleNumber()
  60.238 -      this.pstmtGetLastArticleNumber = conn.prepareStatement(
  60.239 -        "SELECT Max(article_index) FROM postings WHERE group_id = ?");
  60.240 -
  60.241 -      // Prepare statement for method getListForGroup()
  60.242 -      this.pstmtGetListForGroup = conn.prepareStatement(
  60.243 -        "SELECT listaddress FROM groups2list INNER JOIN groups " +
  60.244 -        "ON groups.group_id = groups2list.group_id WHERE name = ?");
  60.245 -
  60.246 -      // Prepare statement for method getMaxArticleID()
  60.247 -      this.pstmtGetMaxArticleID = conn.prepareStatement(
  60.248 -        "SELECT Max(article_id) FROM articles");
  60.249 -      
  60.250 -      // Prepare statement for method getMaxArticleIndex()
  60.251 -      this.pstmtGetMaxArticleIndex = conn.prepareStatement(
  60.252 -        "SELECT Max(article_index) FROM postings WHERE group_id = ?");
  60.253 -      
  60.254 -      // Prepare statement for method getOldestArticle()
  60.255 -      this.pstmtGetOldestArticle = conn.prepareStatement(
  60.256 -        "SELECT message_id FROM article_ids WHERE article_id = " +
  60.257 -        "(SELECT Min(article_id) FROM article_ids)");
  60.258 -
  60.259 -      // Prepare statement for method getFirstArticleNumber()
  60.260 -      this.pstmtGetFirstArticleNumber = conn.prepareStatement(
  60.261 -        "SELECT Min(article_index) FROM postings WHERE group_id = ?");
  60.262 -      
  60.263 -      // Prepare statement for method getPostingsCount()
  60.264 -      this.pstmtGetPostingsCount = conn.prepareStatement(
  60.265 -        "SELECT Count(*) FROM postings NATURAL JOIN groups " +
  60.266 -        "WHERE groups.name = ?");
  60.267 -      
  60.268 -      // Prepare statement for method getSubscriptions()
  60.269 -      this.pstmtGetSubscriptions = conn.prepareStatement(
  60.270 -        "SELECT host, port, name FROM peers NATURAL JOIN " +
  60.271 -        "peer_subscriptions NATURAL JOIN groups WHERE feedtype = ?");
  60.272 -      
  60.273 -      // Prepare statement for method isArticleExisting()
  60.274 -      this.pstmtIsArticleExisting = conn.prepareStatement(
  60.275 -        "SELECT Count(article_id) FROM article_ids WHERE message_id = ?");
  60.276 -      
  60.277 -      // Prepare statement for method isGroupExisting()
  60.278 -      this.pstmtIsGroupExisting = conn.prepareStatement(
  60.279 -        "SELECT * FROM groups WHERE name = ?");
  60.280 -      
  60.281 -      // Prepare statement for method setConfigValue()
  60.282 -      this.pstmtSetConfigValue0 = conn.prepareStatement(
  60.283 -        "DELETE FROM config WHERE config_key = ?");
  60.284 -      this.pstmtSetConfigValue1 = conn.prepareStatement(
  60.285 -        "INSERT INTO config VALUES(?, ?)");
  60.286 -
  60.287 -      // Prepare statements for method purgeGroup()
  60.288 -      this.pstmtPurgeGroup0 = conn.prepareStatement(
  60.289 -        "DELETE FROM peer_subscriptions WHERE group_id = ?");
  60.290 -      this.pstmtPurgeGroup1 = conn.prepareStatement(
  60.291 -        "DELETE FROM groups WHERE group_id = ?");
  60.292 -
  60.293 -      // Prepare statement for method update(Group)
  60.294 -      this.pstmtUpdateGroup = conn.prepareStatement(
  60.295 -        "UPDATE groups SET flags = ?, name = ? WHERE group_id = ?");
  60.296 -    }
  60.297 -    catch(ClassNotFoundException ex)
  60.298 -    {
  60.299 -      throw new Error("JDBC Driver not found!", ex);
  60.300 -    }
  60.301 -  }
  60.302 -  
  60.303 -  /**
  60.304 -   * Adds an article to the database.
  60.305 -   * @param article
  60.306 -   * @return
  60.307 -   * @throws java.sql.SQLException
  60.308 -   */
  60.309 -  @Override
  60.310 -  public void addArticle(final Article article)
  60.311 -    throws StorageBackendException
  60.312 -  {
  60.313 -    try
  60.314 -    {
  60.315 -      this.conn.setAutoCommit(false);
  60.316 -
  60.317 -      int newArticleID = getMaxArticleID() + 1;
  60.318 -
  60.319 -      // Fill prepared statement with values;
  60.320 -      // writes body to article table
  60.321 -      pstmtAddArticle1.setInt(1, newArticleID);
  60.322 -      pstmtAddArticle1.setBytes(2, article.getBody());
  60.323 -      pstmtAddArticle1.execute();
  60.324 -
  60.325 -      // Add headers
  60.326 -      Enumeration headers = article.getAllHeaders();
  60.327 -      for(int n = 0; headers.hasMoreElements(); n++)
  60.328 -      {
  60.329 -        Header header = (Header)headers.nextElement();
  60.330 -        pstmtAddArticle2.setInt(1, newArticleID);
  60.331 -        pstmtAddArticle2.setString(2, header.getName().toLowerCase());
  60.332 -        pstmtAddArticle2.setString(3, 
  60.333 -          header.getValue().replaceAll("[\r\n]", ""));
  60.334 -        pstmtAddArticle2.setInt(4, n);
  60.335 -        pstmtAddArticle2.execute();
  60.336 -      }
  60.337 -      
  60.338 -      // For each newsgroup add a reference
  60.339 -      List<Group> groups = article.getGroups();
  60.340 -      for(Group group : groups)
  60.341 -      {
  60.342 -        pstmtAddArticle3.setLong(1, group.getInternalID());
  60.343 -        pstmtAddArticle3.setInt(2, newArticleID);
  60.344 -        pstmtAddArticle3.setLong(3, getMaxArticleIndex(group.getInternalID()) + 1);
  60.345 -        pstmtAddArticle3.execute();
  60.346 -      }
  60.347 -      
  60.348 -      // Write message-id to article_ids table
  60.349 -      this.pstmtAddArticle4.setInt(1, newArticleID);
  60.350 -      this.pstmtAddArticle4.setString(2, article.getMessageID());
  60.351 -      this.pstmtAddArticle4.execute();
  60.352 -
  60.353 -      this.conn.commit();
  60.354 -      this.conn.setAutoCommit(true);
  60.355 -
  60.356 -      this.restarts = 0; // Reset error count
  60.357 -    }
  60.358 -    catch(SQLException ex)
  60.359 -    {
  60.360 -      try
  60.361 -      {
  60.362 -        this.conn.rollback();  // Rollback changes
  60.363 -      }
  60.364 -      catch(SQLException ex2)
  60.365 -      {
  60.366 -        Log.get().severe("Rollback of addArticle() failed: " + ex2);
  60.367 -      }
  60.368 -      
  60.369 -      try
  60.370 -      {
  60.371 -        this.conn.setAutoCommit(true); // and release locks
  60.372 -      }
  60.373 -      catch(SQLException ex2)
  60.374 -      {
  60.375 -        Log.get().severe("setAutoCommit(true) of addArticle() failed: " + ex2);
  60.376 -      }
  60.377 -
  60.378 -      restartConnection(ex);
  60.379 -      addArticle(article);
  60.380 -    }
  60.381 -  }
  60.382 -  
  60.383 -  /**
  60.384 -   * Adds a group to the JDBCDatabase. This method is not accessible via NNTP.
  60.385 -   * @param name
  60.386 -   * @throws java.sql.SQLException
  60.387 -   */
  60.388 -  @Override
  60.389 -  public void addGroup(String name, int flags)
  60.390 -    throws StorageBackendException
  60.391 -  {
  60.392 -    try
  60.393 -    {
  60.394 -      this.conn.setAutoCommit(false);
  60.395 -      pstmtAddGroup0.setString(1, name);
  60.396 -      pstmtAddGroup0.setInt(2, flags);
  60.397 -
  60.398 -      pstmtAddGroup0.executeUpdate();
  60.399 -      this.conn.commit();
  60.400 -      this.conn.setAutoCommit(true);
  60.401 -      this.restarts = 0; // Reset error count
  60.402 -    }
  60.403 -    catch(SQLException ex)
  60.404 -    {
  60.405 -      try
  60.406 -      {
  60.407 -        this.conn.rollback();
  60.408 -        this.conn.setAutoCommit(true);
  60.409 -      }
  60.410 -      catch(SQLException ex2)
  60.411 -      {
  60.412 -        ex2.printStackTrace();
  60.413 -      }
  60.414 -
  60.415 -      restartConnection(ex);
  60.416 -      addGroup(name, flags);
  60.417 -    }
  60.418 -  }
  60.419 -
  60.420 -  @Override
  60.421 -  public void addEvent(long time, int type, long gid)
  60.422 -    throws StorageBackendException
  60.423 -  {
  60.424 -    try
  60.425 -    {
  60.426 -      this.conn.setAutoCommit(false);
  60.427 -      this.pstmtAddEvent.setLong(1, time);
  60.428 -      this.pstmtAddEvent.setInt(2, type);
  60.429 -      this.pstmtAddEvent.setLong(3, gid);
  60.430 -      this.pstmtAddEvent.executeUpdate();
  60.431 -      this.conn.commit();
  60.432 -      this.conn.setAutoCommit(true);
  60.433 -      this.restarts = 0;
  60.434 -    }
  60.435 -    catch(SQLException ex)
  60.436 -    {
  60.437 -      try
  60.438 -      {
  60.439 -        this.conn.rollback();
  60.440 -        this.conn.setAutoCommit(true);
  60.441 -      }
  60.442 -      catch(SQLException ex2)
  60.443 -      {
  60.444 -        ex2.printStackTrace();
  60.445 -      }
  60.446 -
  60.447 -      restartConnection(ex);
  60.448 -      addEvent(time, type, gid);
  60.449 -    }
  60.450 -  }
  60.451 -
  60.452 -  @Override
  60.453 -  public int countArticles()
  60.454 -    throws StorageBackendException
  60.455 -  {
  60.456 -    ResultSet rs = null;
  60.457 -
  60.458 -    try
  60.459 -    {
  60.460 -      rs = this.pstmtCountArticles.executeQuery();
  60.461 -      if(rs.next())
  60.462 -      {
  60.463 -        return rs.getInt(1);
  60.464 -      }
  60.465 -      else
  60.466 -      {
  60.467 -        return -1;
  60.468 -      }
  60.469 -    }
  60.470 -    catch(SQLException ex)
  60.471 -    {
  60.472 -      restartConnection(ex);
  60.473 -      return countArticles();
  60.474 -    }
  60.475 -    finally
  60.476 -    {
  60.477 -      if(rs != null)
  60.478 -      {
  60.479 -        try
  60.480 -        {
  60.481 -          rs.close();
  60.482 -        }
  60.483 -        catch(SQLException ex)
  60.484 -        {
  60.485 -          ex.printStackTrace();
  60.486 -        }
  60.487 -        restarts = 0;
  60.488 -      }
  60.489 -    }
  60.490 -  }
  60.491 -
  60.492 -  @Override
  60.493 -  public int countGroups()
  60.494 -    throws StorageBackendException
  60.495 -  {
  60.496 -    ResultSet rs = null;
  60.497 -
  60.498 -    try
  60.499 -    {
  60.500 -      rs = this.pstmtCountGroups.executeQuery();
  60.501 -      if(rs.next())
  60.502 -      {
  60.503 -        return rs.getInt(1);
  60.504 -      }
  60.505 -      else
  60.506 -      {
  60.507 -        return -1;
  60.508 -      }
  60.509 -    }
  60.510 -    catch(SQLException ex)
  60.511 -    {
  60.512 -      restartConnection(ex);
  60.513 -      return countGroups();
  60.514 -    }
  60.515 -    finally
  60.516 -    {
  60.517 -      if(rs != null)
  60.518 -      {
  60.519 -        try
  60.520 -        {
  60.521 -          rs.close();
  60.522 -        }
  60.523 -        catch(SQLException ex)
  60.524 -        {
  60.525 -          ex.printStackTrace();
  60.526 -        }
  60.527 -        restarts = 0;
  60.528 -      }
  60.529 -    }
  60.530 -  }
  60.531 -
  60.532 -  @Override
  60.533 -  public void delete(final String messageID)
  60.534 -    throws StorageBackendException
  60.535 -  {
  60.536 -    try
  60.537 -    {
  60.538 -      this.conn.setAutoCommit(false);
  60.539 -      
  60.540 -      this.pstmtDeleteArticle0.setString(1, messageID);
  60.541 -      int rs = this.pstmtDeleteArticle0.executeUpdate();
  60.542 -      
  60.543 -      // We do not trust the ON DELETE CASCADE functionality to delete
  60.544 -      // orphaned references...
  60.545 -      this.pstmtDeleteArticle1.setString(1, messageID);
  60.546 -      rs = this.pstmtDeleteArticle1.executeUpdate();
  60.547 -
  60.548 -      this.pstmtDeleteArticle2.setString(1, messageID);
  60.549 -      rs = this.pstmtDeleteArticle2.executeUpdate();
  60.550 -
  60.551 -      this.pstmtDeleteArticle3.setString(1, messageID);
  60.552 -      rs = this.pstmtDeleteArticle3.executeUpdate();
  60.553 -      
  60.554 -      this.conn.commit();
  60.555 -      this.conn.setAutoCommit(true);
  60.556 -    }
  60.557 -    catch(SQLException ex)
  60.558 -    {
  60.559 -      throw new StorageBackendException(ex);
  60.560 -    }
  60.561 -  }
  60.562 -
  60.563 -  @Override
  60.564 -  public Article getArticle(String messageID)
  60.565 -    throws StorageBackendException
  60.566 -  {
  60.567 -    ResultSet rs = null;
  60.568 -    try
  60.569 -    {
  60.570 -      pstmtGetArticle0.setString(1, messageID);
  60.571 -      rs = pstmtGetArticle0.executeQuery();
  60.572 -
  60.573 -      if(!rs.next())
  60.574 -      {
  60.575 -        return null;
  60.576 -      }
  60.577 -      else
  60.578 -      {
  60.579 -        byte[] body     = rs.getBytes("body");
  60.580 -        String headers  = getArticleHeaders(rs.getInt("article_id"));
  60.581 -        return new Article(headers, body);
  60.582 -      }
  60.583 -    }
  60.584 -    catch(SQLException ex)
  60.585 -    {
  60.586 -      restartConnection(ex);
  60.587 -      return getArticle(messageID);
  60.588 -    }
  60.589 -    finally
  60.590 -    {
  60.591 -      if(rs != null)
  60.592 -      {
  60.593 -        try
  60.594 -        {
  60.595 -          rs.close();
  60.596 -        }
  60.597 -        catch(SQLException ex)
  60.598 -        {
  60.599 -          ex.printStackTrace();
  60.600 -        }
  60.601 -        restarts = 0; // Reset error count
  60.602 -      }
  60.603 -    }
  60.604 -  }
  60.605 -  
  60.606 -  /**
  60.607 -   * Retrieves an article by its ID.
  60.608 -   * @param articleID
  60.609 -   * @return
  60.610 -   * @throws StorageBackendException
  60.611 -   */
  60.612 -  @Override
  60.613 -  public Article getArticle(long articleIndex, long gid)
  60.614 -    throws StorageBackendException
  60.615 -  {  
  60.616 -    ResultSet rs = null;
  60.617 -
  60.618 -    try
  60.619 -    {
  60.620 -      this.pstmtGetArticle1.setLong(1, articleIndex);
  60.621 -      this.pstmtGetArticle1.setLong(2, gid);
  60.622 -
  60.623 -      rs = this.pstmtGetArticle1.executeQuery();
  60.624 -
  60.625 -      if(rs.next())
  60.626 -      {
  60.627 -        byte[] body    = rs.getBytes("body");
  60.628 -        String headers = getArticleHeaders(rs.getInt("article_id"));
  60.629 -        return new Article(headers, body);
  60.630 -      }
  60.631 -      else
  60.632 -      {
  60.633 -        return null;
  60.634 -      }
  60.635 -    }
  60.636 -    catch(SQLException ex)
  60.637 -    {
  60.638 -      restartConnection(ex);
  60.639 -      return getArticle(articleIndex, gid);
  60.640 -    }
  60.641 -    finally
  60.642 -    {
  60.643 -      if(rs != null)
  60.644 -      {
  60.645 -        try
  60.646 -        {
  60.647 -          rs.close();
  60.648 -        }
  60.649 -        catch(SQLException ex)
  60.650 -        {
  60.651 -          ex.printStackTrace();
  60.652 -        }
  60.653 -        restarts = 0;
  60.654 -      }
  60.655 -    }
  60.656 -  }
  60.657 -
  60.658 -  /**
  60.659 -   * Searches for fitting header values using the given regular expression.
  60.660 -   * @param group
  60.661 -   * @param start
  60.662 -   * @param end
  60.663 -   * @param headerKey
  60.664 -   * @param pattern
  60.665 -   * @return
  60.666 -   * @throws StorageBackendException
  60.667 -   */
  60.668 -  @Override
  60.669 -  public List<Pair<Long, String>> getArticleHeaders(Channel group, long start,
  60.670 -    long end, String headerKey, String patStr)
  60.671 -    throws StorageBackendException, PatternSyntaxException
  60.672 -  {
  60.673 -    ResultSet rs = null;
  60.674 -    List<Pair<Long, String>> heads = new ArrayList<Pair<Long, String>>();
  60.675 -
  60.676 -    try
  60.677 -    {
  60.678 -      this.pstmtGetArticleHeaders1.setString(1, group.getName());
  60.679 -      this.pstmtGetArticleHeaders1.setString(2, headerKey);
  60.680 -      this.pstmtGetArticleHeaders1.setLong(3, start);
  60.681 -
  60.682 -      rs = this.pstmtGetArticleHeaders1.executeQuery();
  60.683 -
  60.684 -      // Convert the "NNTP" regex to Java regex
  60.685 -      patStr = patStr.replace("*", ".*");
  60.686 -      Pattern pattern = Pattern.compile(patStr);
  60.687 -
  60.688 -      while(rs.next())
  60.689 -      {
  60.690 -        Long articleIndex = rs.getLong(1);
  60.691 -        if(end < 0 || articleIndex <= end) // Match start is done via SQL
  60.692 -        {
  60.693 -          String headerValue  = rs.getString(2);
  60.694 -          Matcher matcher = pattern.matcher(headerValue);
  60.695 -          if(matcher.matches())
  60.696 -          {
  60.697 -            heads.add(new Pair<Long, String>(articleIndex, headerValue));
  60.698 -          }
  60.699 -        }
  60.700 -      }
  60.701 -    }
  60.702 -    catch(SQLException ex)
  60.703 -    {
  60.704 -      restartConnection(ex);
  60.705 -      return getArticleHeaders(group, start, end, headerKey, patStr);
  60.706 -    }
  60.707 -    finally
  60.708 -    {
  60.709 -      if(rs != null)
  60.710 -      {
  60.711 -        try
  60.712 -        {
  60.713 -          rs.close();
  60.714 -        }
  60.715 -        catch(SQLException ex)
  60.716 -        {
  60.717 -          ex.printStackTrace();
  60.718 -        }
  60.719 -      }
  60.720 -    }
  60.721 -
  60.722 -    return heads;
  60.723 -  }
  60.724 -
  60.725 -  private String getArticleHeaders(long articleID)
  60.726 -    throws StorageBackendException
  60.727 -  {
  60.728 -    ResultSet rs = null;
  60.729 -    
  60.730 -    try
  60.731 -    {
  60.732 -      this.pstmtGetArticleHeaders0.setLong(1, articleID);
  60.733 -      rs = this.pstmtGetArticleHeaders0.executeQuery();
  60.734 -      
  60.735 -      StringBuilder buf = new StringBuilder();
  60.736 -      if(rs.next())
  60.737 -      {
  60.738 -        for(;;)
  60.739 -        {
  60.740 -          buf.append(rs.getString(1)); // key
  60.741 -          buf.append(": ");
  60.742 -          String foldedValue = MimeUtility.fold(0, rs.getString(2));
  60.743 -          buf.append(foldedValue); // value
  60.744 -          if(rs.next())
  60.745 -          {
  60.746 -            buf.append("\r\n");
  60.747 -          }
  60.748 -          else
  60.749 -          {
  60.750 -            break;
  60.751 -          }
  60.752 -        }
  60.753 -      }
  60.754 -      
  60.755 -      return buf.toString();
  60.756 -    }
  60.757 -    catch(SQLException ex)
  60.758 -    {
  60.759 -      restartConnection(ex);
  60.760 -      return getArticleHeaders(articleID);
  60.761 -    }
  60.762 -    finally
  60.763 -    {
  60.764 -      if(rs != null)
  60.765 -      {
  60.766 -        try
  60.767 -        {
  60.768 -          rs.close();
  60.769 -        }
  60.770 -        catch(SQLException ex)
  60.771 -        {
  60.772 -          ex.printStackTrace();
  60.773 -        }
  60.774 -      }
  60.775 -    }
  60.776 -  }
  60.777 -
  60.778 -  @Override
  60.779 -  public long getArticleIndex(Article article, Group group)
  60.780 -    throws StorageBackendException
  60.781 -  {
  60.782 -    ResultSet rs = null;
  60.783 -
  60.784 -    try
  60.785 -    {
  60.786 -      this.pstmtGetArticleIndex.setString(1, article.getMessageID());
  60.787 -      this.pstmtGetArticleIndex.setLong(2, group.getInternalID());
  60.788 -      
  60.789 -      rs = this.pstmtGetArticleIndex.executeQuery();
  60.790 -      if(rs.next())
  60.791 -      {
  60.792 -        return rs.getLong(1);
  60.793 -      }
  60.794 -      else
  60.795 -      {
  60.796 -        return -1;
  60.797 -      }
  60.798 -    }
  60.799 -    catch(SQLException ex)
  60.800 -    {
  60.801 -      restartConnection(ex);
  60.802 -      return getArticleIndex(article, group);
  60.803 -    }
  60.804 -    finally
  60.805 -    {
  60.806 -      if(rs != null)
  60.807 -      {
  60.808 -        try
  60.809 -        {
  60.810 -          rs.close();
  60.811 -        }
  60.812 -        catch(SQLException ex)
  60.813 -        {
  60.814 -          ex.printStackTrace();
  60.815 -        }
  60.816 -      }
  60.817 -    }
  60.818 -  }
  60.819 -  
  60.820 -  /**
  60.821 -   * Returns a list of Long/Article Pairs.
  60.822 -   * @throws java.sql.SQLException
  60.823 -   */
  60.824 -  @Override
  60.825 -  public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first,
  60.826 -    long last)
  60.827 -    throws StorageBackendException
  60.828 -  {
  60.829 -    ResultSet rs = null;
  60.830 -
  60.831 -    try
  60.832 -    {
  60.833 -      this.pstmtGetArticleHeads.setLong(1, group.getInternalID());
  60.834 -      this.pstmtGetArticleHeads.setLong(2, first);
  60.835 -      this.pstmtGetArticleHeads.setLong(3, last);
  60.836 -      rs = pstmtGetArticleHeads.executeQuery();
  60.837 -
  60.838 -      List<Pair<Long, ArticleHead>> articles 
  60.839 -        = new ArrayList<Pair<Long, ArticleHead>>();
  60.840 -
  60.841 -      while (rs.next())
  60.842 -      {
  60.843 -        long aid  = rs.getLong("article_id");
  60.844 -        long aidx = rs.getLong("article_index");
  60.845 -        String headers = getArticleHeaders(aid);
  60.846 -        articles.add(new Pair<Long, ArticleHead>(aidx, 
  60.847 -                        new ArticleHead(headers)));
  60.848 -      }
  60.849 -
  60.850 -      return articles;
  60.851 -    }
  60.852 -    catch(SQLException ex)
  60.853 -    {
  60.854 -      restartConnection(ex);
  60.855 -      return getArticleHeads(group, first, last);
  60.856 -    }
  60.857 -    finally
  60.858 -    {
  60.859 -      if(rs != null)
  60.860 -      {
  60.861 -        try
  60.862 -        {
  60.863 -          rs.close();
  60.864 -        }
  60.865 -        catch(SQLException ex)
  60.866 -        {
  60.867 -          ex.printStackTrace();
  60.868 -        }
  60.869 -      }
  60.870 -    }
  60.871 -  }
  60.872 -
  60.873 -  @Override
  60.874 -  public List<Long> getArticleNumbers(long gid)
  60.875 -    throws StorageBackendException
  60.876 -  {
  60.877 -    ResultSet rs = null;
  60.878 -    try
  60.879 -    {
  60.880 -      List<Long> ids = new ArrayList<Long>();
  60.881 -      this.pstmtGetArticleIDs.setLong(1, gid);
  60.882 -      rs = this.pstmtGetArticleIDs.executeQuery();
  60.883 -      while(rs.next())
  60.884 -      {
  60.885 -        ids.add(rs.getLong(1));
  60.886 -      }
  60.887 -      return ids;
  60.888 -    }
  60.889 -    catch(SQLException ex)
  60.890 -    {
  60.891 -      restartConnection(ex);
  60.892 -      return getArticleNumbers(gid);
  60.893 -    }
  60.894 -    finally
  60.895 -    {
  60.896 -      if(rs != null)
  60.897 -      {
  60.898 -        try
  60.899 -        {
  60.900 -          rs.close();
  60.901 -          restarts = 0; // Clear the restart count after successful request
  60.902 -        }
  60.903 -        catch(SQLException ex)
  60.904 -        {
  60.905 -          ex.printStackTrace();
  60.906 -        }
  60.907 -      }
  60.908 -    }
  60.909 -  }
  60.910 -
  60.911 -  @Override
  60.912 -  public String getConfigValue(String key)
  60.913 -    throws StorageBackendException
  60.914 -  {
  60.915 -    ResultSet rs = null;
  60.916 -    try
  60.917 -    {
  60.918 -      this.pstmtGetConfigValue.setString(1, key);
  60.919 -
  60.920 -      rs = this.pstmtGetConfigValue.executeQuery();
  60.921 -      if(rs.next())
  60.922 -      {
  60.923 -        return rs.getString(1); // First data on index 1 not 0
  60.924 -      }
  60.925 -      else
  60.926 -      {
  60.927 -        return null;
  60.928 -      }
  60.929 -    }
  60.930 -    catch(SQLException ex)
  60.931 -    {
  60.932 -      restartConnection(ex);
  60.933 -      return getConfigValue(key);
  60.934 -    }
  60.935 -    finally
  60.936 -    {
  60.937 -      if(rs != null)
  60.938 -      {
  60.939 -        try
  60.940 -        {
  60.941 -          rs.close();
  60.942 -        }
  60.943 -        catch(SQLException ex)
  60.944 -        {
  60.945 -          ex.printStackTrace();
  60.946 -        }
  60.947 -        restarts = 0; // Clear the restart count after successful request
  60.948 -      }
  60.949 -    }
  60.950 -  }
  60.951 -
  60.952 -  @Override
  60.953 -  public int getEventsCount(int type, long start, long end, Channel channel)
  60.954 -    throws StorageBackendException
  60.955 -  {
  60.956 -    ResultSet rs = null;
  60.957 -    
  60.958 -    try
  60.959 -    {
  60.960 -      if(channel == null)
  60.961 -      {
  60.962 -        this.pstmtGetEventsCount0.setInt(1, type);
  60.963 -        this.pstmtGetEventsCount0.setLong(2, start);
  60.964 -        this.pstmtGetEventsCount0.setLong(3, end);
  60.965 -        rs = this.pstmtGetEventsCount0.executeQuery();
  60.966 -      }
  60.967 -      else
  60.968 -      {
  60.969 -        this.pstmtGetEventsCount1.setInt(1, type);
  60.970 -        this.pstmtGetEventsCount1.setLong(2, start);
  60.971 -        this.pstmtGetEventsCount1.setLong(3, end);
  60.972 -        this.pstmtGetEventsCount1.setLong(4, channel.getInternalID());
  60.973 -        rs = this.pstmtGetEventsCount1.executeQuery();
  60.974 -      }
  60.975 -      
  60.976 -      if(rs.next())
  60.977 -      {
  60.978 -        return rs.getInt(1);
  60.979 -      }
  60.980 -      else
  60.981 -      {
  60.982 -        return -1;
  60.983 -      }
  60.984 -    }
  60.985 -    catch(SQLException ex)
  60.986 -    {
  60.987 -      restartConnection(ex);
  60.988 -      return getEventsCount(type, start, end, channel);
  60.989 -    }
  60.990 -    finally
  60.991 -    {
  60.992 -      if(rs != null)
  60.993 -      {
  60.994 -        try
  60.995 -        {
  60.996 -          rs.close();
  60.997 -        }
  60.998 -        catch(SQLException ex)
  60.999 -        {
 60.1000 -          ex.printStackTrace();
 60.1001 -        }
 60.1002 -      }
 60.1003 -    }
 60.1004 -  }
 60.1005 -  
 60.1006 -  /**
 60.1007 -   * Reads all Groups from the JDBCDatabase.
 60.1008 -   * @return
 60.1009 -   * @throws StorageBackendException
 60.1010 -   */
 60.1011 -  @Override
 60.1012 -  public List<Channel> getGroups()
 60.1013 -    throws StorageBackendException
 60.1014 -  {
 60.1015 -    ResultSet   rs;
 60.1016 -    List<Channel> buffer = new ArrayList<Channel>();
 60.1017 -    Statement   stmt   = null;
 60.1018 -
 60.1019 -    try
 60.1020 -    {
 60.1021 -      stmt = conn.createStatement();
 60.1022 -      rs = stmt.executeQuery("SELECT * FROM groups ORDER BY name");
 60.1023 -
 60.1024 -      while(rs.next())
 60.1025 -      {
 60.1026 -        String name  = rs.getString("name");
 60.1027 -        long   id    = rs.getLong("group_id");
 60.1028 -        int    flags = rs.getInt("flags");
 60.1029 -        
 60.1030 -        Group group = new Group(name, id, flags);
 60.1031 -        buffer.add(group);
 60.1032 -      }
 60.1033 -
 60.1034 -      return buffer;
 60.1035 -    }
 60.1036 -    catch(SQLException ex)
 60.1037 -    {
 60.1038 -      restartConnection(ex);
 60.1039 -      return getGroups();
 60.1040 -    }
 60.1041 -    finally
 60.1042 -    {
 60.1043 -      if(stmt != null)
 60.1044 -      {
 60.1045 -        try
 60.1046 -        {
 60.1047 -          stmt.close(); // Implicitely closes ResultSets
 60.1048 -        }
 60.1049 -        catch(SQLException ex)
 60.1050 -        {
 60.1051 -          ex.printStackTrace();
 60.1052 -        }
 60.1053 -      }
 60.1054 -    }
 60.1055 -  }
 60.1056 -
 60.1057 -  @Override
 60.1058 -  public List<String> getGroupsForList(String listAddress)
 60.1059 -    throws StorageBackendException
 60.1060 -  {
 60.1061 -    ResultSet rs = null;
 60.1062 -    
 60.1063 -    try
 60.1064 -    {
 60.1065 -      this.pstmtGetGroupForList.setString(1, listAddress);
 60.1066 -
 60.1067 -      rs = this.pstmtGetGroupForList.executeQuery();
 60.1068 -      List<String> groups = new ArrayList<String>();
 60.1069 -      while(rs.next())
 60.1070 -      {
 60.1071 -        String group = rs.getString(1);
 60.1072 -        groups.add(group);
 60.1073 -      }
 60.1074 -      return groups;
 60.1075 -    }
 60.1076 -    catch(SQLException ex)
 60.1077 -    {
 60.1078 -      restartConnection(ex);
 60.1079 -      return getGroupsForList(listAddress);
 60.1080 -    }
 60.1081 -    finally
 60.1082 -    {
 60.1083 -      if(rs != null)
 60.1084 -      {
 60.1085 -        try
 60.1086 -        {
 60.1087 -          rs.close();
 60.1088 -        }
 60.1089 -        catch(SQLException ex)
 60.1090 -        {
 60.1091 -          ex.printStackTrace();
 60.1092 -        }
 60.1093 -      }
 60.1094 -    }
 60.1095 -  }
 60.1096 -  
 60.1097 -  /**
 60.1098 -   * Returns the Group that is identified by the name.
 60.1099 -   * @param name
 60.1100 -   * @return
 60.1101 -   * @throws StorageBackendException
 60.1102 -   */
 60.1103 -  @Override
 60.1104 -  public Group getGroup(String name)
 60.1105 -    throws StorageBackendException
 60.1106 -  {
 60.1107 -    ResultSet rs = null;
 60.1108 -    
 60.1109 -    try
 60.1110 -    {
 60.1111 -      this.pstmtGetGroup0.setString(1, name);
 60.1112 -      rs = this.pstmtGetGroup0.executeQuery();
 60.1113 -
 60.1114 -      if (!rs.next())
 60.1115 -      {
 60.1116 -        return null;
 60.1117 -      }
 60.1118 -      else
 60.1119 -      {
 60.1120 -        long id = rs.getLong("group_id");
 60.1121 -        int flags = rs.getInt("flags");
 60.1122 -        return new Group(name, id, flags);
 60.1123 -      }
 60.1124 -    }
 60.1125 -    catch(SQLException ex)
 60.1126 -    {
 60.1127 -      restartConnection(ex);
 60.1128 -      return getGroup(name);
 60.1129 -    }
 60.1130 -    finally
 60.1131 -    {
 60.1132 -      if(rs != null)
 60.1133 -      {
 60.1134 -        try
 60.1135 -        {
 60.1136 -          rs.close();
 60.1137 -        }
 60.1138 -        catch(SQLException ex)
 60.1139 -        {
 60.1140 -          ex.printStackTrace();
 60.1141 -        }
 60.1142 -      }
 60.1143 -    }
 60.1144 -  }
 60.1145 -
 60.1146 -  @Override
 60.1147 -  public List<String> getListsForGroup(String group)
 60.1148 -    throws StorageBackendException
 60.1149 -  {
 60.1150 -    ResultSet     rs    = null;
 60.1151 -    List<String>  lists = new ArrayList<String>();
 60.1152 -
 60.1153 -    try
 60.1154 -    {
 60.1155 -      this.pstmtGetListForGroup.setString(1, group);
 60.1156 -      rs = this.pstmtGetListForGroup.executeQuery();
 60.1157 -
 60.1158 -      while(rs.next())
 60.1159 -      {
 60.1160 -        lists.add(rs.getString(1));
 60.1161 -      }
 60.1162 -      return lists;
 60.1163 -    }
 60.1164 -    catch(SQLException ex)
 60.1165 -    {
 60.1166 -      restartConnection(ex);
 60.1167 -      return getListsForGroup(group);
 60.1168 -    }
 60.1169 -    finally
 60.1170 -    {
 60.1171 -      if(rs != null)
 60.1172 -      {
 60.1173 -        try
 60.1174 -        {
 60.1175 -          rs.close();
 60.1176 -        }
 60.1177 -        catch(SQLException ex)
 60.1178 -        {
 60.1179 -          ex.printStackTrace();
 60.1180 -        }
 60.1181 -      }
 60.1182 -    }
 60.1183 -  }
 60.1184 -  
 60.1185 -  private int getMaxArticleIndex(long groupID)
 60.1186 -    throws StorageBackendException
 60.1187 -  {
 60.1188 -    ResultSet rs    = null;
 60.1189 -
 60.1190 -    try
 60.1191 -    {
 60.1192 -      this.pstmtGetMaxArticleIndex.setLong(1, groupID);
 60.1193 -      rs = this.pstmtGetMaxArticleIndex.executeQuery();
 60.1194 -
 60.1195 -      int maxIndex = 0;
 60.1196 -      if (rs.next())
 60.1197 -      {
 60.1198 -        maxIndex = rs.getInt(1);
 60.1199 -      }
 60.1200 -
 60.1201 -      return maxIndex;
 60.1202 -    }
 60.1203 -    catch(SQLException ex)
 60.1204 -    {
 60.1205 -      restartConnection(ex);
 60.1206 -      return getMaxArticleIndex(groupID);
 60.1207 -    }
 60.1208 -    finally
 60.1209 -    {
 60.1210 -      if(rs != null)
 60.1211 -      {
 60.1212 -        try
 60.1213 -        {
 60.1214 -          rs.close();
 60.1215 -        }
 60.1216 -        catch(SQLException ex)
 60.1217 -        {
 60.1218 -          ex.printStackTrace();
 60.1219 -        }
 60.1220 -      }
 60.1221 -    }
 60.1222 -  }
 60.1223 -  
 60.1224 -  private int getMaxArticleID()
 60.1225 -    throws StorageBackendException
 60.1226 -  {
 60.1227 -    ResultSet rs    = null;
 60.1228 -
 60.1229 -    try
 60.1230 -    {
 60.1231 -      rs = this.pstmtGetMaxArticleID.executeQuery();
 60.1232 -
 60.1233 -      int maxIndex = 0;
 60.1234 -      if (rs.next())
 60.1235 -      {
 60.1236 -        maxIndex = rs.getInt(1);
 60.1237 -      }
 60.1238 -
 60.1239 -      return maxIndex;
 60.1240 -    }
 60.1241 -    catch(SQLException ex)
 60.1242 -    {
 60.1243 -      restartConnection(ex);
 60.1244 -      return getMaxArticleID();
 60.1245 -    }
 60.1246 -    finally
 60.1247 -    {
 60.1248 -      if(rs != null)
 60.1249 -      {
 60.1250 -        try
 60.1251 -        {
 60.1252 -          rs.close();
 60.1253 -        }
 60.1254 -        catch(SQLException ex)
 60.1255 -        {
 60.1256 -          ex.printStackTrace();
 60.1257 -        }
 60.1258 -      }
 60.1259 -    }
 60.1260 -  }
 60.1261 -
 60.1262 -  @Override
 60.1263 -  public int getLastArticleNumber(Group group)
 60.1264 -    throws StorageBackendException
 60.1265 -  {
 60.1266 -    ResultSet rs = null;
 60.1267 -
 60.1268 -    try
 60.1269 -    {
 60.1270 -      this.pstmtGetLastArticleNumber.setLong(1, group.getInternalID());
 60.1271 -      rs = this.pstmtGetLastArticleNumber.executeQuery();
 60.1272 -      if (rs.next())
 60.1273 -      {
 60.1274 -        return rs.getInt(1);
 60.1275 -      }
 60.1276 -      else
 60.1277 -      {
 60.1278 -        return 0;
 60.1279 -      }
 60.1280 -    }
 60.1281 -    catch(SQLException ex)
 60.1282 -    {
 60.1283 -      restartConnection(ex);
 60.1284 -      return getLastArticleNumber(group);
 60.1285 -    }
 60.1286 -    finally
 60.1287 -    {
 60.1288 -      if(rs != null)
 60.1289 -      {
 60.1290 -        try
 60.1291 -        {
 60.1292 -          rs.close();
 60.1293 -        }
 60.1294 -        catch(SQLException ex)
 60.1295 -        {
 60.1296 -          ex.printStackTrace();
 60.1297 -        }
 60.1298 -      }
 60.1299 -    }
 60.1300 -  }
 60.1301 -
 60.1302 -  @Override
 60.1303 -  public int getFirstArticleNumber(Group group)
 60.1304 -    throws StorageBackendException
 60.1305 -  {
 60.1306 -    ResultSet rs = null;
 60.1307 -    try
 60.1308 -    {
 60.1309 -      this.pstmtGetFirstArticleNumber.setLong(1, group.getInternalID());
 60.1310 -      rs = this.pstmtGetFirstArticleNumber.executeQuery();
 60.1311 -      if(rs.next())
 60.1312 -      {
 60.1313 -        return rs.getInt(1);
 60.1314 -      }
 60.1315 -      else
 60.1316 -      {
 60.1317 -        return 0;
 60.1318 -      }
 60.1319 -    }
 60.1320 -    catch(SQLException ex)
 60.1321 -    {
 60.1322 -      restartConnection(ex);
 60.1323 -      return getFirstArticleNumber(group);
 60.1324 -    }
 60.1325 -    finally
 60.1326 -    {
 60.1327 -      if(rs != null)
 60.1328 -      {
 60.1329 -        try
 60.1330 -        {
 60.1331 -          rs.close();
 60.1332 -        }
 60.1333 -        catch(SQLException ex)
 60.1334 -        {
 60.1335 -          ex.printStackTrace();
 60.1336 -        }
 60.1337 -      }
 60.1338 -    }
 60.1339 -  }
 60.1340 -  
 60.1341 -  /**
 60.1342 -   * Returns a group name identified by the given id.
 60.1343 -   * @param id
 60.1344 -   * @return
 60.1345 -   * @throws StorageBackendException
 60.1346 -   */
 60.1347 -  public String getGroup(int id)
 60.1348 -    throws StorageBackendException
 60.1349 -  {
 60.1350 -    ResultSet rs = null;
 60.1351 -
 60.1352 -    try
 60.1353 -    {
 60.1354 -      this.pstmtGetGroup1.setInt(1, id);
 60.1355 -      rs = this.pstmtGetGroup1.executeQuery();
 60.1356 -
 60.1357 -      if (rs.next())
 60.1358 -      {
 60.1359 -        return rs.getString(1);
 60.1360 -      }
 60.1361 -      else
 60.1362 -      {
 60.1363 -        return null;
 60.1364 -      }
 60.1365 -    }
 60.1366 -    catch(SQLException ex)
 60.1367 -    {
 60.1368 -      restartConnection(ex);
 60.1369 -      return getGroup(id);
 60.1370 -    }
 60.1371 -    finally
 60.1372 -    {
 60.1373 -      if(rs != null)
 60.1374 -      {
 60.1375 -        try
 60.1376 -        {
 60.1377 -          rs.close();
 60.1378 -        }
 60.1379 -        catch(SQLException ex)
 60.1380 -        {
 60.1381 -          ex.printStackTrace();
 60.1382 -        }
 60.1383 -      }
 60.1384 -    }
 60.1385 -  }
 60.1386 -
 60.1387 -  @Override
 60.1388 -  public double getEventsPerHour(int key, long gid)
 60.1389 -    throws StorageBackendException
 60.1390 -  {
 60.1391 -    String gidquery = "";
 60.1392 -    if(gid >= 0)
 60.1393 -    {
 60.1394 -      gidquery = " AND group_id = " + gid;
 60.1395 -    }
 60.1396 -    
 60.1397 -    Statement stmt = null;
 60.1398 -    ResultSet rs   = null;
 60.1399 -    
 60.1400 -    try
 60.1401 -    {
 60.1402 -      stmt = this.conn.createStatement();
 60.1403 -      rs = stmt.executeQuery("SELECT Count(*) / (Max(event_time) - Min(event_time))" +
 60.1404 -        " * 1000 * 60 * 60 FROM events WHERE event_key = " + key + gidquery);
 60.1405 -      
 60.1406 -      if(rs.next())
 60.1407 -      {
 60.1408 -        restarts = 0; // reset error count
 60.1409 -        return rs.getDouble(1);
 60.1410 -      }
 60.1411 -      else
 60.1412 -      {
 60.1413 -        return Double.NaN;
 60.1414 -      }
 60.1415 -    }
 60.1416 -    catch(SQLException ex)
 60.1417 -    {
 60.1418 -      restartConnection(ex);
 60.1419 -      return getEventsPerHour(key, gid);
 60.1420 -    }
 60.1421 -    finally
 60.1422 -    {
 60.1423 -      try
 60.1424 -      {
 60.1425 -        if(stmt != null)
 60.1426 -        {
 60.1427 -          stmt.close(); // Implicitely closes the result sets
 60.1428 -        }
 60.1429 -      }
 60.1430 -      catch(SQLException ex)
 60.1431 -      {
 60.1432 -        ex.printStackTrace();
 60.1433 -      }
 60.1434 -    }
 60.1435 -  }
 60.1436 -
 60.1437 -  @Override
 60.1438 -  public String getOldestArticle()
 60.1439 -    throws StorageBackendException
 60.1440 -  {
 60.1441 -    ResultSet rs = null;
 60.1442 -
 60.1443 -    try
 60.1444 -    {
 60.1445 -      rs = this.pstmtGetOldestArticle.executeQuery();
 60.1446 -      if(rs.next())
 60.1447 -      {
 60.1448 -        return rs.getString(1);
 60.1449 -      }
 60.1450 -      else
 60.1451 -      {
 60.1452 -        return null;
 60.1453 -      }
 60.1454 -    }
 60.1455 -    catch(SQLException ex)
 60.1456 -    {
 60.1457 -      restartConnection(ex);
 60.1458 -      return getOldestArticle();
 60.1459 -    }
 60.1460 -    finally
 60.1461 -    {
 60.1462 -      if(rs != null)
 60.1463 -      {
 60.1464 -        try
 60.1465 -        {
 60.1466 -          rs.close();
 60.1467 -        }
 60.1468 -        catch(SQLException ex)
 60.1469 -        {
 60.1470 -          ex.printStackTrace();
 60.1471 -        }
 60.1472 -      }
 60.1473 -    }
 60.1474 -  }
 60.1475 -
 60.1476 -  @Override
 60.1477 -  public int getPostingsCount(String groupname)
 60.1478 -    throws StorageBackendException
 60.1479 -  {
 60.1480 -    ResultSet rs = null;
 60.1481 -    
 60.1482 -    try
 60.1483 -    {
 60.1484 -      this.pstmtGetPostingsCount.setString(1, groupname);
 60.1485 -      rs = this.pstmtGetPostingsCount.executeQuery();
 60.1486 -      if(rs.next())
 60.1487 -      {
 60.1488 -        return rs.getInt(1);
 60.1489 -      }
 60.1490 -      else
 60.1491 -      {
 60.1492 -        Log.get().warning("Count on postings return nothing!");
 60.1493 -        return 0;
 60.1494 -      }
 60.1495 -    }
 60.1496 -    catch(SQLException ex)
 60.1497 -    {
 60.1498 -      restartConnection(ex);
 60.1499 -      return getPostingsCount(groupname);
 60.1500 -    }
 60.1501 -    finally
 60.1502 -    {
 60.1503 -      if(rs != null)
 60.1504 -      {
 60.1505 -        try
 60.1506 -        {
 60.1507 -          rs.close();
 60.1508 -        }
 60.1509 -        catch(SQLException ex)
 60.1510 -        {
 60.1511 -          ex.printStackTrace();
 60.1512 -        }
 60.1513 -      }
 60.1514 -    }
 60.1515 -  }
 60.1516 -
 60.1517 -  @Override
 60.1518 -  public List<Subscription> getSubscriptions(int feedtype)
 60.1519 -    throws StorageBackendException
 60.1520 -  {
 60.1521 -    ResultSet rs = null;
 60.1522 -    
 60.1523 -    try
 60.1524 -    {
 60.1525 -      List<Subscription> subs = new ArrayList<Subscription>();
 60.1526 -      this.pstmtGetSubscriptions.setInt(1, feedtype);
 60.1527 -      rs = this.pstmtGetSubscriptions.executeQuery();
 60.1528 -      
 60.1529 -      while(rs.next())
 60.1530 -      {
 60.1531 -        String host  = rs.getString("host");
 60.1532 -        String group = rs.getString("name");
 60.1533 -        int    port  = rs.getInt("port");
 60.1534 -        subs.add(new Subscription(host, port, feedtype, group));
 60.1535 -      }
 60.1536 -      
 60.1537 -      return subs;
 60.1538 -    }
 60.1539 -    catch(SQLException ex)
 60.1540 -    {
 60.1541 -      restartConnection(ex);
 60.1542 -      return getSubscriptions(feedtype);
 60.1543 -    }
 60.1544 -    finally
 60.1545 -    {
 60.1546 -      if(rs != null)
 60.1547 -      {
 60.1548 -        try
 60.1549 -        {
 60.1550 -          rs.close();
 60.1551 -        }
 60.1552 -        catch(SQLException ex)
 60.1553 -        {
 60.1554 -          ex.printStackTrace();
 60.1555 -        }
 60.1556 -      }
 60.1557 -    }
 60.1558 -  }
 60.1559 -
 60.1560 -  /**
 60.1561 -   * Checks if there is an article with the given messageid in the JDBCDatabase.
 60.1562 -   * @param name
 60.1563 -   * @return
 60.1564 -   * @throws StorageBackendException
 60.1565 -   */
 60.1566 -  @Override
 60.1567 -  public boolean isArticleExisting(String messageID)
 60.1568 -    throws StorageBackendException
 60.1569 -  {
 60.1570 -    ResultSet rs = null;
 60.1571 -    
 60.1572 -    try
 60.1573 -    {
 60.1574 -      this.pstmtIsArticleExisting.setString(1, messageID);
 60.1575 -      rs = this.pstmtIsArticleExisting.executeQuery();
 60.1576 -      return rs.next() && rs.getInt(1) == 1;
 60.1577 -    }
 60.1578 -    catch(SQLException ex)
 60.1579 -    {
 60.1580 -      restartConnection(ex);
 60.1581 -      return isArticleExisting(messageID);
 60.1582 -    }
 60.1583 -    finally
 60.1584 -    {
 60.1585 -      if(rs != null)
 60.1586 -      {
 60.1587 -        try
 60.1588 -        {
 60.1589 -          rs.close();
 60.1590 -        }
 60.1591 -        catch(SQLException ex)
 60.1592 -        {
 60.1593 -          ex.printStackTrace();
 60.1594 -        }
 60.1595 -      }
 60.1596 -    }
 60.1597 -  }
 60.1598 -  
 60.1599 -  /**
 60.1600 -   * Checks if there is a group with the given name in the JDBCDatabase.
 60.1601 -   * @param name
 60.1602 -   * @return
 60.1603 -   * @throws StorageBackendException
 60.1604 -   */
 60.1605 -  @Override
 60.1606 -  public boolean isGroupExisting(String name)
 60.1607 -    throws StorageBackendException
 60.1608 -  {
 60.1609 -    ResultSet rs = null;
 60.1610 -    
 60.1611 -    try
 60.1612 -    {
 60.1613 -      this.pstmtIsGroupExisting.setString(1, name);
 60.1614 -      rs = this.pstmtIsGroupExisting.executeQuery();
 60.1615 -      return rs.next();
 60.1616 -    }
 60.1617 -    catch(SQLException ex)
 60.1618 -    {
 60.1619 -      restartConnection(ex);
 60.1620 -      return isGroupExisting(name);
 60.1621 -    }
 60.1622 -    finally
 60.1623 -    {
 60.1624 -      if(rs != null)
 60.1625 -      {
 60.1626 -        try
 60.1627 -        {
 60.1628 -          rs.close();
 60.1629 -        }
 60.1630 -        catch(SQLException ex)
 60.1631 -        {
 60.1632 -          ex.printStackTrace();
 60.1633 -        }
 60.1634 -      }
 60.1635 -    }
 60.1636 -  }
 60.1637 -
 60.1638 -  @Override
 60.1639 -  public void setConfigValue(String key, String value)
 60.1640 -    throws StorageBackendException
 60.1641 -  {
 60.1642 -    try
 60.1643 -    {
 60.1644 -      conn.setAutoCommit(false);
 60.1645 -      this.pstmtSetConfigValue0.setString(1, key);
 60.1646 -      this.pstmtSetConfigValue0.execute();
 60.1647 -      this.pstmtSetConfigValue1.setString(1, key);
 60.1648 -      this.pstmtSetConfigValue1.setString(2, value);
 60.1649 -      this.pstmtSetConfigValue1.execute();
 60.1650 -      conn.commit();
 60.1651 -      conn.setAutoCommit(true);
 60.1652 -    }
 60.1653 -    catch(SQLException ex)
 60.1654 -    {
 60.1655 -      restartConnection(ex);
 60.1656 -      setConfigValue(key, value);
 60.1657 -    }
 60.1658 -  }
 60.1659 -  
 60.1660 -  /**
 60.1661 -   * Closes the JDBCDatabase connection.
 60.1662 -   */
 60.1663 -  public void shutdown()
 60.1664 -    throws StorageBackendException
 60.1665 -  {
 60.1666 -    try
 60.1667 -    {
 60.1668 -      if(this.conn != null)
 60.1669 -      {
 60.1670 -        this.conn.close();
 60.1671 -      }
 60.1672 -    }
 60.1673 -    catch(SQLException ex)
 60.1674 -    {
 60.1675 -      throw new StorageBackendException(ex);
 60.1676 -    }
 60.1677 -  }
 60.1678 -
 60.1679 -  @Override
 60.1680 -  public void purgeGroup(Group group)
 60.1681 -    throws StorageBackendException
 60.1682 -  {
 60.1683 -    try
 60.1684 -    {
 60.1685 -      this.pstmtPurgeGroup0.setLong(1, group.getInternalID());
 60.1686 -      this.pstmtPurgeGroup0.executeUpdate();
 60.1687 -
 60.1688 -      this.pstmtPurgeGroup1.setLong(1, group.getInternalID());
 60.1689 -      this.pstmtPurgeGroup1.executeUpdate();
 60.1690 -    }
 60.1691 -    catch(SQLException ex)
 60.1692 -    {
 60.1693 -      restartConnection(ex);
 60.1694 -      purgeGroup(group);
 60.1695 -    }
 60.1696 -  }
 60.1697 -  
 60.1698 -  private void restartConnection(SQLException cause)
 60.1699 -    throws StorageBackendException
 60.1700 -  {
 60.1701 -    restarts++;
 60.1702 -    Log.get().severe(Thread.currentThread()
 60.1703 -      + ": Database connection was closed (restart " + restarts + ").");
 60.1704 -    
 60.1705 -    if(restarts >= MAX_RESTARTS)
 60.1706 -    {
 60.1707 -      // Delete the current, probably broken JDBCDatabase instance.
 60.1708 -      // So no one can use the instance any more.
 60.1709 -      JDBCDatabaseProvider.instances.remove(Thread.currentThread());
 60.1710 -      
 60.1711 -      // Throw the exception upwards
 60.1712 -      throw new StorageBackendException(cause);
 60.1713 -    }
 60.1714 -    
 60.1715 -    try
 60.1716 -    {
 60.1717 -      Thread.sleep(1500L * restarts);
 60.1718 -    }
 60.1719 -    catch(InterruptedException ex)
 60.1720 -    {
 60.1721 -      Log.get().warning("Interrupted: " + ex.getMessage());
 60.1722 -    }
 60.1723 -    
 60.1724 -    // Try to properly close the old database connection
 60.1725 -    try
 60.1726 -    {
 60.1727 -      if(this.conn != null)
 60.1728 -      {
 60.1729 -        this.conn.close();
 60.1730 -      }
 60.1731 -    }
 60.1732 -    catch(SQLException ex)
 60.1733 -    {
 60.1734 -      Log.get().warning(ex.getMessage());
 60.1735 -    }
 60.1736 -    
 60.1737 -    try
 60.1738 -    {
 60.1739 -      // Try to reinitialize database connection
 60.1740 -      arise();
 60.1741 -    }
 60.1742 -    catch(SQLException ex)
 60.1743 -    {
 60.1744 -      Log.get().warning(ex.getMessage());
 60.1745 -      restartConnection(ex);
 60.1746 -    }
 60.1747 -  }
 60.1748 -
 60.1749 -  @Override
 60.1750 -  public boolean update(Article article)
 60.1751 -    throws StorageBackendException
 60.1752 -  {
 60.1753 -    // DELETE FROM headers WHERE article_id = ?
 60.1754 -
 60.1755 -    // INSERT INTO headers ...
 60.1756 -
 60.1757 -    // SELECT * FROM postings WHERE article_id = ? AND group_id = ?
 60.1758 -    return false;
 60.1759 -  }
 60.1760 -
 60.1761 -  /**
 60.1762 -   * Writes the flags and the name of the given group to the database.
 60.1763 -   * @param group
 60.1764 -   * @throws StorageBackendException
 60.1765 -   */
 60.1766 -  @Override
 60.1767 -  public boolean update(Group group)
 60.1768 -    throws StorageBackendException
 60.1769 -  {
 60.1770 -    try
 60.1771 -    {
 60.1772 -      this.pstmtUpdateGroup.setInt(1, group.getFlags());
 60.1773 -      this.pstmtUpdateGroup.setString(2, group.getName());
 60.1774 -      this.pstmtUpdateGroup.setLong(3, group.getInternalID());
 60.1775 -      int rs = this.pstmtUpdateGroup.executeUpdate();
 60.1776 -      return rs == 1;
 60.1777 -    }
 60.1778 -    catch(SQLException ex)
 60.1779 -    {
 60.1780 -      restartConnection(ex);
 60.1781 -      return update(group);
 60.1782 -    }
 60.1783 -  }
 60.1784 -
 60.1785 -}
    61.1 --- a/org/sonews/storage/impl/JDBCDatabaseProvider.java	Sun Aug 29 17:04:25 2010 +0200
    61.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    61.3 @@ -1,69 +0,0 @@
    61.4 -/*
    61.5 - *   SONEWS News Server
    61.6 - *   see AUTHORS for the list of contributors
    61.7 - *
    61.8 - *   This program is free software: you can redistribute it and/or modify
    61.9 - *   it under the terms of the GNU General Public License as published by
   61.10 - *   the Free Software Foundation, either version 3 of the License, or
   61.11 - *   (at your option) any later version.
   61.12 - *
   61.13 - *   This program is distributed in the hope that it will be useful,
   61.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   61.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   61.16 - *   GNU General Public License for more details.
   61.17 - *
   61.18 - *   You should have received a copy of the GNU General Public License
   61.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   61.20 - */
   61.21 -
   61.22 -package org.sonews.storage.impl;
   61.23 -
   61.24 -import java.sql.SQLException;
   61.25 -import java.util.Map;
   61.26 -import java.util.concurrent.ConcurrentHashMap;
   61.27 -import org.sonews.storage.Storage;
   61.28 -import org.sonews.storage.StorageBackendException;
   61.29 -import org.sonews.storage.StorageProvider;
   61.30 -
   61.31 -/**
   61.32 - *
   61.33 - * @author Christian Lins
   61.34 - * @since sonews/1.0
   61.35 - */
   61.36 -public class JDBCDatabaseProvider implements StorageProvider
   61.37 -{
   61.38 -
   61.39 -  protected static final Map<Thread, JDBCDatabase> instances
   61.40 -    = new ConcurrentHashMap<Thread, JDBCDatabase>();
   61.41 -
   61.42 -  @Override
   61.43 -  public boolean isSupported(String uri)
   61.44 -  {
   61.45 -    throw new UnsupportedOperationException("Not supported yet.");
   61.46 -  }
   61.47 -
   61.48 -  @Override
   61.49 -  public Storage storage(Thread thread)
   61.50 -    throws StorageBackendException
   61.51 -  {
   61.52 -    try
   61.53 -    {
   61.54 -    if(!instances.containsKey(Thread.currentThread()))
   61.55 -    {
   61.56 -      JDBCDatabase db = new JDBCDatabase();
   61.57 -      db.arise();
   61.58 -      instances.put(Thread.currentThread(), db);
   61.59 -      return db;
   61.60 -    }
   61.61 -    else
   61.62 -    {
   61.63 -      return instances.get(Thread.currentThread());
   61.64 -    }
   61.65 -    }
   61.66 -    catch(SQLException ex)
   61.67 -    {
   61.68 -      throw new StorageBackendException(ex);
   61.69 -    }
   61.70 -  }
   61.71 -
   61.72 -}
    62.1 --- a/org/sonews/storage/package.html	Sun Aug 29 17:04:25 2010 +0200
    62.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    62.3 @@ -1,2 +0,0 @@
    62.4 -Contains classes of the storage backend and the Group and Article
    62.5 -abstraction.
    62.6 \ No newline at end of file
    63.1 --- a/org/sonews/util/DatabaseSetup.java	Sun Aug 29 17:04:25 2010 +0200
    63.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    63.3 @@ -1,127 +0,0 @@
    63.4 -/*
    63.5 - *   SONEWS News Server
    63.6 - *   see AUTHORS for the list of contributors
    63.7 - *
    63.8 - *   This program is free software: you can redistribute it and/or modify
    63.9 - *   it under the terms of the GNU General Public License as published by
   63.10 - *   the Free Software Foundation, either version 3 of the License, or
   63.11 - *   (at your option) any later version.
   63.12 - *
   63.13 - *   This program is distributed in the hope that it will be useful,
   63.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   63.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   63.16 - *   GNU General Public License for more details.
   63.17 - *
   63.18 - *   You should have received a copy of the GNU General Public License
   63.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   63.20 - */
   63.21 -
   63.22 -package org.sonews.util;
   63.23 -
   63.24 -import java.io.BufferedReader;
   63.25 -import java.io.InputStreamReader;
   63.26 -import java.sql.Connection;
   63.27 -import java.sql.DriverManager;
   63.28 -import java.sql.Statement;
   63.29 -import java.util.HashMap;
   63.30 -import java.util.Map;
   63.31 -import org.sonews.config.Config;
   63.32 -import org.sonews.util.io.Resource;
   63.33 -
   63.34 -/**
   63.35 - * Database setup utility class.
   63.36 - * @author Christian Lins
   63.37 - * @since sonews/0.5.0
   63.38 - */
   63.39 -public final class DatabaseSetup 
   63.40 -{
   63.41 -
   63.42 -  private static final Map<String, String> templateMap 
   63.43 -    = new HashMap<String, String>();
   63.44 -  private static final Map<String, StringTemplate> urlMap
   63.45 -    = new HashMap<String, StringTemplate>();
   63.46 -  private static final Map<String, String> driverMap
   63.47 -    = new HashMap<String, String>();
   63.48 -  
   63.49 -  static
   63.50 -  {
   63.51 -    templateMap.put("1", "helpers/database_mysql5_tmpl.sql");
   63.52 -    templateMap.put("2", "helpers/database_postgresql8_tmpl.sql");
   63.53 -    
   63.54 -    urlMap.put("1", new StringTemplate("jdbc:mysql://%HOSTNAME/%DB"));
   63.55 -    urlMap.put("2", new StringTemplate("jdbc:postgresql://%HOSTNAME/%DB"));
   63.56 -    
   63.57 -    driverMap.put("1", "com.mysql.jdbc.Driver");
   63.58 -    driverMap.put("2", "org.postgresql.Driver");
   63.59 -  }
   63.60 -  
   63.61 -  public static void main(String[] args)
   63.62 -    throws Exception
   63.63 -  {
   63.64 -    System.out.println("sonews Database setup helper");
   63.65 -    System.out.println("This program will create a initial database table structure");
   63.66 -    System.out.println("for the sonews Newsserver.");
   63.67 -    System.out.println("You need to create a database and a db user manually before!");
   63.68 -    
   63.69 -    System.out.println("Select DBMS type:");
   63.70 -    System.out.println("[1] MySQL 5.x or higher");
   63.71 -    System.out.println("[2] PostgreSQL 8.x or higher");
   63.72 -    System.out.print("Your choice: ");
   63.73 -    
   63.74 -    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
   63.75 -    String dbmsType = in.readLine();
   63.76 -    String tmplName = templateMap.get(dbmsType);
   63.77 -    if(tmplName == null)
   63.78 -    {
   63.79 -      System.err.println("Invalid choice. Try again you fool!");
   63.80 -      main(args);
   63.81 -      return;
   63.82 -    }
   63.83 -    
   63.84 -    // Load JDBC Driver class
   63.85 -    Class.forName(driverMap.get(dbmsType));
   63.86 -    
   63.87 -    String tmpl = Resource.getAsString(tmplName, true);
   63.88 -    
   63.89 -    System.out.print("Database server hostname (e.g. localhost): ");
   63.90 -    String dbHostname = in.readLine();
   63.91 -    
   63.92 -    System.out.print("Database name: ");
   63.93 -    String dbName = in.readLine();
   63.94 -
   63.95 -    System.out.print("Give name of DB user that can create tables: ");
   63.96 -    String dbUser = in.readLine();
   63.97 -
   63.98 -    System.out.print("Password: ");
   63.99 -    String dbPassword = in.readLine();
  63.100 -    
  63.101 -    String url = urlMap.get(dbmsType)
  63.102 -      .set("HOSTNAME", dbHostname)
  63.103 -      .set("DB", dbName).toString();
  63.104 -    
  63.105 -    Connection conn = 
  63.106 -      DriverManager.getConnection(url, dbUser, dbPassword);
  63.107 -    conn.setAutoCommit(false);
  63.108 -    
  63.109 -    String[] tmplChunks = tmpl.split(";");
  63.110 -    
  63.111 -    for(String chunk : tmplChunks)
  63.112 -    {
  63.113 -      if(chunk.trim().equals(""))
  63.114 -      {
  63.115 -        continue;
  63.116 -      }
  63.117 -      
  63.118 -      Statement stmt = conn.createStatement();
  63.119 -      stmt.execute(chunk);
  63.120 -    }
  63.121 -    
  63.122 -    conn.commit();
  63.123 -    conn.setAutoCommit(true);
  63.124 -    
  63.125 -    // Create config file
  63.126 -    
  63.127 -    System.out.println("Ok");
  63.128 -  }
  63.129 -  
  63.130 -}
    64.1 --- a/org/sonews/util/Log.java	Sun Aug 29 17:04:25 2010 +0200
    64.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    64.3 @@ -1,57 +0,0 @@
    64.4 -/*
    64.5 - *   SONEWS News Server
    64.6 - *   see AUTHORS for the list of contributors
    64.7 - *
    64.8 - *   This program is free software: you can redistribute it and/or modify
    64.9 - *   it under the terms of the GNU General Public License as published by
   64.10 - *   the Free Software Foundation, either version 3 of the License, or
   64.11 - *   (at your option) any later version.
   64.12 - *
   64.13 - *   This program is distributed in the hope that it will be useful,
   64.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   64.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   64.16 - *   GNU General Public License for more details.
   64.17 - *
   64.18 - *   You should have received a copy of the GNU General Public License
   64.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   64.20 - */
   64.21 -
   64.22 -package org.sonews.util;
   64.23 -
   64.24 -import java.util.logging.Level;
   64.25 -import java.util.logging.LogManager;
   64.26 -import java.util.logging.Logger;
   64.27 -import java.util.logging.SimpleFormatter;
   64.28 -import java.util.logging.StreamHandler;
   64.29 -import org.sonews.config.Config;
   64.30 -
   64.31 -/**
   64.32 - * Provides logging and debugging methods.
   64.33 - * @author Christian Lins
   64.34 - * @since sonews/0.5.0
   64.35 - */
   64.36 -public class Log extends Logger
   64.37 -{
   64.38 -
   64.39 -  private static Log instance = new Log();
   64.40 -
   64.41 -  private Log()
   64.42 -  {
   64.43 -    super("org.sonews", null);
   64.44 -
   64.45 -    StreamHandler handler = new StreamHandler(System.out, new SimpleFormatter());
   64.46 -    Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
   64.47 -    handler.setLevel(level);
   64.48 -    addHandler(handler);
   64.49 -    setLevel(level);
   64.50 -    LogManager.getLogManager().addLogger(this);
   64.51 -  }
   64.52 -
   64.53 -  public static Logger get()
   64.54 -  {
   64.55 -    Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
   64.56 -    instance.setLevel(level);
   64.57 -    return instance;
   64.58 -  }
   64.59 -
   64.60 -}
    65.1 --- a/org/sonews/util/Pair.java	Sun Aug 29 17:04:25 2010 +0200
    65.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    65.3 @@ -1,48 +0,0 @@
    65.4 -/*
    65.5 - *   SONEWS News Server
    65.6 - *   see AUTHORS for the list of contributors
    65.7 - *
    65.8 - *   This program is free software: you can redistribute it and/or modify
    65.9 - *   it under the terms of the GNU General Public License as published by
   65.10 - *   the Free Software Foundation, either version 3 of the License, or
   65.11 - *   (at your option) any later version.
   65.12 - *
   65.13 - *   This program is distributed in the hope that it will be useful,
   65.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   65.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   65.16 - *   GNU General Public License for more details.
   65.17 - *
   65.18 - *   You should have received a copy of the GNU General Public License
   65.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   65.20 - */
   65.21 -
   65.22 -package org.sonews.util;
   65.23 -
   65.24 -/**
   65.25 - * A pair of two objects.
   65.26 - * @author Christian Lins
   65.27 - * @since sonews/0.5.0
   65.28 - */
   65.29 -public class Pair<T1, T2> 
   65.30 -{
   65.31 - 
   65.32 -  private T1 a;
   65.33 -  private T2 b;
   65.34 -  
   65.35 -  public Pair(T1 a, T2 b)
   65.36 -  {
   65.37 -    this.a = a;
   65.38 -    this.b = b;
   65.39 -  }
   65.40 -
   65.41 -  public T1 getA()
   65.42 -  {
   65.43 -    return a;
   65.44 -  }
   65.45 -
   65.46 -  public T2 getB()
   65.47 -  {
   65.48 -    return b;
   65.49 -  } 
   65.50 - 
   65.51 -}
    66.1 --- a/org/sonews/util/Purger.java	Sun Aug 29 17:04:25 2010 +0200
    66.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    66.3 @@ -1,149 +0,0 @@
    66.4 -/*
    66.5 - *   SONEWS News Server
    66.6 - *   see AUTHORS for the list of contributors
    66.7 - *
    66.8 - *   This program is free software: you can redistribute it and/or modify
    66.9 - *   it under the terms of the GNU General Public License as published by
   66.10 - *   the Free Software Foundation, either version 3 of the License, or
   66.11 - *   (at your option) any later version.
   66.12 - *
   66.13 - *   This program is distributed in the hope that it will be useful,
   66.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   66.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   66.16 - *   GNU General Public License for more details.
   66.17 - *
   66.18 - *   You should have received a copy of the GNU General Public License
   66.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   66.20 - */
   66.21 -
   66.22 -package org.sonews.util;
   66.23 -
   66.24 -import org.sonews.daemon.AbstractDaemon;
   66.25 -import org.sonews.config.Config;
   66.26 -import org.sonews.storage.Article;
   66.27 -import org.sonews.storage.Headers;
   66.28 -import java.util.Date;
   66.29 -import java.util.List;
   66.30 -import org.sonews.storage.Channel;
   66.31 -import org.sonews.storage.Group;
   66.32 -import org.sonews.storage.StorageBackendException;
   66.33 -import org.sonews.storage.StorageManager;
   66.34 -
   66.35 -/**
   66.36 - * The purger is started in configurable intervals to search
   66.37 - * for messages that can be purged. A message must be deleted if its lifetime
   66.38 - * has exceeded, if it was marked as deleted or if the maximum number of
   66.39 - * articles in the database is reached.
   66.40 - * @author Christian Lins
   66.41 - * @since sonews/0.5.0
   66.42 - */
   66.43 -public class Purger extends AbstractDaemon
   66.44 -{
   66.45 -
   66.46 -  /**
   66.47 -   * Loops through all messages and deletes them if their time
   66.48 -   * has come.
   66.49 -   */
   66.50 -  @Override
   66.51 -  public void run()
   66.52 -  {
   66.53 -    try
   66.54 -    {
   66.55 -      while(isRunning())
   66.56 -      {
   66.57 -        purgeDeleted();
   66.58 -        purgeOutdated();
   66.59 -
   66.60 -        Thread.sleep(120000); // Sleep for two minutes
   66.61 -      }
   66.62 -    }
   66.63 -    catch(StorageBackendException ex)
   66.64 -    {
   66.65 -      ex.printStackTrace();
   66.66 -    }
   66.67 -    catch(InterruptedException ex)
   66.68 -    {
   66.69 -      Log.get().warning("Purger interrupted: " + ex);
   66.70 -    }
   66.71 -  }
   66.72 -
   66.73 -  private void purgeDeleted()
   66.74 -    throws StorageBackendException
   66.75 -  {
   66.76 -    List<Channel> groups = StorageManager.current().getGroups();
   66.77 -    for(Channel channel : groups)
   66.78 -    {
   66.79 -      if(!(channel instanceof Group))
   66.80 -        continue;
   66.81 -      
   66.82 -      Group group = (Group)channel;
   66.83 -      // Look for groups that are marked as deleted
   66.84 -      if(group.isDeleted())
   66.85 -      {
   66.86 -        List<Long> ids = StorageManager.current().getArticleNumbers(group.getInternalID());
   66.87 -        if(ids.size() == 0)
   66.88 -        {
   66.89 -          StorageManager.current().purgeGroup(group);
   66.90 -          Log.get().info("Group " + group.getName() + " purged.");
   66.91 -        }
   66.92 -
   66.93 -        for(int n = 0; n < ids.size() && n < 10; n++)
   66.94 -        {
   66.95 -          Article art = StorageManager.current().getArticle(ids.get(n), group.getInternalID());
   66.96 -          StorageManager.current().delete(art.getMessageID());
   66.97 -          Log.get().info("Article " + art.getMessageID() + " purged.");
   66.98 -        }
   66.99 -      }
  66.100 -    }
  66.101 -  }
  66.102 -
  66.103 -  private void purgeOutdated()
  66.104 -    throws InterruptedException, StorageBackendException
  66.105 -  {
  66.106 -    long articleMaximum =
  66.107 -      Config.inst().get("sonews.article.maxnum", Long.MAX_VALUE);
  66.108 -    long lifetime =
  66.109 -      Config.inst().get("sonews.article.lifetime", -1);
  66.110 -
  66.111 -    if(lifetime > 0 || articleMaximum < Stats.getInstance().getNumberOfNews())
  66.112 -    {
  66.113 -      Log.get().info("Purging old messages...");
  66.114 -      String mid = StorageManager.current().getOldestArticle();
  66.115 -      if (mid == null) // No articles in the database
  66.116 -      {
  66.117 -        return;
  66.118 -      }
  66.119 -
  66.120 -      Article art = StorageManager.current().getArticle(mid);
  66.121 -      long artDate = 0;
  66.122 -      String dateStr = art.getHeader(Headers.DATE)[0];
  66.123 -      try
  66.124 -      {
  66.125 -        artDate = Date.parse(dateStr) / 1000 / 60 / 60 / 24;
  66.126 -      }
  66.127 -      catch (IllegalArgumentException ex)
  66.128 -      {
  66.129 -        Log.get().warning("Could not parse date string: " + dateStr + " " + ex);
  66.130 -      }
  66.131 -
  66.132 -      // Should we delete the message because of its age or because the
  66.133 -      // article maximum was reached?
  66.134 -      if (lifetime < 0 || artDate < (new Date().getTime() + lifetime))
  66.135 -      {
  66.136 -        StorageManager.current().delete(mid);
  66.137 -        System.out.println("Deleted: " + mid);
  66.138 -      }
  66.139 -      else
  66.140 -      {
  66.141 -        Thread.sleep(1000 * 60); // Wait 60 seconds
  66.142 -        return;
  66.143 -      }
  66.144 -    }
  66.145 -    else
  66.146 -    {
  66.147 -      Log.get().info("Lifetime purger is disabled");
  66.148 -      Thread.sleep(1000 * 60 * 30); // Wait 30 minutes
  66.149 -    }
  66.150 -  }
  66.151 -
  66.152 -}
    67.1 --- a/org/sonews/util/Stats.java	Sun Aug 29 17:04:25 2010 +0200
    67.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    67.3 @@ -1,206 +0,0 @@
    67.4 -/*
    67.5 - *   SONEWS News Server
    67.6 - *   see AUTHORS for the list of contributors
    67.7 - *
    67.8 - *   This program is free software: you can redistribute it and/or modify
    67.9 - *   it under the terms of the GNU General Public License as published by
   67.10 - *   the Free Software Foundation, either version 3 of the License, or
   67.11 - *   (at your option) any later version.
   67.12 - *
   67.13 - *   This program is distributed in the hope that it will be useful,
   67.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   67.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   67.16 - *   GNU General Public License for more details.
   67.17 - *
   67.18 - *   You should have received a copy of the GNU General Public License
   67.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   67.20 - */
   67.21 -
   67.22 -package org.sonews.util;
   67.23 -
   67.24 -import java.util.Calendar;
   67.25 -import org.sonews.config.Config;
   67.26 -import org.sonews.storage.Channel;
   67.27 -import org.sonews.storage.StorageBackendException;
   67.28 -import org.sonews.storage.StorageManager;
   67.29 -
   67.30 -/**
   67.31 - * Class that capsulates statistical data gathering.
   67.32 - * @author Christian Lins
   67.33 - * @since sonews/0.5.0
   67.34 - */
   67.35 -public final class Stats 
   67.36 -{
   67.37 -      
   67.38 -  public static final byte CONNECTIONS    = 1;
   67.39 -  public static final byte POSTED_NEWS    = 2;
   67.40 -  public static final byte GATEWAYED_NEWS = 3;
   67.41 -  public static final byte FEEDED_NEWS    = 4;
   67.42 -  public static final byte MLGW_RUNSTART  = 5;
   67.43 -  public static final byte MLGW_RUNEND    = 6;
   67.44 -
   67.45 -  private static Stats instance = new Stats();
   67.46 -  
   67.47 -  public static Stats getInstance()
   67.48 -  {
   67.49 -    return Stats.instance;
   67.50 -  }
   67.51 -  
   67.52 -  private Stats() {}
   67.53 -  
   67.54 -  private volatile int connectedClients = 0;
   67.55 -
   67.56 -  /**
   67.57 -   * A generic method that writes event data to the storage backend.
   67.58 -   * If event logging is disabled with sonews.eventlog=false this method
   67.59 -   * simply does nothing.
   67.60 -   * @param type
   67.61 -   * @param groupname
   67.62 -   */
   67.63 -  private void addEvent(byte type, String groupname)
   67.64 -  {
   67.65 -    try
   67.66 -    {
   67.67 -      if (Config.inst().get(Config.EVENTLOG, true))
   67.68 -      {
   67.69 -
   67.70 -        Channel group = Channel.getByName(groupname);
   67.71 -        if (group != null)
   67.72 -        {
   67.73 -          StorageManager.current().addEvent(
   67.74 -                  System.currentTimeMillis(), type, group.getInternalID());
   67.75 -        }
   67.76 -      } 
   67.77 -      else
   67.78 -      {
   67.79 -        Log.get().info("Group " + groupname + " does not exist.");
   67.80 -      }
   67.81 -    } 
   67.82 -    catch (StorageBackendException ex)
   67.83 -    {
   67.84 -      ex.printStackTrace();
   67.85 -    }
   67.86 -  }
   67.87 -  
   67.88 -  public void clientConnect()
   67.89 -  {
   67.90 -    this.connectedClients++;
   67.91 -  }
   67.92 -  
   67.93 -  public void clientDisconnect()
   67.94 -  {
   67.95 -    this.connectedClients--;
   67.96 -  }
   67.97 -  
   67.98 -  public int connectedClients()
   67.99 -  {
  67.100 -    return this.connectedClients;
  67.101 -  }
  67.102 -  
  67.103 -  public int getNumberOfGroups()
  67.104 -  {
  67.105 -    try
  67.106 -    {
  67.107 -      return StorageManager.current().countGroups();
  67.108 -    }
  67.109 -    catch(StorageBackendException ex)
  67.110 -    {
  67.111 -      ex.printStackTrace();
  67.112 -      return -1;
  67.113 -    }
  67.114 -  }
  67.115 -  
  67.116 -  public int getNumberOfNews()
  67.117 -  {
  67.118 -    try
  67.119 -    {
  67.120 -      return StorageManager.current().countArticles();
  67.121 -    }
  67.122 -    catch(StorageBackendException ex)
  67.123 -    {
  67.124 -      ex.printStackTrace();
  67.125 -      return -1;
  67.126 -    }
  67.127 -  }
  67.128 -  
  67.129 -  public int getYesterdaysEvents(final byte eventType, final int hour,
  67.130 -    final Channel group)
  67.131 -  {
  67.132 -    // Determine the timestamp values for yesterday and the given hour
  67.133 -    Calendar cal = Calendar.getInstance();
  67.134 -    int year  = cal.get(Calendar.YEAR);
  67.135 -    int month = cal.get(Calendar.MONTH);
  67.136 -    int dayom = cal.get(Calendar.DAY_OF_MONTH) - 1; // Yesterday
  67.137 -    
  67.138 -    cal.set(year, month, dayom, hour, 0, 0);
  67.139 -    long startTimestamp = cal.getTimeInMillis();
  67.140 -    
  67.141 -    cal.set(year, month, dayom, hour + 1, 0, 0);
  67.142 -    long endTimestamp = cal.getTimeInMillis();
  67.143 -    
  67.144 -    try
  67.145 -    {
  67.146 -      return StorageManager.current()
  67.147 -        .getEventsCount(eventType, startTimestamp, endTimestamp, group);
  67.148 -    }
  67.149 -    catch(StorageBackendException ex)
  67.150 -    {
  67.151 -      ex.printStackTrace();
  67.152 -      return -1;
  67.153 -    }
  67.154 -  }
  67.155 -  
  67.156 -  public void mailPosted(String groupname)
  67.157 -  {
  67.158 -    addEvent(POSTED_NEWS, groupname);
  67.159 -  }
  67.160 -  
  67.161 -  public void mailGatewayed(String groupname)
  67.162 -  {
  67.163 -    addEvent(GATEWAYED_NEWS, groupname);
  67.164 -  }
  67.165 -  
  67.166 -  public void mailFeeded(String groupname)
  67.167 -  {
  67.168 -    addEvent(FEEDED_NEWS, groupname);
  67.169 -  }
  67.170 -  
  67.171 -  public void mlgwRunStart()
  67.172 -  {
  67.173 -    addEvent(MLGW_RUNSTART, "control");
  67.174 -  }
  67.175 -  
  67.176 -  public void mlgwRunEnd()
  67.177 -  {
  67.178 -    addEvent(MLGW_RUNEND, "control");
  67.179 -  }
  67.180 -  
  67.181 -  private double perHour(int key, long gid)
  67.182 -  {
  67.183 -    try
  67.184 -    {
  67.185 -      return StorageManager.current().getEventsPerHour(key, gid);
  67.186 -    }
  67.187 -    catch(StorageBackendException ex)
  67.188 -    {
  67.189 -      ex.printStackTrace();
  67.190 -      return -1;
  67.191 -    }
  67.192 -  }
  67.193 -  
  67.194 -  public double postedPerHour(long gid)
  67.195 -  {
  67.196 -    return perHour(POSTED_NEWS, gid);
  67.197 -  }
  67.198 -  
  67.199 -  public double gatewayedPerHour(long gid)
  67.200 -  {
  67.201 -    return perHour(GATEWAYED_NEWS, gid);
  67.202 -  }
  67.203 -  
  67.204 -  public double feededPerHour(long gid)
  67.205 -  {
  67.206 -    return perHour(FEEDED_NEWS, gid);
  67.207 -  }
  67.208 -  
  67.209 -}
    68.1 --- a/org/sonews/util/StringTemplate.java	Sun Aug 29 17:04:25 2010 +0200
    68.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    68.3 @@ -1,97 +0,0 @@
    68.4 -/*
    68.5 - *   SONEWS News Server
    68.6 - *   see AUTHORS for the list of contributors
    68.7 - *
    68.8 - *   This program is free software: you can redistribute it and/or modify
    68.9 - *   it under the terms of the GNU General Public License as published by
   68.10 - *   the Free Software Foundation, either version 3 of the License, or
   68.11 - *   (at your option) any later version.
   68.12 - *
   68.13 - *   This program is distributed in the hope that it will be useful,
   68.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   68.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   68.16 - *   GNU General Public License for more details.
   68.17 - *
   68.18 - *   You should have received a copy of the GNU General Public License
   68.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   68.20 - */
   68.21 -
   68.22 -package org.sonews.util;
   68.23 -
   68.24 -import java.util.HashMap;
   68.25 -import java.util.Map;
   68.26 -
   68.27 -/**
   68.28 - * Class that allows simple String template handling.
   68.29 - * @author Christian Lins
   68.30 - * @since sonews/0.5.0
   68.31 - */
   68.32 -public class StringTemplate 
   68.33 -{
   68.34 -
   68.35 -  private String              str               = null;
   68.36 -  private String              templateDelimiter = "%";
   68.37 -  private Map<String, String> templateValues    = new HashMap<String, String>();
   68.38 -  
   68.39 -  public StringTemplate(String str, final String templateDelimiter)
   68.40 -  {
   68.41 -    if(str == null || templateDelimiter == null)
   68.42 -    {
   68.43 -      throw new IllegalArgumentException("null arguments not allowed");
   68.44 -    }
   68.45 -
   68.46 -    this.str               = str;
   68.47 -    this.templateDelimiter = templateDelimiter;
   68.48 -  }
   68.49 -  
   68.50 -  public StringTemplate(String str)
   68.51 -  {
   68.52 -    this(str, "%");
   68.53 -  }
   68.54 -  
   68.55 -  public StringTemplate set(String template, String value)
   68.56 -  {
   68.57 -    if(template == null || value == null)
   68.58 -    {
   68.59 -      throw new IllegalArgumentException("null arguments not allowed");
   68.60 -    }
   68.61 -    
   68.62 -    this.templateValues.put(template, value);
   68.63 -    return this;
   68.64 -  }
   68.65 -  
   68.66 -  public StringTemplate set(String template, long value)
   68.67 -  {
   68.68 -    return set(template, Long.toString(value));
   68.69 -  }
   68.70 -  
   68.71 -  public StringTemplate set(String template, double value)
   68.72 -  {
   68.73 -    return set(template, Double.toString(value));
   68.74 -  }
   68.75 -  
   68.76 -  public StringTemplate set(String template, Object obj)
   68.77 -  {
   68.78 -    if(template == null || obj == null)
   68.79 -    {
   68.80 -      throw new IllegalArgumentException("null arguments not allowed");
   68.81 -    }
   68.82 -
   68.83 -    return set(template, obj.toString());
   68.84 -  }
   68.85 -  
   68.86 -  @Override
   68.87 -  public String toString()
   68.88 -  {
   68.89 -    String ret = str;
   68.90 -
   68.91 -    for(String key : this.templateValues.keySet())
   68.92 -    {
   68.93 -      String value = this.templateValues.get(key);
   68.94 -      ret = ret.replace(templateDelimiter + key, value);
   68.95 -    }
   68.96 -    
   68.97 -    return ret;
   68.98 -  }
   68.99 -
  68.100 -}
    69.1 --- a/org/sonews/util/TimeoutMap.java	Sun Aug 29 17:04:25 2010 +0200
    69.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    69.3 @@ -1,145 +0,0 @@
    69.4 -/*
    69.5 - *   SONEWS News Server
    69.6 - *   see AUTHORS for the list of contributors
    69.7 - *
    69.8 - *   This program is free software: you can redistribute it and/or modify
    69.9 - *   it under the terms of the GNU General Public License as published by
   69.10 - *   the Free Software Foundation, either version 3 of the License, or
   69.11 - *   (at your option) any later version.
   69.12 - *
   69.13 - *   This program is distributed in the hope that it will be useful,
   69.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   69.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   69.16 - *   GNU General Public License for more details.
   69.17 - *
   69.18 - *   You should have received a copy of the GNU General Public License
   69.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   69.20 - */
   69.21 -
   69.22 -package org.sonews.util;
   69.23 -
   69.24 -import java.util.HashMap;
   69.25 -import java.util.HashSet;
   69.26 -import java.util.Map;
   69.27 -import java.util.Set;
   69.28 -import java.util.concurrent.ConcurrentHashMap;
   69.29 -
   69.30 -/**
   69.31 - * Implementation of a Map that will loose its stored values after a 
   69.32 - * configurable amount of time.
   69.33 - * This class may be used to cache config values for example.
   69.34 - * @author Christian Lins
   69.35 - * @since sonews/0.5.0
   69.36 - */
   69.37 -public class TimeoutMap<K,V> extends ConcurrentHashMap<K, V>
   69.38 -{
   69.39 -  
   69.40 -  private static final long serialVersionUID = 453453467700345L;
   69.41 -
   69.42 -  private int                    timeout     = 60000; // 60 sec
   69.43 -  private transient Map<K, Long> timeoutMap  = new HashMap<K, Long>();
   69.44 -  
   69.45 -  /**
   69.46 -   * Constructor.
   69.47 -   * @param timeout Timeout in milliseconds
   69.48 -   */
   69.49 -  public TimeoutMap(final int timeout)
   69.50 -  {
   69.51 -    this.timeout = timeout;
   69.52 -  }
   69.53 -  
   69.54 -  /**
   69.55 -   * Uses default timeout (60 sec).
   69.56 -   */
   69.57 -  public TimeoutMap()
   69.58 -  {
   69.59 -  }
   69.60 -  
   69.61 -  /**
   69.62 -   * 
   69.63 -   * @param key
   69.64 -   * @return true if key is still valid.
   69.65 -   */
   69.66 -  protected boolean checkTimeOut(Object key)
   69.67 -  {
   69.68 -    synchronized(this.timeoutMap)
   69.69 -    {
   69.70 -      if(this.timeoutMap.containsKey(key))
   69.71 -      {
   69.72 -        long keytime = this.timeoutMap.get(key);
   69.73 -        if((System.currentTimeMillis() - keytime) < this.timeout)
   69.74 -        {
   69.75 -          return true;
   69.76 -        }
   69.77 -        else
   69.78 -        {
   69.79 -          remove(key);
   69.80 -          return false;
   69.81 -        }
   69.82 -      }
   69.83 -      else
   69.84 -      {
   69.85 -        return false;
   69.86 -      }
   69.87 -    }
   69.88 -  }
   69.89 -  
   69.90 -  @Override
   69.91 -  public boolean containsKey(Object key)
   69.92 -  {
   69.93 -    return checkTimeOut(key);
   69.94 -  }
   69.95 -
   69.96 -  @Override
   69.97 -  public synchronized V get(Object key)
   69.98 -  {
   69.99 -    if(checkTimeOut(key))
  69.100 -    {
  69.101 -      return super.get(key);
  69.102 -    }
  69.103 -    else
  69.104 -    {
  69.105 -      return null;
  69.106 -    }
  69.107 -  }
  69.108 -
  69.109 -  @Override
  69.110 -  public V put(K key, V value)
  69.111 -  {
  69.112 -    synchronized(this.timeoutMap)
  69.113 -    {
  69.114 -      removeStaleKeys();
  69.115 -      this.timeoutMap.put(key, System.currentTimeMillis());
  69.116 -      return super.put(key, value);
  69.117 -    }
  69.118 -  }
  69.119 -
  69.120 -  /**
  69.121 -   * @param arg0
  69.122 -   * @return
  69.123 -   */
  69.124 -  @Override
  69.125 -  public V remove(Object arg0)
  69.126 -  {
  69.127 -    synchronized(this.timeoutMap)
  69.128 -    {
  69.129 -      this.timeoutMap.remove(arg0);
  69.130 -      V val = super.remove(arg0);
  69.131 -      return val;
  69.132 -    }
  69.133 -  }
  69.134 -
  69.135 -  protected void removeStaleKeys()
  69.136 -  {
  69.137 -    synchronized(this.timeoutMap)
  69.138 -    {
  69.139 -      Set<Object> keySet = new HashSet<Object>(this.timeoutMap.keySet());
  69.140 -      for(Object key : keySet)
  69.141 -      {
  69.142 -        // The key/value is removed by the checkTimeOut() method if true
  69.143 -        checkTimeOut(key);
  69.144 -      }
  69.145 -    }
  69.146 -  }
  69.147 -  
  69.148 -}
    70.1 --- a/org/sonews/util/io/ArticleInputStream.java	Sun Aug 29 17:04:25 2010 +0200
    70.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    70.3 @@ -1,71 +0,0 @@
    70.4 -/*
    70.5 - *   SONEWS News Server
    70.6 - *   see AUTHORS for the list of contributors
    70.7 - *
    70.8 - *   This program is free software: you can redistribute it and/or modify
    70.9 - *   it under the terms of the GNU General Public License as published by
   70.10 - *   the Free Software Foundation, either version 3 of the License, or
   70.11 - *   (at your option) any later version.
   70.12 - *
   70.13 - *   This program is distributed in the hope that it will be useful,
   70.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   70.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   70.16 - *   GNU General Public License for more details.
   70.17 - *
   70.18 - *   You should have received a copy of the GNU General Public License
   70.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   70.20 - */
   70.21 -
   70.22 -package org.sonews.util.io;
   70.23 -
   70.24 -import java.io.ByteArrayOutputStream;
   70.25 -import java.io.IOException;
   70.26 -import java.io.InputStream;
   70.27 -import java.io.UnsupportedEncodingException;
   70.28 -import org.sonews.storage.Article;
   70.29 -
   70.30 -/**
   70.31 - * Capsulates an Article to provide a raw InputStream.
   70.32 - * @author Christian Lins
   70.33 - * @since sonews/0.5.0
   70.34 - */
   70.35 -public class ArticleInputStream extends InputStream
   70.36 -{
   70.37 -
   70.38 -  private byte[] buf;
   70.39 -  private int    pos = 0;
   70.40 -  
   70.41 -  public ArticleInputStream(final Article art)
   70.42 -    throws IOException, UnsupportedEncodingException
   70.43 -  {
   70.44 -    final ByteArrayOutputStream out = new ByteArrayOutputStream();
   70.45 -    out.write(art.getHeaderSource().getBytes("UTF-8"));
   70.46 -    out.write("\r\n\r\n".getBytes());
   70.47 -    out.write(art.getBody()); // Without CRLF
   70.48 -    out.flush();
   70.49 -    this.buf = out.toByteArray();
   70.50 -  }
   70.51 -
   70.52 -  /**
   70.53 -   * This method reads one byte from the stream.  The <code>pos</code>
   70.54 -   * counter is advanced to the next byte to be read.  The byte read is
   70.55 -   * returned as an int in the range of 0-255.  If the stream position
   70.56 -   * is already at the end of the buffer, no byte is read and a -1 is
   70.57 -   * returned in order to indicate the end of the stream.
   70.58 -   *
   70.59 -   * @return The byte read, or -1 if end of stream
   70.60 -   */
   70.61 -  @Override
   70.62 -  public synchronized int read()
   70.63 -  {
   70.64 -    if(pos < buf.length)
   70.65 -    {
   70.66 -      return ((int)buf[pos++]) & 0xFF;
   70.67 -    }
   70.68 -    else
   70.69 -    {
   70.70 -      return -1;
   70.71 -    }
   70.72 -  }
   70.73 -  
   70.74 -}
    71.1 --- a/org/sonews/util/io/ArticleReader.java	Sun Aug 29 17:04:25 2010 +0200
    71.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    71.3 @@ -1,135 +0,0 @@
    71.4 -/*
    71.5 - *   SONEWS News Server
    71.6 - *   see AUTHORS for the list of contributors
    71.7 - *
    71.8 - *   This program is free software: you can redistribute it and/or modify
    71.9 - *   it under the terms of the GNU General Public License as published by
   71.10 - *   the Free Software Foundation, either version 3 of the License, or
   71.11 - *   (at your option) any later version.
   71.12 - *
   71.13 - *   This program is distributed in the hope that it will be useful,
   71.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   71.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   71.16 - *   GNU General Public License for more details.
   71.17 - *
   71.18 - *   You should have received a copy of the GNU General Public License
   71.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   71.20 - */
   71.21 -
   71.22 -package org.sonews.util.io;
   71.23 -
   71.24 -import java.io.BufferedInputStream;
   71.25 -import java.io.BufferedOutputStream;
   71.26 -import java.io.ByteArrayOutputStream;
   71.27 -import java.io.IOException;
   71.28 -import java.io.InputStream;
   71.29 -import java.io.UnsupportedEncodingException;
   71.30 -import java.net.Socket;
   71.31 -import java.net.UnknownHostException;
   71.32 -import org.sonews.config.Config;
   71.33 -import org.sonews.util.Log;
   71.34 -
   71.35 -/**
   71.36 - * Reads an news article from a NNTP server.
   71.37 - * @author Christian Lins
   71.38 - * @since sonews/0.5.0
   71.39 - */
   71.40 -public class ArticleReader 
   71.41 -{
   71.42 -
   71.43 -  private BufferedOutputStream out;
   71.44 -  private BufferedInputStream  in;
   71.45 -  private String               messageID;
   71.46 -  
   71.47 -  public ArticleReader(String host, int port, String messageID)
   71.48 -    throws IOException, UnknownHostException
   71.49 -  {
   71.50 -    this.messageID = messageID;
   71.51 -
   71.52 -    // Connect to NNTP server
   71.53 -    Socket socket = new Socket(host, port);
   71.54 -    this.out = new BufferedOutputStream(socket.getOutputStream());
   71.55 -    this.in  = new BufferedInputStream(socket.getInputStream());
   71.56 -    String line = readln(this.in);
   71.57 -    if(!line.startsWith("200 "))
   71.58 -    {
   71.59 -      throw new IOException("Invalid hello from server: " + line);
   71.60 -    }
   71.61 -  }
   71.62 -  
   71.63 -  private boolean eofArticle(byte[] buf)
   71.64 -  {
   71.65 -    if(buf.length < 4)
   71.66 -    {
   71.67 -      return false;
   71.68 -    }
   71.69 -    
   71.70 -    int l = buf.length - 1;
   71.71 -    return buf[l-3] == 10 // '*\n'
   71.72 -        && buf[l-2] == '.'                   // '.'
   71.73 -        && buf[l-1] == 13 && buf[l] == 10;  // '\r\n'
   71.74 -  }
   71.75 -  
   71.76 -  public byte[] getArticleData()
   71.77 -    throws IOException, UnsupportedEncodingException
   71.78 -  {
   71.79 -    long maxSize = Config.inst().get(Config.ARTICLE_MAXSIZE, 1024) * 1024L;
   71.80 -
   71.81 -    try
   71.82 -    {
   71.83 -      this.out.write(("ARTICLE " + this.messageID + "\r\n").getBytes("UTF-8"));
   71.84 -      this.out.flush();
   71.85 -
   71.86 -      String line = readln(this.in);
   71.87 -      if(line.startsWith("220 "))
   71.88 -      {
   71.89 -        ByteArrayOutputStream buf = new ByteArrayOutputStream();
   71.90 -        
   71.91 -        while(!eofArticle(buf.toByteArray()))
   71.92 -        {
   71.93 -          for(int b = in.read(); b != 10; b = in.read())
   71.94 -          {
   71.95 -            buf.write(b);
   71.96 -          }
   71.97 -
   71.98 -          buf.write(10);
   71.99 -          if(buf.size() > maxSize)
  71.100 -          {
  71.101 -            Log.get().warning("Skipping message that is too large: " + buf.size());
  71.102 -            return null;
  71.103 -          }
  71.104 -        }
  71.105 -        
  71.106 -        return buf.toByteArray();
  71.107 -      }
  71.108 -      else
  71.109 -      {
  71.110 -        Log.get().warning("ArticleReader: " + line);
  71.111 -        return null;
  71.112 -      }
  71.113 -    }
  71.114 -    catch(IOException ex)
  71.115 -    {
  71.116 -      throw ex;
  71.117 -    }
  71.118 -    finally
  71.119 -    {
  71.120 -      this.out.write("QUIT\r\n".getBytes("UTF-8"));
  71.121 -      this.out.flush();
  71.122 -      this.out.close();
  71.123 -    }
  71.124 -  }
  71.125 -  
  71.126 -  private String readln(InputStream in)
  71.127 -    throws IOException
  71.128 -  {
  71.129 -    ByteArrayOutputStream buf = new ByteArrayOutputStream();
  71.130 -    for(int b = in.read(); b != 10 /* \n */; b = in.read())
  71.131 -    {
  71.132 -      buf.write(b);
  71.133 -    }
  71.134 -    
  71.135 -    return new String(buf.toByteArray());
  71.136 -  }
  71.137 -
  71.138 -}
    72.1 --- a/org/sonews/util/io/ArticleWriter.java	Sun Aug 29 17:04:25 2010 +0200
    72.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    72.3 @@ -1,133 +0,0 @@
    72.4 -/*
    72.5 - *   SONEWS News Server
    72.6 - *   see AUTHORS for the list of contributors
    72.7 - *
    72.8 - *   This program is free software: you can redistribute it and/or modify
    72.9 - *   it under the terms of the GNU General Public License as published by
   72.10 - *   the Free Software Foundation, either version 3 of the License, or
   72.11 - *   (at your option) any later version.
   72.12 - *
   72.13 - *   This program is distributed in the hope that it will be useful,
   72.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   72.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   72.16 - *   GNU General Public License for more details.
   72.17 - *
   72.18 - *   You should have received a copy of the GNU General Public License
   72.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   72.20 - */
   72.21 -
   72.22 -package org.sonews.util.io;
   72.23 -
   72.24 -import java.io.BufferedOutputStream;
   72.25 -import java.io.BufferedReader;
   72.26 -import java.io.IOException;
   72.27 -import java.io.InputStreamReader;
   72.28 -import java.io.UnsupportedEncodingException;
   72.29 -import java.net.Socket;
   72.30 -import java.net.UnknownHostException;
   72.31 -import org.sonews.storage.Article;
   72.32 -
   72.33 -/**
   72.34 - * Posts an Article to a NNTP server using the POST command.
   72.35 - * @author Christian Lins
   72.36 - * @since sonews/0.5.0
   72.37 - */
   72.38 -public class ArticleWriter 
   72.39 -{
   72.40 -  
   72.41 -  private BufferedOutputStream out;
   72.42 -  private BufferedReader       inr;
   72.43 -
   72.44 -  public ArticleWriter(String host, int port)
   72.45 -    throws IOException, UnknownHostException
   72.46 -  {
   72.47 -    // Connect to NNTP server
   72.48 -    Socket socket = new Socket(host, port);
   72.49 -    this.out = new BufferedOutputStream(socket.getOutputStream());
   72.50 -    this.inr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
   72.51 -    String line = inr.readLine();
   72.52 -    if(line == null || !line.startsWith("200 "))
   72.53 -    {
   72.54 -      throw new IOException("Invalid hello from server: " + line);
   72.55 -    }
   72.56 -  }
   72.57 -  
   72.58 -  public void close()
   72.59 -    throws IOException, UnsupportedEncodingException
   72.60 -  {
   72.61 -    this.out.write("QUIT\r\n".getBytes("UTF-8"));
   72.62 -    this.out.flush();
   72.63 -  }
   72.64 -
   72.65 -  protected void finishPOST()
   72.66 -    throws IOException
   72.67 -  {
   72.68 -    this.out.write("\r\n.\r\n".getBytes());
   72.69 -    this.out.flush();
   72.70 -    String line = inr.readLine();
   72.71 -    if(line == null || !line.startsWith("240 ") || !line.startsWith("441 "))
   72.72 -    {
   72.73 -      throw new IOException(line);
   72.74 -    }
   72.75 -  }
   72.76 -
   72.77 -  protected void preparePOST()
   72.78 -    throws IOException
   72.79 -  {
   72.80 -    this.out.write("POST\r\n".getBytes("UTF-8"));
   72.81 -    this.out.flush();
   72.82 -
   72.83 -    String line = this.inr.readLine();
   72.84 -    if(line == null || !line.startsWith("340 "))
   72.85 -    {
   72.86 -      throw new IOException(line);
   72.87 -    }
   72.88 -  }
   72.89 -
   72.90 -  public void writeArticle(Article article)
   72.91 -    throws IOException, UnsupportedEncodingException
   72.92 -  {
   72.93 -    byte[] buf = new byte[512];
   72.94 -    ArticleInputStream in = new ArticleInputStream(article);
   72.95 -
   72.96 -    preparePOST();
   72.97 -    
   72.98 -    int len = in.read(buf);
   72.99 -    while(len != -1)
  72.100 -    {
  72.101 -      writeLine(buf, len);
  72.102 -      len = in.read(buf);
  72.103 -    }
  72.104 -
  72.105 -    finishPOST();
  72.106 -  }
  72.107 -
  72.108 -  /**
  72.109 -   * Writes the raw content of an article to the remote server. This method
  72.110 -   * does no charset conversion/handling of any kind so its the preferred
  72.111 -   * method for sending an article to remote peers.
  72.112 -   * @param rawArticle
  72.113 -   * @throws IOException
  72.114 -   */
  72.115 -  public void writeArticle(byte[] rawArticle)
  72.116 -    throws IOException
  72.117 -  {
  72.118 -    preparePOST();
  72.119 -    writeLine(rawArticle, rawArticle.length);
  72.120 -    finishPOST();
  72.121 -  }
  72.122 -
  72.123 -  /**
  72.124 -   * Writes the given buffer to the connect remote server.
  72.125 -   * @param buffer
  72.126 -   * @param len
  72.127 -   * @throws IOException
  72.128 -   */
  72.129 -  protected void writeLine(byte[] buffer, int len)
  72.130 -    throws IOException
  72.131 -  {
  72.132 -    this.out.write(buffer, 0, len);
  72.133 -    this.out.flush();
  72.134 -  }
  72.135 -
  72.136 -}
    73.1 --- a/org/sonews/util/io/Resource.java	Sun Aug 29 17:04:25 2010 +0200
    73.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    73.3 @@ -1,132 +0,0 @@
    73.4 -/*
    73.5 - *   SONEWS News Server
    73.6 - *   see AUTHORS for the list of contributors
    73.7 - *
    73.8 - *   This program is free software: you can redistribute it and/or modify
    73.9 - *   it under the terms of the GNU General Public License as published by
   73.10 - *   the Free Software Foundation, either version 3 of the License, or
   73.11 - *   (at your option) any later version.
   73.12 - *
   73.13 - *   This program is distributed in the hope that it will be useful,
   73.14 - *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   73.15 - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   73.16 - *   GNU General Public License for more details.
   73.17 - *
   73.18 - *   You should have received a copy of the GNU General Public License
   73.19 - *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   73.20 - */
   73.21 -
   73.22 -package org.sonews.util.io;
   73.23 -
   73.24 -import java.io.BufferedReader;
   73.25 -import java.io.IOException;
   73.26 -import java.io.InputStream;
   73.27 -import java.io.InputStreamReader;
   73.28 -import java.net.URL;
   73.29 -import java.nio.charset.Charset;
   73.30 -
   73.31 -/**
   73.32 - * Provides method for loading of resources.
   73.33 - * @author Christian Lins
   73.34 - * @since sonews/0.5.0
   73.35 - */
   73.36 -public final class Resource
   73.37 -{
   73.38 -  
   73.39 -  /**
   73.40 -   * Loads a resource and returns it as URL reference.
   73.41 -   * The Resource's classloader is used to load the resource, not
   73.42 -   * the System's ClassLoader so it may be safe to use this method
   73.43 -   * in a sandboxed environment.
   73.44 -   * @return
   73.45 -   */
   73.46 -  public static URL getAsURL(final String name)
   73.47 -  {
   73.48 -    if(name == null)
   73.49 -    {
   73.50 -      return null;
   73.51 -    }
   73.52 -
   73.53 -    return Resource.class.getClassLoader().getResource(name);
   73.54 -  }
   73.55 -  
   73.56 -  /**
   73.57 -   * Loads a resource and returns an InputStream to it.
   73.58 -   * @param name
   73.59 -   * @return
   73.60 -   */
   73.61 -  public static InputStream getAsStream(String name)
   73.62 -  {
   73.63 -    try
   73.64 -    {
   73.65 -      URL url = getAsURL(name);
   73.66 -      if(url == null)
   73.67 -      {
   73.68 -        return null;
   73.69 -      }
   73.70 -      else
   73.71 -      {
   73.72 -        return url.openStream();
   73.73 -      }
   73.74 -    }
   73.75 -    catch(IOException e)
   73.76 -    {
   73.77 -      e.printStackTrace();
   73.78 -      return null;
   73.79 -    }
   73.80 -  }
   73.81 -
   73.82 -  /**
   73.83 -   * Loads a plain text resource.
   73.84 -   * @param withNewline If false all newlines are removed from the 
   73.85 -   * return String
   73.86 -   */
   73.87 -  public static String getAsString(String name, boolean withNewline)
   73.88 -  {
   73.89 -    if(name == null)
   73.90 -      return null;
   73.91 -
   73.92 -    BufferedReader in = null;
   73.93 -    try
   73.94 -    {
   73.95 -      InputStream ins = getAsStream(name);
   73.96 -      if(ins == null)
   73.97 -        return null;
   73.98 -
   73.99 -      in = new BufferedReader(
  73.100 -        new InputStreamReader(ins, Charset.forName("UTF-8")));
  73.101 -      StringBuffer buf = new StringBuffer();
  73.102 -
  73.103 -      for(;;)
  73.104 -      {
  73.105 -        String line = in.readLine();
  73.106 -        if(line == null)
  73.107 -          break;
  73.108 -
  73.109 -        buf.append(line);
  73.110 -        if(withNewline)
  73.111 -          buf.append('\n');
  73.112 -      }
  73.113 -
  73.114 -      return buf.toString();
  73.115 -    }
  73.116 -    catch(Exception e)
  73.117 -    {
  73.118 -      e.printStackTrace();
  73.119 -      return null;
  73.120 -    }
  73.121 -    finally
  73.122 -    {
  73.123 -      try
  73.124 -      {
  73.125 -        if(in != null)
  73.126 -          in.close();
  73.127 -      }
  73.128 -      catch(IOException ex)
  73.129 -      {
  73.130 -        ex.printStackTrace();
  73.131 -      }
  73.132 -    }
  73.133 -  }
  73.134 -
  73.135 -}
    74.1 --- a/org/sonews/util/io/package.html	Sun Aug 29 17:04:25 2010 +0200
    74.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    74.3 @@ -1,1 +0,0 @@
    74.4 -Contains I/O utilitiy classes.
    74.5 \ No newline at end of file
    75.1 --- a/org/sonews/util/package.html	Sun Aug 29 17:04:25 2010 +0200
    75.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    75.3 @@ -1,1 +0,0 @@
    75.4 -Contains various utility classes.
    75.5 \ No newline at end of file
    76.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    76.2 +++ b/src/org/sonews/Main.java	Sun Aug 29 17:28:58 2010 +0200
    76.3 @@ -0,0 +1,198 @@
    76.4 +/*
    76.5 + *   SONEWS News Server
    76.6 + *   see AUTHORS for the list of contributors
    76.7 + *
    76.8 + *   This program is free software: you can redistribute it and/or modify
    76.9 + *   it under the terms of the GNU General Public License as published by
   76.10 + *   the Free Software Foundation, either version 3 of the License, or
   76.11 + *   (at your option) any later version.
   76.12 + *
   76.13 + *   This program is distributed in the hope that it will be useful,
   76.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   76.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   76.16 + *   GNU General Public License for more details.
   76.17 + *
   76.18 + *   You should have received a copy of the GNU General Public License
   76.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   76.20 + */
   76.21 +
   76.22 +package org.sonews;
   76.23 +
   76.24 +import java.sql.Driver;
   76.25 +import java.sql.DriverManager;
   76.26 +import java.util.Enumeration;
   76.27 +import java.util.Date;
   76.28 +import java.util.logging.Level;
   76.29 +import org.sonews.config.Config;
   76.30 +import org.sonews.daemon.ChannelLineBuffers;
   76.31 +import org.sonews.daemon.CommandSelector;
   76.32 +import org.sonews.daemon.Connections;
   76.33 +import org.sonews.daemon.NNTPDaemon;
   76.34 +import org.sonews.feed.FeedManager;
   76.35 +import org.sonews.mlgw.MailPoller;
   76.36 +import org.sonews.storage.StorageBackendException;
   76.37 +import org.sonews.storage.StorageManager;
   76.38 +import org.sonews.storage.StorageProvider;
   76.39 +import org.sonews.util.Log;
   76.40 +import org.sonews.util.Purger;
   76.41 +import org.sonews.util.io.Resource;
   76.42 +
   76.43 +/**
   76.44 + * Startup class of the daemon.
   76.45 + * @author Christian Lins
   76.46 + * @since sonews/0.5.0
   76.47 + */
   76.48 +public final class Main
   76.49 +{
   76.50 +  
   76.51 +  private Main()
   76.52 +  {
   76.53 +  }
   76.54 +
   76.55 +  /** Version information of the sonews daemon */
   76.56 +  public static final String VERSION = "sonews/1.1.0";
   76.57 +  public static final Date   STARTDATE = new Date();
   76.58 +  
   76.59 +  /**
   76.60 +   * The main entrypoint.
   76.61 +   * @param args
   76.62 +   * @throws Exception
   76.63 +   */
   76.64 +  public static void main(String[] args) throws Exception
   76.65 +  {
   76.66 +    System.out.println(VERSION);
   76.67 +    Thread.currentThread().setName("Mainthread");
   76.68 +
   76.69 +    // Command line arguments
   76.70 +    boolean feed    = false;  // Enable feeding?
   76.71 +    boolean mlgw    = false;  // Enable Mailinglist gateway?
   76.72 +    int     port    = -1;
   76.73 +    
   76.74 +    for(int n = 0; n < args.length; n++)
   76.75 +    {
   76.76 +      if(args[n].equals("-c") || args[n].equals("-config"))
   76.77 +      {
   76.78 +        Config.inst().set(Config.LEVEL_CLI, Config.CONFIGFILE, args[++n]);
   76.79 +        System.out.println("Using config file " + args[n]);
   76.80 +      }
   76.81 +      else if(args[n].equals("-dumpjdbcdriver"))
   76.82 +      {
   76.83 +        System.out.println("Available JDBC drivers:");
   76.84 +        Enumeration<Driver> drvs =  DriverManager.getDrivers();
   76.85 +        while(drvs.hasMoreElements())
   76.86 +        {
   76.87 +          System.out.println(drvs.nextElement());
   76.88 +        }
   76.89 +        return;
   76.90 +      }
   76.91 +      else if(args[n].equals("-feed"))
   76.92 +      {
   76.93 +        feed = true;
   76.94 +      }
   76.95 +      else if(args[n].equals("-h") || args[n].equals("-help"))
   76.96 +      {
   76.97 +        printArguments();
   76.98 +        return;
   76.99 +      }
  76.100 +      else if(args[n].equals("-mlgw"))
  76.101 +      {
  76.102 +        mlgw = true;
  76.103 +      }
  76.104 +      else if(args[n].equals("-p"))
  76.105 +      {
  76.106 +        port = Integer.parseInt(args[++n]);
  76.107 +      }
  76.108 +      else if(args[n].equals("-plugin"))
  76.109 +      {
  76.110 +        System.out.println("Warning: -plugin-storage is not implemented!");
  76.111 +      }
  76.112 +      else if(args[n].equals("-plugin-command"))
  76.113 +      {
  76.114 +        try
  76.115 +        {
  76.116 +          CommandSelector.addCommandHandler(args[++n]);
  76.117 +        }
  76.118 +        catch(Exception ex)
  76.119 +        {
  76.120 +          Log.get().warning("Could not load command plugin: " + args[n]);
  76.121 +          Log.get().log(Level.INFO, "Main.java", ex);
  76.122 +        }
  76.123 +      }
  76.124 +      else if(args[n].equals("-plugin-storage"))
  76.125 +      {
  76.126 +        System.out.println("Warning: -plugin-storage is not implemented!");
  76.127 +      }
  76.128 +      else if(args[n].equals("-v") || args[n].equals("-version"))
  76.129 +      {
  76.130 +        // Simply return as the version info is already printed above
  76.131 +        return;
  76.132 +      }
  76.133 +    }
  76.134 +    
  76.135 +    // Try to load the JDBCDatabase;
  76.136 +    // Do NOT USE BackendConfig or Log classes before this point because they require
  76.137 +    // a working JDBCDatabase connection.
  76.138 +    try
  76.139 +    {
  76.140 +      StorageProvider sprov =
  76.141 +        StorageManager.loadProvider("org.sonews.storage.impl.JDBCDatabaseProvider");
  76.142 +      StorageManager.enableProvider(sprov);
  76.143 +      
  76.144 +      // Make sure some elementary groups are existing
  76.145 +      if(!StorageManager.current().isGroupExisting("control"))
  76.146 +      {
  76.147 +        StorageManager.current().addGroup("control", 0);
  76.148 +        Log.get().info("Group 'control' created.");
  76.149 +      }
  76.150 +    }
  76.151 +    catch(StorageBackendException ex)
  76.152 +    {
  76.153 +      ex.printStackTrace();
  76.154 +      System.err.println("Database initialization failed with " + ex.toString());
  76.155 +      System.err.println("Make sure you have specified the correct database" +
  76.156 +        " settings in sonews.conf!");
  76.157 +      return;
  76.158 +    }
  76.159 +    
  76.160 +    ChannelLineBuffers.allocateDirect();
  76.161 +    
  76.162 +    // Add shutdown hook
  76.163 +    Runtime.getRuntime().addShutdownHook(new ShutdownHook());
  76.164 +    
  76.165 +    // Start the listening daemon
  76.166 +    if(port <= 0)
  76.167 +    {
  76.168 +      port = Config.inst().get(Config.PORT, 119);
  76.169 +    }
  76.170 +    final NNTPDaemon daemon = NNTPDaemon.createInstance(port);
  76.171 +    daemon.start();
  76.172 +    
  76.173 +    // Start Connections purger thread...
  76.174 +    Connections.getInstance().start();
  76.175 +    
  76.176 +    // Start mailinglist gateway...
  76.177 +    if(mlgw)
  76.178 +    {
  76.179 +      new MailPoller().start();
  76.180 +    }
  76.181 +    
  76.182 +    // Start feeds
  76.183 +    if(feed)
  76.184 +    {
  76.185 +      FeedManager.startFeeding();
  76.186 +    }
  76.187 +
  76.188 +    Purger purger = new Purger();
  76.189 +    purger.start();
  76.190 +    
  76.191 +    // Wait for main thread to exit (setDaemon(false))
  76.192 +    daemon.join();
  76.193 +  }
  76.194 +  
  76.195 +  private static void printArguments()
  76.196 +  {
  76.197 +    String usage = Resource.getAsString("helpers/usage", true);
  76.198 +    System.out.println(usage);
  76.199 +  }
  76.200 +
  76.201 +}
    77.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    77.2 +++ b/src/org/sonews/ShutdownHook.java	Sun Aug 29 17:28:58 2010 +0200
    77.3 @@ -0,0 +1,84 @@
    77.4 +/*
    77.5 + *   SONEWS News Server
    77.6 + *   see AUTHORS for the list of contributors
    77.7 + *
    77.8 + *   This program is free software: you can redistribute it and/or modify
    77.9 + *   it under the terms of the GNU General Public License as published by
   77.10 + *   the Free Software Foundation, either version 3 of the License, or
   77.11 + *   (at your option) any later version.
   77.12 + *
   77.13 + *   This program is distributed in the hope that it will be useful,
   77.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   77.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   77.16 + *   GNU General Public License for more details.
   77.17 + *
   77.18 + *   You should have received a copy of the GNU General Public License
   77.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   77.20 + */
   77.21 +
   77.22 +package org.sonews;
   77.23 +
   77.24 +import java.sql.SQLException;
   77.25 +import java.util.Map;
   77.26 +import org.sonews.daemon.AbstractDaemon;
   77.27 +
   77.28 +/**
   77.29 + * Will force all other threads to shutdown cleanly.
   77.30 + * @author Christian Lins
   77.31 + * @since sonews/0.5.0
   77.32 + */
   77.33 +class ShutdownHook extends Thread
   77.34 +{
   77.35 +
   77.36 +  /**
   77.37 +   * Called when the JVM exits.
   77.38 +   */
   77.39 +  @Override
   77.40 +  public void run()
   77.41 +  {
   77.42 +    System.out.println("sonews: Trying to shutdown all threads...");
   77.43 +
   77.44 +    Map<Thread, StackTraceElement[]> threadsMap = Thread.getAllStackTraces();
   77.45 +    for(Thread thread : threadsMap.keySet())
   77.46 +    {
   77.47 +      // Interrupt the thread if it's a AbstractDaemon
   77.48 +      AbstractDaemon daemon;
   77.49 +      if(thread instanceof AbstractDaemon && thread.isAlive())
   77.50 +      {
   77.51 +        try
   77.52 +        {
   77.53 +          daemon = (AbstractDaemon)thread;
   77.54 +          daemon.shutdownNow();
   77.55 +        }
   77.56 +        catch(SQLException ex)
   77.57 +        {
   77.58 +          System.out.println("sonews: " + ex);
   77.59 +        }
   77.60 +      }
   77.61 +    }
   77.62 +    
   77.63 +    for(Thread thread : threadsMap.keySet())
   77.64 +    {
   77.65 +      AbstractDaemon daemon;
   77.66 +      if(thread instanceof AbstractDaemon && thread.isAlive())
   77.67 +      {
   77.68 +        daemon = (AbstractDaemon)thread;
   77.69 +        System.out.println("sonews: Waiting for " + daemon + " to exit...");
   77.70 +        try
   77.71 +        {
   77.72 +          daemon.join(500);
   77.73 +        }
   77.74 +        catch(InterruptedException ex)
   77.75 +        {
   77.76 +          System.out.println(ex.getLocalizedMessage());
   77.77 +        }
   77.78 +      }
   77.79 +    }
   77.80 +    
   77.81 +    // We have notified all not-sleeping AbstractDaemons of the shutdown;
   77.82 +    // all other threads can be simply purged on VM shutdown
   77.83 +    
   77.84 +    System.out.println("sonews: Clean shutdown.");
   77.85 +  }
   77.86 +  
   77.87 +}
    78.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    78.2 +++ b/src/org/sonews/acl/AccessControl.java	Sun Aug 29 17:28:58 2010 +0200
    78.3 @@ -0,0 +1,31 @@
    78.4 +/*
    78.5 + *   SONEWS News Server
    78.6 + *   see AUTHORS for the list of contributors
    78.7 + *
    78.8 + *   This program is free software: you can redistribute it and/or modify
    78.9 + *   it under the terms of the GNU General Public License as published by
   78.10 + *   the Free Software Foundation, either version 3 of the License, or
   78.11 + *   (at your option) any later version.
   78.12 + *
   78.13 + *   This program is distributed in the hope that it will be useful,
   78.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   78.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   78.16 + *   GNU General Public License for more details.
   78.17 + *
   78.18 + *   You should have received a copy of the GNU General Public License
   78.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   78.20 + */
   78.21 +
   78.22 +package org.sonews.acl;
   78.23 +
   78.24 +/**
   78.25 + *
   78.26 + * @author Christian Lins
   78.27 + * @since sonews/1.1
   78.28 + */
   78.29 +public interface AccessControl
   78.30 +{
   78.31 +
   78.32 +  boolean hasPermission(String user, char[] secret, String permission);
   78.33 +
   78.34 +}
    79.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    79.2 +++ b/src/org/sonews/acl/AuthInfoCommand.java	Sun Aug 29 17:28:58 2010 +0200
    79.3 @@ -0,0 +1,64 @@
    79.4 +/*
    79.5 + *   SONEWS News Server
    79.6 + *   see AUTHORS for the list of contributors
    79.7 + *
    79.8 + *   This program is free software: you can redistribute it and/or modify
    79.9 + *   it under the terms of the GNU General Public License as published by
   79.10 + *   the Free Software Foundation, either version 3 of the License, or
   79.11 + *   (at your option) any later version.
   79.12 + *
   79.13 + *   This program is distributed in the hope that it will be useful,
   79.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   79.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   79.16 + *   GNU General Public License for more details.
   79.17 + *
   79.18 + *   You should have received a copy of the GNU General Public License
   79.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   79.20 + */
   79.21 +
   79.22 +package org.sonews.acl;
   79.23 +
   79.24 +import java.io.IOException;
   79.25 +import org.sonews.daemon.NNTPConnection;
   79.26 +import org.sonews.daemon.command.Command;
   79.27 +import org.sonews.storage.StorageBackendException;
   79.28 +
   79.29 +/**
   79.30 + *
   79.31 + * @author Christian Lins
   79.32 + * @since sonews/1.1
   79.33 + */
   79.34 +public class AuthInfoCommand implements Command
   79.35 +{
   79.36 +
   79.37 +  @Override
   79.38 +  public String[] getSupportedCommandStrings()
   79.39 +  {
   79.40 +    throw new UnsupportedOperationException("Not supported yet.");
   79.41 +  }
   79.42 +
   79.43 +  @Override
   79.44 +  public boolean hasFinished()
   79.45 +  {
   79.46 +    throw new UnsupportedOperationException("Not supported yet.");
   79.47 +  }
   79.48 +
   79.49 +  @Override
   79.50 +  public String impliedCapability()
   79.51 +  {
   79.52 +    throw new UnsupportedOperationException("Not supported yet.");
   79.53 +  }
   79.54 +
   79.55 +  @Override
   79.56 +  public boolean isStateful()
   79.57 +  {
   79.58 +    throw new UnsupportedOperationException("Not supported yet.");
   79.59 +  }
   79.60 +
   79.61 +  @Override
   79.62 +  public void processLine(NNTPConnection conn, String line, byte[] rawLine) throws IOException, StorageBackendException
   79.63 +  {
   79.64 +    throw new UnsupportedOperationException("Not supported yet.");
   79.65 +  }
   79.66 +
   79.67 +}
    80.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    80.2 +++ b/src/org/sonews/config/AbstractConfig.java	Sun Aug 29 17:28:58 2010 +0200
    80.3 @@ -0,0 +1,57 @@
    80.4 +/*
    80.5 + *   SONEWS News Server
    80.6 + *   see AUTHORS for the list of contributors
    80.7 + *
    80.8 + *   This program is free software: you can redistribute it and/or modify
    80.9 + *   it under the terms of the GNU General Public License as published by
   80.10 + *   the Free Software Foundation, either version 3 of the License, or
   80.11 + *   (at your option) any later version.
   80.12 + *
   80.13 + *   This program is distributed in the hope that it will be useful,
   80.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   80.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   80.16 + *   GNU General Public License for more details.
   80.17 + *
   80.18 + *   You should have received a copy of the GNU General Public License
   80.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   80.20 + */
   80.21 +
   80.22 +package org.sonews.config;
   80.23 +
   80.24 +/**
   80.25 + * Base class for Config and BootstrapConfig.
   80.26 + * @author Christian Lins
   80.27 + * @since sonews/0.5.0
   80.28 + */
   80.29 +public abstract class AbstractConfig 
   80.30 +{
   80.31 +  
   80.32 +  public abstract String get(String key, String defVal);
   80.33 +  
   80.34 +  public int get(final String key, final int defVal)
   80.35 +  {
   80.36 +    return Integer.parseInt(
   80.37 +      get(key, Integer.toString(defVal)));
   80.38 +  }
   80.39 +  
   80.40 +  public boolean get(String key, boolean defVal)
   80.41 +  {
   80.42 +    String val = get(key, Boolean.toString(defVal));
   80.43 +    return Boolean.parseBoolean(val);
   80.44 +  }
   80.45 +
   80.46 +  /**
   80.47 +   * Returns a long config value specified via the given key.
   80.48 +   * @param key
   80.49 +   * @param defVal
   80.50 +   * @return
   80.51 +   */
   80.52 +  public long get(String key, long defVal)
   80.53 +  {
   80.54 +    String val = get(key, Long.toString(defVal));
   80.55 +    return Long.parseLong(val);
   80.56 +  }
   80.57 +
   80.58 +  protected abstract void set(String key, String val);
   80.59 +  
   80.60 +}
    81.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    81.2 +++ b/src/org/sonews/config/BackendConfig.java	Sun Aug 29 17:28:58 2010 +0200
    81.3 @@ -0,0 +1,115 @@
    81.4 +/*
    81.5 + *   SONEWS News Server
    81.6 + *   see AUTHORS for the list of contributors
    81.7 + *
    81.8 + *   This program is free software: you can redistribute it and/or modify
    81.9 + *   it under the terms of the GNU General Public License as published by
   81.10 + *   the Free Software Foundation, either version 3 of the License, or
   81.11 + *   (at your option) any later version.
   81.12 + *
   81.13 + *   This program is distributed in the hope that it will be useful,
   81.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   81.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   81.16 + *   GNU General Public License for more details.
   81.17 + *
   81.18 + *   You should have received a copy of the GNU General Public License
   81.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   81.20 + */
   81.21 +
   81.22 +package org.sonews.config;
   81.23 +
   81.24 +import java.util.logging.Level;
   81.25 +import org.sonews.util.Log;
   81.26 +import org.sonews.storage.StorageBackendException;
   81.27 +import org.sonews.storage.StorageManager;
   81.28 +import org.sonews.util.TimeoutMap;
   81.29 +
   81.30 +/**
   81.31 + * Provides access to the program wide configuration that is stored within
   81.32 + * the server's database.
   81.33 + * @author Christian Lins
   81.34 + * @since sonews/0.5.0
   81.35 + */
   81.36 +class BackendConfig extends AbstractConfig
   81.37 +{
   81.38 +
   81.39 +  private static BackendConfig instance = new BackendConfig();
   81.40 +  
   81.41 +  public static BackendConfig getInstance()
   81.42 +  {
   81.43 +    return instance;
   81.44 +  }
   81.45 +  
   81.46 +  private final TimeoutMap<String, String> values 
   81.47 +    = new TimeoutMap<String, String>();
   81.48 +  
   81.49 +  private BackendConfig()
   81.50 +  {
   81.51 +    super();
   81.52 +  }
   81.53 +  
   81.54 +  /**
   81.55 +   * Returns the config value for the given key or the defaultValue if the
   81.56 +   * key is not found in config.
   81.57 +   * @param key
   81.58 +   * @param defaultValue
   81.59 +   * @return
   81.60 +   */
   81.61 +  @Override
   81.62 +  public String get(String key, String defaultValue)
   81.63 +  {
   81.64 +    try
   81.65 +    {
   81.66 +      String configValue = values.get(key);
   81.67 +      if(configValue == null)
   81.68 +      {
   81.69 +        if(StorageManager.current() == null)
   81.70 +        {
   81.71 +          Log.get().warning("BackendConfig not available, using default.");
   81.72 +          return defaultValue;
   81.73 +        }
   81.74 +
   81.75 +        configValue = StorageManager.current().getConfigValue(key);
   81.76 +        if(configValue == null)
   81.77 +        {
   81.78 +          return defaultValue;
   81.79 +        }
   81.80 +        else
   81.81 +        {
   81.82 +          values.put(key, configValue);
   81.83 +          return configValue;
   81.84 +        }
   81.85 +      }
   81.86 +      else
   81.87 +      {
   81.88 +        return configValue;
   81.89 +      }
   81.90 +    }
   81.91 +    catch(StorageBackendException ex)
   81.92 +    {
   81.93 +      Log.get().log(Level.SEVERE, "Storage backend problem", ex);
   81.94 +      return defaultValue;
   81.95 +    }
   81.96 +  }
   81.97 +  
   81.98 +  /**
   81.99 +   * Sets the config value which is identified by the given key.
  81.100 +   * @param key
  81.101 +   * @param value
  81.102 +   */
  81.103 +  public void set(String key, String value)
  81.104 +  {
  81.105 +    values.put(key, value);
  81.106 +    
  81.107 +    try
  81.108 +    {
  81.109 +      // Write values to database
  81.110 +      StorageManager.current().setConfigValue(key, value);
  81.111 +    }
  81.112 +    catch(StorageBackendException ex)
  81.113 +    {
  81.114 +      ex.printStackTrace();
  81.115 +    }
  81.116 +  }
  81.117 +  
  81.118 +}
    82.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    82.2 +++ b/src/org/sonews/config/CommandLineConfig.java	Sun Aug 29 17:28:58 2010 +0200
    82.3 @@ -0,0 +1,64 @@
    82.4 +/*
    82.5 + *   SONEWS News Server
    82.6 + *   see AUTHORS for the list of contributors
    82.7 + *
    82.8 + *   This program is free software: you can redistribute it and/or modify
    82.9 + *   it under the terms of the GNU General Public License as published by
   82.10 + *   the Free Software Foundation, either version 3 of the License, or
   82.11 + *   (at your option) any later version.
   82.12 + *
   82.13 + *   This program is distributed in the hope that it will be useful,
   82.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   82.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   82.16 + *   GNU General Public License for more details.
   82.17 + *
   82.18 + *   You should have received a copy of the GNU General Public License
   82.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   82.20 + */
   82.21 +
   82.22 +package org.sonews.config;
   82.23 +
   82.24 +import java.util.Map;
   82.25 +import java.util.HashMap;
   82.26 +
   82.27 +/**
   82.28 + *
   82.29 + * @author Christian Lins
   82.30 + */
   82.31 +class CommandLineConfig extends AbstractConfig
   82.32 +{
   82.33 +
   82.34 +  private static final CommandLineConfig instance = new CommandLineConfig();
   82.35 +
   82.36 +  public static CommandLineConfig getInstance()
   82.37 +  {
   82.38 +    return instance;
   82.39 +  }
   82.40 +
   82.41 +  private final Map<String, String> values = new HashMap<String, String>();
   82.42 +  
   82.43 +  private CommandLineConfig() {}
   82.44 +
   82.45 +  @Override
   82.46 +  public String get(String key, String def)
   82.47 +  {
   82.48 +    synchronized(this.values)
   82.49 +    {
   82.50 +      if(this.values.containsKey(key))
   82.51 +      {
   82.52 +        def = this.values.get(key);
   82.53 +      }
   82.54 +    }
   82.55 +    return def;
   82.56 +  }
   82.57 +
   82.58 +  @Override
   82.59 +  public void set(String key, String val)
   82.60 +  {
   82.61 +    synchronized(this.values)
   82.62 +    {
   82.63 +      this.values.put(key, val);
   82.64 +    }
   82.65 +  }
   82.66 +
   82.67 +}
    83.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    83.2 +++ b/src/org/sonews/config/Config.java	Sun Aug 29 17:28:58 2010 +0200
    83.3 @@ -0,0 +1,175 @@
    83.4 +/*
    83.5 + *   SONEWS News Server
    83.6 + *   see AUTHORS for the list of contributors
    83.7 + *
    83.8 + *   This program is free software: you can redistribute it and/or modify
    83.9 + *   it under the terms of the GNU General Public License as published by
   83.10 + *   the Free Software Foundation, either version 3 of the License, or
   83.11 + *   (at your option) any later version.
   83.12 + *
   83.13 + *   This program is distributed in the hope that it will be useful,
   83.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   83.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   83.16 + *   GNU General Public License for more details.
   83.17 + *
   83.18 + *   You should have received a copy of the GNU General Public License
   83.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   83.20 + */
   83.21 +
   83.22 +package org.sonews.config;
   83.23 +
   83.24 +/**
   83.25 + * Configuration facade class.
   83.26 + * @author Christian Lins
   83.27 + * @since sonews/1.0
   83.28 + */
   83.29 +public class Config extends AbstractConfig
   83.30 +{
   83.31 +  
   83.32 +  public static final int LEVEL_CLI     = 1;
   83.33 +  public static final int LEVEL_FILE    = 2;
   83.34 +  public static final int LEVEL_BACKEND = 3;
   83.35 +
   83.36 +  public static final String CONFIGFILE = "sonews.configfile";
   83.37 +  
   83.38 +    /** BackendConfig key constant. Value is the maximum article size in kilobytes. */
   83.39 +  public static final String ARTICLE_MAXSIZE   = "sonews.article.maxsize";
   83.40 +
   83.41 +  /** BackendConfig key constant. Value: Amount of news that are feeded per run. */
   83.42 +  public static final String EVENTLOG          = "sonews.eventlog";
   83.43 +  public static final String FEED_NEWSPERRUN   = "sonews.feed.newsperrun";
   83.44 +  public static final String FEED_PULLINTERVAL = "sonews.feed.pullinterval";
   83.45 +  public static final String HOSTNAME          = "sonews.hostname";
   83.46 +  public static final String PORT              = "sonews.port";
   83.47 +  public static final String TIMEOUT           = "sonews.timeout";
   83.48 +  public static final String LOGLEVEL          = "sonews.loglevel";
   83.49 +  public static final String MLPOLL_DELETEUNKNOWN = "sonews.mlpoll.deleteunknown";
   83.50 +  public static final String MLPOLL_HOST       = "sonews.mlpoll.host";
   83.51 +  public static final String MLPOLL_PASSWORD   = "sonews.mlpoll.password";
   83.52 +  public static final String MLPOLL_USER       = "sonews.mlpoll.user";
   83.53 +  public static final String MLSEND_ADDRESS    = "sonews.mlsend.address";
   83.54 +  public static final String MLSEND_RW_FROM    = "sonews.mlsend.rewrite.from";
   83.55 +  public static final String MLSEND_RW_SENDER  = "sonews.mlsend.rewrite.sender";
   83.56 +  public static final String MLSEND_HOST       = "sonews.mlsend.host";
   83.57 +  public static final String MLSEND_PASSWORD   = "sonews.mlsend.password";
   83.58 +  public static final String MLSEND_PORT       = "sonews.mlsend.port";
   83.59 +  public static final String MLSEND_USER       = "sonews.mlsend.user";
   83.60 +  
   83.61 +  /** Key constant. If value is "true" every I/O is written to logfile
   83.62 +   * (which is a lot!)
   83.63 +   */
   83.64 +  public static final String DEBUG              = "sonews.debug";
   83.65 +
   83.66 +  /** Key constant. Value is classname of the JDBC driver */
   83.67 +  public static final String STORAGE_DBMSDRIVER = "sonews.storage.dbmsdriver";
   83.68 +
   83.69 +  /** Key constant. Value is JDBC connect String to the database. */
   83.70 +  public static final String STORAGE_DATABASE   = "sonews.storage.database";
   83.71 +
   83.72 +  /** Key constant. Value is the username for the DBMS. */
   83.73 +  public static final String STORAGE_USER       = "sonews.storage.user";
   83.74 +
   83.75 +  /** Key constant. Value is the password for the DBMS. */
   83.76 +  public static final String STORAGE_PASSWORD   = "sonews.storage.password";
   83.77 +
   83.78 +  /** Key constant. Value is the name of the host which is allowed to use the
   83.79 +   *  XDAEMON command; default: "localhost" */
   83.80 +  public static final String XDAEMON_HOST       = "sonews.xdaemon.host";
   83.81 +
   83.82 +  /** The config key for the filename of the logfile */
   83.83 +  public static final String LOGFILE = "sonews.log";
   83.84 +
   83.85 +  public static final String[] AVAILABLE_KEYS = {
   83.86 +      ARTICLE_MAXSIZE,
   83.87 +      EVENTLOG,
   83.88 +      FEED_NEWSPERRUN,
   83.89 +      FEED_PULLINTERVAL,
   83.90 +      HOSTNAME,
   83.91 +      MLPOLL_DELETEUNKNOWN,
   83.92 +      MLPOLL_HOST,
   83.93 +      MLPOLL_PASSWORD,
   83.94 +      MLPOLL_USER,
   83.95 +      MLSEND_ADDRESS,
   83.96 +      MLSEND_HOST,
   83.97 +      MLSEND_PASSWORD,
   83.98 +      MLSEND_PORT,
   83.99 +      MLSEND_RW_FROM,
  83.100 +      MLSEND_RW_SENDER,
  83.101 +      MLSEND_USER,
  83.102 +      PORT,
  83.103 +      TIMEOUT,
  83.104 +      XDAEMON_HOST
  83.105 +  };
  83.106 +
  83.107 +  private static Config instance = new Config();
  83.108 +  
  83.109 +  public static Config inst()
  83.110 +  {
  83.111 +    return instance;
  83.112 +  }
  83.113 +  
  83.114 +  private Config(){}
  83.115 +
  83.116 +  @Override
  83.117 +  public String get(String key, String def)
  83.118 +  {
  83.119 +    String val = CommandLineConfig.getInstance().get(key, null);
  83.120 +    
  83.121 +    if(val == null)
  83.122 +    {
  83.123 +      val = FileConfig.getInstance().get(key, null);
  83.124 +    }
  83.125 +
  83.126 +    if(val == null)
  83.127 +    {
  83.128 +      val = BackendConfig.getInstance().get(key, def);
  83.129 +    }
  83.130 +
  83.131 +    return val;
  83.132 +  }
  83.133 +
  83.134 +  public String get(int maxLevel, String key, String def)
  83.135 +  {
  83.136 +    String val = CommandLineConfig.getInstance().get(key, null);
  83.137 +
  83.138 +    if(val == null && maxLevel >= LEVEL_FILE)
  83.139 +    {
  83.140 +      val = FileConfig.getInstance().get(key, null);
  83.141 +      if(val == null && maxLevel >= LEVEL_BACKEND)
  83.142 +      {
  83.143 +        val = BackendConfig.getInstance().get(key, def);
  83.144 +      }
  83.145 +    }
  83.146 +
  83.147 +    return val != null ? val : def;
  83.148 +  }
  83.149 +
  83.150 +  @Override
  83.151 +  public void set(String key, String val)
  83.152 +  {
  83.153 +    set(LEVEL_BACKEND, key, val);
  83.154 +  }
  83.155 +
  83.156 +  public void set(int level, String key, String val)
  83.157 +  {
  83.158 +    switch(level)
  83.159 +    {
  83.160 +      case LEVEL_CLI:
  83.161 +      {
  83.162 +        CommandLineConfig.getInstance().set(key, val);
  83.163 +        break;
  83.164 +      }
  83.165 +      case LEVEL_FILE:
  83.166 +      {
  83.167 +        FileConfig.getInstance().set(key, val);
  83.168 +        break;
  83.169 +      }
  83.170 +      case LEVEL_BACKEND:
  83.171 +      {
  83.172 +        BackendConfig.getInstance().set(key, val);
  83.173 +        break;
  83.174 +      }
  83.175 +    }
  83.176 +  }
  83.177 +
  83.178 +}
    84.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    84.2 +++ b/src/org/sonews/config/FileConfig.java	Sun Aug 29 17:28:58 2010 +0200
    84.3 @@ -0,0 +1,170 @@
    84.4 +/*
    84.5 + *   SONEWS News Server
    84.6 + *   see AUTHORS for the list of contributors
    84.7 + *
    84.8 + *   This program is free software: you can redistribute it and/or modify
    84.9 + *   it under the terms of the GNU General Public License as published by
   84.10 + *   the Free Software Foundation, either version 3 of the License, or
   84.11 + *   (at your option) any later version.
   84.12 + *
   84.13 + *   This program is distributed in the hope that it will be useful,
   84.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   84.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   84.16 + *   GNU General Public License for more details.
   84.17 + *
   84.18 + *   You should have received a copy of the GNU General Public License
   84.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   84.20 + */
   84.21 +
   84.22 +package org.sonews.config;
   84.23 +
   84.24 +import java.io.FileInputStream;
   84.25 +import java.io.FileNotFoundException;
   84.26 +import java.io.FileOutputStream;
   84.27 +import java.io.IOException;
   84.28 +import java.util.Properties;
   84.29 +
   84.30 +/**
   84.31 + * Manages the bootstrap configuration. It MUST contain all config values
   84.32 + * that are needed to establish a database connection.
   84.33 + * For further configuration values use the Config class instead as that class
   84.34 + * stores its values within the database.
   84.35 + * @author Christian Lins
   84.36 + * @since sonews/0.5.0
   84.37 + */
   84.38 +class FileConfig extends AbstractConfig
   84.39 +{
   84.40 +
   84.41 +  private static final Properties defaultConfig = new Properties();
   84.42 +  
   84.43 +  private static FileConfig instance = null;
   84.44 +  
   84.45 +  static
   84.46 +  {
   84.47 +    // Set some default values
   84.48 +    defaultConfig.setProperty(Config.STORAGE_DATABASE, "jdbc:mysql://localhost/sonews");
   84.49 +    defaultConfig.setProperty(Config.STORAGE_DBMSDRIVER, "com.mysql.jdbc.Driver");
   84.50 +    defaultConfig.setProperty(Config.STORAGE_USER, "sonews_user");
   84.51 +    defaultConfig.setProperty(Config.STORAGE_PASSWORD, "mysecret");
   84.52 +    defaultConfig.setProperty(Config.DEBUG, "false");
   84.53 +  }
   84.54 +  
   84.55 +  /**
   84.56 +   * Note: this method is not thread-safe
   84.57 +   * @return A Config instance
   84.58 +   */
   84.59 +  public static synchronized FileConfig getInstance()
   84.60 +  {
   84.61 +    if(instance == null)
   84.62 +    {
   84.63 +      instance = new FileConfig();
   84.64 +    }
   84.65 +    return instance;
   84.66 +  }
   84.67 +
   84.68 +  // Every config instance is initialized with the default values.
   84.69 +  private final Properties settings = (Properties)defaultConfig.clone();
   84.70 +
   84.71 +  /**
   84.72 +   * Config is a singelton class with only one instance at time.
   84.73 +   * So the constructor is private to prevent the creation of more
   84.74 +   * then one Config instance.
   84.75 +   * @see Config.getInstance() to retrieve an instance of Config
   84.76 +   */
   84.77 +  private FileConfig()
   84.78 +  {
   84.79 +    try
   84.80 +    {
   84.81 +      // Load settings from file
   84.82 +      load();
   84.83 +    }
   84.84 +    catch(IOException ex)
   84.85 +    {
   84.86 +      ex.printStackTrace();
   84.87 +    }
   84.88 +  }
   84.89 +
   84.90 +  /**
   84.91 +   * Loads the configuration from the config file. By default this is done
   84.92 +   * by the (private) constructor but it can be useful to reload the config
   84.93 +   * by invoking this method.
   84.94 +   * @throws IOException
   84.95 +   */
   84.96 +  public void load() 
   84.97 +    throws IOException
   84.98 +  {
   84.99 +    FileInputStream in = null;
  84.100 +    
  84.101 +    try
  84.102 +    {
  84.103 +      in = new FileInputStream(
  84.104 +        Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
  84.105 +      settings.load(in);
  84.106 +    }
  84.107 +    catch (FileNotFoundException e)
  84.108 +    {
  84.109 +      // MUST NOT use Log otherwise endless loop
  84.110 +      System.err.println(e.getMessage());
  84.111 +      save();
  84.112 +    }
  84.113 +    finally
  84.114 +    {
  84.115 +      if(in != null)
  84.116 +        in.close();
  84.117 +    }
  84.118 +  }
  84.119 +
  84.120 +  /**
  84.121 +   * Saves this Config to the config file. By default this is done
  84.122 +   * at program end.
  84.123 +   * @throws FileNotFoundException
  84.124 +   * @throws IOException
  84.125 +   */
  84.126 +  public void save() throws FileNotFoundException, IOException
  84.127 +  {
  84.128 +    FileOutputStream out = null;
  84.129 +    try
  84.130 +    {
  84.131 +      out = new FileOutputStream(
  84.132 +        Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
  84.133 +      settings.store(out, "SONEWS Config File");
  84.134 +      out.flush();
  84.135 +    }
  84.136 +    catch(IOException ex)
  84.137 +    {
  84.138 +      throw ex;
  84.139 +    }
  84.140 +    finally
  84.141 +    {
  84.142 +      if(out != null)
  84.143 +        out.close();
  84.144 +    }
  84.145 +  }
  84.146 +  
  84.147 +  /**
  84.148 +   * Returns the value that is stored within this config
  84.149 +   * identified by the given key. If the key cannot be found
  84.150 +   * the default value is returned.
  84.151 +   * @param key Key to identify the value.
  84.152 +   * @param def The default value that is returned if the key
  84.153 +   * is not found in this Config.
  84.154 +   * @return
  84.155 +   */
  84.156 +  @Override
  84.157 +  public String get(String key, String def)
  84.158 +  {
  84.159 +    return settings.getProperty(key, def);
  84.160 +  }
  84.161 +
  84.162 +  /**
  84.163 +   * Sets the value for a given key.
  84.164 +   * @param key
  84.165 +   * @param value
  84.166 +   */
  84.167 +  @Override
  84.168 +  public void set(final String key, final String value)
  84.169 +  {
  84.170 +    settings.setProperty(key, value);
  84.171 +  }
  84.172 +
  84.173 +}
    85.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    85.2 +++ b/src/org/sonews/daemon/AbstractDaemon.java	Sun Aug 29 17:28:58 2010 +0200
    85.3 @@ -0,0 +1,101 @@
    85.4 +/*
    85.5 + *   SONEWS News Server
    85.6 + *   see AUTHORS for the list of contributors
    85.7 + *
    85.8 + *   This program is free software: you can redistribute it and/or modify
    85.9 + *   it under the terms of the GNU General Public License as published by
   85.10 + *   the Free Software Foundation, either version 3 of the License, or
   85.11 + *   (at your option) any later version.
   85.12 + *
   85.13 + *   This program is distributed in the hope that it will be useful,
   85.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   85.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   85.16 + *   GNU General Public License for more details.
   85.17 + *
   85.18 + *   You should have received a copy of the GNU General Public License
   85.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   85.20 + */
   85.21 +
   85.22 +package org.sonews.daemon;
   85.23 +
   85.24 +import java.sql.SQLException;
   85.25 +import org.sonews.storage.StorageManager;
   85.26 +import org.sonews.util.Log;
   85.27 +
   85.28 +/**
   85.29 + * Base class of all sonews threads.
   85.30 + * Instances of this class will be automatically registered at the ShutdownHook
   85.31 + * to be cleanly exited when the server is forced to exit.
   85.32 + * @author Christian Lins
   85.33 + * @since sonews/0.5.0
   85.34 + */
   85.35 +public abstract class AbstractDaemon extends Thread
   85.36 +{
   85.37 +
   85.38 +  /** This variable is write synchronized through setRunning */
   85.39 +  private boolean isRunning = false;
   85.40 +
   85.41 +  /**
   85.42 +   * Protected constructor. Will be called by derived classes.
   85.43 +   */
   85.44 +  protected AbstractDaemon()
   85.45 +  {
   85.46 +    setDaemon(true); // VM will exit when all threads are daemons
   85.47 +    setName(getClass().getSimpleName());
   85.48 +  }
   85.49 +  
   85.50 +  /**
   85.51 +   * @return true if shutdown() was not yet called.
   85.52 +   */
   85.53 +  public boolean isRunning()
   85.54 +  {
   85.55 +    synchronized(this)
   85.56 +    {
   85.57 +      return this.isRunning;
   85.58 +    }
   85.59 +  }
   85.60 +  
   85.61 +  /**
   85.62 +   * Marks this thread to exit soon. Closes the associated JDBCDatabase connection
   85.63 +   * if available.
   85.64 +   * @throws java.sql.SQLException
   85.65 +   */
   85.66 +  public void shutdownNow()
   85.67 +    throws SQLException
   85.68 +  {
   85.69 +    synchronized(this)
   85.70 +    {
   85.71 +      this.isRunning = false;
   85.72 +      StorageManager.disableProvider();
   85.73 +    }
   85.74 +  }
   85.75 +  
   85.76 +  /**
   85.77 +   * Calls shutdownNow() but catches SQLExceptions if occurring.
   85.78 +   */
   85.79 +  public void shutdown()
   85.80 +  {
   85.81 +    try
   85.82 +    {
   85.83 +      shutdownNow();
   85.84 +    }
   85.85 +    catch(SQLException ex)
   85.86 +    {
   85.87 +      Log.get().warning(ex.toString());
   85.88 +    }
   85.89 +  }
   85.90 +  
   85.91 +  /**
   85.92 +   * Starts this daemon.
   85.93 +   */
   85.94 +  @Override
   85.95 +  public void start()
   85.96 +  {
   85.97 +    synchronized(this)
   85.98 +    {
   85.99 +      this.isRunning = true;
  85.100 +    }
  85.101 +    super.start();
  85.102 +  }
  85.103 +  
  85.104 +}
    86.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    86.2 +++ b/src/org/sonews/daemon/ChannelLineBuffers.java	Sun Aug 29 17:28:58 2010 +0200
    86.3 @@ -0,0 +1,283 @@
    86.4 +/*
    86.5 + *   SONEWS News Server
    86.6 + *   see AUTHORS for the list of contributors
    86.7 + *
    86.8 + *   This program is free software: you can redistribute it and/or modify
    86.9 + *   it under the terms of the GNU General Public License as published by
   86.10 + *   the Free Software Foundation, either version 3 of the License, or
   86.11 + *   (at your option) any later version.
   86.12 + *
   86.13 + *   This program is distributed in the hope that it will be useful,
   86.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   86.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   86.16 + *   GNU General Public License for more details.
   86.17 + *
   86.18 + *   You should have received a copy of the GNU General Public License
   86.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   86.20 + */
   86.21 +
   86.22 +package org.sonews.daemon;
   86.23 +
   86.24 +import java.nio.ByteBuffer;
   86.25 +import java.nio.channels.ClosedChannelException;
   86.26 +import java.util.ArrayList;
   86.27 +import java.util.List;
   86.28 +
   86.29 +/**
   86.30 + * Class holding ByteBuffers for SocketChannels/NNTPConnection.
   86.31 + * Due to the complex nature of AIO/NIO we must properly handle the line 
   86.32 + * buffers for the input and output of the SocketChannels.
   86.33 + * @author Christian Lins
   86.34 + * @since sonews/0.5.0
   86.35 + */
   86.36 +public class ChannelLineBuffers 
   86.37 +{
   86.38 +  
   86.39 +  /**
   86.40 +   * Size of one small buffer; 
   86.41 +   * per default this is 512 bytes to fit one standard line.
   86.42 +   */
   86.43 +  public static final int BUFFER_SIZE = 512;
   86.44 +  
   86.45 +  private static int maxCachedBuffers = 2048; // Cached buffers maximum
   86.46 +  
   86.47 +  private static final List<ByteBuffer> freeSmallBuffers
   86.48 +    = new ArrayList<ByteBuffer>(maxCachedBuffers);
   86.49 +  
   86.50 +  /**
   86.51 +   * Allocates a predefined number of direct ByteBuffers (allocated via
   86.52 +   * ByteBuffer.allocateDirect()). This method is Thread-safe, but should only
   86.53 +   * called at startup.
   86.54 +   */
   86.55 +  public static void allocateDirect()
   86.56 +  {
   86.57 +    synchronized(freeSmallBuffers)
   86.58 +    {
   86.59 +      for(int n = 0; n < maxCachedBuffers; n++)
   86.60 +      {
   86.61 +        ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
   86.62 +        freeSmallBuffers.add(buffer);
   86.63 +      }
   86.64 +    }
   86.65 +  }
   86.66 +  
   86.67 +  private ByteBuffer       inputBuffer   = newLineBuffer();
   86.68 +  private List<ByteBuffer> outputBuffers = new ArrayList<ByteBuffer>();
   86.69 +  
   86.70 +  /**
   86.71 +   * Add the given ByteBuffer to the list of buffers to be send to the client.
   86.72 +   * This method is Thread-safe.
   86.73 +   * @param buffer
   86.74 +   * @throws java.nio.channels.ClosedChannelException If the client channel was
   86.75 +   * already closed.
   86.76 +   */
   86.77 +  public void addOutputBuffer(ByteBuffer buffer)
   86.78 +    throws ClosedChannelException
   86.79 +  {
   86.80 +    if(outputBuffers == null)
   86.81 +    {
   86.82 +      throw new ClosedChannelException();
   86.83 +    }
   86.84 +    
   86.85 +    synchronized(outputBuffers)
   86.86 +    {
   86.87 +      outputBuffers.add(buffer);
   86.88 +    }
   86.89 +  }
   86.90 +  
   86.91 +  /**
   86.92 +   * Currently a channel has only one input buffer. This *may* be a bottleneck
   86.93 +   * and should investigated in the future.
   86.94 +   * @param channel
   86.95 +   * @return The input buffer associated with given channel.
   86.96 +   */
   86.97 +  public ByteBuffer getInputBuffer()
   86.98 +  {
   86.99 +    return inputBuffer;
  86.100 +  }
  86.101 +  
  86.102 +  /**
  86.103 +   * Returns the current output buffer for writing(!) to SocketChannel.
  86.104 +   * @param channel
  86.105 +   * @return The next input buffer that contains unprocessed data or null
  86.106 +   * if the connection was closed or there are no more unprocessed buffers.
  86.107 +   */
  86.108 +  public ByteBuffer getOutputBuffer()
  86.109 +  {
  86.110 +    synchronized(outputBuffers)
  86.111 +    {
  86.112 +      if(outputBuffers == null || outputBuffers.isEmpty())
  86.113 +      {
  86.114 +        return null;
  86.115 +      }
  86.116 +      else
  86.117 +      {
  86.118 +        ByteBuffer buffer = outputBuffers.get(0);
  86.119 +        if(buffer.remaining() == 0)
  86.120 +        {
  86.121 +          outputBuffers.remove(0);
  86.122 +          // Add old buffers to the list of free buffers
  86.123 +          recycleBuffer(buffer);
  86.124 +          buffer = getOutputBuffer();
  86.125 +        }
  86.126 +        return buffer;
  86.127 +      }
  86.128 +    }
  86.129 +  }
  86.130 +
  86.131 +  /**
  86.132 +   * @return false if there are output buffers pending to be written to the
  86.133 +   * client.
  86.134 +   */
  86.135 +  boolean isOutputBufferEmpty()
  86.136 +  {
  86.137 +    synchronized(outputBuffers)
  86.138 +    {
  86.139 +      return outputBuffers.isEmpty();
  86.140 +    }
  86.141 +  }
  86.142 +  
  86.143 +  /**
  86.144 +   * Goes through the input buffer of the given channel and searches
  86.145 +   * for next line terminator. If a '\n' is found, the bytes up to the
  86.146 +   * line terminator are returned as array of bytes (the line terminator
  86.147 +   * is omitted). If none is found the method returns null.
  86.148 +   * @param channel
  86.149 +   * @return A ByteBuffer wrapping the line.
  86.150 +   */
  86.151 +  ByteBuffer nextInputLine()
  86.152 +  {
  86.153 +    if(inputBuffer == null)
  86.154 +    {
  86.155 +      return null;
  86.156 +    }
  86.157 +    
  86.158 +    synchronized(inputBuffer)
  86.159 +    {
  86.160 +      ByteBuffer buffer = inputBuffer;
  86.161 +
  86.162 +      // Mark the current write position
  86.163 +      int mark = buffer.position();
  86.164 +
  86.165 +      // Set position to 0 and limit to current position
  86.166 +      buffer.flip();
  86.167 +
  86.168 +      ByteBuffer lineBuffer = newLineBuffer();
  86.169 +
  86.170 +      while (buffer.position() < buffer.limit())
  86.171 +      {
  86.172 +        byte b = buffer.get();
  86.173 +        if (b == 10) // '\n'
  86.174 +        {
  86.175 +          // The bytes between the buffer's current position and its limit, 
  86.176 +          // if any, are copied to the beginning of the buffer. That is, the 
  86.177 +          // byte at index p = position() is copied to index zero, the byte at 
  86.178 +          // index p + 1 is copied to index one, and so forth until the byte 
  86.179 +          // at index limit() - 1 is copied to index n = limit() - 1 - p. 
  86.180 +          // The buffer's position is then set to n+1 and its limit is set to 
  86.181 +          // its capacity.
  86.182 +          buffer.compact();
  86.183 +
  86.184 +          lineBuffer.flip(); // limit to position, position to 0
  86.185 +          return lineBuffer;
  86.186 +        }
  86.187 +        else
  86.188 +        {
  86.189 +          lineBuffer.put(b);
  86.190 +        }
  86.191 +      }
  86.192 +
  86.193 +      buffer.limit(BUFFER_SIZE);
  86.194 +      buffer.position(mark);
  86.195 +
  86.196 +      if(buffer.hasRemaining())
  86.197 +      {
  86.198 +        return null;
  86.199 +      }
  86.200 +      else
  86.201 +      {
  86.202 +        // In the first 512 was no newline found, so the input is not standard
  86.203 +        // compliant. We return the current buffer as new line and add a space
  86.204 +        // to the beginning of the next line which corrects some overlong header
  86.205 +        // lines.
  86.206 +        inputBuffer = newLineBuffer();
  86.207 +        inputBuffer.put((byte)' ');
  86.208 +        buffer.flip();
  86.209 +        return buffer;
  86.210 +      }
  86.211 +    }
  86.212 +  }
  86.213 +  
  86.214 +  /**
  86.215 +   * Returns a at least 512 bytes long ByteBuffer ready for usage.
  86.216 +   * The method first try to reuse an already allocated (cached) buffer but
  86.217 +   * if that fails returns a newly allocated direct buffer.
  86.218 +   * Use recycleBuffer() method when you do not longer use the allocated buffer.
  86.219 +   */
  86.220 +  static ByteBuffer newLineBuffer()
  86.221 +  {
  86.222 +    ByteBuffer buf = null;
  86.223 +    synchronized(freeSmallBuffers)
  86.224 +    {
  86.225 +      if(!freeSmallBuffers.isEmpty())
  86.226 +      {
  86.227 +        buf = freeSmallBuffers.remove(0);
  86.228 +      }
  86.229 +    }
  86.230 +      
  86.231 +    if(buf == null)
  86.232 +    {
  86.233 +      // Allocate a non-direct buffer
  86.234 +      buf = ByteBuffer.allocate(BUFFER_SIZE);
  86.235 +    }
  86.236 +    
  86.237 +    assert buf.position() == 0;
  86.238 +    assert buf.limit() >= BUFFER_SIZE;
  86.239 +    
  86.240 +    return buf;
  86.241 +  }
  86.242 +  
  86.243 +  /**
  86.244 +   * Adds the given buffer to the list of free buffers if it is a valuable
  86.245 +   * direct allocated buffer.
  86.246 +   * @param buffer
  86.247 +   */
  86.248 +  public static void recycleBuffer(ByteBuffer buffer)
  86.249 +  {
  86.250 +    assert buffer != null;
  86.251 +
  86.252 +    if(buffer.isDirect())
  86.253 +    {
  86.254 +      assert buffer.capacity() >= BUFFER_SIZE;
  86.255 +      
  86.256 +      // Add old buffers to the list of free buffers
  86.257 +      synchronized(freeSmallBuffers)
  86.258 +      {
  86.259 +        buffer.clear(); // Set position to 0 and limit to capacity
  86.260 +        freeSmallBuffers.add(buffer);
  86.261 +      }
  86.262 +    } // if(buffer.isDirect())
  86.263 +  }
  86.264 +  
  86.265 +  /**
  86.266 +   * Recycles all buffers of this ChannelLineBuffers object.
  86.267 +   */
  86.268 +  public void recycleBuffers()
  86.269 +  {
  86.270 +    synchronized(inputBuffer)
  86.271 +    {
  86.272 +      recycleBuffer(inputBuffer);
  86.273 +      this.inputBuffer = null;
  86.274 +    }
  86.275 +    
  86.276 +    synchronized(outputBuffers)
  86.277 +    {
  86.278 +      for(ByteBuffer buf : outputBuffers)
  86.279 +      {
  86.280 +        recycleBuffer(buf);
  86.281 +      }
  86.282 +      outputBuffers = null;
  86.283 +    }
  86.284 +  }
  86.285 +  
  86.286 +}
    87.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    87.2 +++ b/src/org/sonews/daemon/ChannelReader.java	Sun Aug 29 17:28:58 2010 +0200
    87.3 @@ -0,0 +1,202 @@
    87.4 +/*
    87.5 + *   SONEWS News Server
    87.6 + *   see AUTHORS for the list of contributors
    87.7 + *
    87.8 + *   This program is free software: you can redistribute it and/or modify
    87.9 + *   it under the terms of the GNU General Public License as published by
   87.10 + *   the Free Software Foundation, either version 3 of the License, or
   87.11 + *   (at your option) any later version.
   87.12 + *
   87.13 + *   This program is distributed in the hope that it will be useful,
   87.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   87.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   87.16 + *   GNU General Public License for more details.
   87.17 + *
   87.18 + *   You should have received a copy of the GNU General Public License
   87.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   87.20 + */
   87.21 +
   87.22 +package org.sonews.daemon;
   87.23 +
   87.24 +import java.io.IOException;
   87.25 +import java.nio.ByteBuffer;
   87.26 +import java.nio.channels.CancelledKeyException;
   87.27 +import java.nio.channels.SelectionKey;
   87.28 +import java.nio.channels.Selector;
   87.29 +import java.nio.channels.SocketChannel;
   87.30 +import java.util.Iterator;
   87.31 +import java.util.Set;
   87.32 +import java.util.logging.Level;
   87.33 +import org.sonews.util.Log;
   87.34 +
   87.35 +/**
   87.36 + * A Thread task listening for OP_READ events from SocketChannels.
   87.37 + * @author Christian Lins
   87.38 + * @since sonews/0.5.0
   87.39 + */
   87.40 +class ChannelReader extends AbstractDaemon
   87.41 +{
   87.42 +
   87.43 +  private static ChannelReader instance = new ChannelReader();
   87.44 +
   87.45 +  /**
   87.46 +   * @return Active ChannelReader instance.
   87.47 +   */
   87.48 +  public static ChannelReader getInstance()
   87.49 +  {
   87.50 +    return instance;
   87.51 +  }
   87.52 +  
   87.53 +  private Selector selector = null;
   87.54 +  
   87.55 +  protected ChannelReader()
   87.56 +  {
   87.57 +  }
   87.58 +  
   87.59 +  /**
   87.60 +   * Sets the selector which is used by this reader to determine the channel
   87.61 +   * to read from.
   87.62 +   * @param selector
   87.63 +   */
   87.64 +  public void setSelector(final Selector selector)
   87.65 +  {
   87.66 +    this.selector = selector;
   87.67 +  }
   87.68 +  
   87.69 +  /**
   87.70 +   * Run loop. Blocks until some data is available in a channel.
   87.71 +   */
   87.72 +  @Override
   87.73 +  public void run()
   87.74 +  {
   87.75 +    assert selector != null;
   87.76 +
   87.77 +    while(isRunning())
   87.78 +    {
   87.79 +      try
   87.80 +      {
   87.81 +        // select() blocks until some SelectableChannels are ready for
   87.82 +        // processing. There is no need to lock the selector as we have only
   87.83 +        // one thread per selector.
   87.84 +        selector.select();
   87.85 +
   87.86 +        // Get list of selection keys with pending events.
   87.87 +        // Note: the selected key set is not thread-safe
   87.88 +        SocketChannel channel = null;
   87.89 +        NNTPConnection conn = null;
   87.90 +        final Set<SelectionKey> selKeys = selector.selectedKeys();
   87.91 +        SelectionKey selKey = null;
   87.92 +
   87.93 +        synchronized (selKeys)
   87.94 +        {
   87.95 +          Iterator it = selKeys.iterator();
   87.96 +
   87.97 +          // Process the first pending event
   87.98 +          while (it.hasNext())
   87.99 +          {
  87.100 +            selKey = (SelectionKey) it.next();
  87.101 +            channel = (SocketChannel) selKey.channel();
  87.102 +            conn = Connections.getInstance().get(channel);
  87.103 +
  87.104 +            // Because we cannot lock the selKey as that would cause a deadlock
  87.105 +            // we lock the connection. To preserve the order of the received
  87.106 +            // byte blocks a selection key for a connection that has pending
  87.107 +            // read events is skipped.
  87.108 +            if (conn == null || conn.tryReadLock())
  87.109 +            {
  87.110 +              // Remove from set to indicate that it's being processed
  87.111 +              it.remove();
  87.112 +              if (conn != null)
  87.113 +              {
  87.114 +                break; // End while loop
  87.115 +              }
  87.116 +            }
  87.117 +            else
  87.118 +            {
  87.119 +              selKey = null;
  87.120 +              channel = null;
  87.121 +              conn = null;
  87.122 +            }
  87.123 +          }
  87.124 +        }
  87.125 +
  87.126 +        // Do not lock the selKeys while processing because this causes
  87.127 +        // a deadlock in sun.nio.ch.SelectorImpl.lockAndDoSelect()
  87.128 +        if (selKey != null && channel != null && conn != null)
  87.129 +        {
  87.130 +          processSelectionKey(conn, channel, selKey);
  87.131 +          conn.unlockReadLock();
  87.132 +        }
  87.133 +
  87.134 +      }
  87.135 +      catch(CancelledKeyException ex)
  87.136 +      {
  87.137 +        Log.get().warning("ChannelReader.run(): " + ex);
  87.138 +        Log.get().log(Level.INFO, "", ex);
  87.139 +      }
  87.140 +      catch(Exception ex)
  87.141 +      {
  87.142 +        ex.printStackTrace();
  87.143 +      }
  87.144 +      
  87.145 +      // Eventually wait for a register operation
  87.146 +      synchronized (NNTPDaemon.RegisterGate)
  87.147 +      {
  87.148 +      // Do nothing; FindBugs may warn about an empty synchronized 
  87.149 +      // statement, but we cannot use a wait()/notify() mechanism here.
  87.150 +      // If we used something like RegisterGate.wait() we block here
  87.151 +      // until the NNTPDaemon calls notify(). But the daemon only
  87.152 +      // calls notify() if itself is NOT blocked in the listening socket.
  87.153 +      }
  87.154 +    } // while(isRunning())
  87.155 +  }
  87.156 +  
  87.157 +  private void processSelectionKey(final NNTPConnection connection,
  87.158 +    final SocketChannel socketChannel, final SelectionKey selKey)
  87.159 +    throws InterruptedException, IOException
  87.160 +  {
  87.161 +    assert selKey != null;
  87.162 +    assert selKey.isReadable();
  87.163 +    
  87.164 +    // Some bytes are available for reading
  87.165 +    if(selKey.isValid())
  87.166 +    {   
  87.167 +      // Lock the channel
  87.168 +      //synchronized(socketChannel)
  87.169 +      {
  87.170 +        // Read the data into the appropriate buffer
  87.171 +        ByteBuffer buf = connection.getInputBuffer();
  87.172 +        int read = -1;
  87.173 +        try 
  87.174 +        {
  87.175 +          read = socketChannel.read(buf);
  87.176 +        }
  87.177 +        catch(IOException ex)
  87.178 +        {
  87.179 +          // The connection was probably closed by the remote host
  87.180 +          // in a non-clean fashion
  87.181 +          Log.get().info("ChannelReader.processSelectionKey(): " + ex);
  87.182 +        }
  87.183 +        catch(Exception ex) 
  87.184 +        {
  87.185 +          Log.get().warning("ChannelReader.processSelectionKey(): " + ex);
  87.186 +        }
  87.187 +        
  87.188 +        if(read == -1) // End of stream
  87.189 +        {
  87.190 +          selKey.cancel();
  87.191 +        }
  87.192 +        else if(read > 0) // If some data was read
  87.193 +        {
  87.194 +          ConnectionWorker.addChannel(socketChannel);
  87.195 +        }
  87.196 +      }
  87.197 +    }
  87.198 +    else
  87.199 +    {
  87.200 +      // Should not happen
  87.201 +      Log.get().severe("Should not happen: " + selKey.toString());
  87.202 +    }
  87.203 +  }
  87.204 +  
  87.205 +}
    88.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    88.2 +++ b/src/org/sonews/daemon/ChannelWriter.java	Sun Aug 29 17:28:58 2010 +0200
    88.3 @@ -0,0 +1,210 @@
    88.4 +/*
    88.5 + *   SONEWS News Server
    88.6 + *   see AUTHORS for the list of contributors
    88.7 + *
    88.8 + *   This program is free software: you can redistribute it and/or modify
    88.9 + *   it under the terms of the GNU General Public License as published by
   88.10 + *   the Free Software Foundation, either version 3 of the License, or
   88.11 + *   (at your option) any later version.
   88.12 + *
   88.13 + *   This program is distributed in the hope that it will be useful,
   88.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   88.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   88.16 + *   GNU General Public License for more details.
   88.17 + *
   88.18 + *   You should have received a copy of the GNU General Public License
   88.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   88.20 + */
   88.21 +
   88.22 +package org.sonews.daemon;
   88.23 +
   88.24 +import org.sonews.util.Log;
   88.25 +import java.io.IOException;
   88.26 +import java.nio.ByteBuffer;
   88.27 +import java.nio.channels.CancelledKeyException;
   88.28 +import java.nio.channels.SelectionKey;
   88.29 +import java.nio.channels.Selector;
   88.30 +import java.nio.channels.SocketChannel;
   88.31 +import java.util.Iterator;
   88.32 +
   88.33 +/**
   88.34 + * A Thread task that processes OP_WRITE events for SocketChannels.
   88.35 + * @author Christian Lins
   88.36 + * @since sonews/0.5.0
   88.37 + */
   88.38 +class ChannelWriter extends AbstractDaemon
   88.39 +{
   88.40 +
   88.41 +  private static ChannelWriter instance = new ChannelWriter();
   88.42 +
   88.43 +  /**
   88.44 +   * @return Returns the active ChannelWriter instance.
   88.45 +   */
   88.46 +  public static ChannelWriter getInstance()
   88.47 +  {
   88.48 +    return instance;
   88.49 +  }
   88.50 +  
   88.51 +  private Selector selector = null;
   88.52 +  
   88.53 +  protected ChannelWriter()
   88.54 +  {
   88.55 +  }
   88.56 +  
   88.57 +  /**
   88.58 +   * @return Selector associated with this instance.
   88.59 +   */
   88.60 +  public Selector getSelector()
   88.61 +  {
   88.62 +    return this.selector;
   88.63 +  }
   88.64 +  
   88.65 +  /**
   88.66 +   * Sets the selector that is used by this ChannelWriter.
   88.67 +   * @param selector
   88.68 +   */
   88.69 +  public void setSelector(final Selector selector)
   88.70 +  {
   88.71 +    this.selector = selector;
   88.72 +  }
   88.73 +  
   88.74 +  /**
   88.75 +   * Run loop.
   88.76 +   */
   88.77 +  @Override
   88.78 +  public void run()
   88.79 +  {
   88.80 +    assert selector != null;
   88.81 +
   88.82 +    while(isRunning())
   88.83 +    {
   88.84 +      try
   88.85 +      {
   88.86 +        SelectionKey   selKey        = null;
   88.87 +        SocketChannel  socketChannel = null;
   88.88 +        NNTPConnection connection    = null;
   88.89 +
   88.90 +        // select() blocks until some SelectableChannels are ready for
   88.91 +        // processing. There is no need to synchronize the selector as we
   88.92 +        // have only one thread per selector.
   88.93 +        selector.select(); // The return value of select can be ignored
   88.94 +
   88.95 +        // Get list of selection keys with pending OP_WRITE events.
   88.96 +        // The keySET is not thread-safe whereas the keys itself are.
   88.97 +        Iterator it = selector.selectedKeys().iterator();
   88.98 +
   88.99 +        while (it.hasNext())
  88.100 +        {
  88.101 +          // We remove the first event from the set and store it for
  88.102 +          // later processing.
  88.103 +          selKey = (SelectionKey) it.next();
  88.104 +          socketChannel = (SocketChannel) selKey.channel();
  88.105 +          connection = Connections.getInstance().get(socketChannel);
  88.106 +
  88.107 +          it.remove();
  88.108 +          if (connection != null)
  88.109 +          {
  88.110 +            break;
  88.111 +          }
  88.112 +          else
  88.113 +          {
  88.114 +            selKey = null;
  88.115 +          }
  88.116 +        }
  88.117 +        
  88.118 +        if (selKey != null)
  88.119 +        {
  88.120 +          try
  88.121 +          {
  88.122 +            // Process the selected key.
  88.123 +            // As there is only one OP_WRITE key for a given channel, we need
  88.124 +            // not to synchronize this processing to retain the order.
  88.125 +            processSelectionKey(connection, socketChannel, selKey);
  88.126 +          }
  88.127 +          catch (IOException ex)
  88.128 +          {
  88.129 +            Log.get().warning("Error writing to channel: " + ex);
  88.130 +
  88.131 +            // Cancel write events for this channel
  88.132 +            selKey.cancel();
  88.133 +            connection.shutdownInput();
  88.134 +            connection.shutdownOutput();
  88.135 +          }
  88.136 +        }
  88.137 +        
  88.138 +        // Eventually wait for a register operation
  88.139 +        synchronized(NNTPDaemon.RegisterGate) { /* do nothing */ }
  88.140 +      }
  88.141 +      catch(CancelledKeyException ex)
  88.142 +      {
  88.143 +        Log.get().info("ChannelWriter.run(): " + ex);
  88.144 +      }
  88.145 +      catch(Exception ex)
  88.146 +      {
  88.147 +        ex.printStackTrace();
  88.148 +      }
  88.149 +    } // while(isRunning())
  88.150 +  }
  88.151 +  
  88.152 +  private void processSelectionKey(final NNTPConnection connection,
  88.153 +    final SocketChannel socketChannel, final SelectionKey selKey)
  88.154 +    throws InterruptedException, IOException
  88.155 +  {
  88.156 +    assert connection != null;
  88.157 +    assert socketChannel != null;
  88.158 +    assert selKey != null;
  88.159 +    assert selKey.isWritable();
  88.160 +
  88.161 +    // SocketChannel is ready for writing
  88.162 +    if(selKey.isValid())
  88.163 +    {
  88.164 +      // Lock the socket channel
  88.165 +      synchronized(socketChannel)
  88.166 +      {
  88.167 +        // Get next output buffer
  88.168 +        ByteBuffer buf = connection.getOutputBuffer();
  88.169 +        if(buf == null)
  88.170 +        {
  88.171 +          // Currently we have nothing to write, so we stop the writeable
  88.172 +          // events until we have something to write to the socket channel
  88.173 +          //selKey.cancel();
  88.174 +          selKey.interestOps(0);
  88.175 +          // Update activity timestamp to prevent too early disconnects
  88.176 +          // on slow client connections
  88.177 +          connection.setLastActivity(System.currentTimeMillis());
  88.178 +          return;
  88.179 +        }
  88.180 + 
  88.181 +        while(buf != null) // There is data to be send
  88.182 +        {
  88.183 +          // Write buffer to socket channel; this method does not block
  88.184 +          if(socketChannel.write(buf) <= 0)
  88.185 +          {
  88.186 +            // Perhaps there is data to be written, but the SocketChannel's
  88.187 +            // buffer is full, so we stop writing to until the next event.
  88.188 +            break;
  88.189 +          }
  88.190 +          else
  88.191 +          {
  88.192 +            // Retrieve next buffer if available; method may return the same
  88.193 +            // buffer instance if it still have some bytes remaining
  88.194 +            buf = connection.getOutputBuffer();
  88.195 +          }
  88.196 +        }
  88.197 +      }
  88.198 +    }
  88.199 +    else
  88.200 +    {
  88.201 +      Log.get().warning("Invalid OP_WRITE key: " + selKey);
  88.202 +
  88.203 +      if(socketChannel.socket().isClosed())
  88.204 +      {
  88.205 +        connection.shutdownInput();
  88.206 +        connection.shutdownOutput();
  88.207 +        socketChannel.close();
  88.208 +        Log.get().info("Connection closed.");
  88.209 +      }
  88.210 +    }
  88.211 +  }
  88.212 +  
  88.213 +}
    89.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    89.2 +++ b/src/org/sonews/daemon/CommandSelector.java	Sun Aug 29 17:28:58 2010 +0200
    89.3 @@ -0,0 +1,141 @@
    89.4 +/*
    89.5 + *   SONEWS News Server
    89.6 + *   see AUTHORS for the list of contributors
    89.7 + *
    89.8 + *   This program is free software: you can redistribute it and/or modify
    89.9 + *   it under the terms of the GNU General Public License as published by
   89.10 + *   the Free Software Foundation, either version 3 of the License, or
   89.11 + *   (at your option) any later version.
   89.12 + *
   89.13 + *   This program is distributed in the hope that it will be useful,
   89.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   89.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   89.16 + *   GNU General Public License for more details.
   89.17 + *
   89.18 + *   You should have received a copy of the GNU General Public License
   89.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   89.20 + */
   89.21 +
   89.22 +package org.sonews.daemon;
   89.23 +
   89.24 +import java.util.HashMap;
   89.25 +import java.util.Map;
   89.26 +import java.util.Set;
   89.27 +import java.util.concurrent.ConcurrentHashMap;
   89.28 +import org.sonews.daemon.command.Command;
   89.29 +import org.sonews.daemon.command.UnsupportedCommand;
   89.30 +import org.sonews.util.Log;
   89.31 +import org.sonews.util.io.Resource;
   89.32 +
   89.33 +/**
   89.34 + * Selects the correct command processing class.
   89.35 + * @author Christian Lins
   89.36 + * @since sonews/1.0
   89.37 + */
   89.38 +public class CommandSelector
   89.39 +{
   89.40 +
   89.41 +  private static Map<Thread, CommandSelector> instances
   89.42 +    = new ConcurrentHashMap<Thread, CommandSelector>();
   89.43 +  private static Map<String, Class<?>> commandClassesMapping
   89.44 +    = new ConcurrentHashMap<String, Class<?>>();
   89.45 +
   89.46 +  static
   89.47 +  {
   89.48 +    String[] classes = Resource.getAsString("helpers/commands.list", true).split("\n");
   89.49 +    for(String className : classes)
   89.50 +    {
   89.51 +      if(className.charAt(0) == '#')
   89.52 +      {
   89.53 +        // Skip comments
   89.54 +        continue;
   89.55 +      }
   89.56 +
   89.57 +      try
   89.58 +      {
   89.59 +        addCommandHandler(className);
   89.60 +      }
   89.61 +      catch(ClassNotFoundException ex)
   89.62 +      {
   89.63 +        Log.get().warning("Could not load command class: " + ex);
   89.64 +      }
   89.65 +      catch(InstantiationException ex)
   89.66 +      {
   89.67 +        Log.get().severe("Could not instantiate command class: " + ex);
   89.68 +      }
   89.69 +      catch(IllegalAccessException ex)
   89.70 +      {
   89.71 +        Log.get().severe("Could not access command class: " + ex);
   89.72 +      }
   89.73 +    }
   89.74 +  }
   89.75 +
   89.76 +  public static void addCommandHandler(String className)
   89.77 +    throws ClassNotFoundException, InstantiationException, IllegalAccessException
   89.78 +  {
   89.79 +    Class<?> clazz = Class.forName(className);
   89.80 +    Command cmd = (Command)clazz.newInstance();
   89.81 +    String[] cmdStrs = cmd.getSupportedCommandStrings();
   89.82 +    for (String cmdStr : cmdStrs)
   89.83 +    {
   89.84 +      commandClassesMapping.put(cmdStr, clazz);
   89.85 +    }
   89.86 +  }
   89.87 +
   89.88 +  public static Set<String> getCommandNames()
   89.89 +  {
   89.90 +    return commandClassesMapping.keySet();
   89.91 +  }
   89.92 +
   89.93 +  public static CommandSelector getInstance()
   89.94 +  {
   89.95 +    CommandSelector csel = instances.get(Thread.currentThread());
   89.96 +    if(csel == null)
   89.97 +    {
   89.98 +      csel = new CommandSelector();
   89.99 +      instances.put(Thread.currentThread(), csel);
  89.100 +    }
  89.101 +    return csel;
  89.102 +  }
  89.103 +
  89.104 +  private Map<String, Command> commandMapping = new HashMap<String, Command>();
  89.105 +  private Command              unsupportedCmd = new UnsupportedCommand();
  89.106 +
  89.107 +  private CommandSelector()
  89.108 +  {}
  89.109 +
  89.110 +  public Command get(String commandName)
  89.111 +  {
  89.112 +    try
  89.113 +    {
  89.114 +      commandName = commandName.toUpperCase();
  89.115 +      Command cmd = this.commandMapping.get(commandName);
  89.116 +
  89.117 +      if(cmd == null)
  89.118 +      {
  89.119 +        Class<?> clazz = commandClassesMapping.get(commandName);
  89.120 +        if(clazz == null)
  89.121 +        {
  89.122 +          cmd = this.unsupportedCmd;
  89.123 +        }
  89.124 +        else
  89.125 +        {
  89.126 +          cmd = (Command)clazz.newInstance();
  89.127 +          this.commandMapping.put(commandName, cmd);
  89.128 +        }
  89.129 +      }
  89.130 +      else if(cmd.isStateful())
  89.131 +      {
  89.132 +        cmd = cmd.getClass().newInstance();
  89.133 +      }
  89.134 +
  89.135 +      return cmd;
  89.136 +    }
  89.137 +    catch(Exception ex)
  89.138 +    {
  89.139 +      ex.printStackTrace();
  89.140 +      return this.unsupportedCmd;
  89.141 +    }
  89.142 +  }
  89.143 +
  89.144 +}
    90.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    90.2 +++ b/src/org/sonews/daemon/ConnectionWorker.java	Sun Aug 29 17:28:58 2010 +0200
    90.3 @@ -0,0 +1,102 @@
    90.4 +/*
    90.5 + *   SONEWS News Server
    90.6 + *   see AUTHORS for the list of contributors
    90.7 + *
    90.8 + *   This program is free software: you can redistribute it and/or modify
    90.9 + *   it under the terms of the GNU General Public License as published by
   90.10 + *   the Free Software Foundation, either version 3 of the License, or
   90.11 + *   (at your option) any later version.
   90.12 + *
   90.13 + *   This program is distributed in the hope that it will be useful,
   90.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   90.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   90.16 + *   GNU General Public License for more details.
   90.17 + *
   90.18 + *   You should have received a copy of the GNU General Public License
   90.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   90.20 + */
   90.21 +
   90.22 +package org.sonews.daemon;
   90.23 +
   90.24 +import org.sonews.util.Log;
   90.25 +import java.nio.ByteBuffer;
   90.26 +import java.nio.channels.SocketChannel;
   90.27 +import java.util.concurrent.ArrayBlockingQueue;
   90.28 +
   90.29 +/**
   90.30 + * Does most of the work: parsing input, talking to client and Database.
   90.31 + * @author Christian Lins
   90.32 + * @since sonews/0.5.0
   90.33 + */
   90.34 +class ConnectionWorker extends AbstractDaemon
   90.35 +{
   90.36 +
   90.37 +  // 256 pending events should be enough
   90.38 +  private static ArrayBlockingQueue<SocketChannel> pendingChannels
   90.39 +    = new ArrayBlockingQueue<SocketChannel>(256, true);
   90.40 +  
   90.41 +  /**
   90.42 +   * Registers the given channel for further event processing.
   90.43 +   * @param channel
   90.44 +   */
   90.45 +  public static void addChannel(SocketChannel channel)
   90.46 +    throws InterruptedException
   90.47 +  {
   90.48 +    pendingChannels.put(channel);
   90.49 +  }
   90.50 +  
   90.51 +  /**
   90.52 +   * Processing loop.
   90.53 +   */
   90.54 +  @Override
   90.55 +  public void run()
   90.56 +  {
   90.57 +    while(isRunning())
   90.58 +    {
   90.59 +      try
   90.60 +      {
   90.61 +        // Retrieve and remove if available, otherwise wait.
   90.62 +        SocketChannel channel = pendingChannels.take();
   90.63 +
   90.64 +        if(channel != null)
   90.65 +        {
   90.66 +          // Connections.getInstance().get() MAY return null
   90.67 +          NNTPConnection conn = Connections.getInstance().get(channel);
   90.68 +          
   90.69 +          // Try to lock the connection object
   90.70 +          if(conn != null && conn.tryReadLock())
   90.71 +          {
   90.72 +            ByteBuffer buf = conn.getBuffers().nextInputLine();
   90.73 +            while(buf != null) // Complete line was received
   90.74 +            {
   90.75 +              final byte[] line = new byte[buf.limit()];
   90.76 +              buf.get(line);
   90.77 +              ChannelLineBuffers.recycleBuffer(buf);
   90.78 +              
   90.79 +              // Here is the actual work done
   90.80 +              conn.lineReceived(line);
   90.81 +
   90.82 +              // Read next line as we could have already received the next line
   90.83 +              buf = conn.getBuffers().nextInputLine();
   90.84 +            }
   90.85 +            conn.unlockReadLock();
   90.86 +          }
   90.87 +          else
   90.88 +          {
   90.89 +            addChannel(channel);
   90.90 +          }
   90.91 +        }
   90.92 +      }
   90.93 +      catch(InterruptedException ex)
   90.94 +      {
   90.95 +        Log.get().info("ConnectionWorker interrupted: " + ex);
   90.96 +      }
   90.97 +      catch(Exception ex)
   90.98 +      {
   90.99 +        Log.get().severe("Exception in ConnectionWorker: " + ex);
  90.100 +        ex.printStackTrace();
  90.101 +      }
  90.102 +    } // end while(isRunning())
  90.103 +  }
  90.104 +  
  90.105 +}
    91.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    91.2 +++ b/src/org/sonews/daemon/Connections.java	Sun Aug 29 17:28:58 2010 +0200
    91.3 @@ -0,0 +1,181 @@
    91.4 +/*
    91.5 + *   SONEWS News Server
    91.6 + *   see AUTHORS for the list of contributors
    91.7 + *
    91.8 + *   This program is free software: you can redistribute it and/or modify
    91.9 + *   it under the terms of the GNU General Public License as published by
   91.10 + *   the Free Software Foundation, either version 3 of the License, or
   91.11 + *   (at your option) any later version.
   91.12 + *
   91.13 + *   This program is distributed in the hope that it will be useful,
   91.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   91.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   91.16 + *   GNU General Public License for more details.
   91.17 + *
   91.18 + *   You should have received a copy of the GNU General Public License
   91.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   91.20 + */
   91.21 +
   91.22 +package org.sonews.daemon;
   91.23 +
   91.24 +import org.sonews.config.Config;
   91.25 +import org.sonews.util.Log;
   91.26 +import org.sonews.util.Stats;
   91.27 +import java.io.IOException;
   91.28 +import java.net.InetSocketAddress;
   91.29 +import java.net.Socket;
   91.30 +import java.nio.channels.SocketChannel;
   91.31 +import java.util.ArrayList;
   91.32 +import java.util.HashMap;
   91.33 +import java.util.List;
   91.34 +import java.util.ListIterator;
   91.35 +import java.util.Map;
   91.36 +
   91.37 +/**
   91.38 + * Daemon thread collecting all NNTPConnection instances. The thread
   91.39 + * checks periodically if there are stale/timed out connections and
   91.40 + * removes and purges them properly.
   91.41 + * @author Christian Lins
   91.42 + * @since sonews/0.5.0
   91.43 + */
   91.44 +public final class Connections extends AbstractDaemon
   91.45 +{
   91.46 +
   91.47 +  private static final Connections instance = new Connections();
   91.48 +  
   91.49 +  /**
   91.50 +   * @return Active Connections instance.
   91.51 +   */
   91.52 +  public static Connections getInstance()
   91.53 +  {
   91.54 +    return Connections.instance;
   91.55 +  }
   91.56 +  
   91.57 +  private final List<NNTPConnection> connections 
   91.58 +    = new ArrayList<NNTPConnection>();
   91.59 +  private final Map<SocketChannel, NNTPConnection> connByChannel 
   91.60 +    = new HashMap<SocketChannel, NNTPConnection>();
   91.61 +  
   91.62 +  private Connections()
   91.63 +  {
   91.64 +    setName("Connections");
   91.65 +  }
   91.66 +  
   91.67 +  /**
   91.68 +   * Adds the given NNTPConnection to the Connections management.
   91.69 +   * @param conn
   91.70 +   * @see org.sonews.daemon.NNTPConnection
   91.71 +   */
   91.72 +  public void add(final NNTPConnection conn)
   91.73 +  {
   91.74 +    synchronized(this.connections)
   91.75 +    {
   91.76 +      this.connections.add(conn);
   91.77 +      this.connByChannel.put(conn.getSocketChannel(), conn);
   91.78 +    }
   91.79 +  }
   91.80 +  
   91.81 +  /**
   91.82 +   * @param channel
   91.83 +   * @return NNTPConnection instance that is associated with the given
   91.84 +   * SocketChannel.
   91.85 +   */
   91.86 +  public NNTPConnection get(final SocketChannel channel)
   91.87 +  {
   91.88 +    synchronized(this.connections)
   91.89 +    {
   91.90 +      return this.connByChannel.get(channel);
   91.91 +    }
   91.92 +  }
   91.93 +
   91.94 +  int getConnectionCount(String remote)
   91.95 +  {
   91.96 +    int cnt = 0;
   91.97 +    synchronized(this.connections)
   91.98 +    {
   91.99 +      for(NNTPConnection conn : this.connections)
  91.100 +      {
  91.101 +        assert conn != null;
  91.102 +        assert conn.getSocketChannel() != null;
  91.103 +
  91.104 +        Socket socket = conn.getSocketChannel().socket();
  91.105 +        if(socket != null)
  91.106 +        {
  91.107 +          InetSocketAddress sockAddr = (InetSocketAddress)socket.getRemoteSocketAddress();
  91.108 +          if(sockAddr != null)
  91.109 +          {
  91.110 +            if(sockAddr.getHostName().equals(remote))
  91.111 +            {
  91.112 +              cnt++;
  91.113 +            }
  91.114 +          }
  91.115 +        } // if(socket != null)
  91.116 +      }
  91.117 +    }
  91.118 +    return cnt;
  91.119 +  }
  91.120 +  
  91.121 +  /**
  91.122 +   * Run loops. Checks periodically for timed out connections and purged them
  91.123 +   * from the lists.
  91.124 +   */
  91.125 +  @Override
  91.126 +  public void run()
  91.127 +  {
  91.128 +    while(isRunning())
  91.129 +    {
  91.130 +      int timeoutMillis = 1000 * Config.inst().get(Config.TIMEOUT, 180);
  91.131 +      
  91.132 +      synchronized (this.connections)
  91.133 +      {
  91.134 +        final ListIterator<NNTPConnection> iter = this.connections.listIterator();
  91.135 +        NNTPConnection conn;
  91.136 +
  91.137 +        while (iter.hasNext())
  91.138 +        {
  91.139 +          conn = iter.next();
  91.140 +          if((System.currentTimeMillis() - conn.getLastActivity()) > timeoutMillis
  91.141 +              && conn.getBuffers().isOutputBufferEmpty())
  91.142 +          {
  91.143 +            // A connection timeout has occurred so purge the connection
  91.144 +            iter.remove();
  91.145 +
  91.146 +            // Close and remove the channel
  91.147 +            SocketChannel channel = conn.getSocketChannel();
  91.148 +            connByChannel.remove(channel);
  91.149 +            
  91.150 +            try
  91.151 +            {
  91.152 +              assert channel != null;
  91.153 +              assert channel.socket() != null;
  91.154 +      
  91.155 +              // Close the channel; implicitely cancels all selectionkeys
  91.156 +              channel.close();
  91.157 +              Log.get().info("Disconnected: " + channel.socket().getRemoteSocketAddress() +
  91.158 +                " (timeout)");
  91.159 +            }
  91.160 +            catch(IOException ex)
  91.161 +            {
  91.162 +              Log.get().warning("Connections.run(): " + ex);
  91.163 +            }
  91.164 +
  91.165 +            // Recycle the used buffers
  91.166 +            conn.getBuffers().recycleBuffers();
  91.167 +            
  91.168 +            Stats.getInstance().clientDisconnect();
  91.169 +          }
  91.170 +        }
  91.171 +      }
  91.172 +
  91.173 +      try
  91.174 +      {
  91.175 +        Thread.sleep(10000); // Sleep ten seconds
  91.176 +      }
  91.177 +      catch(InterruptedException ex)
  91.178 +      {
  91.179 +        Log.get().warning("Connections Thread was interrupted: " + ex.getMessage());
  91.180 +      }
  91.181 +    }
  91.182 +  }
  91.183 +  
  91.184 +}
    92.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    92.2 +++ b/src/org/sonews/daemon/LineEncoder.java	Sun Aug 29 17:28:58 2010 +0200
    92.3 @@ -0,0 +1,80 @@
    92.4 +/*
    92.5 + *   SONEWS News Server
    92.6 + *   see AUTHORS for the list of contributors
    92.7 + *
    92.8 + *   This program is free software: you can redistribute it and/or modify
    92.9 + *   it under the terms of the GNU General Public License as published by
   92.10 + *   the Free Software Foundation, either version 3 of the License, or
   92.11 + *   (at your option) any later version.
   92.12 + *
   92.13 + *   This program is distributed in the hope that it will be useful,
   92.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   92.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   92.16 + *   GNU General Public License for more details.
   92.17 + *
   92.18 + *   You should have received a copy of the GNU General Public License
   92.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   92.20 + */
   92.21 +
   92.22 +package org.sonews.daemon;
   92.23 +
   92.24 +import java.nio.ByteBuffer;
   92.25 +import java.nio.CharBuffer;
   92.26 +import java.nio.channels.ClosedChannelException;
   92.27 +import java.nio.charset.Charset;
   92.28 +import java.nio.charset.CharsetEncoder;
   92.29 +import java.nio.charset.CoderResult;
   92.30 +
   92.31 +/**
   92.32 + * Encodes a line to buffers using the correct charset.
   92.33 + * @author Christian Lins
   92.34 + * @since sonews/0.5.0
   92.35 + */
   92.36 +class LineEncoder
   92.37 +{
   92.38 +
   92.39 +  private CharBuffer    characters;
   92.40 +  private Charset       charset;
   92.41 +  
   92.42 +  /**
   92.43 +   * Constructs new LineEncoder.
   92.44 +   * @param characters
   92.45 +   * @param charset
   92.46 +   */
   92.47 +  public LineEncoder(CharBuffer characters, Charset charset)
   92.48 +  {
   92.49 +    this.characters = characters;
   92.50 +    this.charset    = charset;
   92.51 +  }
   92.52 +  
   92.53 +  /**
   92.54 +   * Encodes the characters of this instance to the given ChannelLineBuffers
   92.55 +   * using the Charset of this instance.
   92.56 +   * @param buffer
   92.57 +   * @throws java.nio.channels.ClosedChannelException
   92.58 +   */
   92.59 +  public void encode(ChannelLineBuffers buffer)
   92.60 +    throws ClosedChannelException
   92.61 +  {
   92.62 +    CharsetEncoder encoder = charset.newEncoder();
   92.63 +    while (characters.hasRemaining())
   92.64 +    {
   92.65 +      ByteBuffer buf = ChannelLineBuffers.newLineBuffer();
   92.66 +      assert buf.position() == 0;
   92.67 +      assert buf.capacity() >= 512;
   92.68 +
   92.69 +      CoderResult res = encoder.encode(characters, buf, true);
   92.70 +
   92.71 +      // Set limit to current position and current position to 0;
   92.72 +      // means make ready for read from buffer
   92.73 +      buf.flip();
   92.74 +      buffer.addOutputBuffer(buf);
   92.75 +
   92.76 +      if (res.isUnderflow()) // All input processed
   92.77 +      {
   92.78 +        break;
   92.79 +      }
   92.80 +    }
   92.81 +  }
   92.82 +  
   92.83 +}
    93.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    93.2 +++ b/src/org/sonews/daemon/NNTPConnection.java	Sun Aug 29 17:28:58 2010 +0200
    93.3 @@ -0,0 +1,428 @@
    93.4 +/*
    93.5 + *   SONEWS News Server
    93.6 + *   see AUTHORS for the list of contributors
    93.7 + *
    93.8 + *   This program is free software: you can redistribute it and/or modify
    93.9 + *   it under the terms of the GNU General Public License as published by
   93.10 + *   the Free Software Foundation, either version 3 of the License, or
   93.11 + *   (at your option) any later version.
   93.12 + *
   93.13 + *   This program is distributed in the hope that it will be useful,
   93.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   93.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   93.16 + *   GNU General Public License for more details.
   93.17 + *
   93.18 + *   You should have received a copy of the GNU General Public License
   93.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   93.20 + */
   93.21 +
   93.22 +package org.sonews.daemon;
   93.23 +
   93.24 +import java.io.IOException;
   93.25 +import java.net.InetSocketAddress;
   93.26 +import java.net.SocketException;
   93.27 +import java.nio.ByteBuffer;
   93.28 +import java.nio.CharBuffer;
   93.29 +import java.nio.channels.ClosedChannelException;
   93.30 +import java.nio.channels.SelectionKey;
   93.31 +import java.nio.channels.SocketChannel;
   93.32 +import java.nio.charset.Charset;
   93.33 +import java.util.Arrays;
   93.34 +import java.util.Timer;
   93.35 +import java.util.TimerTask;
   93.36 +import org.sonews.daemon.command.Command;
   93.37 +import org.sonews.storage.Article;
   93.38 +import org.sonews.storage.Channel;
   93.39 +import org.sonews.storage.StorageBackendException;
   93.40 +import org.sonews.util.Log;
   93.41 +import org.sonews.util.Stats;
   93.42 +
   93.43 +/**
   93.44 + * For every SocketChannel (so TCP/IP connection) there is an instance of
   93.45 + * this class.
   93.46 + * @author Christian Lins
   93.47 + * @since sonews/0.5.0
   93.48 + */
   93.49 +public final class NNTPConnection
   93.50 +{
   93.51 +
   93.52 +  public static final String NEWLINE            = "\r\n";    // RFC defines this as newline
   93.53 +  public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
   93.54 +  
   93.55 +  private static final Timer cancelTimer = new Timer(true); // Thread-safe? True for run as daemon
   93.56 +  
   93.57 +  /** SocketChannel is generally thread-safe */
   93.58 +  private SocketChannel   channel        = null;
   93.59 +  private Charset         charset        = Charset.forName("UTF-8");
   93.60 +  private Command         command        = null;
   93.61 +  private Article         currentArticle = null;
   93.62 +  private Channel         currentGroup   = null;
   93.63 +  private volatile long   lastActivity   = System.currentTimeMillis();
   93.64 +  private ChannelLineBuffers lineBuffers = new ChannelLineBuffers();
   93.65 +  private int             readLock       = 0;
   93.66 +  private final Object    readLockGate   = new Object();
   93.67 +  private SelectionKey    writeSelKey    = null;
   93.68 +  
   93.69 +  public NNTPConnection(final SocketChannel channel)
   93.70 +    throws IOException
   93.71 +  {
   93.72 +    if(channel == null)
   93.73 +    {
   93.74 +      throw new IllegalArgumentException("channel is null");
   93.75 +    }
   93.76 +
   93.77 +    this.channel = channel;
   93.78 +    Stats.getInstance().clientConnect();
   93.79 +  }
   93.80 +  
   93.81 +  /**
   93.82 +   * Tries to get the read lock for this NNTPConnection. This method is Thread-
   93.83 +   * safe and returns true of the read lock was successfully set. If the lock
   93.84 +   * is still hold by another Thread the method returns false.
   93.85 +   */
   93.86 +  boolean tryReadLock()
   93.87 +  {
   93.88 +    // As synchronizing simple types may cause deadlocks,
   93.89 +    // we use a gate object.
   93.90 +    synchronized(readLockGate)
   93.91 +    {
   93.92 +      if(readLock != 0)
   93.93 +      {
   93.94 +        return false;
   93.95 +      }
   93.96 +      else
   93.97 +      {
   93.98 +        readLock = Thread.currentThread().hashCode();
   93.99 +        return true;
  93.100 +      }
  93.101 +    }
  93.102 +  }
  93.103 +  
  93.104 +  /**
  93.105 +   * Releases the read lock in a Thread-safe way.
  93.106 +   * @throws IllegalMonitorStateException if a Thread not holding the lock
  93.107 +   * tries to release it.
  93.108 +   */
  93.109 +  void unlockReadLock()
  93.110 +  {
  93.111 +    synchronized(readLockGate)
  93.112 +    {
  93.113 +      if(readLock == Thread.currentThread().hashCode())
  93.114 +      {
  93.115 +        readLock = 0;
  93.116 +      }
  93.117 +      else
  93.118 +      {
  93.119 +        throw new IllegalMonitorStateException();
  93.120 +      }
  93.121 +    }
  93.122 +  }
  93.123 +  
  93.124 +  /**
  93.125 +   * @return Current input buffer of this NNTPConnection instance.
  93.126 +   */
  93.127 +  public ByteBuffer getInputBuffer()
  93.128 +  {
  93.129 +    return this.lineBuffers.getInputBuffer();
  93.130 +  }
  93.131 +  
  93.132 +  /**
  93.133 +   * @return Output buffer of this NNTPConnection which has at least one byte
  93.134 +   * free storage.
  93.135 +   */
  93.136 +  public ByteBuffer getOutputBuffer()
  93.137 +  {
  93.138 +    return this.lineBuffers.getOutputBuffer();
  93.139 +  }
  93.140 +  
  93.141 +  /**
  93.142 +   * @return ChannelLineBuffers instance associated with this NNTPConnection.
  93.143 +   */
  93.144 +  public ChannelLineBuffers getBuffers()
  93.145 +  {
  93.146 +    return this.lineBuffers;
  93.147 +  }
  93.148 +  
  93.149 +  /**
  93.150 +   * @return true if this connection comes from a local remote address.
  93.151 +   */
  93.152 +  public boolean isLocalConnection()
  93.153 +  {
  93.154 +    return ((InetSocketAddress)this.channel.socket().getRemoteSocketAddress())
  93.155 +      .getHostName().equalsIgnoreCase("localhost");
  93.156 +  }
  93.157 +
  93.158 +  void setWriteSelectionKey(SelectionKey selKey)
  93.159 +  {
  93.160 +    this.writeSelKey = selKey;
  93.161 +  }
  93.162 +
  93.163 +  public void shutdownInput()
  93.164 +  {
  93.165 +    try
  93.166 +    {
  93.167 +      // Closes the input line of the channel's socket, so no new data
  93.168 +      // will be received and a timeout can be triggered.
  93.169 +      this.channel.socket().shutdownInput();
  93.170 +    }
  93.171 +    catch(IOException ex)
  93.172 +    {
  93.173 +      Log.get().warning("Exception in NNTPConnection.shutdownInput(): " + ex);
  93.174 +    }
  93.175 +  }
  93.176 +  
  93.177 +  public void shutdownOutput()
  93.178 +  {
  93.179 +    cancelTimer.schedule(new TimerTask() 
  93.180 +    {
  93.181 +      @Override
  93.182 +      public void run()
  93.183 +      {
  93.184 +        try
  93.185 +        {
  93.186 +          // Closes the output line of the channel's socket.
  93.187 +          channel.socket().shutdownOutput();
  93.188 +          channel.close();
  93.189 +        }
  93.190 +        catch(SocketException ex)
  93.191 +        {
  93.192 +          // Socket was already disconnected
  93.193 +          Log.get().info("NNTPConnection.shutdownOutput(): " + ex);
  93.194 +        }
  93.195 +        catch(Exception ex)
  93.196 +        {
  93.197 +          Log.get().warning("NNTPConnection.shutdownOutput(): " + ex);
  93.198 +        }
  93.199 +      }
  93.200 +    }, 3000);
  93.201 +  }
  93.202 +  
  93.203 +  public SocketChannel getSocketChannel()
  93.204 +  {
  93.205 +    return this.channel;
  93.206 +  }
  93.207 +  
  93.208 +  public Article getCurrentArticle()
  93.209 +  {
  93.210 +    return this.currentArticle;
  93.211 +  }
  93.212 +  
  93.213 +  public Charset getCurrentCharset()
  93.214 +  {
  93.215 +    return this.charset;
  93.216 +  }
  93.217 +
  93.218 +  /**
  93.219 +   * @return The currently selected communication channel (not SocketChannel)
  93.220 +   */
  93.221 +  public Channel getCurrentChannel()
  93.222 +  {
  93.223 +    return this.currentGroup;
  93.224 +  }
  93.225 +  
  93.226 +  public void setCurrentArticle(final Article article)
  93.227 +  {
  93.228 +    this.currentArticle = article;
  93.229 +  }
  93.230 +  
  93.231 +  public void setCurrentGroup(final Channel group)
  93.232 +  {
  93.233 +    this.currentGroup = group;
  93.234 +  }
  93.235 +  
  93.236 +  public long getLastActivity()
  93.237 +  {
  93.238 +    return this.lastActivity;
  93.239 +  }
  93.240 +  
  93.241 +  /**
  93.242 +   * Due to the readLockGate there is no need to synchronize this method.
  93.243 +   * @param raw
  93.244 +   * @throws IllegalArgumentException if raw is null.
  93.245 +   * @throws IllegalStateException if calling thread does not own the readLock.
  93.246 +   */
  93.247 +  void lineReceived(byte[] raw)
  93.248 +  {
  93.249 +    if(raw == null)
  93.250 +    {
  93.251 +      throw new IllegalArgumentException("raw is null");
  93.252 +    }
  93.253 +    
  93.254 +    if(readLock == 0 || readLock != Thread.currentThread().hashCode())
  93.255 +    {
  93.256 +      throw new IllegalStateException("readLock not properly set");
  93.257 +    }
  93.258 +
  93.259 +    this.lastActivity = System.currentTimeMillis();
  93.260 +    
  93.261 +    String line = new String(raw, this.charset);
  93.262 +    
  93.263 +    // There might be a trailing \r, but trim() is a bad idea
  93.264 +    // as it removes also leading spaces from long header lines.
  93.265 +    if(line.endsWith("\r"))
  93.266 +    {
  93.267 +      line = line.substring(0, line.length() - 1);
  93.268 +      raw  = Arrays.copyOf(raw, raw.length - 1);
  93.269 +    }
  93.270 +    
  93.271 +    Log.get().fine("<< " + line);
  93.272 +    
  93.273 +    if(command == null)
  93.274 +    {
  93.275 +      command = parseCommandLine(line);
  93.276 +      assert command != null;
  93.277 +    }
  93.278 +
  93.279 +    try
  93.280 +    {
  93.281 +      // The command object will process the line we just received
  93.282 +      try
  93.283 +      {
  93.284 +        command.processLine(this, line, raw);
  93.285 +      }
  93.286 +      catch(StorageBackendException ex)
  93.287 +      {
  93.288 +        Log.get().info("Retry command processing after StorageBackendException");
  93.289 +
  93.290 +        // Try it a second time, so that the backend has time to recover
  93.291 +        command.processLine(this, line, raw);
  93.292 +      }
  93.293 +    }
  93.294 +    catch(ClosedChannelException ex0)
  93.295 +    {
  93.296 +      try
  93.297 +      {
  93.298 +        Log.get().info("Connection to " + channel.socket().getRemoteSocketAddress()
  93.299 +            + " closed: " + ex0);
  93.300 +      }
  93.301 +      catch(Exception ex0a)
  93.302 +      {
  93.303 +        ex0a.printStackTrace();
  93.304 +      }
  93.305 +    }
  93.306 +    catch(Exception ex1) // This will catch a second StorageBackendException
  93.307 +    {
  93.308 +      try
  93.309 +      {
  93.310 +        command = null;
  93.311 +        ex1.printStackTrace();
  93.312 +        println("500 Internal server error");
  93.313 +      }
  93.314 +      catch(Exception ex2)
  93.315 +      {
  93.316 +        ex2.printStackTrace();
  93.317 +      }
  93.318 +    }
  93.319 +
  93.320 +    if(command == null || command.hasFinished())
  93.321 +    {
  93.322 +      command = null;
  93.323 +      charset = Charset.forName("UTF-8"); // Reset to default
  93.324 +    }
  93.325 +  }
  93.326 +  
  93.327 +  /**
  93.328 +   * This method determines the fitting command processing class.
  93.329 +   * @param line
  93.330 +   * @return
  93.331 +   */
  93.332 +  private Command parseCommandLine(String line)
  93.333 +  {
  93.334 +    String cmdStr = line.split(" ")[0];
  93.335 +    return CommandSelector.getInstance().get(cmdStr);
  93.336 +  }
  93.337 +  
  93.338 +  /**
  93.339 +   * Puts the given line into the output buffer, adds a newline character
  93.340 +   * and returns. The method returns immediately and does not block until
  93.341 +   * the line was sent. If line is longer than 510 octets it is split up in
  93.342 +   * several lines. Each line is terminated by \r\n (NNTPConnection.NEWLINE).
  93.343 +   * @param line
  93.344 +   */
  93.345 +  public void println(final CharSequence line, final Charset charset)
  93.346 +    throws IOException
  93.347 +  {    
  93.348 +    writeToChannel(CharBuffer.wrap(line), charset, line);
  93.349 +    writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
  93.350 +  }
  93.351 +
  93.352 +  /**
  93.353 +   * Writes the given raw lines to the output buffers and finishes with
  93.354 +   * a newline character (\r\n).
  93.355 +   * @param rawLines
  93.356 +   */
  93.357 +  public void println(final byte[] rawLines)
  93.358 +    throws IOException
  93.359 +  {
  93.360 +    this.lineBuffers.addOutputBuffer(ByteBuffer.wrap(rawLines));
  93.361 +    writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
  93.362 +  }
  93.363 +  
  93.364 +  /**
  93.365 +   * Encodes the given CharBuffer using the given Charset to a bunch of
  93.366 +   * ByteBuffers (each 512 bytes large) and enqueues them for writing at the
  93.367 +   * connected SocketChannel.
  93.368 +   * @throws java.io.IOException
  93.369 +   */
  93.370 +  private void writeToChannel(CharBuffer characters, final Charset charset,
  93.371 +    CharSequence debugLine)
  93.372 +    throws IOException
  93.373 +  {
  93.374 +    if(!charset.canEncode())
  93.375 +    {
  93.376 +      Log.get().severe("FATAL: Charset " + charset + " cannot encode!");
  93.377 +      return;
  93.378 +    }
  93.379 +    
  93.380 +    // Write characters to output buffers
  93.381 +    LineEncoder lenc = new LineEncoder(characters, charset);
  93.382 +    lenc.encode(lineBuffers);
  93.383 +    
  93.384 +    enableWriteEvents(debugLine);
  93.385 +  }
  93.386 +
  93.387 +  private void enableWriteEvents(CharSequence debugLine)
  93.388 +  {
  93.389 +    // Enable OP_WRITE events so that the buffers are processed
  93.390 +    try
  93.391 +    {
  93.392 +      this.writeSelKey.interestOps(SelectionKey.OP_WRITE);
  93.393 +      ChannelWriter.getInstance().getSelector().wakeup();
  93.394 +    }
  93.395 +    catch(Exception ex) // CancelledKeyException and ChannelCloseException
  93.396 +    {
  93.397 +      Log.get().warning("NNTPConnection.writeToChannel(): " + ex);
  93.398 +      return;
  93.399 +    }
  93.400 +
  93.401 +    // Update last activity timestamp
  93.402 +    this.lastActivity = System.currentTimeMillis();
  93.403 +    if(debugLine != null)
  93.404 +    {
  93.405 +      Log.get().fine(">> " + debugLine);
  93.406 +    }
  93.407 +  }
  93.408 +  
  93.409 +  public void println(final CharSequence line)
  93.410 +    throws IOException
  93.411 +  {
  93.412 +    println(line, charset);
  93.413 +  }
  93.414 +  
  93.415 +  public void print(final String line)
  93.416 +    throws IOException
  93.417 +  {
  93.418 +    writeToChannel(CharBuffer.wrap(line), charset, line);
  93.419 +  }
  93.420 +  
  93.421 +  public void setCurrentCharset(final Charset charset)
  93.422 +  {
  93.423 +    this.charset = charset;
  93.424 +  }
  93.425 +
  93.426 +  void setLastActivity(long timestamp)
  93.427 +  {
  93.428 +    this.lastActivity = timestamp;
  93.429 +  }
  93.430 +  
  93.431 +}
    94.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    94.2 +++ b/src/org/sonews/daemon/NNTPDaemon.java	Sun Aug 29 17:28:58 2010 +0200
    94.3 @@ -0,0 +1,197 @@
    94.4 +/*
    94.5 + *   SONEWS News Server
    94.6 + *   see AUTHORS for the list of contributors
    94.7 + *
    94.8 + *   This program is free software: you can redistribute it and/or modify
    94.9 + *   it under the terms of the GNU General Public License as published by
   94.10 + *   the Free Software Foundation, either version 3 of the License, or
   94.11 + *   (at your option) any later version.
   94.12 + *
   94.13 + *   This program is distributed in the hope that it will be useful,
   94.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   94.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   94.16 + *   GNU General Public License for more details.
   94.17 + *
   94.18 + *   You should have received a copy of the GNU General Public License
   94.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   94.20 + */
   94.21 +
   94.22 +package org.sonews.daemon;
   94.23 +
   94.24 +import org.sonews.config.Config;
   94.25 +import org.sonews.Main;
   94.26 +import org.sonews.util.Log;
   94.27 +import java.io.IOException;
   94.28 +import java.net.BindException;
   94.29 +import java.net.InetSocketAddress;
   94.30 +import java.net.ServerSocket;
   94.31 +import java.nio.channels.CancelledKeyException;
   94.32 +import java.nio.channels.ClosedChannelException;
   94.33 +import java.nio.channels.SelectionKey;
   94.34 +import java.nio.channels.Selector;
   94.35 +import java.nio.channels.ServerSocketChannel;
   94.36 +import java.nio.channels.SocketChannel;
   94.37 +
   94.38 +/**
   94.39 + * NNTP daemon using SelectableChannels.
   94.40 + * @author Christian Lins
   94.41 + * @since sonews/0.5.0
   94.42 + */
   94.43 +public final class NNTPDaemon extends AbstractDaemon
   94.44 +{
   94.45 +
   94.46 +  public static final Object RegisterGate = new Object();
   94.47 +  
   94.48 +  private static NNTPDaemon instance = null;
   94.49 +  
   94.50 +  public static synchronized NNTPDaemon createInstance(int port)
   94.51 +  {
   94.52 +    if(instance == null)
   94.53 +    {
   94.54 +      instance = new NNTPDaemon(port);
   94.55 +      return instance;
   94.56 +    }
   94.57 +    else
   94.58 +    {
   94.59 +      throw new RuntimeException("NNTPDaemon.createInstance() called twice");
   94.60 +    }
   94.61 +  }
   94.62 +  
   94.63 +  private int port;
   94.64 +  
   94.65 +  private NNTPDaemon(final int port)
   94.66 +  {
   94.67 +    Log.get().info("Server listening on port " + port);
   94.68 +    this.port = port;
   94.69 +  }
   94.70 +
   94.71 +  @Override
   94.72 +  public void run()
   94.73 +  {
   94.74 +    try
   94.75 +    {
   94.76 +      // Create a Selector that handles the SocketChannel multiplexing
   94.77 +      final Selector readSelector  = Selector.open();
   94.78 +      final Selector writeSelector = Selector.open();
   94.79 +      
   94.80 +      // Start working threads
   94.81 +      final int workerThreads = Runtime.getRuntime().availableProcessors() * 4;
   94.82 +      ConnectionWorker[] cworkers = new ConnectionWorker[workerThreads];
   94.83 +      for(int n = 0; n < workerThreads; n++)
   94.84 +      {
   94.85 +        cworkers[n] = new ConnectionWorker();
   94.86 +        cworkers[n].start();
   94.87 +      }
   94.88 +      
   94.89 +      ChannelWriter.getInstance().setSelector(writeSelector);
   94.90 +      ChannelReader.getInstance().setSelector(readSelector);
   94.91 +      ChannelWriter.getInstance().start();
   94.92 +      ChannelReader.getInstance().start();
   94.93 +      
   94.94 +      final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
   94.95 +      serverSocketChannel.configureBlocking(true);  // Set to blocking mode
   94.96 +      
   94.97 +      // Configure ServerSocket; bind to socket...
   94.98 +      final ServerSocket serverSocket = serverSocketChannel.socket();
   94.99 +      serverSocket.bind(new InetSocketAddress(this.port));
  94.100 +      
  94.101 +      while(isRunning())
  94.102 +      {
  94.103 +        SocketChannel socketChannel;
  94.104 +        
  94.105 +        try
  94.106 +        {
  94.107 +          // As we set the server socket channel to blocking mode the accept()
  94.108 +          // method will block.
  94.109 +          socketChannel = serverSocketChannel.accept();
  94.110 +          socketChannel.configureBlocking(false);
  94.111 +          assert socketChannel.isConnected();
  94.112 +          assert socketChannel.finishConnect();
  94.113 +        }
  94.114 +        catch(IOException ex)
  94.115 +        {
  94.116 +          // Under heavy load an IOException "Too many open files may
  94.117 +          // be thrown. It most cases we should slow down the connection
  94.118 +          // accepting, to give the worker threads some time to process work.
  94.119 +          Log.get().severe("IOException while accepting connection: " + ex.getMessage());
  94.120 +          Log.get().info("Connection accepting sleeping for seconds...");
  94.121 +          Thread.sleep(5000); // 5 seconds
  94.122 +          continue;
  94.123 +        }
  94.124 +        
  94.125 +        final NNTPConnection conn;
  94.126 +        try
  94.127 +        {
  94.128 +          conn = new NNTPConnection(socketChannel);
  94.129 +          Connections.getInstance().add(conn);
  94.130 +        }
  94.131 +        catch(IOException ex)
  94.132 +        {
  94.133 +          Log.get().warning(ex.toString());
  94.134 +          socketChannel.close();
  94.135 +          continue;
  94.136 +        }
  94.137 +        
  94.138 +        try
  94.139 +        {
  94.140 +          SelectionKey selKeyWrite =
  94.141 +            registerSelector(writeSelector, socketChannel, SelectionKey.OP_WRITE);
  94.142 +          registerSelector(readSelector, socketChannel, SelectionKey.OP_READ);
  94.143 +          
  94.144 +          Log.get().info("Connected: " + socketChannel.socket().getRemoteSocketAddress());
  94.145 +
  94.146 +          // Set write selection key and send hello to client
  94.147 +          conn.setWriteSelectionKey(selKeyWrite);
  94.148 +          conn.println("200 " + Config.inst().get(Config.HOSTNAME, "localhost")
  94.149 +              + " " + Main.VERSION + " news server ready - (posting ok).");
  94.150 +        }
  94.151 +        catch(CancelledKeyException cke)
  94.152 +        {
  94.153 +          Log.get().warning("CancelledKeyException " + cke.getMessage() + " was thrown: "
  94.154 +            + socketChannel.socket());
  94.155 +        }
  94.156 +        catch(ClosedChannelException cce)
  94.157 +        {
  94.158 +          Log.get().warning("ClosedChannelException " + cce.getMessage() + " was thrown: "
  94.159 +            + socketChannel.socket());
  94.160 +        }
  94.161 +      }
  94.162 +    }
  94.163 +    catch(BindException ex)
  94.164 +    {
  94.165 +      // Could not bind to socket; this is a fatal problem; so perform shutdown
  94.166 +      ex.printStackTrace();
  94.167 +      System.exit(1);
  94.168 +    }
  94.169 +    catch(IOException ex)
  94.170 +    {
  94.171 +      ex.printStackTrace();
  94.172 +    }
  94.173 +    catch(Exception ex)
  94.174 +    {
  94.175 +      ex.printStackTrace();
  94.176 +    }
  94.177 +  }
  94.178 +  
  94.179 +  public static SelectionKey registerSelector(final Selector selector,
  94.180 +    final SocketChannel channel, final int op)
  94.181 +    throws CancelledKeyException, ClosedChannelException
  94.182 +  {
  94.183 +    // Register the selector at the channel, so that it will be notified
  94.184 +    // on the socket's events
  94.185 +    synchronized(RegisterGate)
  94.186 +    {
  94.187 +      // Wakeup the currently blocking reader/writer thread; we have locked
  94.188 +      // the RegisterGate to prevent the awakened thread to block again
  94.189 +      selector.wakeup();
  94.190 +      
  94.191 +      // Lock the selector to prevent the waiting worker threads going into
  94.192 +      // selector.select() which would block the selector.
  94.193 +      synchronized (selector)
  94.194 +      {
  94.195 +        return channel.register(selector, op, null);
  94.196 +      }
  94.197 +    }
  94.198 +  }
  94.199 +  
  94.200 +}
    95.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    95.2 +++ b/src/org/sonews/daemon/command/ArticleCommand.java	Sun Aug 29 17:28:58 2010 +0200
    95.3 @@ -0,0 +1,174 @@
    95.4 +/*
    95.5 + *   SONEWS News Server
    95.6 + *   see AUTHORS for the list of contributors
    95.7 + *
    95.8 + *   This program is free software: you can redistribute it and/or modify
    95.9 + *   it under the terms of the GNU General Public License as published by
   95.10 + *   the Free Software Foundation, either version 3 of the License, or
   95.11 + *   (at your option) any later version.
   95.12 + *
   95.13 + *   This program is distributed in the hope that it will be useful,
   95.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   95.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   95.16 + *   GNU General Public License for more details.
   95.17 + *
   95.18 + *   You should have received a copy of the GNU General Public License
   95.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   95.20 + */
   95.21 +
   95.22 +package org.sonews.daemon.command;
   95.23 +
   95.24 +import java.io.IOException;
   95.25 +import org.sonews.storage.Article;
   95.26 +import org.sonews.daemon.NNTPConnection;
   95.27 +import org.sonews.storage.Channel;
   95.28 +import org.sonews.storage.StorageBackendException;
   95.29 +
   95.30 +/**
   95.31 + * Class handling the ARTICLE, BODY and HEAD commands.
   95.32 + * @author Christian Lins
   95.33 + * @author Dennis Schwerdel
   95.34 + * @since n3tpd/0.1
   95.35 + */
   95.36 +public class ArticleCommand implements Command
   95.37 +{
   95.38 +
   95.39 +  @Override
   95.40 +  public String[] getSupportedCommandStrings()
   95.41 +  {
   95.42 +    return new String[] {"ARTICLE", "BODY", "HEAD"};
   95.43 +  }
   95.44 +
   95.45 +  @Override
   95.46 +  public boolean hasFinished()
   95.47 +  {
   95.48 +    return true;
   95.49 +  }
   95.50 +
   95.51 +  @Override
   95.52 +  public String impliedCapability()
   95.53 +  {
   95.54 +    return null;
   95.55 +  }
   95.56 +
   95.57 +  @Override
   95.58 +  public boolean isStateful()
   95.59 +  {
   95.60 +    return false;
   95.61 +  }
   95.62 +
   95.63 +  // TODO: Refactor this method to reduce its complexity!
   95.64 +  @Override
   95.65 +  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   95.66 +    throws IOException
   95.67 +  {
   95.68 +    final String[] command = line.split(" ");
   95.69 +    
   95.70 +    Article article  = null;
   95.71 +    long    artIndex = -1;
   95.72 +    if (command.length == 1)
   95.73 +    {
   95.74 +      article = conn.getCurrentArticle();
   95.75 +      if (article == null)
   95.76 +      {
   95.77 +        conn.println("420 no current article has been selected");
   95.78 +        return;
   95.79 +      }
   95.80 +    }
   95.81 +    else if (command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN))
   95.82 +    {
   95.83 +      // Message-ID
   95.84 +      article = Article.getByMessageID(command[1]);
   95.85 +      if (article == null)
   95.86 +      {
   95.87 +        conn.println("430 no such article found");
   95.88 +        return;
   95.89 +      }
   95.90 +    }
   95.91 +    else
   95.92 +    {
   95.93 +      // Message Number
   95.94 +      try
   95.95 +      {
   95.96 +        Channel currentGroup = conn.getCurrentChannel();
   95.97 +        if(currentGroup == null)
   95.98 +        {
   95.99 +          conn.println("400 no group selected");
  95.100 +          return;
  95.101 +        }
  95.102 +        
  95.103 +        artIndex = Long.parseLong(command[1]);
  95.104 +        article  = currentGroup.getArticle(artIndex);
  95.105 +      }
  95.106 +      catch(NumberFormatException ex)
  95.107 +      {
  95.108 +        ex.printStackTrace();
  95.109 +      }
  95.110 +      catch(StorageBackendException ex)
  95.111 +      {
  95.112 +        ex.printStackTrace();
  95.113 +      }
  95.114 +
  95.115 +      if (article == null)
  95.116 +      {
  95.117 +        conn.println("423 no such article number in this group");
  95.118 +        return;
  95.119 +      }
  95.120 +      conn.setCurrentArticle(article);
  95.121 +    }
  95.122 +
  95.123 +    if(command[0].equalsIgnoreCase("ARTICLE"))
  95.124 +    {
  95.125 +      conn.println("220 " + artIndex + " " + article.getMessageID()
  95.126 +          + " article retrieved - head and body follow");
  95.127 +      conn.println(article.getHeaderSource());
  95.128 +      conn.println("");
  95.129 +      conn.println(article.getBody());
  95.130 +      conn.println(".");
  95.131 +    }
  95.132 +    else if(command[0].equalsIgnoreCase("BODY"))
  95.133 +    {
  95.134 +      conn.println("222 " + artIndex + " " + article.getMessageID() + " body");
  95.135 +      conn.println(article.getBody());
  95.136 +      conn.println(".");
  95.137 +    }
  95.138 +    
  95.139 +    /*
  95.140 +     * HEAD: This command is mandatory.
  95.141 +     *
  95.142 +     * Syntax
  95.143 +     *    HEAD message-id
  95.144 +     *    HEAD number
  95.145 +     *    HEAD
  95.146 +     *
  95.147 +     * Responses
  95.148 +     *
  95.149 +     * First form (message-id specified)
  95.150 +     *  221 0|n message-id    Headers follow (multi-line)
  95.151 +     *  430                   No article with that message-id
  95.152 +     *
  95.153 +     * Second form (article number specified)
  95.154 +     *  221 n message-id      Headers follow (multi-line)
  95.155 +     *  412                   No newsgroup selected
  95.156 +     *  423                   No article with that number
  95.157 +     *
  95.158 +     * Third form (current article number used)
  95.159 +     *  221 n message-id      Headers follow (multi-line)
  95.160 +     *  412                   No newsgroup selected
  95.161 +     *  420                   Current article number is invalid
  95.162 +     *
  95.163 +     * Parameters
  95.164 +     *  number        Requested article number
  95.165 +     *  n             Returned article number
  95.166 +     *  message-id    Article message-id
  95.167 +     */
  95.168 +    else if(command[0].equalsIgnoreCase("HEAD"))
  95.169 +    {
  95.170 +      conn.println("221 " + artIndex + " " + article.getMessageID()
  95.171 +          + " Headers follow (multi-line)");
  95.172 +      conn.println(article.getHeaderSource());
  95.173 +      conn.println(".");
  95.174 +    }
  95.175 +  }  
  95.176 +  
  95.177 +}
    96.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    96.2 +++ b/src/org/sonews/daemon/command/CapabilitiesCommand.java	Sun Aug 29 17:28:58 2010 +0200
    96.3 @@ -0,0 +1,93 @@
    96.4 +/*
    96.5 + *   SONEWS News Server
    96.6 + *   see AUTHORS for the list of contributors
    96.7 + *
    96.8 + *   This program is free software: you can redistribute it and/or modify
    96.9 + *   it under the terms of the GNU General Public License as published by
   96.10 + *   the Free Software Foundation, either version 3 of the License, or
   96.11 + *   (at your option) any later version.
   96.12 + *
   96.13 + *   This program is distributed in the hope that it will be useful,
   96.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   96.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   96.16 + *   GNU General Public License for more details.
   96.17 + *
   96.18 + *   You should have received a copy of the GNU General Public License
   96.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   96.20 + */
   96.21 +
   96.22 +package org.sonews.daemon.command;
   96.23 +
   96.24 +import java.io.IOException;
   96.25 +import org.sonews.daemon.NNTPConnection;
   96.26 +
   96.27 +/**
   96.28 + * <pre>
   96.29 + *  The CAPABILITIES command allows a client to determine the
   96.30 + *  capabilities of the server at any given time.
   96.31 + *
   96.32 + *  This command MAY be issued at any time; the server MUST NOT require
   96.33 + *  it to be issued in order to make use of any capability. The response
   96.34 + *  generated by this command MAY change during a session because of
   96.35 + *  other state information (which, in turn, may be changed by the
   96.36 + *  effects of other commands or by external events).  An NNTP client is
   96.37 + *  only able to get the current and correct information concerning
   96.38 + *  available capabilities at any point during a session by issuing a
   96.39 + *  CAPABILITIES command at that point of that session and processing the
   96.40 + *  response.
   96.41 + * </pre>
   96.42 + * @author Christian Lins
   96.43 + * @since sonews/0.5.0
   96.44 + */
   96.45 +public class CapabilitiesCommand implements Command
   96.46 +{
   96.47 +
   96.48 +  static final String[] CAPABILITIES = new String[]
   96.49 +    {
   96.50 +      "VERSION 2", // MUST be the first one; VERSION 2 refers to RFC3977
   96.51 +      "READER",    // Server implements commands for reading
   96.52 +      "POST",      // Server implements POST command
   96.53 +      "OVER"       // Server implements OVER command
   96.54 +    };
   96.55 +
   96.56 +  @Override
   96.57 +  public String[] getSupportedCommandStrings()
   96.58 +  {
   96.59 +    return new String[] {"CAPABILITIES"};
   96.60 +  }
   96.61 +
   96.62 +  /**
   96.63 +   * First called after one call to processLine().
   96.64 +   * @return
   96.65 +   */
   96.66 +  @Override
   96.67 +  public boolean hasFinished()
   96.68 +  {
   96.69 +    return true;
   96.70 +  }
   96.71 +
   96.72 +  @Override
   96.73 +  public String impliedCapability()
   96.74 +  {
   96.75 +    return null;
   96.76 +  }
   96.77 +  
   96.78 +  @Override
   96.79 +  public boolean isStateful()
   96.80 +  {
   96.81 +    return false;
   96.82 +  }
   96.83 +
   96.84 +  @Override
   96.85 +  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   96.86 +    throws IOException
   96.87 +  {
   96.88 +    conn.println("101 Capabilities list:");
   96.89 +    for(String cap : CAPABILITIES)
   96.90 +    {
   96.91 +      conn.println(cap);
   96.92 +    }
   96.93 +    conn.println(".");
   96.94 +  }
   96.95 +
   96.96 +}
    97.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    97.2 +++ b/src/org/sonews/daemon/command/Command.java	Sun Aug 29 17:28:58 2010 +0200
    97.3 @@ -0,0 +1,51 @@
    97.4 +/*
    97.5 + *   SONEWS News Server
    97.6 + *   see AUTHORS for the list of contributors
    97.7 + *
    97.8 + *   This program is free software: you can redistribute it and/or modify
    97.9 + *   it under the terms of the GNU General Public License as published by
   97.10 + *   the Free Software Foundation, either version 3 of the License, or
   97.11 + *   (at your option) any later version.
   97.12 + *
   97.13 + *   This program is distributed in the hope that it will be useful,
   97.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   97.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   97.16 + *   GNU General Public License for more details.
   97.17 + *
   97.18 + *   You should have received a copy of the GNU General Public License
   97.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   97.20 + */
   97.21 +
   97.22 +package org.sonews.daemon.command;
   97.23 +
   97.24 +import java.io.IOException;
   97.25 +import org.sonews.daemon.NNTPConnection;
   97.26 +import org.sonews.storage.StorageBackendException;
   97.27 +
   97.28 +/**
   97.29 + * Interface for pluggable NNTP commands handling classes.
   97.30 + * @author Christian Lins
   97.31 + * @since sonews/0.6.0
   97.32 + */
   97.33 +public interface Command
   97.34 +{
   97.35 +
   97.36 +  /**
   97.37 +   * @return true if this instance can be reused.
   97.38 +   */
   97.39 +  boolean hasFinished();
   97.40 +
   97.41 +  /**
   97.42 +   * Returns capability string that is implied by this command class.
   97.43 +   * MAY return null if the command is required by the NNTP standard.
   97.44 +   */
   97.45 +  String impliedCapability();
   97.46 +
   97.47 +  boolean isStateful();
   97.48 +
   97.49 +  String[] getSupportedCommandStrings();
   97.50 +
   97.51 +  void processLine(NNTPConnection conn, String line, byte[] rawLine)
   97.52 +    throws IOException, StorageBackendException;
   97.53 +
   97.54 +}
    98.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    98.2 +++ b/src/org/sonews/daemon/command/GroupCommand.java	Sun Aug 29 17:28:58 2010 +0200
    98.3 @@ -0,0 +1,102 @@
    98.4 +/*
    98.5 + *   SONEWS News Server
    98.6 + *   see AUTHORS for the list of contributors
    98.7 + *
    98.8 + *   This program is free software: you can redistribute it and/or modify
    98.9 + *   it under the terms of the GNU General Public License as published by
   98.10 + *   the Free Software Foundation, either version 3 of the License, or
   98.11 + *   (at your option) any later version.
   98.12 + *
   98.13 + *   This program is distributed in the hope that it will be useful,
   98.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   98.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   98.16 + *   GNU General Public License for more details.
   98.17 + *
   98.18 + *   You should have received a copy of the GNU General Public License
   98.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   98.20 + */
   98.21 +
   98.22 +package org.sonews.daemon.command;
   98.23 +
   98.24 +import java.io.IOException;
   98.25 +import org.sonews.daemon.NNTPConnection;
   98.26 +import org.sonews.storage.Channel;
   98.27 +import org.sonews.storage.StorageBackendException;
   98.28 +
   98.29 +/**
   98.30 + * Class handling the GROUP command.
   98.31 + * <pre>
   98.32 + *  Syntax
   98.33 + *    GROUP group
   98.34 + *
   98.35 + *  Responses
   98.36 + *    211 number low high group     Group successfully selected
   98.37 + *    411                           No such newsgroup
   98.38 + *
   98.39 + *  Parameters
   98.40 + *    group     Name of newsgroup
   98.41 + *    number    Estimated number of articles in the group
   98.42 + *    low       Reported low water mark
   98.43 + *    high      Reported high water mark
   98.44 + * </pre>
   98.45 + * (from RFC 3977)
   98.46 + * 
   98.47 + * @author Christian Lins
   98.48 + * @author Dennis Schwerdel
   98.49 + * @since n3tpd/0.1
   98.50 + */
   98.51 +public class GroupCommand implements Command
   98.52 +{
   98.53 +
   98.54 +  @Override
   98.55 +  public String[] getSupportedCommandStrings()
   98.56 +  {
   98.57 +    return new String[]{"GROUP"};
   98.58 +  }
   98.59 +
   98.60 +  @Override
   98.61 +  public boolean hasFinished()
   98.62 +  {
   98.63 +    return true;
   98.64 +  }
   98.65 +
   98.66 +  @Override
   98.67 +  public String impliedCapability()
   98.68 +  {
   98.69 +    return null;
   98.70 +  }
   98.71 +
   98.72 +  @Override
   98.73 +  public boolean isStateful()
   98.74 +  {
   98.75 +    return true;
   98.76 +  }
   98.77 +  
   98.78 +  @Override
   98.79 +  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   98.80 +    throws IOException, StorageBackendException
   98.81 +  {
   98.82 +    final String[] command = line.split(" ");
   98.83 +
   98.84 +    Channel group;
   98.85 +    if(command.length >= 2)
   98.86 +    {
   98.87 +      group = Channel.getByName(command[1]);
   98.88 +      if(group == null || group.isDeleted())
   98.89 +      {
   98.90 +        conn.println("411 no such news group");
   98.91 +      }
   98.92 +      else
   98.93 +      {
   98.94 +        conn.setCurrentGroup(group);
   98.95 +        conn.println("211 " + group.getPostingsCount() + " " + group.getFirstArticleNumber()
   98.96 +          + " " + group.getLastArticleNumber() + " " + group.getName() + " group selected");
   98.97 +      }
   98.98 +    }
   98.99 +    else
  98.100 +    {
  98.101 +      conn.println("500 no group name given");
  98.102 +    }
  98.103 +  }
  98.104 +
  98.105 +}
    99.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    99.2 +++ b/src/org/sonews/daemon/command/HelpCommand.java	Sun Aug 29 17:28:58 2010 +0200
    99.3 @@ -0,0 +1,100 @@
    99.4 +/*
    99.5 + *   SONEWS News Server
    99.6 + *   see AUTHORS for the list of contributors
    99.7 + *
    99.8 + *   This program is free software: you can redistribute it and/or modify
    99.9 + *   it under the terms of the GNU General Public License as published by
   99.10 + *   the Free Software Foundation, either version 3 of the License, or
   99.11 + *   (at your option) any later version.
   99.12 + *
   99.13 + *   This program is distributed in the hope that it will be useful,
   99.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   99.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   99.16 + *   GNU General Public License for more details.
   99.17 + *
   99.18 + *   You should have received a copy of the GNU General Public License
   99.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   99.20 + */
   99.21 +
   99.22 +package org.sonews.daemon.command;
   99.23 +
   99.24 +import java.io.IOException;
   99.25 +import java.util.Set;
   99.26 +import org.sonews.daemon.CommandSelector;
   99.27 +import org.sonews.daemon.NNTPConnection;
   99.28 +import org.sonews.util.io.Resource;
   99.29 +
   99.30 +/**
   99.31 + * This command provides a short summary of the commands that are
   99.32 + * understood by this implementation of the server. The help text will
   99.33 + * be presented as a multi-line data block following the 100 response
   99.34 + * code (taken from RFC).
   99.35 + * @author Christian Lins
   99.36 + * @since sonews/0.5.0
   99.37 + */
   99.38 +public class HelpCommand implements Command
   99.39 +{
   99.40 +
   99.41 +  @Override
   99.42 +  public boolean hasFinished()
   99.43 +  {
   99.44 +    return true;
   99.45 +  }
   99.46 +
   99.47 +  @Override
   99.48 +  public String impliedCapability()
   99.49 +  {
   99.50 +    return null;
   99.51 +  }
   99.52 +
   99.53 +  @Override
   99.54 +  public boolean isStateful()
   99.55 +  {
   99.56 +    return true;
   99.57 +  }
   99.58 +
   99.59 +  @Override
   99.60 +  public String[] getSupportedCommandStrings()
   99.61 +  {
   99.62 +    return new String[]{"HELP"};
   99.63 +  }
   99.64 +  
   99.65 +  @Override
   99.66 +  public void processLine(NNTPConnection conn, final String line, byte[] raw)
   99.67 +    throws IOException
   99.68 +  {
   99.69 +    final String[] command = line.split(" ");
   99.70 +    conn.println("100 help text follows");
   99.71 +
   99.72 +    if(line.length() <= 1)
   99.73 +    {
   99.74 +      final String[] help = Resource
   99.75 +        .getAsString("helpers/helptext", true).split("\n");
   99.76 +      for(String hstr : help)
   99.77 +      {
   99.78 +        conn.println(hstr);
   99.79 +      }
   99.80 +
   99.81 +      Set<String> commandNames = CommandSelector.getCommandNames();
   99.82 +      for(String cmdName : commandNames)
   99.83 +      {
   99.84 +        conn.println(cmdName);
   99.85 +      }
   99.86 +    }
   99.87 +    else
   99.88 +    {
   99.89 +      Command cmd = CommandSelector.getInstance().get(command[1]);
   99.90 +      if(cmd instanceof HelpfulCommand)
   99.91 +      {
   99.92 +        conn.println(((HelpfulCommand)cmd).getHelpString());
   99.93 +      }
   99.94 +      else
   99.95 +      {
   99.96 +        conn.println("No further help information available.");
   99.97 +      }
   99.98 +    }
   99.99 +    
  99.100 +    conn.println(".");
  99.101 +  }
  99.102 +  
  99.103 +}
   100.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   100.2 +++ b/src/org/sonews/daemon/command/HelpfulCommand.java	Sun Aug 29 17:28:58 2010 +0200
   100.3 @@ -0,0 +1,35 @@
   100.4 +/*
   100.5 + *   SONEWS News Server
   100.6 + *   see AUTHORS for the list of contributors
   100.7 + *
   100.8 + *   This program is free software: you can redistribute it and/or modify
   100.9 + *   it under the terms of the GNU General Public License as published by
  100.10 + *   the Free Software Foundation, either version 3 of the License, or
  100.11 + *   (at your option) any later version.
  100.12 + *
  100.13 + *   This program is distributed in the hope that it will be useful,
  100.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  100.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  100.16 + *   GNU General Public License for more details.
  100.17 + *
  100.18 + *   You should have received a copy of the GNU General Public License
  100.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  100.20 + */
  100.21 +
  100.22 +package org.sonews.daemon.command;
  100.23 +
  100.24 +/**
  100.25 + *
  100.26 + * @since sonews/1.1
  100.27 + * @author Christian Lins
  100.28 + */
  100.29 +public interface HelpfulCommand extends Command
  100.30 +{
  100.31 +
  100.32 +  /**
  100.33 +   * @return A short description of this command, that is
  100.34 +   * used within the output of the HELP command.
  100.35 +   */
  100.36 +  String getHelpString();
  100.37 +
  100.38 +}
   101.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   101.2 +++ b/src/org/sonews/daemon/command/ListCommand.java	Sun Aug 29 17:28:58 2010 +0200
   101.3 @@ -0,0 +1,153 @@
   101.4 +/*
   101.5 + *   SONEWS News Server
   101.6 + *   see AUTHORS for the list of contributors
   101.7 + *
   101.8 + *   This program is free software: you can redistribute it and/or modify
   101.9 + *   it under the terms of the GNU General Public License as published by
  101.10 + *   the Free Software Foundation, either version 3 of the License, or
  101.11 + *   (at your option) any later version.
  101.12 + *
  101.13 + *   This program is distributed in the hope that it will be useful,
  101.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  101.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  101.16 + *   GNU General Public License for more details.
  101.17 + *
  101.18 + *   You should have received a copy of the GNU General Public License
  101.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  101.20 + */
  101.21 +
  101.22 +package org.sonews.daemon.command;
  101.23 +
  101.24 +import java.io.IOException;
  101.25 +import java.util.List;
  101.26 +import java.util.regex.Matcher;
  101.27 +import java.util.regex.Pattern;
  101.28 +import java.util.regex.PatternSyntaxException;
  101.29 +import org.sonews.daemon.NNTPConnection;
  101.30 +import org.sonews.storage.Channel;
  101.31 +import org.sonews.storage.StorageBackendException;
  101.32 +import org.sonews.util.Log;
  101.33 +
  101.34 +/**
  101.35 + * Class handling the LIST command.
  101.36 + * @author Christian Lins
  101.37 + * @author Dennis Schwerdel
  101.38 + * @since n3tpd/0.1
  101.39 + */
  101.40 +public class ListCommand implements Command
  101.41 +{
  101.42 +
  101.43 +  @Override
  101.44 +  public String[] getSupportedCommandStrings()
  101.45 +  {
  101.46 +    return new String[]{"LIST"};
  101.47 +  }
  101.48 +
  101.49 +  @Override
  101.50 +  public boolean hasFinished()
  101.51 +  {
  101.52 +    return true;
  101.53 +  }
  101.54 +
  101.55 +  @Override
  101.56 +  public String impliedCapability()
  101.57 +  {
  101.58 +    return null;
  101.59 +  }
  101.60 +
  101.61 +  @Override
  101.62 +  public boolean isStateful()
  101.63 +  {
  101.64 +    return false;
  101.65 +  }
  101.66 +  
  101.67 +  @Override
  101.68 +  public void processLine(NNTPConnection conn, final String line, byte[] raw)
  101.69 +    throws IOException, StorageBackendException
  101.70 +  {
  101.71 +    final String[] command = line.split(" ");
  101.72 +    
  101.73 +    if(command.length >= 2)
  101.74 +    {
  101.75 +      if(command[1].equalsIgnoreCase("OVERVIEW.FMT"))
  101.76 +      {
  101.77 +        conn.println("215 information follows");
  101.78 +        conn.println("Subject:\nFrom:\nDate:\nMessage-ID:\nReferences:\nBytes:\nLines:\nXref");
  101.79 +        conn.println(".");
  101.80 +      }
  101.81 +      else if(command[1].equalsIgnoreCase("NEWSGROUPS"))
  101.82 +      {
  101.83 +        conn.println("215 information follows");
  101.84 +        final List<Channel> list = Channel.getAll();
  101.85 +        for (Channel g : list)
  101.86 +        {
  101.87 +          conn.println(g.getName() + "\t" + "-");
  101.88 +        }
  101.89 +        conn.println(".");
  101.90 +      }
  101.91 +      else if(command[1].equalsIgnoreCase("SUBSCRIPTIONS"))
  101.92 +      {
  101.93 +        conn.println("215 information follows");
  101.94 +        conn.println(".");
  101.95 +      }
  101.96 +      else if(command[1].equalsIgnoreCase("EXTENSIONS"))
  101.97 +      {
  101.98 +        conn.println("202 Supported NNTP extensions.");
  101.99 +        conn.println("LISTGROUP");
 101.100 +        conn.println("XDAEMON");
 101.101 +        conn.println("XPAT");
 101.102 +        conn.println(".");
 101.103 +      }
 101.104 +      else if(command[1].equalsIgnoreCase("ACTIVE"))
 101.105 +      {
 101.106 +        String  pattern  = command.length == 2
 101.107 +          ? null : command[2].replace("*", "\\w*");
 101.108 +        printGroupInfo(conn, pattern);
 101.109 +      }
 101.110 +      else
 101.111 +      {
 101.112 +        conn.println("500 unknown argument to LIST command");
 101.113 +      }
 101.114 +    }
 101.115 +    else
 101.116 +    {
 101.117 +      printGroupInfo(conn, null);
 101.118 +    }
 101.119 +  }
 101.120 +
 101.121 +  private void printGroupInfo(NNTPConnection conn, String pattern)
 101.122 +    throws IOException, StorageBackendException
 101.123 +  {
 101.124 +    final List<Channel> groups = Channel.getAll();
 101.125 +    if(groups != null)
 101.126 +    {
 101.127 +      conn.println("215 list of newsgroups follows");
 101.128 +      for(Channel g : groups)
 101.129 +      {
 101.130 +        try
 101.131 +        {
 101.132 +          Matcher matcher = pattern == null ?
 101.133 +            null : Pattern.compile(pattern).matcher(g.getName());
 101.134 +          if(!g.isDeleted() &&
 101.135 +            (matcher == null || matcher.find()))
 101.136 +          {
 101.137 +            String writeable = g.isWriteable() ? " y" : " n";
 101.138 +            // Indeed first the higher article number then the lower
 101.139 +            conn.println(g.getName() + " " + g.getLastArticleNumber() + " "
 101.140 +              + g.getFirstArticleNumber() + writeable);
 101.141 +          }
 101.142 +        }
 101.143 +        catch(PatternSyntaxException ex)
 101.144 +        {
 101.145 +          Log.get().info(ex.toString());
 101.146 +        }
 101.147 +      }
 101.148 +      conn.println(".");
 101.149 +    }
 101.150 +    else
 101.151 +    {
 101.152 +      conn.println("500 server backend malfunction");
 101.153 +    }
 101.154 +  }
 101.155 +
 101.156 +}
   102.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   102.2 +++ b/src/org/sonews/daemon/command/ListGroupCommand.java	Sun Aug 29 17:28:58 2010 +0200
   102.3 @@ -0,0 +1,94 @@
   102.4 +/*
   102.5 + *   SONEWS News Server
   102.6 + *   see AUTHORS for the list of contributors
   102.7 + *
   102.8 + *   This program is free software: you can redistribute it and/or modify
   102.9 + *   it under the terms of the GNU General Public License as published by
  102.10 + *   the Free Software Foundation, either version 3 of the License, or
  102.11 + *   (at your option) any later version.
  102.12 + *
  102.13 + *   This program is distributed in the hope that it will be useful,
  102.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  102.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  102.16 + *   GNU General Public License for more details.
  102.17 + *
  102.18 + *   You should have received a copy of the GNU General Public License
  102.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  102.20 + */
  102.21 +
  102.22 +package org.sonews.daemon.command;
  102.23 +
  102.24 +import java.io.IOException;
  102.25 +import java.util.List;
  102.26 +import org.sonews.daemon.NNTPConnection;
  102.27 +import org.sonews.storage.Channel;
  102.28 +import org.sonews.storage.StorageBackendException;
  102.29 +
  102.30 +/**
  102.31 + * Class handling the LISTGROUP command.
  102.32 + * @author Christian Lins
  102.33 + * @author Dennis Schwerdel
  102.34 + * @since n3tpd/0.1
  102.35 + */
  102.36 +public class ListGroupCommand implements Command
  102.37 +{
  102.38 +
  102.39 +  @Override
  102.40 +  public String[] getSupportedCommandStrings()
  102.41 +  {
  102.42 +    return new String[]{"LISTGROUP"};
  102.43 +  }
  102.44 +
  102.45 +  @Override
  102.46 +  public boolean hasFinished()
  102.47 +  {
  102.48 +    return true;
  102.49 +  }
  102.50 +
  102.51 +  @Override
  102.52 +  public String impliedCapability()
  102.53 +  {
  102.54 +    return null;
  102.55 +  }
  102.56 +
  102.57 +  @Override
  102.58 +  public boolean isStateful()
  102.59 +  {
  102.60 +    return false;
  102.61 +  }
  102.62 +
  102.63 +  @Override
  102.64 +  public void processLine(NNTPConnection conn, final String commandName, byte[] raw)
  102.65 +    throws IOException, StorageBackendException
  102.66 +  {
  102.67 +    final String[] command = commandName.split(" ");
  102.68 +
  102.69 +    Channel group;
  102.70 +    if(command.length >= 2)
  102.71 +    {
  102.72 +      group = Channel.getByName(command[1]);
  102.73 +    }
  102.74 +    else
  102.75 +    {
  102.76 +      group = conn.getCurrentChannel();
  102.77 +    }
  102.78 +
  102.79 +    if (group == null)
  102.80 +    {
  102.81 +      conn.println("412 no group selected; use GROUP <group> command");
  102.82 +      return;
  102.83 +    }
  102.84 +
  102.85 +    List<Long> ids = group.getArticleNumbers();
  102.86 +    conn.println("211 " + ids.size() + " " +
  102.87 +      group.getFirstArticleNumber() + " " + 
  102.88 +      group.getLastArticleNumber() + " list of article numbers follow");
  102.89 +    for(long id : ids)
  102.90 +    {
  102.91 +      // One index number per line
  102.92 +      conn.println(Long.toString(id));
  102.93 +    }
  102.94 +    conn.println(".");
  102.95 +  }
  102.96 +
  102.97 +}
   103.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   103.2 +++ b/src/org/sonews/daemon/command/ModeReaderCommand.java	Sun Aug 29 17:28:58 2010 +0200
   103.3 @@ -0,0 +1,72 @@
   103.4 +/*
   103.5 + *   SONEWS News Server
   103.6 + *   see AUTHORS for the list of contributors
   103.7 + *
   103.8 + *   This program is free software: you can redistribute it and/or modify
   103.9 + *   it under the terms of the GNU General Public License as published by
  103.10 + *   the Free Software Foundation, either version 3 of the License, or
  103.11 + *   (at your option) any later version.
  103.12 + *
  103.13 + *   This program is distributed in the hope that it will be useful,
  103.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  103.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  103.16 + *   GNU General Public License for more details.
  103.17 + *
  103.18 + *   You should have received a copy of the GNU General Public License
  103.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  103.20 + */
  103.21 +
  103.22 +package org.sonews.daemon.command;
  103.23 +
  103.24 +import java.io.IOException;
  103.25 +import org.sonews.daemon.NNTPConnection;
  103.26 +import org.sonews.storage.StorageBackendException;
  103.27 +
  103.28 +/**
  103.29 + * Class handling the MODE READER command. This command actually does nothing
  103.30 + * but returning a success status code.
  103.31 + * @author Christian Lins
  103.32 + * @since sonews/0.5.0
  103.33 + */
  103.34 +public class ModeReaderCommand implements Command
  103.35 +{
  103.36 +  
  103.37 +  @Override
  103.38 +  public String[] getSupportedCommandStrings()
  103.39 +  {
  103.40 +    return new String[]{"MODE"};
  103.41 +  }
  103.42 +
  103.43 +  @Override
  103.44 +  public boolean hasFinished()
  103.45 +  {
  103.46 +    return true;
  103.47 +  }
  103.48 +
  103.49 +  @Override
  103.50 +  public String impliedCapability()
  103.51 +  {
  103.52 +    return null;
  103.53 +  }
  103.54 +
  103.55 +  @Override
  103.56 +  public boolean isStateful()
  103.57 +  {
  103.58 +    return false;
  103.59 +  }
  103.60 +
  103.61 +  @Override
  103.62 +  public void processLine(NNTPConnection conn, final String line, byte[] raw)
  103.63 +    throws IOException, StorageBackendException
  103.64 +  {
  103.65 +    if(line.equalsIgnoreCase("MODE READER"))
  103.66 +    {
  103.67 +      conn.println("200 hello you can post");
  103.68 +    }
  103.69 +    else
  103.70 +    {
  103.71 +      conn.println("500 I do not know this mode command");
  103.72 +    }
  103.73 +  }
  103.74 +
  103.75 +}
   104.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   104.2 +++ b/src/org/sonews/daemon/command/NewGroupsCommand.java	Sun Aug 29 17:28:58 2010 +0200
   104.3 @@ -0,0 +1,78 @@
   104.4 +/*
   104.5 + *   SONEWS News Server
   104.6 + *   see AUTHORS for the list of contributors
   104.7 + *
   104.8 + *   This program is free software: you can redistribute it and/or modify
   104.9 + *   it under the terms of the GNU General Public License as published by
  104.10 + *   the Free Software Foundation, either version 3 of the License, or
  104.11 + *   (at your option) any later version.
  104.12 + *
  104.13 + *   This program is distributed in the hope that it will be useful,
  104.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  104.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  104.16 + *   GNU General Public License for more details.
  104.17 + *
  104.18 + *   You should have received a copy of the GNU General Public License
  104.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  104.20 + */
  104.21 +
  104.22 +package org.sonews.daemon.command;
  104.23 +
  104.24 +import java.io.IOException;
  104.25 +import org.sonews.daemon.NNTPConnection;
  104.26 +import org.sonews.storage.StorageBackendException;
  104.27 +
  104.28 +/**
  104.29 + * Class handling the NEWGROUPS command.
  104.30 + * @author Christian Lins
  104.31 + * @author Dennis Schwerdel
  104.32 + * @since n3tpd/0.1
  104.33 + */
  104.34 +public class NewGroupsCommand implements Command
  104.35 +{
  104.36 +
  104.37 +  @Override
  104.38 +  public String[] getSupportedCommandStrings()
  104.39 +  {
  104.40 +    return new String[]{"NEWGROUPS"};
  104.41 +  }
  104.42 +
  104.43 +  @Override
  104.44 +  public boolean hasFinished()
  104.45 +  {
  104.46 +    return true;
  104.47 +  }
  104.48 +
  104.49 +  @Override
  104.50 +  public String impliedCapability()
  104.51 +  {
  104.52 +    return null;
  104.53 +  }
  104.54 +
  104.55 +  @Override
  104.56 +  public boolean isStateful()
  104.57 +  {
  104.58 +    return false;
  104.59 +  }
  104.60 +
  104.61 +  @Override
  104.62 +  public void processLine(NNTPConnection conn, final String line, byte[] raw)
  104.63 +    throws IOException, StorageBackendException
  104.64 +  {
  104.65 +    final String[] command = line.split(" ");
  104.66 +
  104.67 +    if(command.length == 3)
  104.68 +    {
  104.69 +      conn.println("231 list of new newsgroups follows");
  104.70 +
  104.71 +      // Currently we do not store a group's creation date;
  104.72 +      // so we return an empty list which is a valid response
  104.73 +      conn.println(".");
  104.74 +    }
  104.75 +    else
  104.76 +    {
  104.77 +      conn.println("500 invalid command usage");
  104.78 +    }
  104.79 +  }
  104.80 +
  104.81 +}
   105.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   105.2 +++ b/src/org/sonews/daemon/command/NextPrevCommand.java	Sun Aug 29 17:28:58 2010 +0200
   105.3 @@ -0,0 +1,116 @@
   105.4 +/*
   105.5 + *   SONEWS News Server
   105.6 + *   see AUTHORS for the list of contributors
   105.7 + *
   105.8 + *   This program is free software: you can redistribute it and/or modify
   105.9 + *   it under the terms of the GNU General Public License as published by
  105.10 + *   the Free Software Foundation, either version 3 of the License, or
  105.11 + *   (at your option) any later version.
  105.12 + *
  105.13 + *   This program is distributed in the hope that it will be useful,
  105.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  105.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  105.16 + *   GNU General Public License for more details.
  105.17 + *
  105.18 + *   You should have received a copy of the GNU General Public License
  105.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  105.20 + */
  105.21 +
  105.22 +package org.sonews.daemon.command;
  105.23 +
  105.24 +import java.io.IOException;
  105.25 +import org.sonews.daemon.NNTPConnection;
  105.26 +import org.sonews.storage.Article;
  105.27 +import org.sonews.storage.Channel;
  105.28 +import org.sonews.storage.StorageBackendException;
  105.29 +
  105.30 +/**
  105.31 + * Class handling the NEXT and LAST command.
  105.32 + * @author Christian Lins
  105.33 + * @author Dennis Schwerdel
  105.34 + * @since n3tpd/0.1
  105.35 + */
  105.36 +public class NextPrevCommand implements Command
  105.37 +{
  105.38 +
  105.39 +  @Override
  105.40 +  public String[] getSupportedCommandStrings()
  105.41 +  {
  105.42 +    return new String[]{"NEXT", "PREV"};
  105.43 +  }
  105.44 +
  105.45 +  @Override
  105.46 +  public boolean hasFinished()
  105.47 +  {
  105.48 +    return true;
  105.49 +  }
  105.50 +
  105.51 +  @Override
  105.52 +  public String impliedCapability()
  105.53 +  {
  105.54 +    return null;
  105.55 +  }
  105.56 +
  105.57 +  @Override
  105.58 +  public boolean isStateful()
  105.59 +  {
  105.60 +    return false;
  105.61 +  }
  105.62 +
  105.63 +  @Override
  105.64 +  public void processLine(NNTPConnection conn, final String line, byte[] raw)
  105.65 +    throws IOException, StorageBackendException
  105.66 +  {
  105.67 +    final Article currA = conn.getCurrentArticle();
  105.68 +    final Channel currG = conn.getCurrentChannel();
  105.69 +    
  105.70 +    if (currA == null)
  105.71 +    {
  105.72 +      conn.println("420 no current article has been selected");
  105.73 +      return;
  105.74 +    }
  105.75 +    
  105.76 +    if (currG == null)
  105.77 +    {
  105.78 +      conn.println("412 no newsgroup selected");
  105.79 +      return;
  105.80 +    }
  105.81 +    
  105.82 +    final String[] command = line.split(" ");
  105.83 +
  105.84 +    if(command[0].equalsIgnoreCase("NEXT"))
  105.85 +    {
  105.86 +      selectNewArticle(conn, currA, currG, 1);
  105.87 +    }
  105.88 +    else if(command[0].equalsIgnoreCase("PREV"))
  105.89 +    {
  105.90 +      selectNewArticle(conn, currA, currG, -1);
  105.91 +    }
  105.92 +    else
  105.93 +    {
  105.94 +      conn.println("500 internal server error");
  105.95 +    }
  105.96 +  }
  105.97 +  
  105.98 +  private void selectNewArticle(NNTPConnection conn, Article article, Channel grp,
  105.99 +    final int delta)
 105.100 +    throws IOException, StorageBackendException
 105.101 +  {
 105.102 +    assert article != null;
 105.103 +
 105.104 +    article = grp.getArticle(grp.getIndexOf(article) + delta);
 105.105 +
 105.106 +    if(article == null)
 105.107 +    {
 105.108 +      conn.println("421 no next article in this group");
 105.109 +    }
 105.110 +    else
 105.111 +    {
 105.112 +      conn.setCurrentArticle(article);
 105.113 +      conn.println("223 " + conn.getCurrentChannel().getIndexOf(article)
 105.114 +                    + " " + article.getMessageID()
 105.115 +                    + " article retrieved - request text separately");
 105.116 +    }
 105.117 +  }
 105.118 +
 105.119 +}
   106.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   106.2 +++ b/src/org/sonews/daemon/command/OverCommand.java	Sun Aug 29 17:28:58 2010 +0200
   106.3 @@ -0,0 +1,294 @@
   106.4 +/*
   106.5 + *   SONEWS News Server
   106.6 + *   see AUTHORS for the list of contributors
   106.7 + *
   106.8 + *   This program is free software: you can redistribute it and/or modify
   106.9 + *   it under the terms of the GNU General Public License as published by
  106.10 + *   the Free Software Foundation, either version 3 of the License, or
  106.11 + *   (at your option) any later version.
  106.12 + *
  106.13 + *   This program is distributed in the hope that it will be useful,
  106.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  106.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  106.16 + *   GNU General Public License for more details.
  106.17 + *
  106.18 + *   You should have received a copy of the GNU General Public License
  106.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  106.20 + */
  106.21 +
  106.22 +package org.sonews.daemon.command;
  106.23 +
  106.24 +import java.io.IOException;
  106.25 +import java.util.List;
  106.26 +import org.sonews.util.Log;
  106.27 +import org.sonews.daemon.NNTPConnection;
  106.28 +import org.sonews.storage.Article;
  106.29 +import org.sonews.storage.ArticleHead;
  106.30 +import org.sonews.storage.Headers;
  106.31 +import org.sonews.storage.StorageBackendException;
  106.32 +import org.sonews.util.Pair;
  106.33 +
  106.34 +/**
  106.35 + * Class handling the OVER/XOVER command.
  106.36 + * 
  106.37 + * Description of the XOVER command:
  106.38 + * <pre>
  106.39 + * XOVER [range]
  106.40 + *
  106.41 + * The XOVER command returns information from the overview
  106.42 + * database for the article(s) specified.
  106.43 + *
  106.44 + * The optional range argument may be any of the following:
  106.45 + *              an article number
  106.46 + *              an article number followed by a dash to indicate
  106.47 + *                 all following
  106.48 + *              an article number followed by a dash followed by
  106.49 + *                 another article number
  106.50 + *
  106.51 + * If no argument is specified, then information from the
  106.52 + * current article is displayed. Successful responses start
  106.53 + * with a 224 response followed by the overview information
  106.54 + * for all matched messages. Once the output is complete, a
  106.55 + * period is sent on a line by itself. If no argument is
  106.56 + * specified, the information for the current article is
  106.57 + * returned.  A news group must have been selected earlier,
  106.58 + * else a 412 error response is returned. If no articles are
  106.59 + * in the range specified, a 420 error response is returned
  106.60 + * by the server. A 502 response will be returned if the
  106.61 + * client only has permission to transfer articles.
  106.62 + *
  106.63 + * Each line of output will be formatted with the article number,
  106.64 + * followed by each of the headers in the overview database or the
  106.65 + * article itself (when the data is not available in the overview
  106.66 + * database) for that article separated by a tab character.  The
  106.67 + * sequence of fields must be in this order: subject, author,
  106.68 + * date, message-id, references, byte count, and line count. Other
  106.69 + * optional fields may follow line count. Other optional fields may
  106.70 + * follow line count. These fields are specified by examining the
  106.71 + * response to the LIST OVERVIEW.FMT command. Where no data exists,
  106.72 + * a null field must be provided (i.e. the output will have two tab
  106.73 + * characters adjacent to each other). Servers should not output
  106.74 + * fields for articles that have been removed since the XOVER database
  106.75 + * was created.
  106.76 + *
  106.77 + * The LIST OVERVIEW.FMT command should be implemented if XOVER
  106.78 + * is implemented. A client can use LIST OVERVIEW.FMT to determine
  106.79 + * what optional fields  and in which order all fields will be
  106.80 + * supplied by the XOVER command. 
  106.81 + *
  106.82 + * Note that any tab and end-of-line characters in any header
  106.83 + * data that is returned will be converted to a space character.
  106.84 + *
  106.85 + * Responses:
  106.86 + *
  106.87 + *   224 Overview information follows
  106.88 + *   412 No news group current selected
  106.89 + *   420 No article(s) selected
  106.90 + *   502 no permission
  106.91 + *
  106.92 + * OVER defines additional responses:
  106.93 + *
  106.94 + *  First form (message-id specified)
  106.95 + *    224    Overview information follows (multi-line)
  106.96 + *    430    No article with that message-id
  106.97 + *
  106.98 + *  Second form (range specified)
  106.99 + *    224    Overview information follows (multi-line)
 106.100 + *    412    No newsgroup selected
 106.101 + *    423    No articles in that range
 106.102 + *
 106.103 + *  Third form (current article number used)
 106.104 + *    224    Overview information follows (multi-line)
 106.105 + *    412    No newsgroup selected
 106.106 + *    420    Current article number is invalid
 106.107 + *
 106.108 + * </pre>
 106.109 + * @author Christian Lins
 106.110 + * @since sonews/0.5.0
 106.111 + */
 106.112 +public class OverCommand implements Command
 106.113 +{
 106.114 +
 106.115 +  public static final int MAX_LINES_PER_DBREQUEST = 200;
 106.116 +
 106.117 +  @Override
 106.118 +  public String[] getSupportedCommandStrings()
 106.119 +  {
 106.120 +    return new String[]{"OVER", "XOVER"};
 106.121 +  }
 106.122 +
 106.123 +  @Override
 106.124 +  public boolean hasFinished()
 106.125 +  {
 106.126 +    return true;
 106.127 +  }
 106.128 +
 106.129 +  @Override
 106.130 +  public String impliedCapability()
 106.131 +  {
 106.132 +    return null;
 106.133 +  }
 106.134 +
 106.135 +  @Override
 106.136 +  public boolean isStateful()
 106.137 +  {
 106.138 +    return false;
 106.139 +  }
 106.140 +
 106.141 +  @Override
 106.142 +  public void processLine(NNTPConnection conn, final String line, byte[] raw)
 106.143 +    throws IOException, StorageBackendException
 106.144 +  {
 106.145 +    if(conn.getCurrentChannel() == null)
 106.146 +    {
 106.147 +      conn.println("412 no newsgroup selected");
 106.148 +    }
 106.149 +    else
 106.150 +    {
 106.151 +      String[] command = line.split(" ");
 106.152 +
 106.153 +      // If no parameter was specified, show information about
 106.154 +      // the currently selected article(s)
 106.155 +      if(command.length == 1)
 106.156 +      {
 106.157 +        final Article art = conn.getCurrentArticle();
 106.158 +        if(art == null)
 106.159 +        {
 106.160 +          conn.println("420 no article(s) selected");
 106.161 +          return;
 106.162 +        }
 106.163 +
 106.164 +        conn.println(buildOverview(art, -1));
 106.165 +      }
 106.166 +      // otherwise print information about the specified range
 106.167 +      else
 106.168 +      {
 106.169 +        long artStart;
 106.170 +        long artEnd   = conn.getCurrentChannel().getLastArticleNumber();
 106.171 +        String[] nums = command[1].split("-");
 106.172 +        if(nums.length >= 1)
 106.173 +        {
 106.174 +          try
 106.175 +          {
 106.176 +            artStart = Integer.parseInt(nums[0]);
 106.177 +          }
 106.178 +          catch(NumberFormatException e) 
 106.179 +          {
 106.180 +            Log.get().info(e.getMessage());
 106.181 +            artStart = Integer.parseInt(command[1]);
 106.182 +          }
 106.183 +        }
 106.184 +        else
 106.185 +        {
 106.186 +          artStart = conn.getCurrentChannel().getFirstArticleNumber();
 106.187 +        }
 106.188 +
 106.189 +        if(nums.length >=2)
 106.190 +        {
 106.191 +          try
 106.192 +          {
 106.193 +            artEnd = Integer.parseInt(nums[1]);
 106.194 +          }
 106.195 +          catch(NumberFormatException e) 
 106.196 +          {
 106.197 +            e.printStackTrace();
 106.198 +          }
 106.199 +        }
 106.200 +
 106.201 +        if(artStart > artEnd)
 106.202 +        {
 106.203 +          if(command[0].equalsIgnoreCase("OVER"))
 106.204 +          {
 106.205 +            conn.println("423 no articles in that range");
 106.206 +          }
 106.207 +          else
 106.208 +          {
 106.209 +            conn.println("224 (empty) overview information follows:");
 106.210 +            conn.println(".");
 106.211 +          }
 106.212 +        }
 106.213 +        else
 106.214 +        {
 106.215 +          for(long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST)
 106.216 +          {
 106.217 +            long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
 106.218 +            List<Pair<Long, ArticleHead>> articleHeads = conn.getCurrentChannel()
 106.219 +              .getArticleHeads(n, nEnd);
 106.220 +            if(articleHeads.isEmpty() && n == artStart
 106.221 +              && command[0].equalsIgnoreCase("OVER"))
 106.222 +            {
 106.223 +              // This reply is only valid for OVER, not for XOVER command
 106.224 +              conn.println("423 no articles in that range");
 106.225 +              return;
 106.226 +            }
 106.227 +            else if(n == artStart)
 106.228 +            {
 106.229 +              // XOVER replies this although there is no data available
 106.230 +              conn.println("224 overview information follows");
 106.231 +            }
 106.232 +
 106.233 +            for(Pair<Long, ArticleHead> article : articleHeads)
 106.234 +            {
 106.235 +              String overview = buildOverview(article.getB(), article.getA());
 106.236 +              conn.println(overview);
 106.237 +            }
 106.238 +          } // for
 106.239 +          conn.println(".");
 106.240 +        }
 106.241 +      }
 106.242 +    }
 106.243 +  }
 106.244 +  
 106.245 +  private String buildOverview(ArticleHead art, long nr)
 106.246 +  {
 106.247 +    StringBuilder overview = new StringBuilder();
 106.248 +    overview.append(nr);
 106.249 +    overview.append('\t');
 106.250 +
 106.251 +    String subject = art.getHeader(Headers.SUBJECT)[0];
 106.252 +    if("".equals(subject))
 106.253 +    {
 106.254 +      subject = "<empty>";
 106.255 +    }
 106.256 +    overview.append(escapeString(subject));
 106.257 +    overview.append('\t');
 106.258 +
 106.259 +    overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
 106.260 +    overview.append('\t');
 106.261 +    overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
 106.262 +    overview.append('\t');
 106.263 +    overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
 106.264 +    overview.append('\t');
 106.265 +    overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
 106.266 +    overview.append('\t');
 106.267 +
 106.268 +    String bytes = art.getHeader(Headers.BYTES)[0];
 106.269 +    if("".equals(bytes))
 106.270 +    {
 106.271 +      bytes = "0";
 106.272 +    }
 106.273 +    overview.append(escapeString(bytes));
 106.274 +    overview.append('\t');
 106.275 +
 106.276 +    String lines = art.getHeader(Headers.LINES)[0];
 106.277 +    if("".equals(lines))
 106.278 +    {
 106.279 +      lines = "0";
 106.280 +    }
 106.281 +    overview.append(escapeString(lines));
 106.282 +    overview.append('\t');
 106.283 +    overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
 106.284 +
 106.285 +    // Remove trailing tabs if some data is empty
 106.286 +    return overview.toString().trim();
 106.287 +  }
 106.288 +  
 106.289 +  private String escapeString(String str)
 106.290 +  {
 106.291 +    String nstr = str.replace("\r", "");
 106.292 +    nstr = nstr.replace('\n', ' ');
 106.293 +    nstr = nstr.replace('\t', ' ');
 106.294 +    return nstr.trim();
 106.295 +  }
 106.296 +  
 106.297 +}
   107.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   107.2 +++ b/src/org/sonews/daemon/command/PostCommand.java	Sun Aug 29 17:28:58 2010 +0200
   107.3 @@ -0,0 +1,332 @@
   107.4 +/*
   107.5 + *   SONEWS News Server
   107.6 + *   see AUTHORS for the list of contributors
   107.7 + *
   107.8 + *   This program is free software: you can redistribute it and/or modify
   107.9 + *   it under the terms of the GNU General Public License as published by
  107.10 + *   the Free Software Foundation, either version 3 of the License, or
  107.11 + *   (at your option) any later version.
  107.12 + *
  107.13 + *   This program is distributed in the hope that it will be useful,
  107.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  107.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  107.16 + *   GNU General Public License for more details.
  107.17 + *
  107.18 + *   You should have received a copy of the GNU General Public License
  107.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  107.20 + */
  107.21 +
  107.22 +package org.sonews.daemon.command;
  107.23 +
  107.24 +import java.io.IOException;
  107.25 +import java.io.ByteArrayInputStream;
  107.26 +import java.io.ByteArrayOutputStream;
  107.27 +import java.sql.SQLException;
  107.28 +import java.util.Arrays;
  107.29 +import javax.mail.MessagingException;
  107.30 +import javax.mail.internet.AddressException;
  107.31 +import javax.mail.internet.InternetHeaders;
  107.32 +import org.sonews.config.Config;
  107.33 +import org.sonews.util.Log;
  107.34 +import org.sonews.mlgw.Dispatcher;
  107.35 +import org.sonews.storage.Article;
  107.36 +import org.sonews.storage.Group;
  107.37 +import org.sonews.daemon.NNTPConnection;
  107.38 +import org.sonews.storage.Headers;
  107.39 +import org.sonews.storage.StorageBackendException;
  107.40 +import org.sonews.storage.StorageManager;
  107.41 +import org.sonews.feed.FeedManager;
  107.42 +import org.sonews.util.Stats;
  107.43 +
  107.44 +/**
  107.45 + * Implementation of the POST command. This command requires multiple lines
  107.46 + * from the client, so the handling of asynchronous reading is a little tricky
  107.47 + * to handle.
  107.48 + * @author Christian Lins
  107.49 + * @since sonews/0.5.0
  107.50 + */
  107.51 +public class PostCommand implements Command
  107.52 +{
  107.53 +  
  107.54 +  private final Article article   = new Article();
  107.55 +  private int           lineCount = 0;
  107.56 +  private long          bodySize  = 0;
  107.57 +  private InternetHeaders headers = null;
  107.58 +  private long          maxBodySize  = 
  107.59 +    Config.inst().get(Config.ARTICLE_MAXSIZE, 128) * 1024L; // Size in bytes
  107.60 +  private PostState     state     = PostState.WaitForLineOne;
  107.61 +  private final ByteArrayOutputStream bufBody   = new ByteArrayOutputStream();
  107.62 +  private final StringBuilder         strHead   = new StringBuilder();
  107.63 +
  107.64 +  @Override
  107.65 +  public String[] getSupportedCommandStrings()
  107.66 +  {
  107.67 +    return new String[]{"POST"};
  107.68 +  }
  107.69 +
  107.70 +  @Override
  107.71 +  public boolean hasFinished()
  107.72 +  {
  107.73 +    return this.state == PostState.Finished;
  107.74 +  }
  107.75 +
  107.76 +  @Override
  107.77 +  public String impliedCapability()
  107.78 +  {
  107.79 +    return null;
  107.80 +  }
  107.81 +
  107.82 +  @Override
  107.83 +  public boolean isStateful()
  107.84 +  {
  107.85 +    return true;
  107.86 +  }
  107.87 +
  107.88 +  /**
  107.89 +   * Process the given line String. line.trim() was called by NNTPConnection.
  107.90 +   * @param line
  107.91 +   * @throws java.io.IOException
  107.92 +   * @throws java.sql.SQLException
  107.93 +   */
  107.94 +  @Override // TODO: Refactor this method to reduce complexity!
  107.95 +  public void processLine(NNTPConnection conn, String line, byte[] raw)
  107.96 +    throws IOException, StorageBackendException
  107.97 +  {
  107.98 +    switch(state)
  107.99 +    {
 107.100 +      case WaitForLineOne:
 107.101 +      {
 107.102 +        if(line.equalsIgnoreCase("POST"))
 107.103 +        {
 107.104 +          conn.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
 107.105 +          state = PostState.ReadingHeaders;
 107.106 +        }
 107.107 +        else
 107.108 +        {
 107.109 +          conn.println("500 invalid command usage");
 107.110 +        }
 107.111 +        break;
 107.112 +      }
 107.113 +      case ReadingHeaders:
 107.114 +      {
 107.115 +        strHead.append(line);
 107.116 +        strHead.append(NNTPConnection.NEWLINE);
 107.117 +        
 107.118 +        if("".equals(line) || ".".equals(line))
 107.119 +        {
 107.120 +          // we finally met the blank line
 107.121 +          // separating headers from body
 107.122 +          
 107.123 +          try
 107.124 +          {
 107.125 +            // Parse the header using the InternetHeader class from JavaMail API
 107.126 +            headers = new InternetHeaders(
 107.127 +              new ByteArrayInputStream(strHead.toString().trim()
 107.128 +                .getBytes(conn.getCurrentCharset())));
 107.129 +
 107.130 +            // add the header entries for the article
 107.131 +            article.setHeaders(headers);
 107.132 +          }
 107.133 +          catch (MessagingException e)
 107.134 +          {
 107.135 +            e.printStackTrace();
 107.136 +            conn.println("500 posting failed - invalid header");
 107.137 +            state = PostState.Finished;
 107.138 +            break;
 107.139 +          }
 107.140 +
 107.141 +          // Change charset for reading body; 
 107.142 +          // for multipart messages UTF-8 is returned
 107.143 +          //conn.setCurrentCharset(article.getBodyCharset());
 107.144 +          
 107.145 +          state = PostState.ReadingBody;
 107.146 +          
 107.147 +          if(".".equals(line))
 107.148 +          {
 107.149 +            // Post an article without body
 107.150 +            postArticle(conn, article);
 107.151 +            state = PostState.Finished;
 107.152 +          }
 107.153 +        }
 107.154 +        break;
 107.155 +      }
 107.156 +      case ReadingBody:
 107.157 +      {
 107.158 +        if(".".equals(line))
 107.159 +        {    
 107.160 +          // Set some headers needed for Over command
 107.161 +          headers.setHeader(Headers.LINES, Integer.toString(lineCount));
 107.162 +          headers.setHeader(Headers.BYTES, Long.toString(bodySize));
 107.163 +
 107.164 +          byte[] body = bufBody.toByteArray();
 107.165 +          if(body.length >= 2)
 107.166 +          {
 107.167 +            // Remove trailing CRLF
 107.168 +            body = Arrays.copyOf(body, body.length - 2);
 107.169 +          }
 107.170 +          article.setBody(body); // set the article body
 107.171 +          
 107.172 +          postArticle(conn, article);
 107.173 +          state = PostState.Finished;
 107.174 +        }
 107.175 +        else
 107.176 +        {
 107.177 +          bodySize += line.length() + 1;
 107.178 +          lineCount++;
 107.179 +          
 107.180 +          // Add line to body buffer
 107.181 +          bufBody.write(raw, 0, raw.length);
 107.182 +          bufBody.write(NNTPConnection.NEWLINE.getBytes());
 107.183 +          
 107.184 +          if(bodySize > maxBodySize)
 107.185 +          {
 107.186 +            conn.println("500 article is too long");
 107.187 +            state = PostState.Finished;
 107.188 +            break;
 107.189 +          }
 107.190 +        }
 107.191 +        break;
 107.192 +      }
 107.193 +      default:
 107.194 +      {
 107.195 +        // Should never happen
 107.196 +        Log.get().severe("PostCommand::processLine(): already finished...");
 107.197 +      }
 107.198 +    }
 107.199 +  }
 107.200 +  
 107.201 +  /**
 107.202 +   * Article is a control message and needs special handling.
 107.203 +   * @param article
 107.204 +   */
 107.205 +  private void controlMessage(NNTPConnection conn, Article article)
 107.206 +    throws IOException
 107.207 +  {
 107.208 +    String[] ctrl = article.getHeader(Headers.CONTROL)[0].split(" ");
 107.209 +    if(ctrl.length == 2) // "cancel <mid>"
 107.210 +    {
 107.211 +      try
 107.212 +      {
 107.213 +        StorageManager.current().delete(ctrl[1]);
 107.214 +        
 107.215 +        // Move cancel message to "control" group
 107.216 +        article.setHeader(Headers.NEWSGROUPS, "control");
 107.217 +        StorageManager.current().addArticle(article);
 107.218 +        conn.println("240 article cancelled");
 107.219 +      }
 107.220 +      catch(StorageBackendException ex)
 107.221 +      {
 107.222 +        Log.get().severe(ex.toString());
 107.223 +        conn.println("500 internal server error");
 107.224 +      }
 107.225 +    }
 107.226 +    else
 107.227 +    {
 107.228 +      conn.println("441 unknown control header");
 107.229 +    }
 107.230 +  }
 107.231 +  
 107.232 +  private void supersedeMessage(NNTPConnection conn, Article article)
 107.233 +    throws IOException
 107.234 +  {
 107.235 +    try
 107.236 +    {
 107.237 +      String oldMsg = article.getHeader(Headers.SUPERSEDES)[0];
 107.238 +      StorageManager.current().delete(oldMsg);
 107.239 +      StorageManager.current().addArticle(article);
 107.240 +      conn.println("240 article replaced");
 107.241 +    }
 107.242 +    catch(StorageBackendException ex)
 107.243 +    {
 107.244 +      Log.get().severe(ex.toString());
 107.245 +      conn.println("500 internal server error");
 107.246 +    }
 107.247 +  }
 107.248 +  
 107.249 +  private void postArticle(NNTPConnection conn, Article article)
 107.250 +    throws IOException
 107.251 +  {
 107.252 +    if(article.getHeader(Headers.CONTROL)[0].length() > 0)
 107.253 +    {
 107.254 +      controlMessage(conn, article);
 107.255 +    }
 107.256 +    else if(article.getHeader(Headers.SUPERSEDES)[0].length() > 0)
 107.257 +    {
 107.258 +      supersedeMessage(conn, article);
 107.259 +    }
 107.260 +    else // Post the article regularily
 107.261 +    {
 107.262 +      // Circle check; note that Path can already contain the hostname here
 107.263 +      String host = Config.inst().get(Config.HOSTNAME, "localhost");
 107.264 +      if(article.getHeader(Headers.PATH)[0].indexOf(host + "!", 1) > 0)
 107.265 +      {
 107.266 +        Log.get().info(article.getMessageID() + " skipped for host " + host);
 107.267 +        conn.println("441 I know this article already");
 107.268 +        return;
 107.269 +      }
 107.270 +
 107.271 +      // Try to create the article in the database or post it to
 107.272 +      // appropriate mailing list
 107.273 +      try
 107.274 +      {
 107.275 +        boolean success = false;
 107.276 +        String[] groupnames = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
 107.277 +        for(String groupname : groupnames)
 107.278 +        {          
 107.279 +          Group group = StorageManager.current().getGroup(groupname);
 107.280 +          if(group != null && !group.isDeleted())
 107.281 +          {
 107.282 +            if(group.isMailingList() && !conn.isLocalConnection())
 107.283 +            {
 107.284 +              // Send to mailing list; the Dispatcher writes 
 107.285 +              // statistics to database
 107.286 +              Dispatcher.toList(article, group.getName());
 107.287 +              success = true;
 107.288 +            }
 107.289 +            else
 107.290 +            {
 107.291 +              // Store in database
 107.292 +              if(!StorageManager.current().isArticleExisting(article.getMessageID()))
 107.293 +              {
 107.294 +                StorageManager.current().addArticle(article);
 107.295 +
 107.296 +                // Log this posting to statistics
 107.297 +                Stats.getInstance().mailPosted(
 107.298 +                  article.getHeader(Headers.NEWSGROUPS)[0]);
 107.299 +              }
 107.300 +              success = true;
 107.301 +            }
 107.302 +          }
 107.303 +        } // end for
 107.304 +
 107.305 +        if(success)
 107.306 +        {
 107.307 +          conn.println("240 article posted ok");
 107.308 +          FeedManager.queueForPush(article);
 107.309 +        }
 107.310 +        else
 107.311 +        {
 107.312 +          conn.println("441 newsgroup not found");
 107.313 +        }
 107.314 +      }
 107.315 +      catch(AddressException ex)
 107.316 +      {
 107.317 +        Log.get().warning(ex.getMessage());
 107.318 +        conn.println("441 invalid sender address");
 107.319 +      }
 107.320 +      catch(MessagingException ex)
 107.321 +      {
 107.322 +        // A MessageException is thrown when the sender email address is
 107.323 +        // invalid or something is wrong with the SMTP server.
 107.324 +        System.err.println(ex.getLocalizedMessage());
 107.325 +        conn.println("441 " + ex.getClass().getCanonicalName() + ": " + ex.getLocalizedMessage());
 107.326 +      }
 107.327 +      catch(StorageBackendException ex)
 107.328 +      {
 107.329 +        ex.printStackTrace();
 107.330 +        conn.println("500 internal server error");
 107.331 +      }
 107.332 +    }
 107.333 +  }
 107.334 +
 107.335 +}
   108.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   108.2 +++ b/src/org/sonews/daemon/command/PostState.java	Sun Aug 29 17:28:58 2010 +0200
   108.3 @@ -0,0 +1,29 @@
   108.4 +/*
   108.5 + *   SONEWS News Server
   108.6 + *   see AUTHORS for the list of contributors
   108.7 + *
   108.8 + *   This program is free software: you can redistribute it and/or modify
   108.9 + *   it under the terms of the GNU General Public License as published by
  108.10 + *   the Free Software Foundation, either version 3 of the License, or
  108.11 + *   (at your option) any later version.
  108.12 + *
  108.13 + *   This program is distributed in the hope that it will be useful,
  108.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  108.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  108.16 + *   GNU General Public License for more details.
  108.17 + *
  108.18 + *   You should have received a copy of the GNU General Public License
  108.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  108.20 + */
  108.21 +
  108.22 +package org.sonews.daemon.command;
  108.23 +
  108.24 +/**
  108.25 + * States of the POST command's finite state machine.
  108.26 + * @author Christian Lins
  108.27 + * @since sonews/0.5.0
  108.28 + */
  108.29 +enum PostState
  108.30 +{
  108.31 +  WaitForLineOne, ReadingHeaders, ReadingBody, Finished
  108.32 +}
   109.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   109.2 +++ b/src/org/sonews/daemon/command/QuitCommand.java	Sun Aug 29 17:28:58 2010 +0200
   109.3 @@ -0,0 +1,67 @@
   109.4 +/*
   109.5 + *   SONEWS News Server
   109.6 + *   see AUTHORS for the list of contributors
   109.7 + *
   109.8 + *   This program is free software: you can redistribute it and/or modify
   109.9 + *   it under the terms of the GNU General Public License as published by
  109.10 + *   the Free Software Foundation, either version 3 of the License, or
  109.11 + *   (at your option) any later version.
  109.12 + *
  109.13 + *   This program is distributed in the hope that it will be useful,
  109.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  109.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  109.16 + *   GNU General Public License for more details.
  109.17 + *
  109.18 + *   You should have received a copy of the GNU General Public License
  109.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  109.20 + */
  109.21 +
  109.22 +package org.sonews.daemon.command;
  109.23 +
  109.24 +import java.io.IOException;
  109.25 +import org.sonews.daemon.NNTPConnection;
  109.26 +import org.sonews.storage.StorageBackendException;
  109.27 +
  109.28 +/**
  109.29 + * Implementation of the QUIT command; client wants to shutdown the connection.
  109.30 + * @author Christian Lins
  109.31 + * @since sonews/0.5.0
  109.32 + */
  109.33 +public class QuitCommand implements Command
  109.34 +{
  109.35 +
  109.36 +  @Override
  109.37 +  public String[] getSupportedCommandStrings()
  109.38 +  {
  109.39 +    return new String[]{"QUIT"};
  109.40 +  }
  109.41 +  
  109.42 +  @Override
  109.43 +  public boolean hasFinished()
  109.44 +  {
  109.45 +    return true;
  109.46 +  }
  109.47 +
  109.48 +  @Override
  109.49 +  public String impliedCapability()
  109.50 +  {
  109.51 +    return null;
  109.52 +  }
  109.53 +
  109.54 +  @Override
  109.55 +  public boolean isStateful()
  109.56 +  {
  109.57 +    return false;
  109.58 +  }
  109.59 +
  109.60 +  @Override
  109.61 +  public void processLine(NNTPConnection conn, final String line, byte[] raw)
  109.62 +    throws IOException, StorageBackendException
  109.63 +  {    
  109.64 +    conn.println("205 cya");
  109.65 +    
  109.66 +    conn.shutdownInput();
  109.67 +    conn.shutdownOutput();
  109.68 +  }
  109.69 +
  109.70 +}
   110.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   110.2 +++ b/src/org/sonews/daemon/command/StatCommand.java	Sun Aug 29 17:28:58 2010 +0200
   110.3 @@ -0,0 +1,114 @@
   110.4 +/*
   110.5 + *   SONEWS News Server
   110.6 + *   see AUTHORS for the list of contributors
   110.7 + *
   110.8 + *   This program is free software: you can redistribute it and/or modify
   110.9 + *   it under the terms of the GNU General Public License as published by
  110.10 + *   the Free Software Foundation, either version 3 of the License, or
  110.11 + *   (at your option) any later version.
  110.12 + *
  110.13 + *   This program is distributed in the hope that it will be useful,
  110.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  110.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  110.16 + *   GNU General Public License for more details.
  110.17 + *
  110.18 + *   You should have received a copy of the GNU General Public License
  110.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  110.20 + */
  110.21 +
  110.22 +package org.sonews.daemon.command;
  110.23 +
  110.24 +import java.io.IOException;
  110.25 +import org.sonews.storage.Article;
  110.26 +import org.sonews.daemon.NNTPConnection;
  110.27 +import org.sonews.storage.StorageBackendException;
  110.28 +
  110.29 +/**
  110.30 + * Implementation of the STAT command.
  110.31 + * @author Christian Lins
  110.32 + * @since sonews/0.5.0
  110.33 + */
  110.34 +public class StatCommand implements Command
  110.35 +{
  110.36 +
  110.37 +  @Override
  110.38 +  public String[] getSupportedCommandStrings()
  110.39 +  {
  110.40 +    return new String[]{"STAT"};
  110.41 +  }
  110.42 +
  110.43 +  @Override
  110.44 +  public boolean hasFinished()
  110.45 +  {
  110.46 +    return true;
  110.47 +  }
  110.48 +
  110.49 +  @Override
  110.50 +  public String impliedCapability()
  110.51 +  {
  110.52 +    return null;
  110.53 +  }
  110.54 +
  110.55 +  @Override
  110.56 +  public boolean isStateful()
  110.57 +  {
  110.58 +    return false;
  110.59 +  }
  110.60 +
  110.61 +  // TODO: Method has various exit points => Refactor!
  110.62 +  @Override
  110.63 +  public void processLine(NNTPConnection conn, final String line, byte[] raw)
  110.64 +    throws IOException, StorageBackendException
  110.65 +  {
  110.66 +    final String[] command = line.split(" ");
  110.67 +
  110.68 +    Article article = null;
  110.69 +    if(command.length == 1)
  110.70 +    {
  110.71 +      article = conn.getCurrentArticle();
  110.72 +      if(article == null)
  110.73 +      {
  110.74 +        conn.println("420 no current article has been selected");
  110.75 +        return;
  110.76 +      }
  110.77 +    }
  110.78 +    else if(command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN))
  110.79 +    {
  110.80 +      // Message-ID
  110.81 +      article = Article.getByMessageID(command[1]);
  110.82 +      if (article == null)
  110.83 +      {
  110.84 +        conn.println("430 no such article found");
  110.85 +        return;
  110.86 +      }
  110.87 +    }
  110.88 +    else
  110.89 +    {
  110.90 +      // Message Number
  110.91 +      try
  110.92 +      {
  110.93 +        long aid = Long.parseLong(command[1]);
  110.94 +        article = conn.getCurrentChannel().getArticle(aid);
  110.95 +      }
  110.96 +      catch(NumberFormatException ex)
  110.97 +      {
  110.98 +        ex.printStackTrace();
  110.99 +      }
 110.100 +      catch(StorageBackendException ex)
 110.101 +      {
 110.102 +        ex.printStackTrace();
 110.103 +      }
 110.104 +      if (article == null)
 110.105 +      {
 110.106 +        conn.println("423 no such article number in this group");
 110.107 +        return;
 110.108 +      }
 110.109 +      conn.setCurrentArticle(article);
 110.110 +    }
 110.111 +    
 110.112 +    conn.println("223 " + conn.getCurrentChannel().getIndexOf(article) + " "
 110.113 +      + article.getMessageID()
 110.114 +      + " article retrieved - request text separately");
 110.115 +  }
 110.116 +  
 110.117 +}
   111.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   111.2 +++ b/src/org/sonews/daemon/command/UnsupportedCommand.java	Sun Aug 29 17:28:58 2010 +0200
   111.3 @@ -0,0 +1,67 @@
   111.4 +/*
   111.5 + *   SONEWS News Server
   111.6 + *   see AUTHORS for the list of contributors
   111.7 + *
   111.8 + *   This program is free software: you can redistribute it and/or modify
   111.9 + *   it under the terms of the GNU General Public License as published by
  111.10 + *   the Free Software Foundation, either version 3 of the License, or
  111.11 + *   (at your option) any later version.
  111.12 + *
  111.13 + *   This program is distributed in the hope that it will be useful,
  111.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  111.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  111.16 + *   GNU General Public License for more details.
  111.17 + *
  111.18 + *   You should have received a copy of the GNU General Public License
  111.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  111.20 + */
  111.21 +
  111.22 +package org.sonews.daemon.command;
  111.23 +
  111.24 +import java.io.IOException;
  111.25 +import org.sonews.daemon.NNTPConnection;
  111.26 +
  111.27 +/**
  111.28 + * A default "Unsupported Command". Simply returns error code 500 and a
  111.29 + * "command not supported" message.
  111.30 + * @author Christian Lins
  111.31 + * @since sonews/0.5.0
  111.32 + */
  111.33 +public class UnsupportedCommand implements Command
  111.34 +{
  111.35 +  
  111.36 +  /**
  111.37 +   * @return Always returns null.
  111.38 +   */
  111.39 +  @Override
  111.40 +  public String[] getSupportedCommandStrings()
  111.41 +  {
  111.42 +    return null;
  111.43 +  }
  111.44 +
  111.45 +  @Override
  111.46 +  public boolean hasFinished()
  111.47 +  {
  111.48 +    return true;
  111.49 +  }
  111.50 +
  111.51 +  @Override
  111.52 +  public String impliedCapability()
  111.53 +  {
  111.54 +    return null;
  111.55 +  }
  111.56 +
  111.57 +  @Override
  111.58 +  public boolean isStateful()
  111.59 +  {
  111.60 +    return false;
  111.61 +  }
  111.62 +
  111.63 +  @Override
  111.64 +  public void processLine(NNTPConnection conn, final String line, byte[] raw)
  111.65 +    throws IOException
  111.66 +  {
  111.67 +    conn.println("500 command not supported");
  111.68 +  }
  111.69 +  
  111.70 +}
   112.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   112.2 +++ b/src/org/sonews/daemon/command/XDaemonCommand.java	Sun Aug 29 17:28:58 2010 +0200
   112.3 @@ -0,0 +1,270 @@
   112.4 +/*
   112.5 + *   SONEWS News Server
   112.6 + *   see AUTHORS for the list of contributors
   112.7 + *
   112.8 + *   This program is free software: you can redistribute it and/or modify
   112.9 + *   it under the terms of the GNU General Public License as published by
  112.10 + *   the Free Software Foundation, either version 3 of the License, or
  112.11 + *   (at your option) any later version.
  112.12 + *
  112.13 + *   This program is distributed in the hope that it will be useful,
  112.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  112.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  112.16 + *   GNU General Public License for more details.
  112.17 + *
  112.18 + *   You should have received a copy of the GNU General Public License
  112.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  112.20 + */
  112.21 +
  112.22 +package org.sonews.daemon.command;
  112.23 +
  112.24 +import java.io.IOException;
  112.25 +import java.net.InetSocketAddress;
  112.26 +import java.util.List;
  112.27 +import org.sonews.config.Config;
  112.28 +import org.sonews.daemon.NNTPConnection;
  112.29 +import org.sonews.storage.StorageBackendException;
  112.30 +import org.sonews.storage.StorageManager;
  112.31 +import org.sonews.feed.FeedManager;
  112.32 +import org.sonews.feed.Subscription;
  112.33 +import org.sonews.storage.Channel;
  112.34 +import org.sonews.storage.Group;
  112.35 +import org.sonews.util.Stats;
  112.36 +
  112.37 +/**
  112.38 + * The XDAEMON command allows a client to get/set properties of the
  112.39 + * running server daemon. Only locally connected clients are allowed to
  112.40 + * use this command.
  112.41 + * The restriction to localhost connection can be suppressed by overriding
  112.42 + * the sonews.xdaemon.host bootstrap config property.
  112.43 + * @author Christian Lins
  112.44 + * @since sonews/0.5.0
  112.45 + */
  112.46 +public class XDaemonCommand implements Command
  112.47 +{
  112.48 +
  112.49 +  @Override
  112.50 +  public String[] getSupportedCommandStrings()
  112.51 +  {
  112.52 +    return new String[]{"XDAEMON"};
  112.53 +  }
  112.54 +
  112.55 +  @Override
  112.56 +  public boolean hasFinished()
  112.57 +  {
  112.58 +    return true;
  112.59 +  }
  112.60 +
  112.61 +  @Override
  112.62 +  public String impliedCapability()
  112.63 +  {
  112.64 +    return null;
  112.65 +  }
  112.66 +
  112.67 +  @Override
  112.68 +  public boolean isStateful()
  112.69 +  {
  112.70 +    return false;
  112.71 +  }
  112.72 +
  112.73 +  private void channelAdd(String[] commands, NNTPConnection conn)
  112.74 +    throws IOException, StorageBackendException
  112.75 +  {
  112.76 +    String groupName = commands[2];
  112.77 +    if(StorageManager.current().isGroupExisting(groupName))
  112.78 +    {
  112.79 +      conn.println("400 group " + groupName + " already existing!");
  112.80 +    }
  112.81 +    else
  112.82 +    {
  112.83 +      StorageManager.current().addGroup(groupName, Integer.parseInt(commands[3]));
  112.84 +      conn.println("200 group " + groupName + " created");
  112.85 +    }
  112.86 +  }
  112.87 +
  112.88 +  // TODO: Refactor this method to reduce complexity!
  112.89 +  @Override
  112.90 +  public void processLine(NNTPConnection conn, String line, byte[] raw)
  112.91 +    throws IOException, StorageBackendException
  112.92 +  {
  112.93 +    InetSocketAddress addr = (InetSocketAddress)conn.getSocketChannel().socket()
  112.94 +      .getRemoteSocketAddress();
  112.95 +    if(addr.getHostName().equals(
  112.96 +      Config.inst().get(Config.XDAEMON_HOST, "localhost")))
  112.97 +    {
  112.98 +      String[] commands = line.split(" ", 4);
  112.99 +      if(commands.length == 3 && commands[1].equalsIgnoreCase("LIST"))
 112.100 +      {
 112.101 +        if(commands[2].equalsIgnoreCase("CONFIGKEYS"))
 112.102 +        {
 112.103 +          conn.println("100 list of available config keys follows");
 112.104 +          for(String key : Config.AVAILABLE_KEYS)
 112.105 +          {
 112.106 +            conn.println(key);
 112.107 +          }
 112.108 +          conn.println(".");
 112.109 +        }
 112.110 +        else if(commands[2].equalsIgnoreCase("PEERINGRULES"))
 112.111 +        {
 112.112 +          List<Subscription> pull = 
 112.113 +            StorageManager.current().getSubscriptions(FeedManager.TYPE_PULL);
 112.114 +          List<Subscription> push =
 112.115 +            StorageManager.current().getSubscriptions(FeedManager.TYPE_PUSH);
 112.116 +          conn.println("100 list of peering rules follows");
 112.117 +          for(Subscription sub : pull)
 112.118 +          {
 112.119 +            conn.println("PULL " + sub.getHost() + ":" + sub.getPort()
 112.120 +              + " " + sub.getGroup());
 112.121 +          }
 112.122 +          for(Subscription sub : push)
 112.123 +          {
 112.124 +            conn.println("PUSH " + sub.getHost() + ":" + sub.getPort()
 112.125 +              + " " + sub.getGroup());
 112.126 +          }
 112.127 +          conn.println(".");
 112.128 +        }
 112.129 +        else
 112.130 +        {
 112.131 +          conn.println("401 unknown sub command");
 112.132 +        }
 112.133 +      }
 112.134 +      else if(commands.length == 3 && commands[1].equalsIgnoreCase("DELETE"))
 112.135 +      {
 112.136 +        StorageManager.current().delete(commands[2]);
 112.137 +        conn.println("200 article " + commands[2] + " deleted");
 112.138 +      }
 112.139 +      else if(commands.length == 4 && commands[1].equalsIgnoreCase("GROUPADD"))
 112.140 +      {
 112.141 +        channelAdd(commands, conn);
 112.142 +      }
 112.143 +      else if(commands.length == 3 && commands[1].equalsIgnoreCase("GROUPDEL"))
 112.144 +      {
 112.145 +        Group group = StorageManager.current().getGroup(commands[2]);
 112.146 +        if(group == null)
 112.147 +        {
 112.148 +          conn.println("400 group not found");
 112.149 +        }
 112.150 +        else
 112.151 +        {
 112.152 +          group.setFlag(Group.DELETED);
 112.153 +          group.update();
 112.154 +          conn.println("200 group " + commands[2] + " marked as deleted");
 112.155 +        }
 112.156 +      }
 112.157 +      else if(commands.length == 4 && commands[1].equalsIgnoreCase("SET"))
 112.158 +      {
 112.159 +        String key = commands[2];
 112.160 +        String val = commands[3];
 112.161 +        Config.inst().set(key, val);
 112.162 +        conn.println("200 new config value set");
 112.163 +      }
 112.164 +      else if(commands.length == 3 && commands[1].equalsIgnoreCase("GET"))
 112.165 +      {
 112.166 +        String key = commands[2];
 112.167 +        String val = Config.inst().get(key, null);
 112.168 +        if(val != null)
 112.169 +        {
 112.170 +          conn.println("100 config value for " + key + " follows");
 112.171 +          conn.println(val);
 112.172 +          conn.println(".");
 112.173 +        }
 112.174 +        else
 112.175 +        {
 112.176 +          conn.println("400 config value not set");
 112.177 +        }
 112.178 +      }
 112.179 +      else if(commands.length >= 3 && commands[1].equalsIgnoreCase("LOG"))
 112.180 +      {
 112.181 +        Group group = null;
 112.182 +        if(commands.length > 3)
 112.183 +        {
 112.184 +          group = (Group)Channel.getByName(commands[3]);
 112.185 +        }
 112.186 +
 112.187 +        if(commands[2].equalsIgnoreCase("CONNECTED_CLIENTS"))
 112.188 +        {
 112.189 +          conn.println("100 number of connections follow");
 112.190 +          conn.println(Integer.toString(Stats.getInstance().connectedClients()));
 112.191 +          conn.println(".");
 112.192 +        }
 112.193 +        else if(commands[2].equalsIgnoreCase("POSTED_NEWS"))
 112.194 +        {
 112.195 +          conn.println("100 hourly numbers of posted news yesterday");
 112.196 +          for(int n = 0; n < 24; n++)
 112.197 +          {
 112.198 +            conn.println(n + " " + Stats.getInstance()
 112.199 +              .getYesterdaysEvents(Stats.POSTED_NEWS, n, group));
 112.200 +          }
 112.201 +          conn.println(".");
 112.202 +        }
 112.203 +        else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS"))
 112.204 +        {
 112.205 +          conn.println("100 hourly numbers of gatewayed news yesterday");
 112.206 +          for(int n = 0; n < 24; n++)
 112.207 +          {
 112.208 +            conn.println(n + " " + Stats.getInstance()
 112.209 +              .getYesterdaysEvents(Stats.GATEWAYED_NEWS, n, group));
 112.210 +          }
 112.211 +          conn.println(".");
 112.212 +        }
 112.213 +        else if(commands[2].equalsIgnoreCase("TRANSMITTED_NEWS"))
 112.214 +        {
 112.215 +          conn.println("100 hourly numbers of news transmitted to peers yesterday");
 112.216 +          for(int n = 0; n < 24; n++)
 112.217 +          {
 112.218 +            conn.println(n + " " + Stats.getInstance()
 112.219 +              .getYesterdaysEvents(Stats.FEEDED_NEWS, n, group));
 112.220 +          }
 112.221 +          conn.println(".");
 112.222 +        }
 112.223 +        else if(commands[2].equalsIgnoreCase("HOSTED_NEWS"))
 112.224 +        {
 112.225 +          conn.println("100 number of overall hosted news");
 112.226 +          conn.println(Integer.toString(Stats.getInstance().getNumberOfNews()));
 112.227 +          conn.println(".");
 112.228 +        }
 112.229 +        else if(commands[2].equalsIgnoreCase("HOSTED_GROUPS"))
 112.230 +        {
 112.231 +          conn.println("100 number of hosted groups");
 112.232 +          conn.println(Integer.toString(Stats.getInstance().getNumberOfGroups()));
 112.233 +          conn.println(".");
 112.234 +        }
 112.235 +        else if(commands[2].equalsIgnoreCase("POSTED_NEWS_PER_HOUR"))
 112.236 +        {
 112.237 +          conn.println("100 posted news per hour");
 112.238 +          conn.println(Double.toString(Stats.getInstance().postedPerHour(-1)));
 112.239 +          conn.println(".");
 112.240 +        }
 112.241 +        else if(commands[2].equalsIgnoreCase("FEEDED_NEWS_PER_HOUR"))
 112.242 +        {
 112.243 +          conn.println("100 feeded news per hour");
 112.244 +          conn.println(Double.toString(Stats.getInstance().feededPerHour(-1)));
 112.245 +          conn.println(".");
 112.246 +        }
 112.247 +        else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS_PER_HOUR"))
 112.248 +        {
 112.249 +          conn.println("100 gatewayed news per hour");
 112.250 +          conn.println(Double.toString(Stats.getInstance().gatewayedPerHour(-1)));
 112.251 +          conn.println(".");
 112.252 +        }
 112.253 +        else
 112.254 +        {
 112.255 +          conn.println("401 unknown sub command");
 112.256 +        }
 112.257 +      }
 112.258 +      else if(commands.length >= 3 && commands[1].equalsIgnoreCase("PLUGIN"))
 112.259 +      {
 112.260 +        
 112.261 +      }
 112.262 +      else
 112.263 +      {
 112.264 +        conn.println("400 invalid command usage");
 112.265 +      }
 112.266 +    }
 112.267 +    else
 112.268 +    {
 112.269 +      conn.println("501 not allowed");
 112.270 +    }
 112.271 +  }
 112.272 +  
 112.273 +}
   113.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   113.2 +++ b/src/org/sonews/daemon/command/XPatCommand.java	Sun Aug 29 17:28:58 2010 +0200
   113.3 @@ -0,0 +1,170 @@
   113.4 +/*
   113.5 + *   SONEWS News Server
   113.6 + *   see AUTHORS for the list of contributors
   113.7 + *
   113.8 + *   This program is free software: you can redistribute it and/or modify
   113.9 + *   it under the terms of the GNU General Public License as published by
  113.10 + *   the Free Software Foundation, either version 3 of the License, or
  113.11 + *   (at your option) any later version.
  113.12 + *
  113.13 + *   This program is distributed in the hope that it will be useful,
  113.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  113.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  113.16 + *   GNU General Public License for more details.
  113.17 + *
  113.18 + *   You should have received a copy of the GNU General Public License
  113.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  113.20 + */
  113.21 +
  113.22 +package org.sonews.daemon.command;
  113.23 +
  113.24 +import java.io.IOException;
  113.25 +import java.util.List;
  113.26 +import java.util.Locale;
  113.27 +import java.util.regex.PatternSyntaxException;
  113.28 +import org.sonews.daemon.NNTPConnection;
  113.29 +import org.sonews.storage.StorageBackendException;
  113.30 +import org.sonews.storage.StorageManager;
  113.31 +import org.sonews.util.Pair;
  113.32 +
  113.33 +/**
  113.34 + * <pre>
  113.35 + *   XPAT header range|<message-id> pat [pat...]
  113.36 + *
  113.37 + *   The XPAT command is used to retrieve specific headers from
  113.38 + *   specific articles, based on pattern matching on the contents of
  113.39 + *   the header. This command was first available in INN.
  113.40 + *
  113.41 + *   The required header parameter is the name of a header line (e.g.
  113.42 + *   "subject") in a news group article. See RFC-1036 for a list
  113.43 + *   of valid header lines. The required range argument may be
  113.44 + *   any of the following:
  113.45 + *               an article number
  113.46 + *               an article number followed by a dash to indicate
  113.47 + *                  all following
  113.48 + *               an article number followed by a dash followed by
  113.49 + *                  another article number
  113.50 + *
  113.51 + *   The required message-id argument indicates a specific
  113.52 + *   article. The range and message-id arguments are mutually
  113.53 + *   exclusive. At least one pattern in wildmat must be specified
  113.54 + *   as well. If there are additional arguments the are joined
  113.55 + *   together separated by a single space to form one complete
  113.56 + *   pattern. Successful responses start with a 221 response
  113.57 + *   followed by a the headers from all messages in which the
  113.58 + *   pattern matched the contents of the specified header line. This
  113.59 + *   includes an empty list. Once the output is complete, a period
  113.60 + *   is sent on a line by itself. If the optional argument is a
  113.61 + *   message-id and no such article exists, the 430 error response
  113.62 + *   is returned. A 502 response will be returned if the client only
  113.63 + *   has permission to transfer articles.
  113.64 + *
  113.65 + *   Responses
  113.66 + *
  113.67 + *       221 Header follows
  113.68 + *       430 no such article
  113.69 + *       502 no permission
  113.70 + *
  113.71 + *   Response Data:
  113.72 + *
  113.73 + *       art_nr fitting_header_value
  113.74 + * 
  113.75 + * </pre>
  113.76 + * [Source:"draft-ietf-nntp-imp-02.txt"] [Copyright: 1998 S. Barber]
  113.77 + * 
  113.78 + * @author Christian Lins
  113.79 + * @since sonews/0.5.0
  113.80 + */
  113.81 +public class XPatCommand implements Command
  113.82 +{
  113.83 +
  113.84 +  @Override
  113.85 +  public String[] getSupportedCommandStrings()
  113.86 +  {
  113.87 +    return new String[]{"XPAT"};
  113.88 +  }
  113.89 +  
  113.90 +  @Override
  113.91 +  public boolean hasFinished()
  113.92 +  {
  113.93 +    return true;
  113.94 +  }
  113.95 +
  113.96 +  @Override
  113.97 +  public String impliedCapability()
  113.98 +  {
  113.99 +    return null;
 113.100 +  }
 113.101 +
 113.102 +  @Override
 113.103 +  public boolean isStateful()
 113.104 +  {
 113.105 +    return false;
 113.106 +  }
 113.107 +
 113.108 +  @Override
 113.109 +  public void processLine(NNTPConnection conn, final String line, byte[] raw)
 113.110 +    throws IOException, StorageBackendException
 113.111 +  {
 113.112 +    if(conn.getCurrentChannel() == null)
 113.113 +    {
 113.114 +      conn.println("430 no group selected");
 113.115 +      return;
 113.116 +    }
 113.117 +
 113.118 +    String[] command = line.split("\\p{Space}+");
 113.119 +
 113.120 +    // There may be multiple patterns and Thunderbird produces
 113.121 +    // additional spaces between range and pattern
 113.122 +    if(command.length >= 4)
 113.123 +    {
 113.124 +      String header  = command[1].toLowerCase(Locale.US);
 113.125 +      String range   = command[2];
 113.126 +      String pattern = command[3];
 113.127 +
 113.128 +      long start = -1;
 113.129 +      long end   = -1;
 113.130 +      if(range.contains("-"))
 113.131 +      {
 113.132 +        String[] rsplit = range.split("-", 2);
 113.133 +        start = Long.parseLong(rsplit[0]);
 113.134 +        if(rsplit[1].length() > 0)
 113.135 +        {
 113.136 +          end = Long.parseLong(rsplit[1]);
 113.137 +        }
 113.138 +      }
 113.139 +      else // TODO: Handle Message-IDs
 113.140 +      {
 113.141 +        start = Long.parseLong(range);
 113.142 +      }
 113.143 +
 113.144 +      try
 113.145 +      {
 113.146 +        List<Pair<Long, String>> heads = StorageManager.current().
 113.147 +          getArticleHeaders(conn.getCurrentChannel(), start, end, header, pattern);
 113.148 +        
 113.149 +        conn.println("221 header follows");
 113.150 +        for(Pair<Long, String> head : heads)
 113.151 +        {
 113.152 +          conn.println(head.getA() + " " + head.getB());
 113.153 +        }
 113.154 +        conn.println(".");
 113.155 +      }
 113.156 +      catch(PatternSyntaxException ex)
 113.157 +      {
 113.158 +        ex.printStackTrace();
 113.159 +        conn.println("500 invalid pattern syntax");
 113.160 +      }
 113.161 +      catch(StorageBackendException ex)
 113.162 +      {
 113.163 +        ex.printStackTrace();
 113.164 +        conn.println("500 internal server error");
 113.165 +      }
 113.166 +    }
 113.167 +    else
 113.168 +    {
 113.169 +      conn.println("430 invalid command usage");
 113.170 +    }
 113.171 +  }
 113.172 +
 113.173 +}
   114.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   114.2 +++ b/src/org/sonews/daemon/command/package.html	Sun Aug 29 17:28:58 2010 +0200
   114.3 @@ -0,0 +1,1 @@
   114.4 +Contains a class for every NNTP command.
   114.5 \ No newline at end of file
   115.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   115.2 +++ b/src/org/sonews/daemon/package.html	Sun Aug 29 17:28:58 2010 +0200
   115.3 @@ -0,0 +1,1 @@
   115.4 +Contains basic classes of the daemon.
   115.5 \ No newline at end of file
   116.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   116.2 +++ b/src/org/sonews/feed/FeedManager.java	Sun Aug 29 17:28:58 2010 +0200
   116.3 @@ -0,0 +1,54 @@
   116.4 +/*
   116.5 + *   SONEWS News Server
   116.6 + *   see AUTHORS for the list of contributors
   116.7 + *
   116.8 + *   This program is free software: you can redistribute it and/or modify
   116.9 + *   it under the terms of the GNU General Public License as published by
  116.10 + *   the Free Software Foundation, either version 3 of the License, or
  116.11 + *   (at your option) any later version.
  116.12 + *
  116.13 + *   This program is distributed in the hope that it will be useful,
  116.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  116.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  116.16 + *   GNU General Public License for more details.
  116.17 + *
  116.18 + *   You should have received a copy of the GNU General Public License
  116.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  116.20 + */
  116.21 +
  116.22 +package org.sonews.feed;
  116.23 +
  116.24 +import org.sonews.storage.Article;
  116.25 +
  116.26 +/**
  116.27 + * Controlls push and pull feeder.
  116.28 + * @author Christian Lins
  116.29 + * @since sonews/0.5.0
  116.30 + */
  116.31 +public final class FeedManager 
  116.32 +{
  116.33 +
  116.34 +  public static final int TYPE_PULL = 0;
  116.35 +  public static final int TYPE_PUSH = 1;
  116.36 +  
  116.37 +  private static PullFeeder pullFeeder = new PullFeeder();
  116.38 +  private static PushFeeder pushFeeder = new PushFeeder();
  116.39 +  
  116.40 +  /**
  116.41 +   * Reads the peer subscriptions from database and starts the appropriate
  116.42 +   * PullFeeder or PushFeeder.
  116.43 +   */
  116.44 +  public static synchronized void startFeeding()
  116.45 +  {
  116.46 +    pullFeeder.start();
  116.47 +    pushFeeder.start();
  116.48 +  }
  116.49 +  
  116.50 +  public static void queueForPush(Article article)
  116.51 +  {
  116.52 +    pushFeeder.queueForPush(article);
  116.53 +  }
  116.54 +  
  116.55 +  private FeedManager() {}
  116.56 +  
  116.57 +}
   117.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   117.2 +++ b/src/org/sonews/feed/PullFeeder.java	Sun Aug 29 17:28:58 2010 +0200
   117.3 @@ -0,0 +1,276 @@
   117.4 +/*
   117.5 + *   SONEWS News Server
   117.6 + *   see AUTHORS for the list of contributors
   117.7 + *
   117.8 + *   This program is free software: you can redistribute it and/or modify
   117.9 + *   it under the terms of the GNU General Public License as published by
  117.10 + *   the Free Software Foundation, either version 3 of the License, or
  117.11 + *   (at your option) any later version.
  117.12 + *
  117.13 + *   This program is distributed in the hope that it will be useful,
  117.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  117.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  117.16 + *   GNU General Public License for more details.
  117.17 + *
  117.18 + *   You should have received a copy of the GNU General Public License
  117.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  117.20 + */
  117.21 +
  117.22 +package org.sonews.feed;
  117.23 +
  117.24 +import java.io.BufferedReader;
  117.25 +import java.io.IOException;
  117.26 +import java.io.InputStreamReader;
  117.27 +import java.io.PrintWriter;
  117.28 +import java.net.Socket;
  117.29 +import java.net.SocketException;
  117.30 +import java.net.UnknownHostException;
  117.31 +import java.util.ArrayList;
  117.32 +import java.util.HashMap;
  117.33 +import java.util.HashSet;
  117.34 +import java.util.List;
  117.35 +import java.util.Map;
  117.36 +import java.util.Set;
  117.37 +import java.util.logging.Level;
  117.38 +import org.sonews.config.Config;
  117.39 +import org.sonews.daemon.AbstractDaemon;
  117.40 +import org.sonews.util.Log;
  117.41 +import org.sonews.storage.StorageBackendException;
  117.42 +import org.sonews.storage.StorageManager;
  117.43 +import org.sonews.util.Stats;
  117.44 +import org.sonews.util.io.ArticleReader;
  117.45 +import org.sonews.util.io.ArticleWriter;
  117.46 +
  117.47 +/**
  117.48 + * The PullFeeder class regularily checks another Newsserver for new
  117.49 + * messages.
  117.50 + * @author Christian Lins
  117.51 + * @since sonews/0.5.0
  117.52 + */
  117.53 +class PullFeeder extends AbstractDaemon
  117.54 +{
  117.55 +  
  117.56 +  private Map<Subscription, Integer> highMarks = new HashMap<Subscription, Integer>();
  117.57 +  private BufferedReader             in;
  117.58 +  private PrintWriter                out;
  117.59 +  private Set<Subscription>          subscriptions = new HashSet<Subscription>();
  117.60 +  
  117.61 +  private void addSubscription(final Subscription sub)
  117.62 +  {
  117.63 +    subscriptions.add(sub);
  117.64 +
  117.65 +    if(!highMarks.containsKey(sub))
  117.66 +    {
  117.67 +      // Set a initial highMark
  117.68 +      this.highMarks.put(sub, 0);
  117.69 +    }
  117.70 +  }
  117.71 +  
  117.72 +  /**
  117.73 +   * Changes to the given group and returns its high mark.
  117.74 +   * @param groupName
  117.75 +   * @return
  117.76 +   */
  117.77 +  private int changeGroup(String groupName)
  117.78 +    throws IOException
  117.79 +  {
  117.80 +    this.out.print("GROUP " + groupName + "\r\n");
  117.81 +    this.out.flush();
  117.82 +    
  117.83 +    String line = this.in.readLine();
  117.84 +    if(line.startsWith("211 "))
  117.85 +    {
  117.86 +      int highmark = Integer.parseInt(line.split(" ")[3]);
  117.87 +      return highmark;
  117.88 +    }
  117.89 +    else
  117.90 +    {
  117.91 +      throw new IOException("GROUP " + groupName + " returned: " + line);
  117.92 +    }
  117.93 +  }
  117.94 +  
  117.95 +  private void connectTo(final String host, final int port)
  117.96 +    throws IOException, UnknownHostException
  117.97 +  {
  117.98 +    Socket socket = new Socket(host, port);
  117.99 +    this.out = new PrintWriter(socket.getOutputStream());
 117.100 +    this.in  = new BufferedReader(new InputStreamReader(socket.getInputStream()));
 117.101 +
 117.102 +    String line = in.readLine();
 117.103 +    if(!(line.charAt(0) == '2')) // Could be 200 or 2xx if posting is not allowed
 117.104 +    {
 117.105 +      throw new IOException(line);
 117.106 +    }
 117.107 +
 117.108 +    // Send MODE READER to peer, some newsservers are friendlier then
 117.109 +    this.out.println("MODE READER\r\n");
 117.110 +    this.out.flush();
 117.111 +    line = this.in.readLine();
 117.112 +  }
 117.113 +  
 117.114 +  private void disconnect()
 117.115 +    throws IOException
 117.116 +  {
 117.117 +    this.out.print("QUIT\r\n");
 117.118 +    this.out.flush();
 117.119 +    this.out.close();
 117.120 +    this.in.close();
 117.121 +    
 117.122 +    this.out = null;
 117.123 +    this.in  = null;
 117.124 +  }
 117.125 +  
 117.126 +  /**
 117.127 +   * Uses the OVER or XOVER command to get a list of message overviews that
 117.128 +   * may be unknown to this feeder and are about to be peered.
 117.129 +   * @param start
 117.130 +   * @param end
 117.131 +   * @return A list of message ids with potentially interesting messages.
 117.132 +   */
 117.133 +  private List<String> over(int start, int end)
 117.134 +    throws IOException
 117.135 +  {
 117.136 +    this.out.print("OVER " + start + "-" + end + "\r\n");
 117.137 +    this.out.flush();
 117.138 +    
 117.139 +    String line = this.in.readLine();
 117.140 +    if(line.startsWith("500 ")) // OVER not supported
 117.141 +    {
 117.142 +      this.out.print("XOVER " + start + "-" + end + "\r\n");
 117.143 +      this.out.flush();
 117.144 +      
 117.145 +      line = this.in.readLine();
 117.146 +    }
 117.147 +    
 117.148 +    if(line.startsWith("224 "))
 117.149 +    {
 117.150 +      List<String> messages = new ArrayList<String>();
 117.151 +      line = this.in.readLine();
 117.152 +      while(!".".equals(line))
 117.153 +      {
 117.154 +        String mid = line.split("\t")[4]; // 5th should be the Message-ID
 117.155 +        messages.add(mid);
 117.156 +        line = this.in.readLine();
 117.157 +      }
 117.158 +      return messages;
 117.159 +    }
 117.160 +    else
 117.161 +    {
 117.162 +      throw new IOException("Server return for OVER/XOVER: " + line);
 117.163 +    }
 117.164 +  }
 117.165 +  
 117.166 +  @Override
 117.167 +  public void run()
 117.168 +  {
 117.169 +    while(isRunning())
 117.170 +    {
 117.171 +      int pullInterval = 1000 * 
 117.172 +        Config.inst().get(Config.FEED_PULLINTERVAL, 3600);
 117.173 +      String host = "localhost";
 117.174 +      int    port = 119;
 117.175 +      
 117.176 +      Log.get().info("Start PullFeeder run...");
 117.177 +
 117.178 +      try
 117.179 +      {
 117.180 +        this.subscriptions.clear();
 117.181 +        List<Subscription> subsPull = StorageManager.current()
 117.182 +          .getSubscriptions(FeedManager.TYPE_PULL);
 117.183 +        for(Subscription sub : subsPull)
 117.184 +        {
 117.185 +          addSubscription(sub);
 117.186 +        }
 117.187 +      }
 117.188 +      catch(StorageBackendException ex)
 117.189 +      {
 117.190 +        Log.get().log(Level.SEVERE, host, ex);
 117.191 +      }
 117.192 +
 117.193 +      try
 117.194 +      {
 117.195 +        for(Subscription sub : this.subscriptions)
 117.196 +        {
 117.197 +          host = sub.getHost();
 117.198 +          port = sub.getPort();
 117.199 +
 117.200 +          try
 117.201 +          {
 117.202 +            Log.get().info("Feeding " + sub.getGroup() + " from " + sub.getHost());
 117.203 +            try
 117.204 +            {
 117.205 +              connectTo(host, port);
 117.206 +            }
 117.207 +            catch(SocketException ex)
 117.208 +            {
 117.209 +              Log.get().info("Skipping " + sub.getHost() + ": " + ex);
 117.210 +              continue;
 117.211 +            }
 117.212 +            
 117.213 +            int oldMark = this.highMarks.get(sub);
 117.214 +            int newMark = changeGroup(sub.getGroup());
 117.215 +            
 117.216 +            if(oldMark != newMark)
 117.217 +            {
 117.218 +              List<String> messageIDs = over(oldMark, newMark);
 117.219 +
 117.220 +              for(String messageID : messageIDs)
 117.221 +              {
 117.222 +                if(!StorageManager.current().isArticleExisting(messageID))
 117.223 +                {
 117.224 +                  try
 117.225 +                  {
 117.226 +                    // Post the message via common socket connection
 117.227 +                    ArticleReader aread =
 117.228 +                      new ArticleReader(sub.getHost(), sub.getPort(), messageID);
 117.229 +                    byte[] abuf = aread.getArticleData();
 117.230 +                    if(abuf == null)
 117.231 +                    {
 117.232 +                      Log.get().warning("Could not feed " + messageID
 117.233 +                        + " from " + sub.getHost());
 117.234 +                    }
 117.235 +                    else
 117.236 +                    {
 117.237 +                      Log.get().info("Feeding " + messageID);
 117.238 +                      ArticleWriter awrite = new ArticleWriter(
 117.239 +                        "localhost", Config.inst().get(Config.PORT, 119));
 117.240 +                      awrite.writeArticle(abuf);
 117.241 +                      awrite.close();
 117.242 +                    }
 117.243 +                    Stats.getInstance().mailFeeded(sub.getGroup());
 117.244 +                  }
 117.245 +                  catch(IOException ex)
 117.246 +                  {
 117.247 +                    // There may be a temporary network failure
 117.248 +                    ex.printStackTrace();
 117.249 +                    Log.get().warning("Skipping mail " + messageID + " due to exception.");
 117.250 +                  }
 117.251 +                }
 117.252 +              } // for(;;)
 117.253 +              this.highMarks.put(sub, newMark);
 117.254 +            }
 117.255 +            
 117.256 +            disconnect();
 117.257 +          }
 117.258 +          catch(StorageBackendException ex)
 117.259 +          {
 117.260 +            ex.printStackTrace();
 117.261 +          }
 117.262 +          catch(IOException ex)
 117.263 +          {
 117.264 +            ex.printStackTrace();
 117.265 +            Log.get().severe("PullFeeder run stopped due to exception.");
 117.266 +          }
 117.267 +        } // for(Subscription sub : subscriptions)
 117.268 +        
 117.269 +        Log.get().info("PullFeeder run ended. Waiting " + pullInterval / 1000 + "s");
 117.270 +        Thread.sleep(pullInterval);
 117.271 +      }
 117.272 +      catch(InterruptedException ex)
 117.273 +      {
 117.274 +        Log.get().warning(ex.getMessage());
 117.275 +      }
 117.276 +    }
 117.277 +  }
 117.278 +  
 117.279 +}
   118.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   118.2 +++ b/src/org/sonews/feed/PushFeeder.java	Sun Aug 29 17:28:58 2010 +0200
   118.3 @@ -0,0 +1,118 @@
   118.4 +/*
   118.5 + *   SONEWS News Server
   118.6 + *   see AUTHORS for the list of contributors
   118.7 + *
   118.8 + *   This program is free software: you can redistribute it and/or modify
   118.9 + *   it under the terms of the GNU General Public License as published by
  118.10 + *   the Free Software Foundation, either version 3 of the License, or
  118.11 + *   (at your option) any later version.
  118.12 + *
  118.13 + *   This program is distributed in the hope that it will be useful,
  118.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  118.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  118.16 + *   GNU General Public License for more details.
  118.17 + *
  118.18 + *   You should have received a copy of the GNU General Public License
  118.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  118.20 + */
  118.21 +
  118.22 +package org.sonews.feed;
  118.23 +
  118.24 +import java.io.IOException;
  118.25 +import java.util.List;
  118.26 +import java.util.concurrent.ConcurrentLinkedQueue;
  118.27 +import org.sonews.daemon.AbstractDaemon;
  118.28 +import org.sonews.storage.Article;
  118.29 +import org.sonews.storage.Headers;
  118.30 +import org.sonews.storage.StorageBackendException;
  118.31 +import org.sonews.storage.StorageManager;
  118.32 +import org.sonews.util.Log;
  118.33 +import org.sonews.util.io.ArticleWriter;
  118.34 +
  118.35 +/**
  118.36 + * Pushes new articles to remote newsservers. This feeder sleeps until a new
  118.37 + * message is posted to the sonews instance.
  118.38 + * @author Christian Lins
  118.39 + * @since sonews/0.5.0
  118.40 + */
  118.41 +class PushFeeder extends AbstractDaemon
  118.42 +{
  118.43 +  
  118.44 +  private ConcurrentLinkedQueue<Article> articleQueue = 
  118.45 +    new ConcurrentLinkedQueue<Article>();
  118.46 +  
  118.47 +  @Override
  118.48 +  public void run()
  118.49 +  {
  118.50 +    while(isRunning())
  118.51 +    {
  118.52 +      try
  118.53 +      {
  118.54 +        synchronized(this)
  118.55 +        {
  118.56 +          this.wait();
  118.57 +        }
  118.58 +        
  118.59 +        List<Subscription> subscriptions = StorageManager.current()
  118.60 +          .getSubscriptions(FeedManager.TYPE_PUSH);
  118.61 +
  118.62 +        Article  article = this.articleQueue.poll();
  118.63 +        String[] groups  = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
  118.64 +        Log.get().info("PushFeed: " + article.getMessageID());
  118.65 +        for(Subscription sub : subscriptions)
  118.66 +        {
  118.67 +          // Circle check
  118.68 +          if(article.getHeader(Headers.PATH)[0].contains(sub.getHost()))
  118.69 +          {
  118.70 +            Log.get().info(article.getMessageID() + " skipped for host "
  118.71 +              + sub.getHost());
  118.72 +            continue;
  118.73 +          }
  118.74 +
  118.75 +          try
  118.76 +          {
  118.77 +            for(String group : groups)
  118.78 +            {
  118.79 +              if(sub.getGroup().equals(group))
  118.80 +              {
  118.81 +                // Delete headers that may cause problems
  118.82 +                article.removeHeader(Headers.NNTP_POSTING_DATE);
  118.83 +                article.removeHeader(Headers.NNTP_POSTING_HOST);
  118.84 +                article.removeHeader(Headers.X_COMPLAINTS_TO);
  118.85 +                article.removeHeader(Headers.X_TRACE);
  118.86 +                article.removeHeader(Headers.XREF);
  118.87 +                
  118.88 +                // POST the message to remote server
  118.89 +                ArticleWriter awriter = new ArticleWriter(sub.getHost(), sub.getPort());
  118.90 +                awriter.writeArticle(article);
  118.91 +                break;
  118.92 +              }
  118.93 +            }
  118.94 +          }
  118.95 +          catch(IOException ex)
  118.96 +          {
  118.97 +            Log.get().warning(ex.toString());
  118.98 +          }
  118.99 +        }
 118.100 +      }
 118.101 +      catch(StorageBackendException ex)
 118.102 +      {
 118.103 +        Log.get().severe(ex.toString());
 118.104 +      }
 118.105 +      catch(InterruptedException ex)
 118.106 +      {
 118.107 +        Log.get().warning("PushFeeder interrupted: " + ex);
 118.108 +      }
 118.109 +    }
 118.110 +  }
 118.111 +  
 118.112 +  public void queueForPush(Article article)
 118.113 +  {
 118.114 +    this.articleQueue.add(article);
 118.115 +    synchronized(this)
 118.116 +    {
 118.117 +      this.notifyAll();
 118.118 +    }
 118.119 +  }
 118.120 +  
 118.121 +}
   119.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   119.2 +++ b/src/org/sonews/feed/Subscription.java	Sun Aug 29 17:28:58 2010 +0200
   119.3 @@ -0,0 +1,84 @@
   119.4 +/*
   119.5 + *   SONEWS News Server
   119.6 + *   see AUTHORS for the list of contributors
   119.7 + *
   119.8 + *   This program is free software: you can redistribute it and/or modify
   119.9 + *   it under the terms of the GNU General Public License as published by
  119.10 + *   the Free Software Foundation, either version 3 of the License, or
  119.11 + *   (at your option) any later version.
  119.12 + *
  119.13 + *   This program is distributed in the hope that it will be useful,
  119.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  119.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  119.16 + *   GNU General Public License for more details.
  119.17 + *
  119.18 + *   You should have received a copy of the GNU General Public License
  119.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  119.20 + */
  119.21 +
  119.22 +package org.sonews.feed;
  119.23 +
  119.24 +/**
  119.25 + * For every group that is synchronized with or from a remote newsserver 
  119.26 + * a Subscription instance exists.
  119.27 + * @author Christian Lins
  119.28 + * @since sonews/0.5.0
  119.29 + */
  119.30 +public class Subscription 
  119.31 +{
  119.32 +
  119.33 +  private String host;
  119.34 +  private int    port;
  119.35 +  private int    feedtype;
  119.36 +  private String group;
  119.37 +  
  119.38 +  public Subscription(String host, int port, int feedtype, String group)
  119.39 +  {
  119.40 +    this.host     = host;
  119.41 +    this.port     = port;
  119.42 +    this.feedtype = feedtype;
  119.43 +    this.group    = group;
  119.44 +  }
  119.45 +
  119.46 +  @Override
  119.47 +  public boolean equals(Object obj)
  119.48 +  {
  119.49 +    if(obj instanceof Subscription)
  119.50 +    {
  119.51 +      Subscription sub = (Subscription)obj;
  119.52 +      return sub.host.equals(host) && sub.group.equals(group) 
  119.53 +        && sub.port == port && sub.feedtype == feedtype;
  119.54 +    }
  119.55 +    else
  119.56 +    {
  119.57 +      return false;
  119.58 +    }
  119.59 +  }
  119.60 +
  119.61 +  @Override
  119.62 +  public int hashCode()
  119.63 +  {
  119.64 +    return host.hashCode() + port + feedtype + group.hashCode();
  119.65 +  }
  119.66 +
  119.67 +  public int getFeedtype()
  119.68 +  {
  119.69 +    return feedtype;
  119.70 +  }
  119.71 +
  119.72 +  public String getGroup()
  119.73 +  {
  119.74 +    return group;
  119.75 +  }
  119.76 +
  119.77 +  public String getHost()
  119.78 +  {
  119.79 +    return host;
  119.80 +  }
  119.81 +
  119.82 +  public int getPort()
  119.83 +  {
  119.84 +    return port;
  119.85 +  }
  119.86 +  
  119.87 +}
   120.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   120.2 +++ b/src/org/sonews/feed/package.html	Sun Aug 29 17:28:58 2010 +0200
   120.3 @@ -0,0 +1,2 @@
   120.4 +Contains classes for the peering functionality, e.g. pulling and pushing
   120.5 +mails from and to remote newsservers.
   120.6 \ No newline at end of file
   121.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   121.2 +++ b/src/org/sonews/mlgw/Dispatcher.java	Sun Aug 29 17:28:58 2010 +0200
   121.3 @@ -0,0 +1,301 @@
   121.4 +/*
   121.5 + *   SONEWS News Server
   121.6 + *   see AUTHORS for the list of contributors
   121.7 + *
   121.8 + *   This program is free software: you can redistribute it and/or modify
   121.9 + *   it under the terms of the GNU General Public License as published by
  121.10 + *   the Free Software Foundation, either version 3 of the License, or
  121.11 + *   (at your option) any later version.
  121.12 + *
  121.13 + *   This program is distributed in the hope that it will be useful,
  121.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  121.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  121.16 + *   GNU General Public License for more details.
  121.17 + *
  121.18 + *   You should have received a copy of the GNU General Public License
  121.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  121.20 + */
  121.21 +
  121.22 +package org.sonews.mlgw;
  121.23 +
  121.24 +import java.io.IOException;
  121.25 +import java.util.ArrayList;
  121.26 +import java.util.List;
  121.27 +import java.util.regex.Matcher;
  121.28 +import java.util.regex.Pattern;
  121.29 +import javax.mail.Address;
  121.30 +import javax.mail.Authenticator;
  121.31 +import javax.mail.Message;
  121.32 +import javax.mail.MessagingException;
  121.33 +import javax.mail.PasswordAuthentication;
  121.34 +import javax.mail.internet.InternetAddress;
  121.35 +import org.sonews.config.Config;
  121.36 +import org.sonews.storage.Article;
  121.37 +import org.sonews.storage.Group;
  121.38 +import org.sonews.storage.Headers;
  121.39 +import org.sonews.storage.StorageBackendException;
  121.40 +import org.sonews.storage.StorageManager;
  121.41 +import org.sonews.util.Log;
  121.42 +import org.sonews.util.Stats;
  121.43 +
  121.44 +/**
  121.45 + * Dispatches messages from mailing list to newsserver or vice versa.
  121.46 + * @author Christian Lins
  121.47 + * @since sonews/0.5.0
  121.48 + */
  121.49 +public class Dispatcher 
  121.50 +{
  121.51 +
  121.52 +  static class PasswordAuthenticator extends Authenticator
  121.53 +  {
  121.54 +    
  121.55 +    @Override
  121.56 +    public PasswordAuthentication getPasswordAuthentication()
  121.57 +    {
  121.58 +      final String username = 
  121.59 +        Config.inst().get(Config.MLSEND_USER, "user");
  121.60 +      final String password = 
  121.61 +        Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
  121.62 +
  121.63 +      return new PasswordAuthentication(username, password);
  121.64 +    }
  121.65 +    
  121.66 +  }
  121.67 +
  121.68 +  /**
  121.69 +   * Chunks out the email address of the full List-Post header field.
  121.70 +   * @param listPostValue
  121.71 +   * @return The matching email address or null
  121.72 +   */
  121.73 +  private static String chunkListPost(String listPostValue)
  121.74 +  {
  121.75 +    // listPostValue is of form "<mailto:dev@openoffice.org>"
  121.76 +    Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
  121.77 +    Matcher mailMatcher = mailPattern.matcher(listPostValue);
  121.78 +    if(mailMatcher.find())
  121.79 +    {
  121.80 +      return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
  121.81 +    }
  121.82 +    else
  121.83 +    {
  121.84 +      return null;
  121.85 +    }
  121.86 +  }
  121.87 +
  121.88 +  /**
  121.89 +   * This method inspects the header of the given message, trying
  121.90 +   * to find the most appropriate recipient.
  121.91 +   * @param msg
  121.92 +   * @param fallback If this is false only List-Post and X-List-Post headers
  121.93 +   *                 are examined.
  121.94 +   * @return null or fitting group name for the given message.
  121.95 +   */
  121.96 +  private static List<String> getGroupFor(final Message msg, final boolean fallback)
  121.97 +    throws MessagingException, StorageBackendException
  121.98 +  {
  121.99 +    List<String> groups = null;
 121.100 +
 121.101 +    // Is there a List-Post header?
 121.102 +    String[]        listPost = msg.getHeader(Headers.LIST_POST);
 121.103 +    InternetAddress listPostAddr;
 121.104 +
 121.105 +    if(listPost == null || listPost.length == 0 || "".equals(listPost[0]))
 121.106 +    {
 121.107 +      // Is there a X-List-Post header?
 121.108 +      listPost = msg.getHeader(Headers.X_LIST_POST);
 121.109 +    }
 121.110 +
 121.111 +    if(listPost != null && listPost.length > 0 
 121.112 +      && !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null)
 121.113 +    {
 121.114 +      // listPost[0] is of form "<mailto:dev@openoffice.org>"
 121.115 +      listPost[0]  = chunkListPost(listPost[0]);
 121.116 +      listPostAddr = new InternetAddress(listPost[0], false);
 121.117 +      groups = StorageManager.current().getGroupsForList(listPostAddr.getAddress());
 121.118 +    }
 121.119 +    else if(fallback)
 121.120 +    {
 121.121 +      Log.get().info("Using fallback recipient discovery for: " + msg.getSubject());
 121.122 +      groups = new ArrayList<String>();
 121.123 +      // Fallback to TO/CC/BCC addresses
 121.124 +      Address[] to = msg.getAllRecipients();
 121.125 +      for(Address toa : to) // Address can have '<' '>' around
 121.126 +      {
 121.127 +        if(toa instanceof InternetAddress)
 121.128 +        {
 121.129 +          List<String> g = StorageManager.current()
 121.130 +            .getGroupsForList(((InternetAddress)toa).getAddress());
 121.131 +          groups.addAll(g);
 121.132 +        }
 121.133 +      }
 121.134 +    }
 121.135 +    
 121.136 +    return groups;
 121.137 +  }
 121.138 +  
 121.139 +  /**
 121.140 +   * Posts a message that was received from a mailing list to the 
 121.141 +   * appropriate newsgroup.
 121.142 +   * If the message already exists in the storage, this message checks
 121.143 +   * if it must be posted in an additional group. This can happen for
 121.144 +   * crosspostings in different mailing lists.
 121.145 +   * @param msg
 121.146 +   */
 121.147 +  public static boolean toGroup(final Message msg)
 121.148 +  {
 121.149 +    try
 121.150 +    {
 121.151 +      // Create new Article object
 121.152 +      Article article = new Article(msg);
 121.153 +      boolean posted  = false;
 121.154 +
 121.155 +      // Check if this mail is already existing the storage
 121.156 +      boolean updateReq = 
 121.157 +        StorageManager.current().isArticleExisting(article.getMessageID());
 121.158 +
 121.159 +      List<String> newsgroups = getGroupFor(msg, !updateReq);
 121.160 +      List<String> oldgroups  = new ArrayList<String>();
 121.161 +      if(updateReq)
 121.162 +      {
 121.163 +        // Check for duplicate entries of the same group
 121.164 +        Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
 121.165 +        List<Group> oldGroups = oldArticle.getGroups();
 121.166 +        for(Group oldGroup : oldGroups)
 121.167 +        {
 121.168 +          if(!newsgroups.contains(oldGroup.getName()))
 121.169 +          {
 121.170 +            oldgroups.add(oldGroup.getName());
 121.171 +          }
 121.172 +        }
 121.173 +      }
 121.174 +
 121.175 +      if(newsgroups.size() > 0)
 121.176 +      {
 121.177 +        newsgroups.addAll(oldgroups);
 121.178 +        StringBuilder groups = new StringBuilder();
 121.179 +        for(int n = 0; n < newsgroups.size(); n++)
 121.180 +        {
 121.181 +          groups.append(newsgroups.get(n));
 121.182 +          if (n + 1 != newsgroups.size())
 121.183 +          {
 121.184 +            groups.append(',');
 121.185 +          }
 121.186 +        }
 121.187 +        Log.get().info("Posting to group " + groups.toString());
 121.188 +
 121.189 +        article.setGroup(groups.toString());
 121.190 +        //article.removeHeader(Headers.REPLY_TO);
 121.191 +        //article.removeHeader(Headers.TO);
 121.192 +
 121.193 +        // Write article to database
 121.194 +        if(updateReq)
 121.195 +        {
 121.196 +          Log.get().info("Updating " + article.getMessageID()
 121.197 +            + " with additional groups");
 121.198 +          StorageManager.current().delete(article.getMessageID());
 121.199 +          StorageManager.current().addArticle(article);
 121.200 +        }
 121.201 +        else
 121.202 +        {
 121.203 +          Log.get().info("Gatewaying " + article.getMessageID() + " to "
 121.204 +            + article.getHeader(Headers.NEWSGROUPS)[0]);
 121.205 +          StorageManager.current().addArticle(article);
 121.206 +          Stats.getInstance().mailGatewayed(
 121.207 +            article.getHeader(Headers.NEWSGROUPS)[0]);
 121.208 +        }
 121.209 +        posted = true;
 121.210 +      }
 121.211 +      else
 121.212 +      {
 121.213 +        StringBuilder buf = new StringBuilder();
 121.214 +        for (Address toa : msg.getAllRecipients())
 121.215 +        {
 121.216 +          buf.append(' ');
 121.217 +          buf.append(toa.toString());
 121.218 +        }
 121.219 +        buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
 121.220 +        Log.get().warning("No group for" + buf.toString());
 121.221 +      }
 121.222 +      return posted;
 121.223 +    }
 121.224 +    catch(Exception ex)
 121.225 +    {
 121.226 +      ex.printStackTrace();
 121.227 +      return false;
 121.228 +    }
 121.229 +  }
 121.230 +  
 121.231 +  /**
 121.232 +   * Mails a message received through NNTP to the appropriate mailing list.
 121.233 +   * This method MAY be called several times by PostCommand for the same
 121.234 +   * article.
 121.235 +   */
 121.236 +  public static void toList(Article article, String group)
 121.237 +    throws IOException, MessagingException, StorageBackendException
 121.238 +  {
 121.239 +    // Get mailing lists for the group of this article
 121.240 +    List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
 121.241 +
 121.242 +    if(rcptAddresses == null || rcptAddresses.size() == 0)
 121.243 +    {
 121.244 +      Log.get().warning("No ML-address for " + group + " found.");
 121.245 +      return;
 121.246 +    }
 121.247 +
 121.248 +    for(String rcptAddress : rcptAddresses)
 121.249 +    {
 121.250 +      // Compose message and send it via given SMTP-Host
 121.251 +      String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
 121.252 +      int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
 121.253 +      String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
 121.254 +      String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
 121.255 +      String smtpFrom = Config.inst().get(
 121.256 +        Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
 121.257 +
 121.258 +      // TODO: Make Article cloneable()
 121.259 +      article.getMessageID(); // Make sure an ID is existing
 121.260 +      article.removeHeader(Headers.NEWSGROUPS);
 121.261 +      article.removeHeader(Headers.PATH);
 121.262 +      article.removeHeader(Headers.LINES);
 121.263 +      article.removeHeader(Headers.BYTES);
 121.264 +
 121.265 +      article.setHeader("To", rcptAddress);
 121.266 +      //article.setHeader("Reply-To", listAddress);
 121.267 +
 121.268 +      if (Config.inst().get(Config.MLSEND_RW_SENDER, false))
 121.269 +      {
 121.270 +        rewriteSenderAddress(article); // Set the SENDER address
 121.271 +      }
 121.272 +
 121.273 +      SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
 121.274 +      smtpTransport.send(article, smtpFrom, rcptAddress);
 121.275 +      smtpTransport.close();
 121.276 +
 121.277 +      Stats.getInstance().mailGatewayed(group);
 121.278 +      Log.get().info("MLGateway: Mail " + article.getHeader("Subject")[0]
 121.279 +        + " was delivered to " + rcptAddress + ".");
 121.280 +    }
 121.281 +  }
 121.282 +  
 121.283 +  /**
 121.284 +   * Sets the SENDER header of the given MimeMessage. This might be necessary
 121.285 +   * for moderated groups that does not allow the "normal" FROM sender.
 121.286 +   * @param msg
 121.287 +   * @throws javax.mail.MessagingException
 121.288 +   */
 121.289 +  private static void rewriteSenderAddress(Article msg)
 121.290 +    throws MessagingException
 121.291 +  {
 121.292 +    String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
 121.293 +
 121.294 +    if(mlAddress != null)
 121.295 +    {
 121.296 +      msg.setHeader(Headers.SENDER, mlAddress);
 121.297 +    }
 121.298 +    else
 121.299 +    {
 121.300 +      throw new MessagingException("Cannot rewrite SENDER header!");
 121.301 +    }
 121.302 +  }
 121.303 +  
 121.304 +}
   122.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   122.2 +++ b/src/org/sonews/mlgw/MailPoller.java	Sun Aug 29 17:28:58 2010 +0200
   122.3 @@ -0,0 +1,151 @@
   122.4 +/*
   122.5 + *   SONEWS News Server
   122.6 + *   see AUTHORS for the list of contributors
   122.7 + *
   122.8 + *   This program is free software: you can redistribute it and/or modify
   122.9 + *   it under the terms of the GNU General Public License as published by
  122.10 + *   the Free Software Foundation, either version 3 of the License, or
  122.11 + *   (at your option) any later version.
  122.12 + *
  122.13 + *   This program is distributed in the hope that it will be useful,
  122.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  122.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  122.16 + *   GNU General Public License for more details.
  122.17 + *
  122.18 + *   You should have received a copy of the GNU General Public License
  122.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  122.20 + */
  122.21 +
  122.22 +package org.sonews.mlgw;
  122.23 +
  122.24 +import java.util.Properties;
  122.25 +import javax.mail.AuthenticationFailedException;
  122.26 +import javax.mail.Authenticator;
  122.27 +import javax.mail.Flags.Flag;
  122.28 +import javax.mail.Folder;
  122.29 +import javax.mail.Message;
  122.30 +import javax.mail.MessagingException;
  122.31 +import javax.mail.NoSuchProviderException;
  122.32 +import javax.mail.PasswordAuthentication;
  122.33 +import javax.mail.Session;
  122.34 +import javax.mail.Store;
  122.35 +import org.sonews.config.Config;
  122.36 +import org.sonews.daemon.AbstractDaemon;
  122.37 +import org.sonews.util.Log;
  122.38 +import org.sonews.util.Stats;
  122.39 +
  122.40 +/**
  122.41 + * Daemon polling for new mails in a POP3 account to be delivered to newsgroups.
  122.42 + * @author Christian Lins
  122.43 + * @since sonews/0.5.0
  122.44 + */
  122.45 +public class MailPoller extends AbstractDaemon
  122.46 +{
  122.47 +
  122.48 +  static class PasswordAuthenticator extends Authenticator
  122.49 +  {
  122.50 +    
  122.51 +    @Override
  122.52 +    public PasswordAuthentication getPasswordAuthentication()
  122.53 +    {
  122.54 +      final String username = 
  122.55 +        Config.inst().get(Config.MLPOLL_USER, "user");
  122.56 +      final String password = 
  122.57 +        Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
  122.58 +
  122.59 +      return new PasswordAuthentication(username, password);
  122.60 +    }
  122.61 +    
  122.62 +  }
  122.63 +  
  122.64 +  @Override
  122.65 +  public void run()
  122.66 +  {
  122.67 +    Log.get().info("Starting Mailinglist Poller...");
  122.68 +    int errors = 0;
  122.69 +    while(isRunning())
  122.70 +    {
  122.71 +      try
  122.72 +      {
  122.73 +        // Wait some time between runs. At the beginning has advantages,
  122.74 +        // because the wait is not skipped if an exception occurs.
  122.75 +        Thread.sleep(60000 * (errors + 1)); // one minute * errors
  122.76 +        
  122.77 +        final String host     = 
  122.78 +          Config.inst().get(Config.MLPOLL_HOST, "samplehost");
  122.79 +        final String username = 
  122.80 +          Config.inst().get(Config.MLPOLL_USER, "user");
  122.81 +        final String password = 
  122.82 +          Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
  122.83 +        
  122.84 +        Stats.getInstance().mlgwRunStart();
  122.85 +        
  122.86 +        // Create empty properties
  122.87 +        Properties props = System.getProperties();
  122.88 +        props.put("mail.pop3.host", host);
  122.89 +        props.put("mail.mime.address.strict", "false");
  122.90 +
  122.91 +        // Get session
  122.92 +        Session session = Session.getInstance(props);
  122.93 +
  122.94 +        // Get the store
  122.95 +        Store store = session.getStore("pop3");
  122.96 +        store.connect(host, 110, username, password);
  122.97 +
  122.98 +        // Get folder
  122.99 +        Folder folder = store.getFolder("INBOX");
 122.100 +        folder.open(Folder.READ_WRITE);
 122.101 +
 122.102 +        // Get directory
 122.103 +        Message[] messages = folder.getMessages();
 122.104 +
 122.105 +        // Dispatch messages and delete it afterwards on the inbox
 122.106 +        for(Message message : messages)
 122.107 +        {
 122.108 +          if(Dispatcher.toGroup(message)
 122.109 +            || Config.inst().get(Config.MLPOLL_DELETEUNKNOWN, false))
 122.110 +          {
 122.111 +            // Delete the message
 122.112 +            message.setFlag(Flag.DELETED, true);
 122.113 +          }
 122.114 +        }
 122.115 +
 122.116 +        // Close connection 
 122.117 +        folder.close(true); // true to expunge deleted messages
 122.118 +        store.close();
 122.119 +        errors = 0;
 122.120 +        
 122.121 +        Stats.getInstance().mlgwRunEnd();
 122.122 +      }
 122.123 +      catch(NoSuchProviderException ex)
 122.124 +      {
 122.125 +        Log.get().severe(ex.toString());
 122.126 +        shutdown();
 122.127 +      }
 122.128 +      catch(AuthenticationFailedException ex)
 122.129 +      {
 122.130 +        // AuthentificationFailedException may be thrown if credentials are
 122.131 +        // bad or if the Mailbox is in use (locked).
 122.132 +        ex.printStackTrace();
 122.133 +        errors = errors < 5 ? errors + 1 : errors;
 122.134 +      }
 122.135 +      catch(InterruptedException ex)
 122.136 +      {
 122.137 +        System.out.println("sonews: " + this + " returns: " + ex);
 122.138 +        return;
 122.139 +      }
 122.140 +      catch(MessagingException ex)
 122.141 +      {
 122.142 +        ex.printStackTrace();
 122.143 +        errors = errors < 5 ? errors + 1 : errors;
 122.144 +      }
 122.145 +      catch(Exception ex)
 122.146 +      {
 122.147 +        ex.printStackTrace();
 122.148 +        errors = errors < 5 ? errors + 1 : errors;
 122.149 +      }
 122.150 +    }
 122.151 +    Log.get().severe("MailPoller exited.");
 122.152 +  }
 122.153 +  
 122.154 +}
   123.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   123.2 +++ b/src/org/sonews/mlgw/SMTPTransport.java	Sun Aug 29 17:28:58 2010 +0200
   123.3 @@ -0,0 +1,133 @@
   123.4 +/*
   123.5 + *   SONEWS News Server
   123.6 + *   see AUTHORS for the list of contributors
   123.7 + *
   123.8 + *   This program is free software: you can redistribute it and/or modify
   123.9 + *   it under the terms of the GNU General Public License as published by
  123.10 + *   the Free Software Foundation, either version 3 of the License, or
  123.11 + *   (at your option) any later version.
  123.12 + *
  123.13 + *   This program is distributed in the hope that it will be useful,
  123.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  123.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  123.16 + *   GNU General Public License for more details.
  123.17 + *
  123.18 + *   You should have received a copy of the GNU General Public License
  123.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  123.20 + */
  123.21 +
  123.22 +package org.sonews.mlgw;
  123.23 +
  123.24 +import java.io.BufferedOutputStream;
  123.25 +import java.io.BufferedReader;
  123.26 +import java.io.IOException;
  123.27 +import java.io.InputStreamReader;
  123.28 +import java.net.Socket;
  123.29 +import java.net.UnknownHostException;
  123.30 +import org.sonews.config.Config;
  123.31 +import org.sonews.storage.Article;
  123.32 +import org.sonews.util.io.ArticleInputStream;
  123.33 +
  123.34 +/**
  123.35 + * Connects to a SMTP server and sends a given Article to it.
  123.36 + * @author Christian Lins
  123.37 + * @since sonews/1.0
  123.38 + */
  123.39 +class SMTPTransport
  123.40 +{
  123.41 +
  123.42 +  protected BufferedReader       in;
  123.43 +  protected BufferedOutputStream out;
  123.44 +  protected Socket               socket;
  123.45 +
  123.46 +  public SMTPTransport(String host, int port)
  123.47 +    throws IOException, UnknownHostException
  123.48 +  {
  123.49 +    socket = new Socket(host, port);
  123.50 +    this.in  = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  123.51 +    this.out = new BufferedOutputStream(socket.getOutputStream());
  123.52 +
  123.53 +    // Read helo from server
  123.54 +    String line = this.in.readLine();
  123.55 +    if(line == null || !line.startsWith("220 "))
  123.56 +    {
  123.57 +      throw new IOException("Invalid helo from server: " + line);
  123.58 +    }
  123.59 +
  123.60 +    // Send HELO to server
  123.61 +    this.out.write(
  123.62 +      ("HELO " + Config.inst().get(Config.HOSTNAME, "localhost") + "\r\n").getBytes("UTF-8"));
  123.63 +    this.out.flush();
  123.64 +    line = this.in.readLine();
  123.65 +    if(line == null || !line.startsWith("250 "))
  123.66 +    {
  123.67 +      throw new IOException("Unexpected reply: " + line);
  123.68 +    }
  123.69 +  }
  123.70 +
  123.71 +  public SMTPTransport(String host)
  123.72 +    throws IOException
  123.73 +  {
  123.74 +    this(host, 25);
  123.75 +  }
  123.76 +
  123.77 +  public void close()
  123.78 +    throws IOException
  123.79 +  {
  123.80 +    this.out.write("QUIT".getBytes("UTF-8"));
  123.81 +    this.out.flush();
  123.82 +    this.in.readLine();
  123.83 +
  123.84 +    this.socket.close();
  123.85 +  }
  123.86 +
  123.87 +  public void send(Article article, String mailFrom, String rcptTo)
  123.88 +    throws IOException
  123.89 +  {
  123.90 +    assert(article != null);
  123.91 +    assert(mailFrom != null);
  123.92 +    assert(rcptTo != null);
  123.93 +
  123.94 +    this.out.write(("MAIL FROM: " + mailFrom).getBytes("UTF-8"));
  123.95 +    this.out.flush();
  123.96 +    String line = this.in.readLine();
  123.97 +    if(line == null || !line.startsWith("250 "))
  123.98 +    {
  123.99 +      throw new IOException("Unexpected reply: " + line);
 123.100 +    }
 123.101 +
 123.102 +    this.out.write(("RCPT TO: " + rcptTo).getBytes("UTF-8"));
 123.103 +    this.out.flush();
 123.104 +    line  = this.in.readLine();
 123.105 +    if(line == null || !line.startsWith("250 "))
 123.106 +    {
 123.107 +      throw new IOException("Unexpected reply: " + line);
 123.108 +    }
 123.109 +
 123.110 +    this.out.write("DATA".getBytes("UTF-8"));
 123.111 +    this.out.flush();
 123.112 +    line = this.in.readLine();
 123.113 +    if(line == null || !line.startsWith("354 "))
 123.114 +    {
 123.115 +      throw new IOException("Unexpected reply: " + line);
 123.116 +    }
 123.117 +
 123.118 +    ArticleInputStream   artStream = new ArticleInputStream(article);
 123.119 +    for(int b = artStream.read(); b >= 0; b = artStream.read())
 123.120 +    {
 123.121 +      this.out.write(b);
 123.122 +    }
 123.123 +
 123.124 +    // Flush the binary stream; important because otherwise the output
 123.125 +    // will be mixed with the PrintWriter.
 123.126 +    this.out.flush();
 123.127 +    this.out.write("\r\n.\r\n".getBytes("UTF-8"));
 123.128 +    this.out.flush();
 123.129 +    line = this.in.readLine();
 123.130 +    if(line == null || !line.startsWith("250 "))
 123.131 +    {
 123.132 +      throw new IOException("Unexpected reply: " + line);
 123.133 +    }
 123.134 +  }
 123.135 +
 123.136 +}
   124.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   124.2 +++ b/src/org/sonews/mlgw/package.html	Sun Aug 29 17:28:58 2010 +0200
   124.3 @@ -0,0 +1,1 @@
   124.4 +Contains classes of the Mailinglist Gateway.
   124.5 \ No newline at end of file
   125.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   125.2 +++ b/src/org/sonews/plugin/Plugin.java	Sun Aug 29 17:28:58 2010 +0200
   125.3 @@ -0,0 +1,42 @@
   125.4 +/*
   125.5 + *   SONEWS News Server
   125.6 + *   see AUTHORS for the list of contributors
   125.7 + *
   125.8 + *   This program is free software: you can redistribute it and/or modify
   125.9 + *   it under the terms of the GNU General Public License as published by
  125.10 + *   the Free Software Foundation, either version 3 of the License, or
  125.11 + *   (at your option) any later version.
  125.12 + *
  125.13 + *   This program is distributed in the hope that it will be useful,
  125.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  125.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  125.16 + *   GNU General Public License for more details.
  125.17 + *
  125.18 + *   You should have received a copy of the GNU General Public License
  125.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  125.20 + */
  125.21 +
  125.22 +package org.sonews.plugin;
  125.23 +
  125.24 +/**
  125.25 + * A generic Plugin for sonews. Implementing classes do not really add new
  125.26 + * functionality to sonews but can use this interface as convenient procedure
  125.27 + * for installing functionality plugins, e.g. Command-Plugins or Storage-Plugins.
  125.28 + * @author Christian Lins
  125.29 + * @since sonews/1.1
  125.30 + */
  125.31 +public interface Plugin
  125.32 +{
  125.33 +
  125.34 +  /**
  125.35 +   * Called when the Plugin is loaded by sonews. This method can be used
  125.36 +   * by implementing classes to install additional or required plugins.
  125.37 +   */
  125.38 +  void load();
  125.39 +
  125.40 +  /**
  125.41 +   * Called when the Plugin is unloaded by sonews.
  125.42 +   */
  125.43 +  void unload();
  125.44 +
  125.45 +}
   126.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   126.2 +++ b/src/org/sonews/storage/Article.java	Sun Aug 29 17:28:58 2010 +0200
   126.3 @@ -0,0 +1,253 @@
   126.4 +/*
   126.5 + *   SONEWS News Server
   126.6 + *   see AUTHORS for the list of contributors
   126.7 + *
   126.8 + *   This program is free software: you can redistribute it and/or modify
   126.9 + *   it under the terms of the GNU General Public License as published by
  126.10 + *   the Free Software Foundation, either version 3 of the License, or
  126.11 + *   (at your option) any later version.
  126.12 + *
  126.13 + *   This program is distributed in the hope that it will be useful,
  126.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  126.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  126.16 + *   GNU General Public License for more details.
  126.17 + *
  126.18 + *   You should have received a copy of the GNU General Public License
  126.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  126.20 + */
  126.21 +
  126.22 +package org.sonews.storage;
  126.23 +
  126.24 +import java.io.ByteArrayInputStream;
  126.25 +import java.io.ByteArrayOutputStream;
  126.26 +import java.io.IOException;
  126.27 +import java.security.MessageDigest;
  126.28 +import java.security.NoSuchAlgorithmException;
  126.29 +import java.util.UUID;
  126.30 +import java.util.ArrayList;
  126.31 +import java.util.Enumeration;
  126.32 +import java.util.List;
  126.33 +import javax.mail.Header;
  126.34 +import javax.mail.Message;
  126.35 +import javax.mail.MessagingException;
  126.36 +import javax.mail.internet.InternetHeaders;
  126.37 +import org.sonews.config.Config;
  126.38 +
  126.39 +/**
  126.40 + * Represents a newsgroup article.
  126.41 + * @author Christian Lins
  126.42 + * @author Denis Schwerdel
  126.43 + * @since n3tpd/0.1
  126.44 + */
  126.45 +public class Article extends ArticleHead
  126.46 +{
  126.47 +  
  126.48 +  /**
  126.49 +   * Loads the Article identified by the given ID from the JDBCDatabase.
  126.50 +   * @param messageID
  126.51 +   * @return null if Article is not found or if an error occurred.
  126.52 +   */
  126.53 +  public static Article getByMessageID(final String messageID)
  126.54 +  {
  126.55 +    try
  126.56 +    {
  126.57 +      return StorageManager.current().getArticle(messageID);
  126.58 +    }
  126.59 +    catch(StorageBackendException ex)
  126.60 +    {
  126.61 +      ex.printStackTrace();
  126.62 +      return null;
  126.63 +    }
  126.64 +  }
  126.65 +  
  126.66 +  private byte[] body       = new byte[0];
  126.67 +  
  126.68 +  /**
  126.69 +   * Default constructor.
  126.70 +   */
  126.71 +  public Article()
  126.72 +  {
  126.73 +  }
  126.74 +  
  126.75 +  /**
  126.76 +   * Creates a new Article object using the date from the given
  126.77 +   * raw data.
  126.78 +   */
  126.79 +  public Article(String headers, byte[] body)
  126.80 +  {
  126.81 +    try
  126.82 +    {
  126.83 +      this.body  = body;
  126.84 +
  126.85 +      // Parse the header
  126.86 +      this.headers = new InternetHeaders(
  126.87 +        new ByteArrayInputStream(headers.getBytes()));
  126.88 +      
  126.89 +      this.headerSrc = headers;
  126.90 +    }
  126.91 +    catch(MessagingException ex)
  126.92 +    {
  126.93 +      ex.printStackTrace();
  126.94 +    }
  126.95 +  }
  126.96 +
  126.97 +  /**
  126.98 +   * Creates an Article instance using the data from the javax.mail.Message
  126.99 +   * object. This constructor is called by the Mailinglist gateway.
 126.100 +   * @see javax.mail.Message
 126.101 +   * @param msg
 126.102 +   * @throws IOException
 126.103 +   * @throws MessagingException
 126.104 +   */
 126.105 +  public Article(final Message msg)
 126.106 +    throws IOException, MessagingException
 126.107 +  {
 126.108 +    this.headers = new InternetHeaders();
 126.109 +
 126.110 +    for(Enumeration e = msg.getAllHeaders() ; e.hasMoreElements();) 
 126.111 +    {
 126.112 +      final Header header = (Header)e.nextElement();
 126.113 +      this.headers.addHeader(header.getName(), header.getValue());
 126.114 +    }
 126.115 +
 126.116 +	// Reads the raw byte body using Message.writeTo(OutputStream out)
 126.117 +	this.body = readContent(msg);
 126.118 +    
 126.119 +    // Validate headers
 126.120 +    validateHeaders();
 126.121 +  }
 126.122 +
 126.123 +  /**
 126.124 +   * Reads from the given Message into a byte array.
 126.125 +   * @param in
 126.126 +   * @return
 126.127 +   * @throws IOException
 126.128 +   */
 126.129 +  private byte[] readContent(Message in)
 126.130 +    throws IOException, MessagingException
 126.131 +  {
 126.132 +    ByteArrayOutputStream out = new ByteArrayOutputStream();
 126.133 +    in.writeTo(out);
 126.134 +    return out.toByteArray();
 126.135 +  }
 126.136 +
 126.137 +  /**
 126.138 +   * Removes the header identified by the given key.
 126.139 +   * @param headerKey
 126.140 +   */
 126.141 +  public void removeHeader(final String headerKey)
 126.142 +  {
 126.143 +    this.headers.removeHeader(headerKey);
 126.144 +    this.headerSrc = null;
 126.145 +  }
 126.146 +
 126.147 +  /**
 126.148 +   * Generates a message id for this article and sets it into
 126.149 +   * the header object. You have to update the JDBCDatabase manually to make this
 126.150 +   * change persistent.
 126.151 +   * Note: a Message-ID should never be changed and only generated once.
 126.152 +   */
 126.153 +  private String generateMessageID()
 126.154 +  {
 126.155 +    String randomString;
 126.156 +    MessageDigest md5;
 126.157 +    try
 126.158 +    {
 126.159 +      md5 = MessageDigest.getInstance("MD5");
 126.160 +      md5.reset();
 126.161 +      md5.update(getBody());
 126.162 +      md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
 126.163 +      md5.update(getHeader(Headers.FROM)[0].getBytes());
 126.164 +      byte[] result = md5.digest();
 126.165 +      StringBuffer hexString = new StringBuffer();
 126.166 +      for (int i = 0; i < result.length; i++)
 126.167 +      {
 126.168 +        hexString.append(Integer.toHexString(0xFF & result[i]));
 126.169 +      }
 126.170 +      randomString = hexString.toString();
 126.171 +    }
 126.172 +    catch (NoSuchAlgorithmException e)
 126.173 +    {
 126.174 +      e.printStackTrace();
 126.175 +      randomString = UUID.randomUUID().toString();
 126.176 +    }
 126.177 +    String msgID = "<" + randomString + "@"
 126.178 +        + Config.inst().get(Config.HOSTNAME, "localhost") + ">";
 126.179 +    
 126.180 +    this.headers.setHeader(Headers.MESSAGE_ID, msgID);
 126.181 +    
 126.182 +    return msgID;
 126.183 +  }
 126.184 +
 126.185 +  /**
 126.186 +   * Returns the body string.
 126.187 +   */
 126.188 +  public byte[] getBody()
 126.189 +  {
 126.190 +    return body;
 126.191 +  }
 126.192 +  
 126.193 +  /**
 126.194 +   * @return Numerical IDs of the newsgroups this Article belongs to.
 126.195 +   */
 126.196 +  public List<Group> getGroups()
 126.197 +  {
 126.198 +    String[]         groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
 126.199 +    ArrayList<Group> groups     = new ArrayList<Group>();
 126.200 +
 126.201 +    try
 126.202 +    {
 126.203 +      for(String newsgroup : groupnames)
 126.204 +      {
 126.205 +        newsgroup = newsgroup.trim();
 126.206 +        Group group = StorageManager.current().getGroup(newsgroup);
 126.207 +        if(group != null &&         // If the server does not provide the group, ignore it
 126.208 +          !groups.contains(group))  // Yes, there may be duplicates
 126.209 +        {
 126.210 +          groups.add(group);
 126.211 +        }
 126.212 +      }
 126.213 +    }
 126.214 +    catch(StorageBackendException ex)
 126.215 +    {
 126.216 +      ex.printStackTrace();
 126.217 +      return null;
 126.218 +    }
 126.219 +    return groups;
 126.220 +  }
 126.221 +
 126.222 +  public void setBody(byte[] body)
 126.223 +  {
 126.224 +    this.body = body;
 126.225 +  }
 126.226 +  
 126.227 +  /**
 126.228 +   * 
 126.229 +   * @param groupname Name(s) of newsgroups
 126.230 +   */
 126.231 +  public void setGroup(String groupname)
 126.232 +  {
 126.233 +    this.headers.setHeader(Headers.NEWSGROUPS, groupname);
 126.234 +  }
 126.235 +
 126.236 +  /**
 126.237 +   * Returns the Message-ID of this Article. If the appropriate header
 126.238 +   * is empty, a new Message-ID is created.
 126.239 +   * @return Message-ID of this Article.
 126.240 +   */
 126.241 +  public String getMessageID()
 126.242 +  {
 126.243 +    String[] msgID = getHeader(Headers.MESSAGE_ID);
 126.244 +    return msgID[0].equals("") ? generateMessageID() : msgID[0];
 126.245 +  }
 126.246 +  
 126.247 +  /**
 126.248 +   * @return String containing the Message-ID.
 126.249 +   */
 126.250 +  @Override
 126.251 +  public String toString()
 126.252 +  {
 126.253 +    return getMessageID();
 126.254 +  }
 126.255 +
 126.256 +}
   127.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   127.2 +++ b/src/org/sonews/storage/ArticleHead.java	Sun Aug 29 17:28:58 2010 +0200
   127.3 @@ -0,0 +1,161 @@
   127.4 +/*
   127.5 + *   SONEWS News Server
   127.6 + *   see AUTHORS for the list of contributors
   127.7 + *
   127.8 + *   This program is free software: you can redistribute it and/or modify
   127.9 + *   it under the terms of the GNU General Public License as published by
  127.10 + *   the Free Software Foundation, either version 3 of the License, or
  127.11 + *   (at your option) any later version.
  127.12 + *
  127.13 + *   This program is distributed in the hope that it will be useful,
  127.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  127.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  127.16 + *   GNU General Public License for more details.
  127.17 + *
  127.18 + *   You should have received a copy of the GNU General Public License
  127.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  127.20 + */
  127.21 +
  127.22 +package org.sonews.storage;
  127.23 +
  127.24 +import java.io.ByteArrayInputStream;
  127.25 +import java.util.Enumeration;
  127.26 +import javax.mail.Header;
  127.27 +import javax.mail.MessagingException;
  127.28 +import javax.mail.internet.InternetHeaders;
  127.29 +import javax.mail.internet.MimeUtility;
  127.30 +import org.sonews.config.Config;
  127.31 +
  127.32 +/**
  127.33 + * An article with no body only headers.
  127.34 + * @author Christian Lins
  127.35 + * @since sonews/0.5.0
  127.36 + */
  127.37 +public class ArticleHead 
  127.38 +{
  127.39 +
  127.40 +  protected InternetHeaders headers   = null;
  127.41 +  protected String          headerSrc = null;
  127.42 +  
  127.43 +  protected ArticleHead()
  127.44 +  {
  127.45 +  }
  127.46 +  
  127.47 +  public ArticleHead(String headers)
  127.48 +  {
  127.49 +    try
  127.50 +    {
  127.51 +      // Parse the header
  127.52 +      this.headers = new InternetHeaders(
  127.53 +          new ByteArrayInputStream(headers.getBytes()));
  127.54 +    }
  127.55 +    catch(MessagingException ex)
  127.56 +    {
  127.57 +      ex.printStackTrace();
  127.58 +    }
  127.59 +  }
  127.60 +  
  127.61 +  /**
  127.62 +   * Returns the header field with given name.
  127.63 +   * @param name Name of the header field(s).
  127.64 +   * @param returnNull If set to true, this method will return null instead
  127.65 +   *                   of an empty array if there is no header field found.
  127.66 +   * @return Header values or empty string.
  127.67 +   */
  127.68 +  public String[] getHeader(String name, boolean returnNull)
  127.69 +  {
  127.70 +    String[] ret = this.headers.getHeader(name);
  127.71 +    if(ret == null && !returnNull)
  127.72 +    {
  127.73 +      ret = new String[]{""};
  127.74 +    }
  127.75 +    return ret;
  127.76 +  }
  127.77 +
  127.78 +  public String[] getHeader(String name)
  127.79 +  {
  127.80 +    return getHeader(name, false);
  127.81 +  }
  127.82 +  
  127.83 +  /**
  127.84 +   * Sets the header value identified through the header name.
  127.85 +   * @param name
  127.86 +   * @param value
  127.87 +   */
  127.88 +  public void setHeader(String name, String value)
  127.89 +  {
  127.90 +    this.headers.setHeader(name, value);
  127.91 +    this.headerSrc = null;
  127.92 +  }
  127.93 +
  127.94 +    public Enumeration getAllHeaders()
  127.95 +  {
  127.96 +    return this.headers.getAllHeaders();
  127.97 +  }
  127.98 +
  127.99 +  /**
 127.100 +   * @return Header source code of this Article.
 127.101 +   */
 127.102 +  public String getHeaderSource()
 127.103 +  {
 127.104 +    if(this.headerSrc != null)
 127.105 +    {
 127.106 +      return this.headerSrc;
 127.107 +    }
 127.108 +
 127.109 +    StringBuffer buf = new StringBuffer();
 127.110 +
 127.111 +    for(Enumeration en = this.headers.getAllHeaders(); en.hasMoreElements();)
 127.112 +    {
 127.113 +      Header entry = (Header)en.nextElement();
 127.114 +
 127.115 +      String value = entry.getValue().replaceAll("[\r\n]", " ");
 127.116 +      buf.append(entry.getName());
 127.117 +      buf.append(": ");
 127.118 +      buf.append(MimeUtility.fold(entry.getName().length() + 2, value));
 127.119 +
 127.120 +      if(en.hasMoreElements())
 127.121 +      {
 127.122 +        buf.append("\r\n");
 127.123 +      }
 127.124 +    }
 127.125 +
 127.126 +    this.headerSrc = buf.toString();
 127.127 +    return this.headerSrc;
 127.128 +  }
 127.129 +
 127.130 +  /**
 127.131 +   * Sets the headers of this Article. If headers contain no
 127.132 +   * Message-Id a new one is created.
 127.133 +   * @param headers
 127.134 +   */
 127.135 +  public void setHeaders(InternetHeaders headers)
 127.136 +  {
 127.137 +    this.headers   = headers;
 127.138 +    this.headerSrc = null;
 127.139 +    validateHeaders();
 127.140 +  }
 127.141 +
 127.142 +  /**
 127.143 +   * Checks some headers for their validity and generates an
 127.144 +   * appropriate Path-header for this host if not yet existing.
 127.145 +   * This method is called by some Article constructors and the
 127.146 +   * method setHeaders().
 127.147 +   * @return true if something on the headers was changed.
 127.148 +   */
 127.149 +  protected void validateHeaders()
 127.150 +  {
 127.151 +    // Check for valid Path-header
 127.152 +    final String path = getHeader(Headers.PATH)[0];
 127.153 +    final String host = Config.inst().get(Config.HOSTNAME, "localhost");
 127.154 +    if(!path.startsWith(host))
 127.155 +    {
 127.156 +      StringBuffer pathBuf = new StringBuffer();
 127.157 +      pathBuf.append(host);
 127.158 +      pathBuf.append('!');
 127.159 +      pathBuf.append(path);
 127.160 +      this.headers.setHeader(Headers.PATH, pathBuf.toString());
 127.161 +    }
 127.162 +  }
 127.163 +  
 127.164 +}
   128.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   128.2 +++ b/src/org/sonews/storage/Channel.java	Sun Aug 29 17:28:58 2010 +0200
   128.3 @@ -0,0 +1,111 @@
   128.4 +/*
   128.5 + *   SONEWS News Server
   128.6 + *   see AUTHORS for the list of contributors
   128.7 + *
   128.8 + *   This program is free software: you can redistribute it and/or modify
   128.9 + *   it under the terms of the GNU General Public License as published by
  128.10 + *   the Free Software Foundation, either version 3 of the License, or
  128.11 + *   (at your option) any later version.
  128.12 + *
  128.13 + *   This program is distributed in the hope that it will be useful,
  128.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  128.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  128.16 + *   GNU General Public License for more details.
  128.17 + *
  128.18 + *   You should have received a copy of the GNU General Public License
  128.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  128.20 + */
  128.21 +
  128.22 +package org.sonews.storage;
  128.23 +
  128.24 +import java.util.ArrayList;
  128.25 +import java.util.List;
  128.26 +import org.sonews.util.Pair;
  128.27 +
  128.28 +/**
  128.29 + * A logical communication Channel is the a generic structural element for sets
  128.30 + * of messages; e.g. a Newsgroup for a set of Articles.
  128.31 + * A Channel can either be a real set of messages or an aggregated set of
  128.32 + * several subsets.
  128.33 + * @author Christian Lins
  128.34 + * @since sonews/1.0
  128.35 + */
  128.36 +public abstract class Channel
  128.37 +{
  128.38 +
  128.39 +  /**
  128.40 +   * If this flag is set the Group is no real newsgroup but a mailing list
  128.41 +   * mirror. In that case every posting and receiving mails must go through
  128.42 +   * the mailing list gateway.
  128.43 +   */
  128.44 +  public static final int MAILINGLIST = 0x1;
  128.45 +
  128.46 +  /**
  128.47 +   * If this flag is set the Group is marked as readonly and the posting
  128.48 +   * is prohibited. This can be useful for groups that are synced only in
  128.49 +   * one direction.
  128.50 +   */
  128.51 +  public static final int READONLY    = 0x2;
  128.52 +
  128.53 +  /**
  128.54 +   * If this flag is set the Group is marked as deleted and must not occur
  128.55 +   * in any output. The deletion is done lazily by a low priority daemon.
  128.56 +   */
  128.57 +  public static final int DELETED     = 0x80;
  128.58 +
  128.59 +  public static List<Channel> getAll()
  128.60 +  {
  128.61 +    List<Channel> all = new ArrayList<Channel>();
  128.62 +
  128.63 +    /*List<Channel> agroups = AggregatedGroup.getAll();
  128.64 +    if(agroups != null)
  128.65 +    {
  128.66 +      all.addAll(agroups);
  128.67 +    }*/
  128.68 +
  128.69 +    List<Channel> groups = Group.getAll();
  128.70 +    if(groups != null)
  128.71 +    {
  128.72 +      all.addAll(groups);
  128.73 +    }
  128.74 +
  128.75 +    return all;
  128.76 +  }
  128.77 +
  128.78 +  public static Channel getByName(String name)
  128.79 +    throws StorageBackendException
  128.80 +  {
  128.81 +    return StorageManager.current().getGroup(name);
  128.82 +  }
  128.83 +
  128.84 +  public abstract Article getArticle(long idx)
  128.85 +    throws StorageBackendException;
  128.86 +
  128.87 +  public abstract List<Pair<Long, ArticleHead>> getArticleHeads(
  128.88 +    final long first, final long last)
  128.89 +    throws StorageBackendException;
  128.90 +
  128.91 +  public abstract List<Long> getArticleNumbers()
  128.92 +    throws StorageBackendException;
  128.93 +
  128.94 +  public abstract long getFirstArticleNumber()
  128.95 +    throws StorageBackendException;
  128.96 +
  128.97 +  public abstract long getIndexOf(Article art)
  128.98 +    throws StorageBackendException;
  128.99 +
 128.100 +  public abstract long getInternalID();
 128.101 +
 128.102 +  public abstract long getLastArticleNumber()
 128.103 +    throws StorageBackendException;
 128.104 +
 128.105 +  public abstract String getName();
 128.106 +  
 128.107 +  public abstract long getPostingsCount()
 128.108 +    throws StorageBackendException;
 128.109 +
 128.110 +  public abstract boolean isDeleted();
 128.111 +
 128.112 +  public abstract boolean isWriteable();
 128.113 +
 128.114 +}
   129.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   129.2 +++ b/src/org/sonews/storage/Group.java	Sun Aug 29 17:28:58 2010 +0200
   129.3 @@ -0,0 +1,184 @@
   129.4 +/*
   129.5 + *   SONEWS News Server
   129.6 + *   see AUTHORS for the list of contributors
   129.7 + *
   129.8 + *   This program is free software: you can redistribute it and/or modify
   129.9 + *   it under the terms of the GNU General Public License as published by
  129.10 + *   the Free Software Foundation, either version 3 of the License, or
  129.11 + *   (at your option) any later version.
  129.12 + *
  129.13 + *   This program is distributed in the hope that it will be useful,
  129.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  129.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  129.16 + *   GNU General Public License for more details.
  129.17 + *
  129.18 + *   You should have received a copy of the GNU General Public License
  129.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  129.20 + */
  129.21 +
  129.22 +package org.sonews.storage;
  129.23 +
  129.24 +import java.sql.SQLException;
  129.25 +import java.util.List;
  129.26 +import org.sonews.util.Log;
  129.27 +import org.sonews.util.Pair;
  129.28 +
  129.29 +/**
  129.30 + * Represents a logical Group within this newsserver.
  129.31 + * @author Christian Lins
  129.32 + * @since sonews/0.5.0
  129.33 + */
  129.34 +// TODO: This class should not be public!
  129.35 +public class Group extends Channel
  129.36 +{
  129.37 +  
  129.38 +  private long   id     = 0;
  129.39 +  private int    flags  = -1;
  129.40 +  private String name   = null;
  129.41 +
  129.42 +  /**
  129.43 +   * @return List of all groups this server handles.
  129.44 +   */
  129.45 +  public static List<Channel> getAll()
  129.46 +  {
  129.47 +    try
  129.48 +    {
  129.49 +      return StorageManager.current().getGroups();
  129.50 +    }
  129.51 +    catch(StorageBackendException ex)
  129.52 +    {
  129.53 +      Log.get().severe(ex.getMessage());
  129.54 +      return null;
  129.55 +    }
  129.56 +  }
  129.57 +  
  129.58 +  /**
  129.59 +   * @param name
  129.60 +   * @param id
  129.61 +   */
  129.62 +  public Group(final String name, final long id, final int flags)
  129.63 +  {
  129.64 +    this.id    = id;
  129.65 +    this.flags = flags;
  129.66 +    this.name  = name;
  129.67 +  }
  129.68 +
  129.69 +  @Override
  129.70 +  public boolean equals(Object obj)
  129.71 +  {
  129.72 +    if(obj instanceof Group)
  129.73 +    {
  129.74 +      return ((Group)obj).id == this.id;
  129.75 +    }
  129.76 +    else
  129.77 +    {
  129.78 +      return false;
  129.79 +    }
  129.80 +  }
  129.81 +
  129.82 +  public Article getArticle(long idx)
  129.83 +    throws StorageBackendException
  129.84 +  {
  129.85 +    return StorageManager.current().getArticle(idx, this.id);
  129.86 +  }
  129.87 +
  129.88 +  public List<Pair<Long, ArticleHead>> getArticleHeads(final long first, final long last)
  129.89 +    throws StorageBackendException
  129.90 +  {
  129.91 +    return StorageManager.current().getArticleHeads(this, first, last);
  129.92 +  }
  129.93 +  
  129.94 +  public List<Long> getArticleNumbers()
  129.95 +    throws StorageBackendException
  129.96 +  {
  129.97 +    return StorageManager.current().getArticleNumbers(id);
  129.98 +  }
  129.99 +
 129.100 +  public long getFirstArticleNumber()
 129.101 +    throws StorageBackendException
 129.102 +  {
 129.103 +    return StorageManager.current().getFirstArticleNumber(this);
 129.104 +  }
 129.105 +
 129.106 +  public int getFlags()
 129.107 +  {
 129.108 +    return this.flags;
 129.109 +  }
 129.110 +
 129.111 +  public long getIndexOf(Article art)
 129.112 +    throws StorageBackendException
 129.113 +  {
 129.114 +    return StorageManager.current().getArticleIndex(art, this);
 129.115 +  }
 129.116 +
 129.117 +  /**
 129.118 +   * Returns the group id.
 129.119 +   */
 129.120 +  public long getInternalID()
 129.121 +  {
 129.122 +    assert id > 0;
 129.123 +
 129.124 +    return id;
 129.125 +  }
 129.126 +
 129.127 +  public boolean isDeleted()
 129.128 +  {
 129.129 +    return (this.flags & DELETED) != 0;
 129.130 +  }
 129.131 +
 129.132 +  public boolean isMailingList()
 129.133 +  {
 129.134 +    return (this.flags & MAILINGLIST) != 0;
 129.135 +  }
 129.136 +
 129.137 +  public boolean isWriteable()
 129.138 +  {
 129.139 +    return true;
 129.140 +  }
 129.141 +
 129.142 +  public long getLastArticleNumber()
 129.143 +    throws StorageBackendException
 129.144 +  {
 129.145 +    return StorageManager.current().getLastArticleNumber(this);
 129.146 +  }
 129.147 +
 129.148 +  public String getName()
 129.149 +  {
 129.150 +    return name;
 129.151 +  }
 129.152 +
 129.153 +  /**
 129.154 +   * Performs this.flags |= flag to set a specified flag and updates the data
 129.155 +   * in the JDBCDatabase.
 129.156 +   * @param flag
 129.157 +   */
 129.158 +  public void setFlag(final int flag)
 129.159 +  {
 129.160 +    this.flags |= flag;
 129.161 +  }
 129.162 +
 129.163 +  public void setName(final String name)
 129.164 +  {
 129.165 +    this.name = name;
 129.166 +  }
 129.167 +
 129.168 +  /**
 129.169 +   * @return Number of posted articles in this group.
 129.170 +   * @throws java.sql.SQLException
 129.171 +   */
 129.172 +  public long getPostingsCount()
 129.173 +    throws StorageBackendException
 129.174 +  {
 129.175 +    return StorageManager.current().getPostingsCount(this.name);
 129.176 +  }
 129.177 +
 129.178 +  /**
 129.179 +   * Updates flags and name in the backend.
 129.180 +   */
 129.181 +  public void update()
 129.182 +    throws StorageBackendException
 129.183 +  {
 129.184 +    StorageManager.current().update(this);
 129.185 +  }
 129.186 +
 129.187 +}
   130.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   130.2 +++ b/src/org/sonews/storage/Headers.java	Sun Aug 29 17:28:58 2010 +0200
   130.3 @@ -0,0 +1,56 @@
   130.4 +/*
   130.5 + *   SONEWS News Server
   130.6 + *   see AUTHORS for the list of contributors
   130.7 + *
   130.8 + *   This program is free software: you can redistribute it and/or modify
   130.9 + *   it under the terms of the GNU General Public License as published by
  130.10 + *   the Free Software Foundation, either version 3 of the License, or
  130.11 + *   (at your option) any later version.
  130.12 + *
  130.13 + *   This program is distributed in the hope that it will be useful,
  130.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  130.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  130.16 + *   GNU General Public License for more details.
  130.17 + *
  130.18 + *   You should have received a copy of the GNU General Public License
  130.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  130.20 + */
  130.21 +
  130.22 +package org.sonews.storage;
  130.23 +
  130.24 +/**
  130.25 + * Contains header constants. These header keys are no way complete but all
  130.26 + * headers that are relevant for sonews.
  130.27 + * @author Christian Lins
  130.28 + * @since sonews/0.5.0
  130.29 + */
  130.30 +public final class Headers
  130.31 +{
  130.32 +
  130.33 +  public static final String BYTES             = "bytes";
  130.34 +  public static final String CONTENT_TYPE      = "content-type";
  130.35 +  public static final String CONTROL           = "control";
  130.36 +  public static final String DATE              = "date";
  130.37 +  public static final String FROM              = "from";
  130.38 +  public static final String LINES             = "lines";
  130.39 +  public static final String LIST_POST         = "list-post";
  130.40 +  public static final String MESSAGE_ID        = "message-id";
  130.41 +  public static final String NEWSGROUPS        = "newsgroups";
  130.42 +  public static final String NNTP_POSTING_DATE = "nntp-posting-date";
  130.43 +  public static final String NNTP_POSTING_HOST = "nntp-posting-host";
  130.44 +  public static final String PATH              = "path";
  130.45 +  public static final String REFERENCES        = "references";
  130.46 +  public static final String REPLY_TO          = "reply-to";
  130.47 +  public static final String SENDER            = "sender";
  130.48 +  public static final String SUBJECT           = "subject";
  130.49 +  public static final String SUPERSEDES        = "subersedes";
  130.50 +  public static final String TO                = "to";
  130.51 +  public static final String X_COMPLAINTS_TO   = "x-complaints-to";
  130.52 +  public static final String X_LIST_POST       = "x-list-post";
  130.53 +  public static final String X_TRACE           = "x-trace";
  130.54 +  public static final String XREF              = "xref";
  130.55 +
  130.56 +  private Headers()
  130.57 +  {}
  130.58 +
  130.59 +}
   131.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   131.2 +++ b/src/org/sonews/storage/Storage.java	Sun Aug 29 17:28:58 2010 +0200
   131.3 @@ -0,0 +1,150 @@
   131.4 +/*
   131.5 + *   SONEWS News Server
   131.6 + *   see AUTHORS for the list of contributors
   131.7 + *
   131.8 + *   This program is free software: you can redistribute it and/or modify
   131.9 + *   it under the terms of the GNU General Public License as published by
  131.10 + *   the Free Software Foundation, either version 3 of the License, or
  131.11 + *   (at your option) any later version.
  131.12 + *
  131.13 + *   This program is distributed in the hope that it will be useful,
  131.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  131.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  131.16 + *   GNU General Public License for more details.
  131.17 + *
  131.18 + *   You should have received a copy of the GNU General Public License
  131.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  131.20 + */
  131.21 +
  131.22 +package org.sonews.storage;
  131.23 +
  131.24 +import java.util.List;
  131.25 +import org.sonews.feed.Subscription;
  131.26 +import org.sonews.util.Pair;
  131.27 +
  131.28 +/**
  131.29 + * A generic storage backend interface.
  131.30 + * @author Christian Lins
  131.31 + * @since sonews/1.0
  131.32 + */
  131.33 +public interface Storage
  131.34 +{
  131.35 +
  131.36 +  /**
  131.37 +   * Stores the given Article in the storage.
  131.38 +   * @param art
  131.39 +   * @throws StorageBackendException
  131.40 +   */
  131.41 +  void addArticle(Article art)
  131.42 +    throws StorageBackendException;
  131.43 +
  131.44 +  void addEvent(long timestamp, int type, long groupID)
  131.45 +    throws StorageBackendException;
  131.46 +
  131.47 +  void addGroup(String groupname, int flags)
  131.48 +    throws StorageBackendException;
  131.49 +
  131.50 +  int countArticles()
  131.51 +    throws StorageBackendException;
  131.52 +
  131.53 +  int countGroups()
  131.54 +    throws StorageBackendException;
  131.55 +
  131.56 +  void delete(String messageID)
  131.57 +    throws StorageBackendException;
  131.58 +
  131.59 +  Article getArticle(String messageID)
  131.60 +    throws StorageBackendException;
  131.61 +
  131.62 +  Article getArticle(long articleIndex, long groupID)
  131.63 +    throws StorageBackendException;
  131.64 +
  131.65 +  List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first, long last)
  131.66 +    throws StorageBackendException;
  131.67 +
  131.68 +  List<Pair<Long, String>> getArticleHeaders(Channel channel, long start, long end,
  131.69 +    String header, String pattern)
  131.70 +    throws StorageBackendException;
  131.71 +
  131.72 +  long getArticleIndex(Article art, Group group)
  131.73 +    throws StorageBackendException;
  131.74 +
  131.75 +  List<Long> getArticleNumbers(long groupID)
  131.76 +    throws StorageBackendException;
  131.77 +
  131.78 +  String getConfigValue(String key)
  131.79 +    throws StorageBackendException;
  131.80 +
  131.81 +  int getEventsCount(int eventType, long startTimestamp, long endTimestamp,
  131.82 +    Channel channel)
  131.83 +    throws StorageBackendException;
  131.84 +
  131.85 +  double getEventsPerHour(int key, long gid)
  131.86 +    throws StorageBackendException;
  131.87 +
  131.88 +  int getFirstArticleNumber(Group group)
  131.89 +    throws StorageBackendException;
  131.90 +
  131.91 +  Group getGroup(String name)
  131.92 +    throws StorageBackendException;
  131.93 +
  131.94 +  List<Channel> getGroups()
  131.95 +    throws StorageBackendException;
  131.96 +
  131.97 +  /**
  131.98 +   * Retrieves the collection of groupnames that are associated with the
  131.99 +   * given list address.
 131.100 +   * @param inetaddress
 131.101 +   * @return
 131.102 +   * @throws StorageBackendException
 131.103 +   */
 131.104 +  List<String> getGroupsForList(String listAddress)
 131.105 +    throws StorageBackendException;
 131.106 +
 131.107 +  int getLastArticleNumber(Group group)
 131.108 +    throws StorageBackendException;
 131.109 +
 131.110 +  /**
 131.111 +   * Returns a list of email addresses that are related to the given
 131.112 +   * groupname. In most cases the list may contain only one entry.
 131.113 +   * @param groupname
 131.114 +   * @return
 131.115 +   * @throws StorageBackendException
 131.116 +   */
 131.117 +  List<String> getListsForGroup(String groupname)
 131.118 +    throws StorageBackendException;
 131.119 +
 131.120 +  String getOldestArticle()
 131.121 +    throws StorageBackendException;
 131.122 +
 131.123 +  int getPostingsCount(String groupname)
 131.124 +    throws StorageBackendException;
 131.125 +
 131.126 +  List<Subscription> getSubscriptions(int type)
 131.127 +    throws StorageBackendException;
 131.128 +
 131.129 +  boolean isArticleExisting(String messageID)
 131.130 +    throws StorageBackendException;
 131.131 +
 131.132 +  boolean isGroupExisting(String groupname)
 131.133 +    throws StorageBackendException;
 131.134 +
 131.135 +  void purgeGroup(Group group)
 131.136 +    throws StorageBackendException;
 131.137 +
 131.138 +  void setConfigValue(String key, String value)
 131.139 +    throws StorageBackendException;
 131.140 +
 131.141 +  /**
 131.142 +   * Updates headers and channel references of the given article.
 131.143 +   * @param article
 131.144 +   * @return
 131.145 +   * @throws StorageBackendException
 131.146 +   */
 131.147 +  boolean update(Article article)
 131.148 +    throws StorageBackendException;
 131.149 +
 131.150 +  boolean update(Group group)
 131.151 +    throws StorageBackendException;
 131.152 +
 131.153 +}
   132.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   132.2 +++ b/src/org/sonews/storage/StorageBackendException.java	Sun Aug 29 17:28:58 2010 +0200
   132.3 @@ -0,0 +1,39 @@
   132.4 +/*
   132.5 + *   SONEWS News Server
   132.6 + *   see AUTHORS for the list of contributors
   132.7 + *
   132.8 + *   This program is free software: you can redistribute it and/or modify
   132.9 + *   it under the terms of the GNU General Public License as published by
  132.10 + *   the Free Software Foundation, either version 3 of the License, or
  132.11 + *   (at your option) any later version.
  132.12 + *
  132.13 + *   This program is distributed in the hope that it will be useful,
  132.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  132.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  132.16 + *   GNU General Public License for more details.
  132.17 + *
  132.18 + *   You should have received a copy of the GNU General Public License
  132.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  132.20 + */
  132.21 +
  132.22 +package org.sonews.storage;
  132.23 +
  132.24 +/**
  132.25 + *
  132.26 + * @author Christian Lins
  132.27 + * @since sonews/1.0
  132.28 + */
  132.29 +public class StorageBackendException extends Exception
  132.30 +{
  132.31 +
  132.32 +  public StorageBackendException(Throwable cause)
  132.33 +  {
  132.34 +    super(cause);
  132.35 +  }
  132.36 +
  132.37 +  public StorageBackendException(String msg)
  132.38 +  {
  132.39 +    super(msg);
  132.40 +  }
  132.41 +
  132.42 +}
   133.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   133.2 +++ b/src/org/sonews/storage/StorageManager.java	Sun Aug 29 17:28:58 2010 +0200
   133.3 @@ -0,0 +1,89 @@
   133.4 +/*
   133.5 + *   SONEWS News Server
   133.6 + *   see AUTHORS for the list of contributors
   133.7 + *
   133.8 + *   This program is free software: you can redistribute it and/or modify
   133.9 + *   it under the terms of the GNU General Public License as published by
  133.10 + *   the Free Software Foundation, either version 3 of the License, or
  133.11 + *   (at your option) any later version.
  133.12 + *
  133.13 + *   This program is distributed in the hope that it will be useful,
  133.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  133.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  133.16 + *   GNU General Public License for more details.
  133.17 + *
  133.18 + *   You should have received a copy of the GNU General Public License
  133.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  133.20 + */
  133.21 +
  133.22 +package org.sonews.storage;
  133.23 +
  133.24 +/**
  133.25 + *
  133.26 + * @author Christian Lins
  133.27 + * @since sonews/1.0
  133.28 + */
  133.29 +public final class StorageManager
  133.30 +{
  133.31 +
  133.32 +  private static StorageProvider provider;
  133.33 +
  133.34 +  public static Storage current()
  133.35 +    throws StorageBackendException
  133.36 +  {
  133.37 +    synchronized(StorageManager.class)
  133.38 +    {
  133.39 +      if(provider == null)
  133.40 +      {
  133.41 +        return null;
  133.42 +      }
  133.43 +      else
  133.44 +      {
  133.45 +        return provider.storage(Thread.currentThread());
  133.46 +      }
  133.47 +    }
  133.48 +  }
  133.49 +
  133.50 +  public static StorageProvider loadProvider(String pluginClassName)
  133.51 +  {
  133.52 +    try
  133.53 +    {
  133.54 +      Class<?> clazz = Class.forName(pluginClassName);
  133.55 +      Object   inst  = clazz.newInstance();
  133.56 +      return (StorageProvider)inst;
  133.57 +    }
  133.58 +    catch(Exception ex)
  133.59 +    {
  133.60 +      System.err.println(ex);
  133.61 +      return null;
  133.62 +    }
  133.63 +  }
  133.64 +
  133.65 +  /**
  133.66 +   * Sets the current storage provider.
  133.67 +   * @param provider
  133.68 +   */
  133.69 +  public static void enableProvider(StorageProvider provider)
  133.70 +  {
  133.71 +    synchronized(StorageManager.class)
  133.72 +    {
  133.73 +      if(StorageManager.provider != null)
  133.74 +      {
  133.75 +        disableProvider();
  133.76 +      }
  133.77 +      StorageManager.provider = provider;
  133.78 +    }
  133.79 +  }
  133.80 +
  133.81 +  /**
  133.82 +   * Disables the current provider.
  133.83 +   */
  133.84 +  public static void disableProvider()
  133.85 +  {
  133.86 +    synchronized(StorageManager.class)
  133.87 +    {
  133.88 +      provider = null;
  133.89 +    }
  133.90 +  }
  133.91 +
  133.92 +}
   134.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   134.2 +++ b/src/org/sonews/storage/StorageProvider.java	Sun Aug 29 17:28:58 2010 +0200
   134.3 @@ -0,0 +1,40 @@
   134.4 +/*
   134.5 + *   SONEWS News Server
   134.6 + *   see AUTHORS for the list of contributors
   134.7 + *
   134.8 + *   This program is free software: you can redistribute it and/or modify
   134.9 + *   it under the terms of the GNU General Public License as published by
  134.10 + *   the Free Software Foundation, either version 3 of the License, or
  134.11 + *   (at your option) any later version.
  134.12 + *
  134.13 + *   This program is distributed in the hope that it will be useful,
  134.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  134.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  134.16 + *   GNU General Public License for more details.
  134.17 + *
  134.18 + *   You should have received a copy of the GNU General Public License
  134.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  134.20 + */
  134.21 +
  134.22 +package org.sonews.storage;
  134.23 +
  134.24 +/**
  134.25 + * Provides access to storage backend instances.
  134.26 + * @author Christian Lins
  134.27 + * @since sonews/1.0
  134.28 + */
  134.29 +public interface StorageProvider
  134.30 +{
  134.31 +
  134.32 +  public boolean isSupported(String uri);
  134.33 +
  134.34 +  /**
  134.35 +   * This method returns the reference to the associated storage.
  134.36 +   * The reference MAY be unique for each thread. In any case it MUST be
  134.37 +   * thread-safe to use this method.
  134.38 +   * @return The reference to the associated Storage.
  134.39 +   */
  134.40 +  public Storage storage(Thread thread)
  134.41 +    throws StorageBackendException;
  134.42 +
  134.43 +}
   135.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   135.2 +++ b/src/org/sonews/storage/impl/JDBCDatabase.java	Sun Aug 29 17:28:58 2010 +0200
   135.3 @@ -0,0 +1,1782 @@
   135.4 +/*
   135.5 + *   SONEWS News Server
   135.6 + *   see AUTHORS for the list of contributors
   135.7 + *
   135.8 + *   This program is free software: you can redistribute it and/or modify
   135.9 + *   it under the terms of the GNU General Public License as published by
  135.10 + *   the Free Software Foundation, either version 3 of the License, or
  135.11 + *   (at your option) any later version.
  135.12 + *
  135.13 + *   This program is distributed in the hope that it will be useful,
  135.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  135.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  135.16 + *   GNU General Public License for more details.
  135.17 + *
  135.18 + *   You should have received a copy of the GNU General Public License
  135.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  135.20 + */
  135.21 +
  135.22 +package org.sonews.storage.impl;
  135.23 +
  135.24 +import java.sql.Connection;
  135.25 +import java.sql.DriverManager;
  135.26 +import java.sql.ResultSet;
  135.27 +import java.sql.SQLException;
  135.28 +import java.sql.Statement;
  135.29 +import java.sql.PreparedStatement;
  135.30 +import java.util.ArrayList;
  135.31 +import java.util.Enumeration;
  135.32 +import java.util.List;
  135.33 +import java.util.regex.Matcher;
  135.34 +import java.util.regex.Pattern;
  135.35 +import java.util.regex.PatternSyntaxException;
  135.36 +import javax.mail.Header;
  135.37 +import javax.mail.internet.MimeUtility;
  135.38 +import org.sonews.config.Config;
  135.39 +import org.sonews.util.Log;
  135.40 +import org.sonews.feed.Subscription;
  135.41 +import org.sonews.storage.Article;
  135.42 +import org.sonews.storage.ArticleHead;
  135.43 +import org.sonews.storage.Channel;
  135.44 +import org.sonews.storage.Group;
  135.45 +import org.sonews.storage.Storage;
  135.46 +import org.sonews.storage.StorageBackendException;
  135.47 +import org.sonews.util.Pair;
  135.48 +
  135.49 +/**
  135.50 + * JDBCDatabase facade class.
  135.51 + * @author Christian Lins
  135.52 + * @since sonews/0.5.0
  135.53 + */
  135.54 +// TODO: Refactor this class to reduce size (e.g. ArticleDatabase GroupDatabase)
  135.55 +public class JDBCDatabase implements Storage
  135.56 +{
  135.57 +
  135.58 +  public static final int MAX_RESTARTS = 2;
  135.59 +  
  135.60 +  private Connection        conn = null;
  135.61 +  private PreparedStatement pstmtAddArticle1 = null;
  135.62 +  private PreparedStatement pstmtAddArticle2 = null;
  135.63 +  private PreparedStatement pstmtAddArticle3 = null;
  135.64 +  private PreparedStatement pstmtAddArticle4 = null;
  135.65 +  private PreparedStatement pstmtAddGroup0   = null;
  135.66 +  private PreparedStatement pstmtAddEvent = null;
  135.67 +  private PreparedStatement pstmtCountArticles = null;
  135.68 +  private PreparedStatement pstmtCountGroups   = null;
  135.69 +  private PreparedStatement pstmtDeleteArticle0 = null;
  135.70 +  private PreparedStatement pstmtDeleteArticle1 = null;
  135.71 +  private PreparedStatement pstmtDeleteArticle2 = null;
  135.72 +  private PreparedStatement pstmtDeleteArticle3 = null;
  135.73 +  private PreparedStatement pstmtGetArticle0 = null;
  135.74 +  private PreparedStatement pstmtGetArticle1 = null;
  135.75 +  private PreparedStatement pstmtGetArticleHeaders0 = null;
  135.76 +  private PreparedStatement pstmtGetArticleHeaders1 = null;
  135.77 +  private PreparedStatement pstmtGetArticleHeads = null;
  135.78 +  private PreparedStatement pstmtGetArticleIDs   = null;
  135.79 +  private PreparedStatement pstmtGetArticleIndex    = null;
  135.80 +  private PreparedStatement pstmtGetConfigValue = null;
  135.81 +  private PreparedStatement pstmtGetEventsCount0 = null;
  135.82 +  private PreparedStatement pstmtGetEventsCount1 = null;
  135.83 +  private PreparedStatement pstmtGetGroupForList = null;
  135.84 +  private PreparedStatement pstmtGetGroup0     = null;
  135.85 +  private PreparedStatement pstmtGetGroup1     = null;
  135.86 +  private PreparedStatement pstmtGetFirstArticleNumber = null;
  135.87 +  private PreparedStatement pstmtGetListForGroup       = null;
  135.88 +  private PreparedStatement pstmtGetLastArticleNumber  = null;
  135.89 +  private PreparedStatement pstmtGetMaxArticleID       = null;
  135.90 +  private PreparedStatement pstmtGetMaxArticleIndex    = null;
  135.91 +  private PreparedStatement pstmtGetOldestArticle      = null;
  135.92 +  private PreparedStatement pstmtGetPostingsCount      = null;
  135.93 +  private PreparedStatement pstmtGetSubscriptions  = null;
  135.94 +  private PreparedStatement pstmtIsArticleExisting = null;
  135.95 +  private PreparedStatement pstmtIsGroupExisting = null;
  135.96 +  private PreparedStatement pstmtPurgeGroup0     = null;
  135.97 +  private PreparedStatement pstmtPurgeGroup1     = null;
  135.98 +  private PreparedStatement pstmtSetConfigValue0 = null;
  135.99 +  private PreparedStatement pstmtSetConfigValue1 = null;
 135.100 +  private PreparedStatement pstmtUpdateGroup     = null;
 135.101 +  
 135.102 +  /** How many times the database connection was reinitialized */
 135.103 +  private int restarts = 0;
 135.104 +  
 135.105 +  /**
 135.106 +   * Rises the database: reconnect and recreate all prepared statements.
 135.107 +   * @throws java.lang.SQLException
 135.108 +   */
 135.109 +  protected void arise()
 135.110 +    throws SQLException
 135.111 +  {
 135.112 +    try
 135.113 +    {
 135.114 +      // Load database driver
 135.115 +      Class.forName(
 135.116 +        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DBMSDRIVER, "java.lang.Object"));
 135.117 +
 135.118 +      // Establish database connection
 135.119 +      this.conn = DriverManager.getConnection(
 135.120 +        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DATABASE, "<not specified>"),
 135.121 +        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_USER, "root"),
 135.122 +        Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_PASSWORD, ""));
 135.123 +
 135.124 +      this.conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
 135.125 +      if(this.conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE)
 135.126 +      {
 135.127 +        Log.get().warning("Database is NOT fully serializable!");
 135.128 +      }
 135.129 +
 135.130 +      // Prepare statements for method addArticle()
 135.131 +      this.pstmtAddArticle1 = conn.prepareStatement(
 135.132 +        "INSERT INTO articles (article_id, body) VALUES(?, ?)");
 135.133 +      this.pstmtAddArticle2 = conn.prepareStatement(
 135.134 +        "INSERT INTO headers (article_id, header_key, header_value, header_index) " +
 135.135 +        "VALUES (?, ?, ?, ?)");
 135.136 +      this.pstmtAddArticle3 = conn.prepareStatement(
 135.137 +        "INSERT INTO postings (group_id, article_id, article_index)" +
 135.138 +        "VALUES (?, ?, ?)");
 135.139 +      this.pstmtAddArticle4 = conn.prepareStatement(
 135.140 +        "INSERT INTO article_ids (article_id, message_id) VALUES (?, ?)");
 135.141 +
 135.142 +      // Prepare statement for method addStatValue()
 135.143 +      this.pstmtAddEvent = conn.prepareStatement(
 135.144 +        "INSERT INTO events VALUES (?, ?, ?)");
 135.145 +     
 135.146 +      // Prepare statement for method addGroup()
 135.147 +      this.pstmtAddGroup0 = conn.prepareStatement(
 135.148 +        "INSERT INTO groups (name, flags) VALUES (?, ?)");
 135.149 +      
 135.150 +      // Prepare statement for method countArticles()
 135.151 +      this.pstmtCountArticles = conn.prepareStatement(
 135.152 +        "SELECT Count(article_id) FROM article_ids");
 135.153 +      
 135.154 +      // Prepare statement for method countGroups()
 135.155 +      this.pstmtCountGroups = conn.prepareStatement(
 135.156 +        "SELECT Count(group_id) FROM groups WHERE " +
 135.157 +        "flags & " + Channel.DELETED + " = 0");
 135.158 +      
 135.159 +      // Prepare statements for method delete(article)
 135.160 +      this.pstmtDeleteArticle0 = conn.prepareStatement(
 135.161 +        "DELETE FROM articles WHERE article_id = " +
 135.162 +        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
 135.163 +      this.pstmtDeleteArticle1 = conn.prepareStatement(
 135.164 +        "DELETE FROM headers WHERE article_id = " +
 135.165 +        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
 135.166 +      this.pstmtDeleteArticle2 = conn.prepareStatement(
 135.167 +        "DELETE FROM postings WHERE article_id = " +
 135.168 +        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
 135.169 +      this.pstmtDeleteArticle3 = conn.prepareStatement(
 135.170 +        "DELETE FROM article_ids WHERE message_id = ?");
 135.171 +
 135.172 +      // Prepare statements for methods getArticle()
 135.173 +      this.pstmtGetArticle0 = conn.prepareStatement(
 135.174 +        "SELECT * FROM articles  WHERE article_id = " +
 135.175 +        "(SELECT article_id FROM article_ids WHERE message_id = ?)");
 135.176 +      this.pstmtGetArticle1 = conn.prepareStatement(
 135.177 +        "SELECT * FROM articles WHERE article_id = " +
 135.178 +        "(SELECT article_id FROM postings WHERE " +
 135.179 +        "article_index = ? AND group_id = ?)");
 135.180 +      
 135.181 +      // Prepare statement for method getArticleHeaders()
 135.182 +      this.pstmtGetArticleHeaders0 = conn.prepareStatement(
 135.183 +        "SELECT header_key, header_value FROM headers WHERE article_id = ? " +
 135.184 +        "ORDER BY header_index ASC");
 135.185 +
 135.186 +      // Prepare statement for method getArticleHeaders(regular expr pattern)
 135.187 +      this.pstmtGetArticleHeaders1 = conn.prepareStatement(
 135.188 +        "SELECT p.article_index, h.header_value FROM headers h " +
 135.189 +          "INNER JOIN postings p ON h.article_id = p.article_id " +
 135.190 +          "INNER JOIN groups g ON p.group_id = g.group_id " +
 135.191 +            "WHERE g.name          =  ? AND " +
 135.192 +                  "h.header_key    =  ? AND " +
 135.193 +                  "p.article_index >= ? " +
 135.194 +        "ORDER BY p.article_index ASC");
 135.195 +
 135.196 +      this.pstmtGetArticleIDs = conn.prepareStatement(
 135.197 +        "SELECT article_index FROM postings WHERE group_id = ?");
 135.198 +      
 135.199 +      // Prepare statement for method getArticleIndex
 135.200 +      this.pstmtGetArticleIndex = conn.prepareStatement(
 135.201 +              "SELECT article_index FROM postings WHERE " +
 135.202 +              "article_id = (SELECT article_id FROM article_ids " +
 135.203 +              "WHERE message_id = ?) " +
 135.204 +              " AND group_id = ?");
 135.205 +
 135.206 +      // Prepare statements for method getArticleHeads()
 135.207 +      this.pstmtGetArticleHeads = conn.prepareStatement(
 135.208 +        "SELECT article_id, article_index FROM postings WHERE " +
 135.209 +        "postings.group_id = ? AND article_index >= ? AND " +
 135.210 +        "article_index <= ?");
 135.211 +
 135.212 +      // Prepare statements for method getConfigValue()
 135.213 +      this.pstmtGetConfigValue = conn.prepareStatement(
 135.214 +        "SELECT config_value FROM config WHERE config_key = ?");
 135.215 +
 135.216 +      // Prepare statements for method getEventsCount()
 135.217 +      this.pstmtGetEventsCount0 = conn.prepareStatement(
 135.218 +        "SELECT Count(*) FROM events WHERE event_key = ? AND " +
 135.219 +        "event_time >= ? AND event_time < ?");
 135.220 +
 135.221 +      this.pstmtGetEventsCount1 = conn.prepareStatement(
 135.222 +        "SELECT Count(*) FROM events WHERE event_key = ? AND " +
 135.223 +        "event_time >= ? AND event_time < ? AND group_id = ?");
 135.224 +      
 135.225 +      // Prepare statement for method getGroupForList()
 135.226 +      this.pstmtGetGroupForList = conn.prepareStatement(
 135.227 +        "SELECT name FROM groups INNER JOIN groups2list " +
 135.228 +        "ON groups.group_id = groups2list.group_id " +
 135.229 +        "WHERE groups2list.listaddress = ?");
 135.230 +
 135.231 +      // Prepare statement for method getGroup()
 135.232 +      this.pstmtGetGroup0 = conn.prepareStatement(
 135.233 +        "SELECT group_id, flags FROM groups WHERE Name = ?");
 135.234 +      this.pstmtGetGroup1 = conn.prepareStatement(
 135.235 +        "SELECT name FROM groups WHERE group_id = ?");
 135.236 +
 135.237 +      // Prepare statement for method getLastArticleNumber()
 135.238 +      this.pstmtGetLastArticleNumber = conn.prepareStatement(
 135.239 +        "SELECT Max(article_index) FROM postings WHERE group_id = ?");
 135.240 +
 135.241 +      // Prepare statement for method getListForGroup()
 135.242 +      this.pstmtGetListForGroup = conn.prepareStatement(
 135.243 +        "SELECT listaddress FROM groups2list INNER JOIN groups " +
 135.244 +        "ON groups.group_id = groups2list.group_id WHERE name = ?");
 135.245 +
 135.246 +      // Prepare statement for method getMaxArticleID()
 135.247 +      this.pstmtGetMaxArticleID = conn.prepareStatement(
 135.248 +        "SELECT Max(article_id) FROM articles");
 135.249 +      
 135.250 +      // Prepare statement for method getMaxArticleIndex()
 135.251 +      this.pstmtGetMaxArticleIndex = conn.prepareStatement(
 135.252 +        "SELECT Max(article_index) FROM postings WHERE group_id = ?");
 135.253 +      
 135.254 +      // Prepare statement for method getOldestArticle()
 135.255 +      this.pstmtGetOldestArticle = conn.prepareStatement(
 135.256 +        "SELECT message_id FROM article_ids WHERE article_id = " +
 135.257 +        "(SELECT Min(article_id) FROM article_ids)");
 135.258 +
 135.259 +      // Prepare statement for method getFirstArticleNumber()
 135.260 +      this.pstmtGetFirstArticleNumber = conn.prepareStatement(
 135.261 +        "SELECT Min(article_index) FROM postings WHERE group_id = ?");
 135.262 +      
 135.263 +      // Prepare statement for method getPostingsCount()
 135.264 +      this.pstmtGetPostingsCount = conn.prepareStatement(
 135.265 +        "SELECT Count(*) FROM postings NATURAL JOIN groups " +
 135.266 +        "WHERE groups.name = ?");
 135.267 +      
 135.268 +      // Prepare statement for method getSubscriptions()
 135.269 +      this.pstmtGetSubscriptions = conn.prepareStatement(
 135.270 +        "SELECT host, port, name FROM peers NATURAL JOIN " +
 135.271 +        "peer_subscriptions NATURAL JOIN groups WHERE feedtype = ?");
 135.272 +      
 135.273 +      // Prepare statement for method isArticleExisting()
 135.274 +      this.pstmtIsArticleExisting = conn.prepareStatement(
 135.275 +        "SELECT Count(article_id) FROM article_ids WHERE message_id = ?");
 135.276 +      
 135.277 +      // Prepare statement for method isGroupExisting()
 135.278 +      this.pstmtIsGroupExisting = conn.prepareStatement(
 135.279 +        "SELECT * FROM groups WHERE name = ?");
 135.280 +      
 135.281 +      // Prepare statement for method setConfigValue()
 135.282 +      this.pstmtSetConfigValue0 = conn.prepareStatement(
 135.283 +        "DELETE FROM config WHERE config_key = ?");
 135.284 +      this.pstmtSetConfigValue1 = conn.prepareStatement(
 135.285 +        "INSERT INTO config VALUES(?, ?)");
 135.286 +
 135.287 +      // Prepare statements for method purgeGroup()
 135.288 +      this.pstmtPurgeGroup0 = conn.prepareStatement(
 135.289 +        "DELETE FROM peer_subscriptions WHERE group_id = ?");
 135.290 +      this.pstmtPurgeGroup1 = conn.prepareStatement(
 135.291 +        "DELETE FROM groups WHERE group_id = ?");
 135.292 +
 135.293 +      // Prepare statement for method update(Group)
 135.294 +      this.pstmtUpdateGroup = conn.prepareStatement(
 135.295 +        "UPDATE groups SET flags = ?, name = ? WHERE group_id = ?");
 135.296 +    }
 135.297 +    catch(ClassNotFoundException ex)
 135.298 +    {
 135.299 +      throw new Error("JDBC Driver not found!", ex);
 135.300 +    }
 135.301 +  }
 135.302 +  
 135.303 +  /**
 135.304 +   * Adds an article to the database.
 135.305 +   * @param article
 135.306 +   * @return
 135.307 +   * @throws java.sql.SQLException
 135.308 +   */
 135.309 +  @Override
 135.310 +  public void addArticle(final Article article)
 135.311 +    throws StorageBackendException
 135.312 +  {
 135.313 +    try
 135.314 +    {
 135.315 +      this.conn.setAutoCommit(false);
 135.316 +
 135.317 +      int newArticleID = getMaxArticleID() + 1;
 135.318 +
 135.319 +      // Fill prepared statement with values;
 135.320 +      // writes body to article table
 135.321 +      pstmtAddArticle1.setInt(1, newArticleID);
 135.322 +      pstmtAddArticle1.setBytes(2, article.getBody());
 135.323 +      pstmtAddArticle1.execute();
 135.324 +
 135.325 +      // Add headers
 135.326 +      Enumeration headers = article.getAllHeaders();
 135.327 +      for(int n = 0; headers.hasMoreElements(); n++)
 135.328 +      {
 135.329 +        Header header = (Header)headers.nextElement();
 135.330 +        pstmtAddArticle2.setInt(1, newArticleID);
 135.331 +        pstmtAddArticle2.setString(2, header.getName().toLowerCase());
 135.332 +        pstmtAddArticle2.setString(3, 
 135.333 +          header.getValue().replaceAll("[\r\n]", ""));
 135.334 +        pstmtAddArticle2.setInt(4, n);
 135.335 +        pstmtAddArticle2.execute();
 135.336 +      }
 135.337 +      
 135.338 +      // For each newsgroup add a reference
 135.339 +      List<Group> groups = article.getGroups();
 135.340 +      for(Group group : groups)
 135.341 +      {
 135.342 +        pstmtAddArticle3.setLong(1, group.getInternalID());
 135.343 +        pstmtAddArticle3.setInt(2, newArticleID);
 135.344 +        pstmtAddArticle3.setLong(3, getMaxArticleIndex(group.getInternalID()) + 1);
 135.345 +        pstmtAddArticle3.execute();
 135.346 +      }
 135.347 +      
 135.348 +      // Write message-id to article_ids table
 135.349 +      this.pstmtAddArticle4.setInt(1, newArticleID);
 135.350 +      this.pstmtAddArticle4.setString(2, article.getMessageID());
 135.351 +      this.pstmtAddArticle4.execute();
 135.352 +
 135.353 +      this.conn.commit();
 135.354 +      this.conn.setAutoCommit(true);
 135.355 +
 135.356 +      this.restarts = 0; // Reset error count
 135.357 +    }
 135.358 +    catch(SQLException ex)
 135.359 +    {
 135.360 +      try
 135.361 +      {
 135.362 +        this.conn.rollback();  // Rollback changes
 135.363 +      }
 135.364 +      catch(SQLException ex2)
 135.365 +      {
 135.366 +        Log.get().severe("Rollback of addArticle() failed: " + ex2);
 135.367 +      }
 135.368 +      
 135.369 +      try
 135.370 +      {
 135.371 +        this.conn.setAutoCommit(true); // and release locks
 135.372 +      }
 135.373 +      catch(SQLException ex2)
 135.374 +      {
 135.375 +        Log.get().severe("setAutoCommit(true) of addArticle() failed: " + ex2);
 135.376 +      }
 135.377 +
 135.378 +      restartConnection(ex);
 135.379 +      addArticle(article);
 135.380 +    }
 135.381 +  }
 135.382 +  
 135.383 +  /**
 135.384 +   * Adds a group to the JDBCDatabase. This method is not accessible via NNTP.
 135.385 +   * @param name
 135.386 +   * @throws java.sql.SQLException
 135.387 +   */
 135.388 +  @Override
 135.389 +  public void addGroup(String name, int flags)
 135.390 +    throws StorageBackendException
 135.391 +  {
 135.392 +    try
 135.393 +    {
 135.394 +      this.conn.setAutoCommit(false);
 135.395 +      pstmtAddGroup0.setString(1, name);
 135.396 +      pstmtAddGroup0.setInt(2, flags);
 135.397 +
 135.398 +      pstmtAddGroup0.executeUpdate();
 135.399 +      this.conn.commit();
 135.400 +      this.conn.setAutoCommit(true);
 135.401 +      this.restarts = 0; // Reset error count
 135.402 +    }
 135.403 +    catch(SQLException ex)
 135.404 +    {
 135.405 +      try
 135.406 +      {
 135.407 +        this.conn.rollback();
 135.408 +        this.conn.setAutoCommit(true);
 135.409 +      }
 135.410 +      catch(SQLException ex2)
 135.411 +      {
 135.412 +        ex2.printStackTrace();
 135.413 +      }
 135.414 +
 135.415 +      restartConnection(ex);
 135.416 +      addGroup(name, flags);
 135.417 +    }
 135.418 +  }
 135.419 +
 135.420 +  @Override
 135.421 +  public void addEvent(long time, int type, long gid)
 135.422 +    throws StorageBackendException
 135.423 +  {
 135.424 +    try
 135.425 +    {
 135.426 +      this.conn.setAutoCommit(false);
 135.427 +      this.pstmtAddEvent.setLong(1, time);
 135.428 +      this.pstmtAddEvent.setInt(2, type);
 135.429 +      this.pstmtAddEvent.setLong(3, gid);
 135.430 +      this.pstmtAddEvent.executeUpdate();
 135.431 +      this.conn.commit();
 135.432 +      this.conn.setAutoCommit(true);
 135.433 +      this.restarts = 0;
 135.434 +    }
 135.435 +    catch(SQLException ex)
 135.436 +    {
 135.437 +      try
 135.438 +      {
 135.439 +        this.conn.rollback();
 135.440 +        this.conn.setAutoCommit(true);
 135.441 +      }
 135.442 +      catch(SQLException ex2)
 135.443 +      {
 135.444 +        ex2.printStackTrace();
 135.445 +      }
 135.446 +
 135.447 +      restartConnection(ex);
 135.448 +      addEvent(time, type, gid);
 135.449 +    }
 135.450 +  }
 135.451 +
 135.452 +  @Override
 135.453 +  public int countArticles()
 135.454 +    throws StorageBackendException
 135.455 +  {
 135.456 +    ResultSet rs = null;
 135.457 +
 135.458 +    try
 135.459 +    {
 135.460 +      rs = this.pstmtCountArticles.executeQuery();
 135.461 +      if(rs.next())
 135.462 +      {
 135.463 +        return rs.getInt(1);
 135.464 +      }
 135.465 +      else
 135.466 +      {
 135.467 +        return -1;
 135.468 +      }
 135.469 +    }
 135.470 +    catch(SQLException ex)
 135.471 +    {
 135.472 +      restartConnection(ex);
 135.473 +      return countArticles();
 135.474 +    }
 135.475 +    finally
 135.476 +    {
 135.477 +      if(rs != null)
 135.478 +      {
 135.479 +        try
 135.480 +        {
 135.481 +          rs.close();
 135.482 +        }
 135.483 +        catch(SQLException ex)
 135.484 +        {
 135.485 +          ex.printStackTrace();
 135.486 +        }
 135.487 +        restarts = 0;
 135.488 +      }
 135.489 +    }
 135.490 +  }
 135.491 +
 135.492 +  @Override
 135.493 +  public int countGroups()
 135.494 +    throws StorageBackendException
 135.495 +  {
 135.496 +    ResultSet rs = null;
 135.497 +
 135.498 +    try
 135.499 +    {
 135.500 +      rs = this.pstmtCountGroups.executeQuery();
 135.501 +      if(rs.next())
 135.502 +      {
 135.503 +        return rs.getInt(1);
 135.504 +      }
 135.505 +      else
 135.506 +      {
 135.507 +        return -1;
 135.508 +      }
 135.509 +    }
 135.510 +    catch(SQLException ex)
 135.511 +    {
 135.512 +      restartConnection(ex);
 135.513 +      return countGroups();
 135.514 +    }
 135.515 +    finally
 135.516 +    {
 135.517 +      if(rs != null)
 135.518 +      {
 135.519 +        try
 135.520 +        {
 135.521 +          rs.close();
 135.522 +        }
 135.523 +        catch(SQLException ex)
 135.524 +        {
 135.525 +          ex.printStackTrace();
 135.526 +        }
 135.527 +        restarts = 0;
 135.528 +      }
 135.529 +    }
 135.530 +  }
 135.531 +
 135.532 +  @Override
 135.533 +  public void delete(final String messageID)
 135.534 +    throws StorageBackendException
 135.535 +  {
 135.536 +    try
 135.537 +    {
 135.538 +      this.conn.setAutoCommit(false);
 135.539 +      
 135.540 +      this.pstmtDeleteArticle0.setString(1, messageID);
 135.541 +      int rs = this.pstmtDeleteArticle0.executeUpdate();
 135.542 +      
 135.543 +      // We do not trust the ON DELETE CASCADE functionality to delete
 135.544 +      // orphaned references...
 135.545 +      this.pstmtDeleteArticle1.setString(1, messageID);
 135.546 +      rs = this.pstmtDeleteArticle1.executeUpdate();
 135.547 +
 135.548 +      this.pstmtDeleteArticle2.setString(1, messageID);
 135.549 +      rs = this.pstmtDeleteArticle2.executeUpdate();
 135.550 +
 135.551 +      this.pstmtDeleteArticle3.setString(1, messageID);
 135.552 +      rs = this.pstmtDeleteArticle3.executeUpdate();
 135.553 +      
 135.554 +      this.conn.commit();
 135.555 +      this.conn.setAutoCommit(true);
 135.556 +    }
 135.557 +    catch(SQLException ex)
 135.558 +    {
 135.559 +      throw new StorageBackendException(ex);
 135.560 +    }
 135.561 +  }
 135.562 +
 135.563 +  @Override
 135.564 +  public Article getArticle(String messageID)
 135.565 +    throws StorageBackendException
 135.566 +  {
 135.567 +    ResultSet rs = null;
 135.568 +    try
 135.569 +    {
 135.570 +      pstmtGetArticle0.setString(1, messageID);
 135.571 +      rs = pstmtGetArticle0.executeQuery();
 135.572 +
 135.573 +      if(!rs.next())
 135.574 +      {
 135.575 +        return null;
 135.576 +      }
 135.577 +      else
 135.578 +      {
 135.579 +        byte[] body     = rs.getBytes("body");
 135.580 +        String headers  = getArticleHeaders(rs.getInt("article_id"));
 135.581 +        return new Article(headers, body);
 135.582 +      }
 135.583 +    }
 135.584 +    catch(SQLException ex)
 135.585 +    {
 135.586 +      restartConnection(ex);
 135.587 +      return getArticle(messageID);
 135.588 +    }
 135.589 +    finally
 135.590 +    {
 135.591 +      if(rs != null)
 135.592 +      {
 135.593 +        try
 135.594 +        {
 135.595 +          rs.close();
 135.596 +        }
 135.597 +        catch(SQLException ex)
 135.598 +        {
 135.599 +          ex.printStackTrace();
 135.600 +        }
 135.601 +        restarts = 0; // Reset error count
 135.602 +      }
 135.603 +    }
 135.604 +  }
 135.605 +  
 135.606 +  /**
 135.607 +   * Retrieves an article by its ID.
 135.608 +   * @param articleID
 135.609 +   * @return
 135.610 +   * @throws StorageBackendException
 135.611 +   */
 135.612 +  @Override
 135.613 +  public Article getArticle(long articleIndex, long gid)
 135.614 +    throws StorageBackendException
 135.615 +  {  
 135.616 +    ResultSet rs = null;
 135.617 +
 135.618 +    try
 135.619 +    {
 135.620 +      this.pstmtGetArticle1.setLong(1, articleIndex);
 135.621 +      this.pstmtGetArticle1.setLong(2, gid);
 135.622 +
 135.623 +      rs = this.pstmtGetArticle1.executeQuery();
 135.624 +
 135.625 +      if(rs.next())
 135.626 +      {
 135.627 +        byte[] body    = rs.getBytes("body");
 135.628 +        String headers = getArticleHeaders(rs.getInt("article_id"));
 135.629 +        return new Article(headers, body);
 135.630 +      }
 135.631 +      else
 135.632 +      {
 135.633 +        return null;
 135.634 +      }
 135.635 +    }
 135.636 +    catch(SQLException ex)
 135.637 +    {
 135.638 +      restartConnection(ex);
 135.639 +      return getArticle(articleIndex, gid);
 135.640 +    }
 135.641 +    finally
 135.642 +    {
 135.643 +      if(rs != null)
 135.644 +      {
 135.645 +        try
 135.646 +        {
 135.647 +          rs.close();
 135.648 +        }
 135.649 +        catch(SQLException ex)
 135.650 +        {
 135.651 +          ex.printStackTrace();
 135.652 +        }
 135.653 +        restarts = 0;
 135.654 +      }
 135.655 +    }
 135.656 +  }
 135.657 +
 135.658 +  /**
 135.659 +   * Searches for fitting header values using the given regular expression.
 135.660 +   * @param group
 135.661 +   * @param start
 135.662 +   * @param end
 135.663 +   * @param headerKey
 135.664 +   * @param pattern
 135.665 +   * @return
 135.666 +   * @throws StorageBackendException
 135.667 +   */
 135.668 +  @Override
 135.669 +  public List<Pair<Long, String>> getArticleHeaders(Channel group, long start,
 135.670 +    long end, String headerKey, String patStr)
 135.671 +    throws StorageBackendException, PatternSyntaxException
 135.672 +  {
 135.673 +    ResultSet rs = null;
 135.674 +    List<Pair<Long, String>> heads = new ArrayList<Pair<Long, String>>();
 135.675 +
 135.676 +    try
 135.677 +    {
 135.678 +      this.pstmtGetArticleHeaders1.setString(1, group.getName());
 135.679 +      this.pstmtGetArticleHeaders1.setString(2, headerKey);
 135.680 +      this.pstmtGetArticleHeaders1.setLong(3, start);
 135.681 +
 135.682 +      rs = this.pstmtGetArticleHeaders1.executeQuery();
 135.683 +
 135.684 +      // Convert the "NNTP" regex to Java regex
 135.685 +      patStr = patStr.replace("*", ".*");
 135.686 +      Pattern pattern = Pattern.compile(patStr);
 135.687 +
 135.688 +      while(rs.next())
 135.689 +      {
 135.690 +        Long articleIndex = rs.getLong(1);
 135.691 +        if(end < 0 || articleIndex <= end) // Match start is done via SQL
 135.692 +        {
 135.693 +          String headerValue  = rs.getString(2);
 135.694 +          Matcher matcher = pattern.matcher(headerValue);
 135.695 +          if(matcher.matches())
 135.696 +          {
 135.697 +            heads.add(new Pair<Long, String>(articleIndex, headerValue));
 135.698 +          }
 135.699 +        }
 135.700 +      }
 135.701 +    }
 135.702 +    catch(SQLException ex)
 135.703 +    {
 135.704 +      restartConnection(ex);
 135.705 +      return getArticleHeaders(group, start, end, headerKey, patStr);
 135.706 +    }
 135.707 +    finally
 135.708 +    {
 135.709 +      if(rs != null)
 135.710 +      {
 135.711 +        try
 135.712 +        {
 135.713 +          rs.close();
 135.714 +        }
 135.715 +        catch(SQLException ex)
 135.716 +        {
 135.717 +          ex.printStackTrace();
 135.718 +        }
 135.719 +      }
 135.720 +    }
 135.721 +
 135.722 +    return heads;
 135.723 +  }
 135.724 +
 135.725 +  private String getArticleHeaders(long articleID)
 135.726 +    throws StorageBackendException
 135.727 +  {
 135.728 +    ResultSet rs = null;
 135.729 +    
 135.730 +    try
 135.731 +    {
 135.732 +      this.pstmtGetArticleHeaders0.setLong(1, articleID);
 135.733 +      rs = this.pstmtGetArticleHeaders0.executeQuery();
 135.734 +      
 135.735 +      StringBuilder buf = new StringBuilder();
 135.736 +      if(rs.next())
 135.737 +      {
 135.738 +        for(;;)
 135.739 +        {
 135.740 +          buf.append(rs.getString(1)); // key
 135.741 +          buf.append(": ");
 135.742 +          String foldedValue = MimeUtility.fold(0, rs.getString(2));
 135.743 +          buf.append(foldedValue); // value
 135.744 +          if(rs.next())
 135.745 +          {
 135.746 +            buf.append("\r\n");
 135.747 +          }
 135.748 +          else
 135.749 +          {
 135.750 +            break;
 135.751 +          }
 135.752 +        }
 135.753 +      }
 135.754 +      
 135.755 +      return buf.toString();
 135.756 +    }
 135.757 +    catch(SQLException ex)
 135.758 +    {
 135.759 +      restartConnection(ex);
 135.760 +      return getArticleHeaders(articleID);
 135.761 +    }
 135.762 +    finally
 135.763 +    {
 135.764 +      if(rs != null)
 135.765 +      {
 135.766 +        try
 135.767 +        {
 135.768 +          rs.close();
 135.769 +        }
 135.770 +        catch(SQLException ex)
 135.771 +        {
 135.772 +          ex.printStackTrace();
 135.773 +        }
 135.774 +      }
 135.775 +    }
 135.776 +  }
 135.777 +
 135.778 +  @Override
 135.779 +  public long getArticleIndex(Article article, Group group)
 135.780 +    throws StorageBackendException
 135.781 +  {
 135.782 +    ResultSet rs = null;
 135.783 +
 135.784 +    try
 135.785 +    {
 135.786 +      this.pstmtGetArticleIndex.setString(1, article.getMessageID());
 135.787 +      this.pstmtGetArticleIndex.setLong(2, group.getInternalID());
 135.788 +      
 135.789 +      rs = this.pstmtGetArticleIndex.executeQuery();
 135.790 +      if(rs.next())
 135.791 +      {
 135.792 +        return rs.getLong(1);
 135.793 +      }
 135.794 +      else
 135.795 +      {
 135.796 +        return -1;
 135.797 +      }
 135.798 +    }
 135.799 +    catch(SQLException ex)
 135.800 +    {
 135.801 +      restartConnection(ex);
 135.802 +      return getArticleIndex(article, group);
 135.803 +    }
 135.804 +    finally
 135.805 +    {
 135.806 +      if(rs != null)
 135.807 +      {
 135.808 +        try
 135.809 +        {
 135.810 +          rs.close();
 135.811 +        }
 135.812 +        catch(SQLException ex)
 135.813 +        {
 135.814 +          ex.printStackTrace();
 135.815 +        }
 135.816 +      }
 135.817 +    }
 135.818 +  }
 135.819 +  
 135.820 +  /**
 135.821 +   * Returns a list of Long/Article Pairs.
 135.822 +   * @throws java.sql.SQLException
 135.823 +   */
 135.824 +  @Override
 135.825 +  public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first,
 135.826 +    long last)
 135.827 +    throws StorageBackendException
 135.828 +  {
 135.829 +    ResultSet rs = null;
 135.830 +
 135.831 +    try
 135.832 +    {
 135.833 +      this.pstmtGetArticleHeads.setLong(1, group.getInternalID());
 135.834 +      this.pstmtGetArticleHeads.setLong(2, first);
 135.835 +      this.pstmtGetArticleHeads.setLong(3, last);
 135.836 +      rs = pstmtGetArticleHeads.executeQuery();
 135.837 +
 135.838 +      List<Pair<Long, ArticleHead>> articles 
 135.839 +        = new ArrayList<Pair<Long, ArticleHead>>();
 135.840 +
 135.841 +      while (rs.next())
 135.842 +      {
 135.843 +        long aid  = rs.getLong("article_id");
 135.844 +        long aidx = rs.getLong("article_index");
 135.845 +        String headers = getArticleHeaders(aid);
 135.846 +        articles.add(new Pair<Long, ArticleHead>(aidx, 
 135.847 +                        new ArticleHead(headers)));
 135.848 +      }
 135.849 +
 135.850 +      return articles;
 135.851 +    }
 135.852 +    catch(SQLException ex)
 135.853 +    {
 135.854 +      restartConnection(ex);
 135.855 +      return getArticleHeads(group, first, last);
 135.856 +    }
 135.857 +    finally
 135.858 +    {
 135.859 +      if(rs != null)
 135.860 +      {
 135.861 +        try
 135.862 +        {
 135.863 +          rs.close();
 135.864 +        }
 135.865 +        catch(SQLException ex)
 135.866 +        {
 135.867 +          ex.printStackTrace();
 135.868 +        }
 135.869 +      }
 135.870 +    }
 135.871 +  }
 135.872 +
 135.873 +  @Override
 135.874 +  public List<Long> getArticleNumbers(long gid)
 135.875 +    throws StorageBackendException
 135.876 +  {
 135.877 +    ResultSet rs = null;
 135.878 +    try
 135.879 +    {
 135.880 +      List<Long> ids = new ArrayList<Long>();
 135.881 +      this.pstmtGetArticleIDs.setLong(1, gid);
 135.882 +      rs = this.pstmtGetArticleIDs.executeQuery();
 135.883 +      while(rs.next())
 135.884 +      {
 135.885 +        ids.add(rs.getLong(1));
 135.886 +      }
 135.887 +      return ids;
 135.888 +    }
 135.889 +    catch(SQLException ex)
 135.890 +    {
 135.891 +      restartConnection(ex);
 135.892 +      return getArticleNumbers(gid);
 135.893 +    }
 135.894 +    finally
 135.895 +    {
 135.896 +      if(rs != null)
 135.897 +      {
 135.898 +        try
 135.899 +        {
 135.900 +          rs.close();
 135.901 +          restarts = 0; // Clear the restart count after successful request
 135.902 +        }
 135.903 +        catch(SQLException ex)
 135.904 +        {
 135.905 +          ex.printStackTrace();
 135.906 +        }
 135.907 +      }
 135.908 +    }
 135.909 +  }
 135.910 +
 135.911 +  @Override
 135.912 +  public String getConfigValue(String key)
 135.913 +    throws StorageBackendException
 135.914 +  {
 135.915 +    ResultSet rs = null;
 135.916 +    try
 135.917 +    {
 135.918 +      this.pstmtGetConfigValue.setString(1, key);
 135.919 +
 135.920 +      rs = this.pstmtGetConfigValue.executeQuery();
 135.921 +      if(rs.next())
 135.922 +      {
 135.923 +        return rs.getString(1); // First data on index 1 not 0
 135.924 +      }
 135.925 +      else
 135.926 +      {
 135.927 +        return null;
 135.928 +      }
 135.929 +    }
 135.930 +    catch(SQLException ex)
 135.931 +    {
 135.932 +      restartConnection(ex);
 135.933 +      return getConfigValue(key);
 135.934 +    }
 135.935 +    finally
 135.936 +    {
 135.937 +      if(rs != null)
 135.938 +      {
 135.939 +        try
 135.940 +        {
 135.941 +          rs.close();
 135.942 +        }
 135.943 +        catch(SQLException ex)
 135.944 +        {
 135.945 +          ex.printStackTrace();
 135.946 +        }
 135.947 +        restarts = 0; // Clear the restart count after successful request
 135.948 +      }
 135.949 +    }
 135.950 +  }
 135.951 +
 135.952 +  @Override
 135.953 +  public int getEventsCount(int type, long start, long end, Channel channel)
 135.954 +    throws StorageBackendException
 135.955 +  {
 135.956 +    ResultSet rs = null;
 135.957 +    
 135.958 +    try
 135.959 +    {
 135.960 +      if(channel == null)
 135.961 +      {
 135.962 +        this.pstmtGetEventsCount0.setInt(1, type);
 135.963 +        this.pstmtGetEventsCount0.setLong(2, start);
 135.964 +        this.pstmtGetEventsCount0.setLong(3, end);
 135.965 +        rs = this.pstmtGetEventsCount0.executeQuery();
 135.966 +      }
 135.967 +      else
 135.968 +      {
 135.969 +        this.pstmtGetEventsCount1.setInt(1, type);
 135.970 +        this.pstmtGetEventsCount1.setLong(2, start);
 135.971 +        this.pstmtGetEventsCount1.setLong(3, end);
 135.972 +        this.pstmtGetEventsCount1.setLong(4, channel.getInternalID());
 135.973 +        rs = this.pstmtGetEventsCount1.executeQuery();
 135.974 +      }
 135.975 +      
 135.976 +      if(rs.next())
 135.977 +      {
 135.978 +        return rs.getInt(1);
 135.979 +      }
 135.980 +      else
 135.981 +      {
 135.982 +        return -1;
 135.983 +      }
 135.984 +    }
 135.985 +    catch(SQLException ex)
 135.986 +    {
 135.987 +      restartConnection(ex);
 135.988 +      return getEventsCount(type, start, end, channel);
 135.989 +    }
 135.990 +    finally
 135.991 +    {
 135.992 +      if(rs != null)
 135.993 +      {
 135.994 +        try
 135.995 +        {
 135.996 +          rs.close();
 135.997 +        }
 135.998 +        catch(SQLException ex)
 135.999 +        {
135.1000 +          ex.printStackTrace();
135.1001 +        }
135.1002 +      }
135.1003 +    }
135.1004 +  }
135.1005 +  
135.1006 +  /**
135.1007 +   * Reads all Groups from the JDBCDatabase.
135.1008 +   * @return
135.1009 +   * @throws StorageBackendException
135.1010 +   */
135.1011 +  @Override
135.1012 +  public List<Channel> getGroups()
135.1013 +    throws StorageBackendException
135.1014 +  {
135.1015 +    ResultSet   rs;
135.1016 +    List<Channel> buffer = new ArrayList<Channel>();
135.1017 +    Statement   stmt   = null;
135.1018 +
135.1019 +    try
135.1020 +    {
135.1021 +      stmt = conn.createStatement();
135.1022 +      rs = stmt.executeQuery("SELECT * FROM groups ORDER BY name");
135.1023 +
135.1024 +      while(rs.next())
135.1025 +      {
135.1026 +        String name  = rs.getString("name");
135.1027 +        long   id    = rs.getLong("group_id");
135.1028 +        int    flags = rs.getInt("flags");
135.1029 +        
135.1030 +        Group group = new Group(name, id, flags);
135.1031 +        buffer.add(group);
135.1032 +      }
135.1033 +
135.1034 +      return buffer;
135.1035 +    }
135.1036 +    catch(SQLException ex)
135.1037 +    {
135.1038 +      restartConnection(ex);
135.1039 +      return getGroups();
135.1040 +    }
135.1041 +    finally
135.1042 +    {
135.1043 +      if(stmt != null)
135.1044 +      {
135.1045 +        try
135.1046 +        {
135.1047 +          stmt.close(); // Implicitely closes ResultSets
135.1048 +        }
135.1049 +        catch(SQLException ex)
135.1050 +        {
135.1051 +          ex.printStackTrace();
135.1052 +        }
135.1053 +      }
135.1054 +    }
135.1055 +  }
135.1056 +
135.1057 +  @Override
135.1058 +  public List<String> getGroupsForList(String listAddress)
135.1059 +    throws StorageBackendException
135.1060 +  {
135.1061 +    ResultSet rs = null;
135.1062 +    
135.1063 +    try
135.1064 +    {
135.1065 +      this.pstmtGetGroupForList.setString(1, listAddress);
135.1066 +
135.1067 +      rs = this.pstmtGetGroupForList.executeQuery();
135.1068 +      List<String> groups = new ArrayList<String>();
135.1069 +      while(rs.next())
135.1070 +      {
135.1071 +        String group = rs.getString(1);
135.1072 +        groups.add(group);
135.1073 +      }
135.1074 +      return groups;
135.1075 +    }
135.1076 +    catch(SQLException ex)
135.1077 +    {
135.1078 +      restartConnection(ex);
135.1079 +      return getGroupsForList(listAddress);
135.1080 +    }
135.1081 +    finally
135.1082 +    {
135.1083 +      if(rs != null)
135.1084 +      {
135.1085 +        try
135.1086 +        {
135.1087 +          rs.close();
135.1088 +        }
135.1089 +        catch(SQLException ex)
135.1090 +        {
135.1091 +          ex.printStackTrace();
135.1092 +        }
135.1093 +      }
135.1094 +    }
135.1095 +  }
135.1096 +  
135.1097 +  /**
135.1098 +   * Returns the Group that is identified by the name.
135.1099 +   * @param name
135.1100 +   * @return
135.1101 +   * @throws StorageBackendException
135.1102 +   */
135.1103 +  @Override
135.1104 +  public Group getGroup(String name)
135.1105 +    throws StorageBackendException
135.1106 +  {
135.1107 +    ResultSet rs = null;
135.1108 +    
135.1109 +    try
135.1110 +    {
135.1111 +      this.pstmtGetGroup0.setString(1, name);
135.1112 +      rs = this.pstmtGetGroup0.executeQuery();
135.1113 +
135.1114 +      if (!rs.next())
135.1115 +      {
135.1116 +        return null;
135.1117 +      }
135.1118 +      else
135.1119 +      {
135.1120 +        long id = rs.getLong("group_id");
135.1121 +        int flags = rs.getInt("flags");
135.1122 +        return new Group(name, id, flags);
135.1123 +      }
135.1124 +    }
135.1125 +    catch(SQLException ex)
135.1126 +    {
135.1127 +      restartConnection(ex);
135.1128 +      return getGroup(name);
135.1129 +    }
135.1130 +    finally
135.1131 +    {
135.1132 +      if(rs != null)
135.1133 +      {
135.1134 +        try
135.1135 +        {
135.1136 +          rs.close();
135.1137 +        }
135.1138 +        catch(SQLException ex)
135.1139 +        {
135.1140 +          ex.printStackTrace();
135.1141 +        }
135.1142 +      }
135.1143 +    }
135.1144 +  }
135.1145 +
135.1146 +  @Override
135.1147 +  public List<String> getListsForGroup(String group)
135.1148 +    throws StorageBackendException
135.1149 +  {
135.1150 +    ResultSet     rs    = null;
135.1151 +    List<String>  lists = new ArrayList<String>();
135.1152 +
135.1153 +    try
135.1154 +    {
135.1155 +      this.pstmtGetListForGroup.setString(1, group);
135.1156 +      rs = this.pstmtGetListForGroup.executeQuery();
135.1157 +
135.1158 +      while(rs.next())
135.1159 +      {
135.1160 +        lists.add(rs.getString(1));
135.1161 +      }
135.1162 +      return lists;
135.1163 +    }
135.1164 +    catch(SQLException ex)
135.1165 +    {
135.1166 +      restartConnection(ex);
135.1167 +      return getListsForGroup(group);
135.1168 +    }
135.1169 +    finally
135.1170 +    {
135.1171 +      if(rs != null)
135.1172 +      {
135.1173 +        try
135.1174 +        {
135.1175 +          rs.close();
135.1176 +        }
135.1177 +        catch(SQLException ex)
135.1178 +        {
135.1179 +          ex.printStackTrace();
135.1180 +        }
135.1181 +      }
135.1182 +    }
135.1183 +  }
135.1184 +  
135.1185 +  private int getMaxArticleIndex(long groupID)
135.1186 +    throws StorageBackendException
135.1187 +  {
135.1188 +    ResultSet rs    = null;
135.1189 +
135.1190 +    try
135.1191 +    {
135.1192 +      this.pstmtGetMaxArticleIndex.setLong(1, groupID);
135.1193 +      rs = this.pstmtGetMaxArticleIndex.executeQuery();
135.1194 +
135.1195 +      int maxIndex = 0;
135.1196 +      if (rs.next())
135.1197 +      {
135.1198 +        maxIndex = rs.getInt(1);
135.1199 +      }
135.1200 +
135.1201 +      return maxIndex;
135.1202 +    }
135.1203 +    catch(SQLException ex)
135.1204 +    {
135.1205 +      restartConnection(ex);
135.1206 +      return getMaxArticleIndex(groupID);
135.1207 +    }
135.1208 +    finally
135.1209 +    {
135.1210 +      if(rs != null)
135.1211 +      {
135.1212 +        try
135.1213 +        {
135.1214 +          rs.close();
135.1215 +        }
135.1216 +        catch(SQLException ex)
135.1217 +        {
135.1218 +          ex.printStackTrace();
135.1219 +        }
135.1220 +      }
135.1221 +    }
135.1222 +  }
135.1223 +  
135.1224 +  private int getMaxArticleID()
135.1225 +    throws StorageBackendException
135.1226 +  {
135.1227 +    ResultSet rs    = null;
135.1228 +
135.1229 +    try
135.1230 +    {
135.1231 +      rs = this.pstmtGetMaxArticleID.executeQuery();
135.1232 +
135.1233 +      int maxIndex = 0;
135.1234 +      if (rs.next())
135.1235 +      {
135.1236 +        maxIndex = rs.getInt(1);
135.1237 +      }
135.1238 +
135.1239 +      return maxIndex;
135.1240 +    }
135.1241 +    catch(SQLException ex)
135.1242 +    {
135.1243 +      restartConnection(ex);
135.1244 +      return getMaxArticleID();
135.1245 +    }
135.1246 +    finally
135.1247 +    {
135.1248 +      if(rs != null)
135.1249 +      {
135.1250 +        try
135.1251 +        {
135.1252 +          rs.close();
135.1253 +        }
135.1254 +        catch(SQLException ex)
135.1255 +        {
135.1256 +          ex.printStackTrace();
135.1257 +        }
135.1258 +      }
135.1259 +    }
135.1260 +  }
135.1261 +
135.1262 +  @Override
135.1263 +  public int getLastArticleNumber(Group group)
135.1264 +    throws StorageBackendException
135.1265 +  {
135.1266 +    ResultSet rs = null;
135.1267 +
135.1268 +    try
135.1269 +    {
135.1270 +      this.pstmtGetLastArticleNumber.setLong(1, group.getInternalID());
135.1271 +      rs = this.pstmtGetLastArticleNumber.executeQuery();
135.1272 +      if (rs.next())
135.1273 +      {
135.1274 +        return rs.getInt(1);
135.1275 +      }
135.1276 +      else
135.1277 +      {
135.1278 +        return 0;
135.1279 +      }
135.1280 +    }
135.1281 +    catch(SQLException ex)
135.1282 +    {
135.1283 +      restartConnection(ex);
135.1284 +      return getLastArticleNumber(group);
135.1285 +    }
135.1286 +    finally
135.1287 +    {
135.1288 +      if(rs != null)
135.1289 +      {
135.1290 +        try
135.1291 +        {
135.1292 +          rs.close();
135.1293 +        }
135.1294 +        catch(SQLException ex)
135.1295 +        {
135.1296 +          ex.printStackTrace();
135.1297 +        }
135.1298 +      }
135.1299 +    }
135.1300 +  }
135.1301 +
135.1302 +  @Override
135.1303 +  public int getFirstArticleNumber(Group group)
135.1304 +    throws StorageBackendException
135.1305 +  {
135.1306 +    ResultSet rs = null;
135.1307 +    try
135.1308 +    {
135.1309 +      this.pstmtGetFirstArticleNumber.setLong(1, group.getInternalID());
135.1310 +      rs = this.pstmtGetFirstArticleNumber.executeQuery();
135.1311 +      if(rs.next())
135.1312 +      {
135.1313 +        return rs.getInt(1);
135.1314 +      }
135.1315 +      else
135.1316 +      {
135.1317 +        return 0;
135.1318 +      }
135.1319 +    }
135.1320 +    catch(SQLException ex)
135.1321 +    {
135.1322 +      restartConnection(ex);
135.1323 +      return getFirstArticleNumber(group);
135.1324 +    }
135.1325 +    finally
135.1326 +    {
135.1327 +      if(rs != null)
135.1328 +      {
135.1329 +        try
135.1330 +        {
135.1331 +          rs.close();
135.1332 +        }
135.1333 +        catch(SQLException ex)
135.1334 +        {
135.1335 +          ex.printStackTrace();
135.1336 +        }
135.1337 +      }
135.1338 +    }
135.1339 +  }
135.1340 +  
135.1341 +  /**
135.1342 +   * Returns a group name identified by the given id.
135.1343 +   * @param id
135.1344 +   * @return
135.1345 +   * @throws StorageBackendException
135.1346 +   */
135.1347 +  public String getGroup(int id)
135.1348 +    throws StorageBackendException
135.1349 +  {
135.1350 +    ResultSet rs = null;
135.1351 +
135.1352 +    try
135.1353 +    {
135.1354 +      this.pstmtGetGroup1.setInt(1, id);
135.1355 +      rs = this.pstmtGetGroup1.executeQuery();
135.1356 +
135.1357 +      if (rs.next())
135.1358 +      {
135.1359 +        return rs.getString(1);
135.1360 +      }
135.1361 +      else
135.1362 +      {
135.1363 +        return null;
135.1364 +      }
135.1365 +    }
135.1366 +    catch(SQLException ex)
135.1367 +    {
135.1368 +      restartConnection(ex);
135.1369 +      return getGroup(id);
135.1370 +    }
135.1371 +    finally
135.1372 +    {
135.1373 +      if(rs != null)
135.1374 +      {
135.1375 +        try
135.1376 +        {
135.1377 +          rs.close();
135.1378 +        }
135.1379 +        catch(SQLException ex)
135.1380 +        {
135.1381 +          ex.printStackTrace();
135.1382 +        }
135.1383 +      }
135.1384 +    }
135.1385 +  }
135.1386 +
135.1387 +  @Override
135.1388 +  public double getEventsPerHour(int key, long gid)
135.1389 +    throws StorageBackendException
135.1390 +  {
135.1391 +    String gidquery = "";
135.1392 +    if(gid >= 0)
135.1393 +    {
135.1394 +      gidquery = " AND group_id = " + gid;
135.1395 +    }
135.1396 +    
135.1397 +    Statement stmt = null;
135.1398 +    ResultSet rs   = null;
135.1399 +    
135.1400 +    try
135.1401 +    {
135.1402 +      stmt = this.conn.createStatement();
135.1403 +      rs = stmt.executeQuery("SELECT Count(*) / (Max(event_time) - Min(event_time))" +
135.1404 +        " * 1000 * 60 * 60 FROM events WHERE event_key = " + key + gidquery);
135.1405 +      
135.1406 +      if(rs.next())
135.1407 +      {
135.1408 +        restarts = 0; // reset error count
135.1409 +        return rs.getDouble(1);
135.1410 +      }
135.1411 +      else
135.1412 +      {
135.1413 +        return Double.NaN;
135.1414 +      }
135.1415 +    }
135.1416 +    catch(SQLException ex)
135.1417 +    {
135.1418 +      restartConnection(ex);
135.1419 +      return getEventsPerHour(key, gid);
135.1420 +    }
135.1421 +    finally
135.1422 +    {
135.1423 +      try
135.1424 +      {
135.1425 +        if(stmt != null)
135.1426 +        {
135.1427 +          stmt.close(); // Implicitely closes the result sets
135.1428 +        }
135.1429 +      }
135.1430 +      catch(SQLException ex)
135.1431 +      {
135.1432 +        ex.printStackTrace();
135.1433 +      }
135.1434 +    }
135.1435 +  }
135.1436 +
135.1437 +  @Override
135.1438 +  public String getOldestArticle()
135.1439 +    throws StorageBackendException
135.1440 +  {
135.1441 +    ResultSet rs = null;
135.1442 +
135.1443 +    try
135.1444 +    {
135.1445 +      rs = this.pstmtGetOldestArticle.executeQuery();
135.1446 +      if(rs.next())
135.1447 +      {
135.1448 +        return rs.getString(1);
135.1449 +      }
135.1450 +      else
135.1451 +      {
135.1452 +        return null;
135.1453 +      }
135.1454 +    }
135.1455 +    catch(SQLException ex)
135.1456 +    {
135.1457 +      restartConnection(ex);
135.1458 +      return getOldestArticle();
135.1459 +    }
135.1460 +    finally
135.1461 +    {
135.1462 +      if(rs != null)
135.1463 +      {
135.1464 +        try
135.1465 +        {
135.1466 +          rs.close();
135.1467 +        }
135.1468 +        catch(SQLException ex)
135.1469 +        {
135.1470 +          ex.printStackTrace();
135.1471 +        }
135.1472 +      }
135.1473 +    }
135.1474 +  }
135.1475 +
135.1476 +  @Override
135.1477 +  public int getPostingsCount(String groupname)
135.1478 +    throws StorageBackendException
135.1479 +  {
135.1480 +    ResultSet rs = null;
135.1481 +    
135.1482 +    try
135.1483 +    {
135.1484 +      this.pstmtGetPostingsCount.setString(1, groupname);
135.1485 +      rs = this.pstmtGetPostingsCount.executeQuery();
135.1486 +      if(rs.next())
135.1487 +      {
135.1488 +        return rs.getInt(1);
135.1489 +      }
135.1490 +      else
135.1491 +      {
135.1492 +        Log.get().warning("Count on postings return nothing!");
135.1493 +        return 0;
135.1494 +      }
135.1495 +    }
135.1496 +    catch(SQLException ex)
135.1497 +    {
135.1498 +      restartConnection(ex);
135.1499 +      return getPostingsCount(groupname);
135.1500 +    }
135.1501 +    finally
135.1502 +    {
135.1503 +      if(rs != null)
135.1504 +      {
135.1505 +        try
135.1506 +        {
135.1507 +          rs.close();
135.1508 +        }
135.1509 +        catch(SQLException ex)
135.1510 +        {
135.1511 +          ex.printStackTrace();
135.1512 +        }
135.1513 +      }
135.1514 +    }
135.1515 +  }
135.1516 +
135.1517 +  @Override
135.1518 +  public List<Subscription> getSubscriptions(int feedtype)
135.1519 +    throws StorageBackendException
135.1520 +  {
135.1521 +    ResultSet rs = null;
135.1522 +    
135.1523 +    try
135.1524 +    {
135.1525 +      List<Subscription> subs = new ArrayList<Subscription>();
135.1526 +      this.pstmtGetSubscriptions.setInt(1, feedtype);
135.1527 +      rs = this.pstmtGetSubscriptions.executeQuery();
135.1528 +      
135.1529 +      while(rs.next())
135.1530 +      {
135.1531 +        String host  = rs.getString("host");
135.1532 +        String group = rs.getString("name");
135.1533 +        int    port  = rs.getInt("port");
135.1534 +        subs.add(new Subscription(host, port, feedtype, group));
135.1535 +      }
135.1536 +      
135.1537 +      return subs;
135.1538 +    }
135.1539 +    catch(SQLException ex)
135.1540 +    {
135.1541 +      restartConnection(ex);
135.1542 +      return getSubscriptions(feedtype);
135.1543 +    }
135.1544 +    finally
135.1545 +    {
135.1546 +      if(rs != null)
135.1547 +      {
135.1548 +        try
135.1549 +        {
135.1550 +          rs.close();
135.1551 +        }
135.1552 +        catch(SQLException ex)
135.1553 +        {
135.1554 +          ex.printStackTrace();
135.1555 +        }
135.1556 +      }
135.1557 +    }
135.1558 +  }
135.1559 +
135.1560 +  /**
135.1561 +   * Checks if there is an article with the given messageid in the JDBCDatabase.
135.1562 +   * @param name
135.1563 +   * @return
135.1564 +   * @throws StorageBackendException
135.1565 +   */
135.1566 +  @Override
135.1567 +  public boolean isArticleExisting(String messageID)
135.1568 +    throws StorageBackendException
135.1569 +  {
135.1570 +    ResultSet rs = null;
135.1571 +    
135.1572 +    try
135.1573 +    {
135.1574 +      this.pstmtIsArticleExisting.setString(1, messageID);
135.1575 +      rs = this.pstmtIsArticleExisting.executeQuery();
135.1576 +      return rs.next() && rs.getInt(1) == 1;
135.1577 +    }
135.1578 +    catch(SQLException ex)
135.1579 +    {
135.1580 +      restartConnection(ex);
135.1581 +      return isArticleExisting(messageID);
135.1582 +    }
135.1583 +    finally
135.1584 +    {
135.1585 +      if(rs != null)
135.1586 +      {
135.1587 +        try
135.1588 +        {
135.1589 +          rs.close();
135.1590 +        }
135.1591 +        catch(SQLException ex)
135.1592 +        {
135.1593 +          ex.printStackTrace();
135.1594 +        }
135.1595 +      }
135.1596 +    }
135.1597 +  }
135.1598 +  
135.1599 +  /**
135.1600 +   * Checks if there is a group with the given name in the JDBCDatabase.
135.1601 +   * @param name
135.1602 +   * @return
135.1603 +   * @throws StorageBackendException
135.1604 +   */
135.1605 +  @Override
135.1606 +  public boolean isGroupExisting(String name)
135.1607 +    throws StorageBackendException
135.1608 +  {
135.1609 +    ResultSet rs = null;
135.1610 +    
135.1611 +    try
135.1612 +    {
135.1613 +      this.pstmtIsGroupExisting.setString(1, name);
135.1614 +      rs = this.pstmtIsGroupExisting.executeQuery();
135.1615 +      return rs.next();
135.1616 +    }
135.1617 +    catch(SQLException ex)
135.1618 +    {
135.1619 +      restartConnection(ex);
135.1620 +      return isGroupExisting(name);
135.1621 +    }
135.1622 +    finally
135.1623 +    {
135.1624 +      if(rs != null)
135.1625 +      {
135.1626 +        try
135.1627 +        {
135.1628 +          rs.close();
135.1629 +        }
135.1630 +        catch(SQLException ex)
135.1631 +        {
135.1632 +          ex.printStackTrace();
135.1633 +        }
135.1634 +      }
135.1635 +    }
135.1636 +  }
135.1637 +
135.1638 +  @Override
135.1639 +  public void setConfigValue(String key, String value)
135.1640 +    throws StorageBackendException
135.1641 +  {
135.1642 +    try
135.1643 +    {
135.1644 +      conn.setAutoCommit(false);
135.1645 +      this.pstmtSetConfigValue0.setString(1, key);
135.1646 +      this.pstmtSetConfigValue0.execute();
135.1647 +      this.pstmtSetConfigValue1.setString(1, key);
135.1648 +      this.pstmtSetConfigValue1.setString(2, value);
135.1649 +      this.pstmtSetConfigValue1.execute();
135.1650 +      conn.commit();
135.1651 +      conn.setAutoCommit(true);
135.1652 +    }
135.1653 +    catch(SQLException ex)
135.1654 +    {
135.1655 +      restartConnection(ex);
135.1656 +      setConfigValue(key, value);
135.1657 +    }
135.1658 +  }
135.1659 +  
135.1660 +  /**
135.1661 +   * Closes the JDBCDatabase connection.
135.1662 +   */
135.1663 +  public void shutdown()
135.1664 +    throws StorageBackendException
135.1665 +  {
135.1666 +    try
135.1667 +    {
135.1668 +      if(this.conn != null)
135.1669 +      {
135.1670 +        this.conn.close();
135.1671 +      }
135.1672 +    }
135.1673 +    catch(SQLException ex)
135.1674 +    {
135.1675 +      throw new StorageBackendException(ex);
135.1676 +    }
135.1677 +  }
135.1678 +
135.1679 +  @Override
135.1680 +  public void purgeGroup(Group group)
135.1681 +    throws StorageBackendException
135.1682 +  {
135.1683 +    try
135.1684 +    {
135.1685 +      this.pstmtPurgeGroup0.setLong(1, group.getInternalID());
135.1686 +      this.pstmtPurgeGroup0.executeUpdate();
135.1687 +
135.1688 +      this.pstmtPurgeGroup1.setLong(1, group.getInternalID());
135.1689 +      this.pstmtPurgeGroup1.executeUpdate();
135.1690 +    }
135.1691 +    catch(SQLException ex)
135.1692 +    {
135.1693 +      restartConnection(ex);
135.1694 +      purgeGroup(group);
135.1695 +    }
135.1696 +  }
135.1697 +  
135.1698 +  private void restartConnection(SQLException cause)
135.1699 +    throws StorageBackendException
135.1700 +  {
135.1701 +    restarts++;
135.1702 +    Log.get().severe(Thread.currentThread()
135.1703 +      + ": Database connection was closed (restart " + restarts + ").");
135.1704 +    
135.1705 +    if(restarts >= MAX_RESTARTS)
135.1706 +    {
135.1707 +      // Delete the current, probably broken JDBCDatabase instance.
135.1708 +      // So no one can use the instance any more.
135.1709 +      JDBCDatabaseProvider.instances.remove(Thread.currentThread());
135.1710 +      
135.1711 +      // Throw the exception upwards
135.1712 +      throw new StorageBackendException(cause);
135.1713 +    }
135.1714 +    
135.1715 +    try
135.1716 +    {
135.1717 +      Thread.sleep(1500L * restarts);
135.1718 +    }
135.1719 +    catch(InterruptedException ex)
135.1720 +    {
135.1721 +      Log.get().warning("Interrupted: " + ex.getMessage());
135.1722 +    }
135.1723 +    
135.1724 +    // Try to properly close the old database connection
135.1725 +    try
135.1726 +    {
135.1727 +      if(this.conn != null)
135.1728 +      {
135.1729 +        this.conn.close();
135.1730 +      }
135.1731 +    }
135.1732 +    catch(SQLException ex)
135.1733 +    {
135.1734 +      Log.get().warning(ex.getMessage());
135.1735 +    }
135.1736 +    
135.1737 +    try
135.1738 +    {
135.1739 +      // Try to reinitialize database connection
135.1740 +      arise();
135.1741 +    }
135.1742 +    catch(SQLException ex)
135.1743 +    {
135.1744 +      Log.get().warning(ex.getMessage());
135.1745 +      restartConnection(ex);
135.1746 +    }
135.1747 +  }
135.1748 +
135.1749 +  @Override
135.1750 +  public boolean update(Article article)
135.1751 +    throws StorageBackendException
135.1752 +  {
135.1753 +    // DELETE FROM headers WHERE article_id = ?
135.1754 +
135.1755 +    // INSERT INTO headers ...
135.1756 +
135.1757 +    // SELECT * FROM postings WHERE article_id = ? AND group_id = ?
135.1758 +    return false;
135.1759 +  }
135.1760 +
135.1761 +  /**
135.1762 +   * Writes the flags and the name of the given group to the database.
135.1763 +   * @param group
135.1764 +   * @throws StorageBackendException
135.1765 +   */
135.1766 +  @Override
135.1767 +  public boolean update(Group group)
135.1768 +    throws StorageBackendException
135.1769 +  {
135.1770 +    try
135.1771 +    {
135.1772 +      this.pstmtUpdateGroup.setInt(1, group.getFlags());
135.1773 +      this.pstmtUpdateGroup.setString(2, group.getName());
135.1774 +      this.pstmtUpdateGroup.setLong(3, group.getInternalID());
135.1775 +      int rs = this.pstmtUpdateGroup.executeUpdate();
135.1776 +      return rs == 1;
135.1777 +    }
135.1778 +    catch(SQLException ex)
135.1779 +    {
135.1780 +      restartConnection(ex);
135.1781 +      return update(group);
135.1782 +    }
135.1783 +  }
135.1784 +
135.1785 +}
   136.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   136.2 +++ b/src/org/sonews/storage/impl/JDBCDatabaseProvider.java	Sun Aug 29 17:28:58 2010 +0200
   136.3 @@ -0,0 +1,69 @@
   136.4 +/*
   136.5 + *   SONEWS News Server
   136.6 + *   see AUTHORS for the list of contributors
   136.7 + *
   136.8 + *   This program is free software: you can redistribute it and/or modify
   136.9 + *   it under the terms of the GNU General Public License as published by
  136.10 + *   the Free Software Foundation, either version 3 of the License, or
  136.11 + *   (at your option) any later version.
  136.12 + *
  136.13 + *   This program is distributed in the hope that it will be useful,
  136.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  136.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  136.16 + *   GNU General Public License for more details.
  136.17 + *
  136.18 + *   You should have received a copy of the GNU General Public License
  136.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  136.20 + */
  136.21 +
  136.22 +package org.sonews.storage.impl;
  136.23 +
  136.24 +import java.sql.SQLException;
  136.25 +import java.util.Map;
  136.26 +import java.util.concurrent.ConcurrentHashMap;
  136.27 +import org.sonews.storage.Storage;
  136.28 +import org.sonews.storage.StorageBackendException;
  136.29 +import org.sonews.storage.StorageProvider;
  136.30 +
  136.31 +/**
  136.32 + *
  136.33 + * @author Christian Lins
  136.34 + * @since sonews/1.0
  136.35 + */
  136.36 +public class JDBCDatabaseProvider implements StorageProvider
  136.37 +{
  136.38 +
  136.39 +  protected static final Map<Thread, JDBCDatabase> instances
  136.40 +    = new ConcurrentHashMap<Thread, JDBCDatabase>();
  136.41 +
  136.42 +  @Override
  136.43 +  public boolean isSupported(String uri)
  136.44 +  {
  136.45 +    throw new UnsupportedOperationException("Not supported yet.");
  136.46 +  }
  136.47 +
  136.48 +  @Override
  136.49 +  public Storage storage(Thread thread)
  136.50 +    throws StorageBackendException
  136.51 +  {
  136.52 +    try
  136.53 +    {
  136.54 +    if(!instances.containsKey(Thread.currentThread()))
  136.55 +    {
  136.56 +      JDBCDatabase db = new JDBCDatabase();
  136.57 +      db.arise();
  136.58 +      instances.put(Thread.currentThread(), db);
  136.59 +      return db;
  136.60 +    }
  136.61 +    else
  136.62 +    {
  136.63 +      return instances.get(Thread.currentThread());
  136.64 +    }
  136.65 +    }
  136.66 +    catch(SQLException ex)
  136.67 +    {
  136.68 +      throw new StorageBackendException(ex);
  136.69 +    }
  136.70 +  }
  136.71 +
  136.72 +}
   137.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   137.2 +++ b/src/org/sonews/storage/package.html	Sun Aug 29 17:28:58 2010 +0200
   137.3 @@ -0,0 +1,2 @@
   137.4 +Contains classes of the storage backend and the Group and Article
   137.5 +abstraction.
   137.6 \ No newline at end of file
   138.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   138.2 +++ b/src/org/sonews/util/DatabaseSetup.java	Sun Aug 29 17:28:58 2010 +0200
   138.3 @@ -0,0 +1,127 @@
   138.4 +/*
   138.5 + *   SONEWS News Server
   138.6 + *   see AUTHORS for the list of contributors
   138.7 + *
   138.8 + *   This program is free software: you can redistribute it and/or modify
   138.9 + *   it under the terms of the GNU General Public License as published by
  138.10 + *   the Free Software Foundation, either version 3 of the License, or
  138.11 + *   (at your option) any later version.
  138.12 + *
  138.13 + *   This program is distributed in the hope that it will be useful,
  138.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  138.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  138.16 + *   GNU General Public License for more details.
  138.17 + *
  138.18 + *   You should have received a copy of the GNU General Public License
  138.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  138.20 + */
  138.21 +
  138.22 +package org.sonews.util;
  138.23 +
  138.24 +import java.io.BufferedReader;
  138.25 +import java.io.InputStreamReader;
  138.26 +import java.sql.Connection;
  138.27 +import java.sql.DriverManager;
  138.28 +import java.sql.Statement;
  138.29 +import java.util.HashMap;
  138.30 +import java.util.Map;
  138.31 +import org.sonews.config.Config;
  138.32 +import org.sonews.util.io.Resource;
  138.33 +
  138.34 +/**
  138.35 + * Database setup utility class.
  138.36 + * @author Christian Lins
  138.37 + * @since sonews/0.5.0
  138.38 + */
  138.39 +public final class DatabaseSetup 
  138.40 +{
  138.41 +
  138.42 +  private static final Map<String, String> templateMap 
  138.43 +    = new HashMap<String, String>();
  138.44 +  private static final Map<String, StringTemplate> urlMap
  138.45 +    = new HashMap<String, StringTemplate>();
  138.46 +  private static final Map<String, String> driverMap
  138.47 +    = new HashMap<String, String>();
  138.48 +  
  138.49 +  static
  138.50 +  {
  138.51 +    templateMap.put("1", "helpers/database_mysql5_tmpl.sql");
  138.52 +    templateMap.put("2", "helpers/database_postgresql8_tmpl.sql");
  138.53 +    
  138.54 +    urlMap.put("1", new StringTemplate("jdbc:mysql://%HOSTNAME/%DB"));
  138.55 +    urlMap.put("2", new StringTemplate("jdbc:postgresql://%HOSTNAME/%DB"));
  138.56 +    
  138.57 +    driverMap.put("1", "com.mysql.jdbc.Driver");
  138.58 +    driverMap.put("2", "org.postgresql.Driver");
  138.59 +  }
  138.60 +  
  138.61 +  public static void main(String[] args)
  138.62 +    throws Exception
  138.63 +  {
  138.64 +    System.out.println("sonews Database setup helper");
  138.65 +    System.out.println("This program will create a initial database table structure");
  138.66 +    System.out.println("for the sonews Newsserver.");
  138.67 +    System.out.println("You need to create a database and a db user manually before!");
  138.68 +    
  138.69 +    System.out.println("Select DBMS type:");
  138.70 +    System.out.println("[1] MySQL 5.x or higher");
  138.71 +    System.out.println("[2] PostgreSQL 8.x or higher");
  138.72 +    System.out.print("Your choice: ");
  138.73 +    
  138.74 +    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
  138.75 +    String dbmsType = in.readLine();
  138.76 +    String tmplName = templateMap.get(dbmsType);
  138.77 +    if(tmplName == null)
  138.78 +    {
  138.79 +      System.err.println("Invalid choice. Try again you fool!");
  138.80 +      main(args);
  138.81 +      return;
  138.82 +    }
  138.83 +    
  138.84 +    // Load JDBC Driver class
  138.85 +    Class.forName(driverMap.get(dbmsType));
  138.86 +    
  138.87 +    String tmpl = Resource.getAsString(tmplName, true);
  138.88 +    
  138.89 +    System.out.print("Database server hostname (e.g. localhost): ");
  138.90 +    String dbHostname = in.readLine();
  138.91 +    
  138.92 +    System.out.print("Database name: ");
  138.93 +    String dbName = in.readLine();
  138.94 +
  138.95 +    System.out.print("Give name of DB user that can create tables: ");
  138.96 +    String dbUser = in.readLine();
  138.97 +
  138.98 +    System.out.print("Password: ");
  138.99 +    String dbPassword = in.readLine();
 138.100 +    
 138.101 +    String url = urlMap.get(dbmsType)
 138.102 +      .set("HOSTNAME", dbHostname)
 138.103 +      .set("DB", dbName).toString();
 138.104 +    
 138.105 +    Connection conn = 
 138.106 +      DriverManager.getConnection(url, dbUser, dbPassword);
 138.107 +    conn.setAutoCommit(false);
 138.108 +    
 138.109 +    String[] tmplChunks = tmpl.split(";");
 138.110 +    
 138.111 +    for(String chunk : tmplChunks)
 138.112 +    {
 138.113 +      if(chunk.trim().equals(""))
 138.114 +      {
 138.115 +        continue;
 138.116 +      }
 138.117 +      
 138.118 +      Statement stmt = conn.createStatement();
 138.119 +      stmt.execute(chunk);
 138.120 +    }
 138.121 +    
 138.122 +    conn.commit();
 138.123 +    conn.setAutoCommit(true);
 138.124 +    
 138.125 +    // Create config file
 138.126 +    
 138.127 +    System.out.println("Ok");
 138.128 +  }
 138.129 +  
 138.130 +}
   139.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   139.2 +++ b/src/org/sonews/util/Log.java	Sun Aug 29 17:28:58 2010 +0200
   139.3 @@ -0,0 +1,57 @@
   139.4 +/*
   139.5 + *   SONEWS News Server
   139.6 + *   see AUTHORS for the list of contributors
   139.7 + *
   139.8 + *   This program is free software: you can redistribute it and/or modify
   139.9 + *   it under the terms of the GNU General Public License as published by
  139.10 + *   the Free Software Foundation, either version 3 of the License, or
  139.11 + *   (at your option) any later version.
  139.12 + *
  139.13 + *   This program is distributed in the hope that it will be useful,
  139.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  139.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  139.16 + *   GNU General Public License for more details.
  139.17 + *
  139.18 + *   You should have received a copy of the GNU General Public License
  139.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  139.20 + */
  139.21 +
  139.22 +package org.sonews.util;
  139.23 +
  139.24 +import java.util.logging.Level;
  139.25 +import java.util.logging.LogManager;
  139.26 +import java.util.logging.Logger;
  139.27 +import java.util.logging.SimpleFormatter;
  139.28 +import java.util.logging.StreamHandler;
  139.29 +import org.sonews.config.Config;
  139.30 +
  139.31 +/**
  139.32 + * Provides logging and debugging methods.
  139.33 + * @author Christian Lins
  139.34 + * @since sonews/0.5.0
  139.35 + */
  139.36 +public class Log extends Logger
  139.37 +{
  139.38 +
  139.39 +  private static Log instance = new Log();
  139.40 +
  139.41 +  private Log()
  139.42 +  {
  139.43 +    super("org.sonews", null);
  139.44 +
  139.45 +    StreamHandler handler = new StreamHandler(System.out, new SimpleFormatter());
  139.46 +    Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
  139.47 +    handler.setLevel(level);
  139.48 +    addHandler(handler);
  139.49 +    setLevel(level);
  139.50 +    LogManager.getLogManager().addLogger(this);
  139.51 +  }
  139.52 +
  139.53 +  public static Logger get()
  139.54 +  {
  139.55 +    Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
  139.56 +    instance.setLevel(level);
  139.57 +    return instance;
  139.58 +  }
  139.59 +
  139.60 +}
   140.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   140.2 +++ b/src/org/sonews/util/Pair.java	Sun Aug 29 17:28:58 2010 +0200
   140.3 @@ -0,0 +1,48 @@
   140.4 +/*
   140.5 + *   SONEWS News Server
   140.6 + *   see AUTHORS for the list of contributors
   140.7 + *
   140.8 + *   This program is free software: you can redistribute it and/or modify
   140.9 + *   it under the terms of the GNU General Public License as published by
  140.10 + *   the Free Software Foundation, either version 3 of the License, or
  140.11 + *   (at your option) any later version.
  140.12 + *
  140.13 + *   This program is distributed in the hope that it will be useful,
  140.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  140.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  140.16 + *   GNU General Public License for more details.
  140.17 + *
  140.18 + *   You should have received a copy of the GNU General Public License
  140.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  140.20 + */
  140.21 +
  140.22 +package org.sonews.util;
  140.23 +
  140.24 +/**
  140.25 + * A pair of two objects.
  140.26 + * @author Christian Lins
  140.27 + * @since sonews/0.5.0
  140.28 + */
  140.29 +public class Pair<T1, T2> 
  140.30 +{
  140.31 + 
  140.32 +  private T1 a;
  140.33 +  private T2 b;
  140.34 +  
  140.35 +  public Pair(T1 a, T2 b)
  140.36 +  {
  140.37 +    this.a = a;
  140.38 +    this.b = b;
  140.39 +  }
  140.40 +
  140.41 +  public T1 getA()
  140.42 +  {
  140.43 +    return a;
  140.44 +  }
  140.45 +
  140.46 +  public T2 getB()
  140.47 +  {
  140.48 +    return b;
  140.49 +  } 
  140.50 + 
  140.51 +}
   141.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   141.2 +++ b/src/org/sonews/util/Purger.java	Sun Aug 29 17:28:58 2010 +0200
   141.3 @@ -0,0 +1,149 @@
   141.4 +/*
   141.5 + *   SONEWS News Server
   141.6 + *   see AUTHORS for the list of contributors
   141.7 + *
   141.8 + *   This program is free software: you can redistribute it and/or modify
   141.9 + *   it under the terms of the GNU General Public License as published by
  141.10 + *   the Free Software Foundation, either version 3 of the License, or
  141.11 + *   (at your option) any later version.
  141.12 + *
  141.13 + *   This program is distributed in the hope that it will be useful,
  141.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  141.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  141.16 + *   GNU General Public License for more details.
  141.17 + *
  141.18 + *   You should have received a copy of the GNU General Public License
  141.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  141.20 + */
  141.21 +
  141.22 +package org.sonews.util;
  141.23 +
  141.24 +import org.sonews.daemon.AbstractDaemon;
  141.25 +import org.sonews.config.Config;
  141.26 +import org.sonews.storage.Article;
  141.27 +import org.sonews.storage.Headers;
  141.28 +import java.util.Date;
  141.29 +import java.util.List;
  141.30 +import org.sonews.storage.Channel;
  141.31 +import org.sonews.storage.Group;
  141.32 +import org.sonews.storage.StorageBackendException;
  141.33 +import org.sonews.storage.StorageManager;
  141.34 +
  141.35 +/**
  141.36 + * The purger is started in configurable intervals to search
  141.37 + * for messages that can be purged. A message must be deleted if its lifetime
  141.38 + * has exceeded, if it was marked as deleted or if the maximum number of
  141.39 + * articles in the database is reached.
  141.40 + * @author Christian Lins
  141.41 + * @since sonews/0.5.0
  141.42 + */
  141.43 +public class Purger extends AbstractDaemon
  141.44 +{
  141.45 +
  141.46 +  /**
  141.47 +   * Loops through all messages and deletes them if their time
  141.48 +   * has come.
  141.49 +   */
  141.50 +  @Override
  141.51 +  public void run()
  141.52 +  {
  141.53 +    try
  141.54 +    {
  141.55 +      while(isRunning())
  141.56 +      {
  141.57 +        purgeDeleted();
  141.58 +        purgeOutdated();
  141.59 +
  141.60 +        Thread.sleep(120000); // Sleep for two minutes
  141.61 +      }
  141.62 +    }
  141.63 +    catch(StorageBackendException ex)
  141.64 +    {
  141.65 +      ex.printStackTrace();
  141.66 +    }
  141.67 +    catch(InterruptedException ex)
  141.68 +    {
  141.69 +      Log.get().warning("Purger interrupted: " + ex);
  141.70 +    }
  141.71 +  }
  141.72 +
  141.73 +  private void purgeDeleted()
  141.74 +    throws StorageBackendException
  141.75 +  {
  141.76 +    List<Channel> groups = StorageManager.current().getGroups();
  141.77 +    for(Channel channel : groups)
  141.78 +    {
  141.79 +      if(!(channel instanceof Group))
  141.80 +        continue;
  141.81 +      
  141.82 +      Group group = (Group)channel;
  141.83 +      // Look for groups that are marked as deleted
  141.84 +      if(group.isDeleted())
  141.85 +      {
  141.86 +        List<Long> ids = StorageManager.current().getArticleNumbers(group.getInternalID());
  141.87 +        if(ids.size() == 0)
  141.88 +        {
  141.89 +          StorageManager.current().purgeGroup(group);
  141.90 +          Log.get().info("Group " + group.getName() + " purged.");
  141.91 +        }
  141.92 +
  141.93 +        for(int n = 0; n < ids.size() && n < 10; n++)
  141.94 +        {
  141.95 +          Article art = StorageManager.current().getArticle(ids.get(n), group.getInternalID());
  141.96 +          StorageManager.current().delete(art.getMessageID());
  141.97 +          Log.get().info("Article " + art.getMessageID() + " purged.");
  141.98 +        }
  141.99 +      }
 141.100 +    }
 141.101 +  }
 141.102 +
 141.103 +  private void purgeOutdated()
 141.104 +    throws InterruptedException, StorageBackendException
 141.105 +  {
 141.106 +    long articleMaximum =
 141.107 +      Config.inst().get("sonews.article.maxnum", Long.MAX_VALUE);
 141.108 +    long lifetime =
 141.109 +      Config.inst().get("sonews.article.lifetime", -1);
 141.110 +
 141.111 +    if(lifetime > 0 || articleMaximum < Stats.getInstance().getNumberOfNews())
 141.112 +    {
 141.113 +      Log.get().info("Purging old messages...");
 141.114 +      String mid = StorageManager.current().getOldestArticle();
 141.115 +      if (mid == null) // No articles in the database
 141.116 +      {
 141.117 +        return;
 141.118 +      }
 141.119 +
 141.120 +      Article art = StorageManager.current().getArticle(mid);
 141.121 +      long artDate = 0;
 141.122 +      String dateStr = art.getHeader(Headers.DATE)[0];
 141.123 +      try
 141.124 +      {
 141.125 +        artDate = Date.parse(dateStr) / 1000 / 60 / 60 / 24;
 141.126 +      }
 141.127 +      catch (IllegalArgumentException ex)
 141.128 +      {
 141.129 +        Log.get().warning("Could not parse date string: " + dateStr + " " + ex);
 141.130 +      }
 141.131 +
 141.132 +      // Should we delete the message because of its age or because the
 141.133 +      // article maximum was reached?
 141.134 +      if (lifetime < 0 || artDate < (new Date().getTime() + lifetime))
 141.135 +      {
 141.136 +        StorageManager.current().delete(mid);
 141.137 +        System.out.println("Deleted: " + mid);
 141.138 +      }
 141.139 +      else
 141.140 +      {
 141.141 +        Thread.sleep(1000 * 60); // Wait 60 seconds
 141.142 +        return;
 141.143 +      }
 141.144 +    }
 141.145 +    else
 141.146 +    {
 141.147 +      Log.get().info("Lifetime purger is disabled");
 141.148 +      Thread.sleep(1000 * 60 * 30); // Wait 30 minutes
 141.149 +    }
 141.150 +  }
 141.151 +
 141.152 +}
   142.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   142.2 +++ b/src/org/sonews/util/Stats.java	Sun Aug 29 17:28:58 2010 +0200
   142.3 @@ -0,0 +1,206 @@
   142.4 +/*
   142.5 + *   SONEWS News Server
   142.6 + *   see AUTHORS for the list of contributors
   142.7 + *
   142.8 + *   This program is free software: you can redistribute it and/or modify
   142.9 + *   it under the terms of the GNU General Public License as published by
  142.10 + *   the Free Software Foundation, either version 3 of the License, or
  142.11 + *   (at your option) any later version.
  142.12 + *
  142.13 + *   This program is distributed in the hope that it will be useful,
  142.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  142.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  142.16 + *   GNU General Public License for more details.
  142.17 + *
  142.18 + *   You should have received a copy of the GNU General Public License
  142.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  142.20 + */
  142.21 +
  142.22 +package org.sonews.util;
  142.23 +
  142.24 +import java.util.Calendar;
  142.25 +import org.sonews.config.Config;
  142.26 +import org.sonews.storage.Channel;
  142.27 +import org.sonews.storage.StorageBackendException;
  142.28 +import org.sonews.storage.StorageManager;
  142.29 +
  142.30 +/**
  142.31 + * Class that capsulates statistical data gathering.
  142.32 + * @author Christian Lins
  142.33 + * @since sonews/0.5.0
  142.34 + */
  142.35 +public final class Stats 
  142.36 +{
  142.37 +      
  142.38 +  public static final byte CONNECTIONS    = 1;
  142.39 +  public static final byte POSTED_NEWS    = 2;
  142.40 +  public static final byte GATEWAYED_NEWS = 3;
  142.41 +  public static final byte FEEDED_NEWS    = 4;
  142.42 +  public static final byte MLGW_RUNSTART  = 5;
  142.43 +  public static final byte MLGW_RUNEND    = 6;
  142.44 +
  142.45 +  private static Stats instance = new Stats();
  142.46 +  
  142.47 +  public static Stats getInstance()
  142.48 +  {
  142.49 +    return Stats.instance;
  142.50 +  }
  142.51 +  
  142.52 +  private Stats() {}
  142.53 +  
  142.54 +  private volatile int connectedClients = 0;
  142.55 +
  142.56 +  /**
  142.57 +   * A generic method that writes event data to the storage backend.
  142.58 +   * If event logging is disabled with sonews.eventlog=false this method
  142.59 +   * simply does nothing.
  142.60 +   * @param type
  142.61 +   * @param groupname
  142.62 +   */
  142.63 +  private void addEvent(byte type, String groupname)
  142.64 +  {
  142.65 +    try
  142.66 +    {
  142.67 +      if (Config.inst().get(Config.EVENTLOG, true))
  142.68 +      {
  142.69 +
  142.70 +        Channel group = Channel.getByName(groupname);
  142.71 +        if (group != null)
  142.72 +        {
  142.73 +          StorageManager.current().addEvent(
  142.74 +                  System.currentTimeMillis(), type, group.getInternalID());
  142.75 +        }
  142.76 +      } 
  142.77 +      else
  142.78 +      {
  142.79 +        Log.get().info("Group " + groupname + " does not exist.");
  142.80 +      }
  142.81 +    } 
  142.82 +    catch (StorageBackendException ex)
  142.83 +    {
  142.84 +      ex.printStackTrace();
  142.85 +    }
  142.86 +  }
  142.87 +  
  142.88 +  public void clientConnect()
  142.89 +  {
  142.90 +    this.connectedClients++;
  142.91 +  }
  142.92 +  
  142.93 +  public void clientDisconnect()
  142.94 +  {
  142.95 +    this.connectedClients--;
  142.96 +  }
  142.97 +  
  142.98 +  public int connectedClients()
  142.99 +  {
 142.100 +    return this.connectedClients;
 142.101 +  }
 142.102 +  
 142.103 +  public int getNumberOfGroups()
 142.104 +  {
 142.105 +    try
 142.106 +    {
 142.107 +      return StorageManager.current().countGroups();
 142.108 +    }
 142.109 +    catch(StorageBackendException ex)
 142.110 +    {
 142.111 +      ex.printStackTrace();
 142.112 +      return -1;
 142.113 +    }
 142.114 +  }
 142.115 +  
 142.116 +  public int getNumberOfNews()
 142.117 +  {
 142.118 +    try
 142.119 +    {
 142.120 +      return StorageManager.current().countArticles();
 142.121 +    }
 142.122 +    catch(StorageBackendException ex)
 142.123 +    {
 142.124 +      ex.printStackTrace();
 142.125 +      return -1;
 142.126 +    }
 142.127 +  }
 142.128 +  
 142.129 +  public int getYesterdaysEvents(final byte eventType, final int hour,
 142.130 +    final Channel group)
 142.131 +  {
 142.132 +    // Determine the timestamp values for yesterday and the given hour
 142.133 +    Calendar cal = Calendar.getInstance();
 142.134 +    int year  = cal.get(Calendar.YEAR);
 142.135 +    int month = cal.get(Calendar.MONTH);
 142.136 +    int dayom = cal.get(Calendar.DAY_OF_MONTH) - 1; // Yesterday
 142.137 +    
 142.138 +    cal.set(year, month, dayom, hour, 0, 0);
 142.139 +    long startTimestamp = cal.getTimeInMillis();
 142.140 +    
 142.141 +    cal.set(year, month, dayom, hour + 1, 0, 0);
 142.142 +    long endTimestamp = cal.getTimeInMillis();
 142.143 +    
 142.144 +    try
 142.145 +    {
 142.146 +      return StorageManager.current()
 142.147 +        .getEventsCount(eventType, startTimestamp, endTimestamp, group);
 142.148 +    }
 142.149 +    catch(StorageBackendException ex)
 142.150 +    {
 142.151 +      ex.printStackTrace();
 142.152 +      return -1;
 142.153 +    }
 142.154 +  }
 142.155 +  
 142.156 +  public void mailPosted(String groupname)
 142.157 +  {
 142.158 +    addEvent(POSTED_NEWS, groupname);
 142.159 +  }
 142.160 +  
 142.161 +  public void mailGatewayed(String groupname)
 142.162 +  {
 142.163 +    addEvent(GATEWAYED_NEWS, groupname);
 142.164 +  }
 142.165 +  
 142.166 +  public void mailFeeded(String groupname)
 142.167 +  {
 142.168 +    addEvent(FEEDED_NEWS, groupname);
 142.169 +  }
 142.170 +  
 142.171 +  public void mlgwRunStart()
 142.172 +  {
 142.173 +    addEvent(MLGW_RUNSTART, "control");
 142.174 +  }
 142.175 +  
 142.176 +  public void mlgwRunEnd()
 142.177 +  {
 142.178 +    addEvent(MLGW_RUNEND, "control");
 142.179 +  }
 142.180 +  
 142.181 +  private double perHour(int key, long gid)
 142.182 +  {
 142.183 +    try
 142.184 +    {
 142.185 +      return StorageManager.current().getEventsPerHour(key, gid);
 142.186 +    }
 142.187 +    catch(StorageBackendException ex)
 142.188 +    {
 142.189 +      ex.printStackTrace();
 142.190 +      return -1;
 142.191 +    }
 142.192 +  }
 142.193 +  
 142.194 +  public double postedPerHour(long gid)
 142.195 +  {
 142.196 +    return perHour(POSTED_NEWS, gid);
 142.197 +  }
 142.198 +  
 142.199 +  public double gatewayedPerHour(long gid)
 142.200 +  {
 142.201 +    return perHour(GATEWAYED_NEWS, gid);
 142.202 +  }
 142.203 +  
 142.204 +  public double feededPerHour(long gid)
 142.205 +  {
 142.206 +    return perHour(FEEDED_NEWS, gid);
 142.207 +  }
 142.208 +  
 142.209 +}
   143.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   143.2 +++ b/src/org/sonews/util/StringTemplate.java	Sun Aug 29 17:28:58 2010 +0200
   143.3 @@ -0,0 +1,97 @@
   143.4 +/*
   143.5 + *   SONEWS News Server
   143.6 + *   see AUTHORS for the list of contributors
   143.7 + *
   143.8 + *   This program is free software: you can redistribute it and/or modify
   143.9 + *   it under the terms of the GNU General Public License as published by
  143.10 + *   the Free Software Foundation, either version 3 of the License, or
  143.11 + *   (at your option) any later version.
  143.12 + *
  143.13 + *   This program is distributed in the hope that it will be useful,
  143.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  143.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  143.16 + *   GNU General Public License for more details.
  143.17 + *
  143.18 + *   You should have received a copy of the GNU General Public License
  143.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  143.20 + */
  143.21 +
  143.22 +package org.sonews.util;
  143.23 +
  143.24 +import java.util.HashMap;
  143.25 +import java.util.Map;
  143.26 +
  143.27 +/**
  143.28 + * Class that allows simple String template handling.
  143.29 + * @author Christian Lins
  143.30 + * @since sonews/0.5.0
  143.31 + */
  143.32 +public class StringTemplate 
  143.33 +{
  143.34 +
  143.35 +  private String              str               = null;
  143.36 +  private String              templateDelimiter = "%";
  143.37 +  private Map<String, String> templateValues    = new HashMap<String, String>();
  143.38 +  
  143.39 +  public StringTemplate(String str, final String templateDelimiter)
  143.40 +  {
  143.41 +    if(str == null || templateDelimiter == null)
  143.42 +    {
  143.43 +      throw new IllegalArgumentException("null arguments not allowed");
  143.44 +    }
  143.45 +
  143.46 +    this.str               = str;
  143.47 +    this.templateDelimiter = templateDelimiter;
  143.48 +  }
  143.49 +  
  143.50 +  public StringTemplate(String str)
  143.51 +  {
  143.52 +    this(str, "%");
  143.53 +  }
  143.54 +  
  143.55 +  public StringTemplate set(String template, String value)
  143.56 +  {
  143.57 +    if(template == null || value == null)
  143.58 +    {
  143.59 +      throw new IllegalArgumentException("null arguments not allowed");
  143.60 +    }
  143.61 +    
  143.62 +    this.templateValues.put(template, value);
  143.63 +    return this;
  143.64 +  }
  143.65 +  
  143.66 +  public StringTemplate set(String template, long value)
  143.67 +  {
  143.68 +    return set(template, Long.toString(value));
  143.69 +  }
  143.70 +  
  143.71 +  public StringTemplate set(String template, double value)
  143.72 +  {
  143.73 +    return set(template, Double.toString(value));
  143.74 +  }
  143.75 +  
  143.76 +  public StringTemplate set(String template, Object obj)
  143.77 +  {
  143.78 +    if(template == null || obj == null)
  143.79 +    {
  143.80 +      throw new IllegalArgumentException("null arguments not allowed");
  143.81 +    }
  143.82 +
  143.83 +    return set(template, obj.toString());
  143.84 +  }
  143.85 +  
  143.86 +  @Override
  143.87 +  public String toString()
  143.88 +  {
  143.89 +    String ret = str;
  143.90 +
  143.91 +    for(String key : this.templateValues.keySet())
  143.92 +    {
  143.93 +      String value = this.templateValues.get(key);
  143.94 +      ret = ret.replace(templateDelimiter + key, value);
  143.95 +    }
  143.96 +    
  143.97 +    return ret;
  143.98 +  }
  143.99 +
 143.100 +}
   144.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   144.2 +++ b/src/org/sonews/util/TimeoutMap.java	Sun Aug 29 17:28:58 2010 +0200
   144.3 @@ -0,0 +1,145 @@
   144.4 +/*
   144.5 + *   SONEWS News Server
   144.6 + *   see AUTHORS for the list of contributors
   144.7 + *
   144.8 + *   This program is free software: you can redistribute it and/or modify
   144.9 + *   it under the terms of the GNU General Public License as published by
  144.10 + *   the Free Software Foundation, either version 3 of the License, or
  144.11 + *   (at your option) any later version.
  144.12 + *
  144.13 + *   This program is distributed in the hope that it will be useful,
  144.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  144.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  144.16 + *   GNU General Public License for more details.
  144.17 + *
  144.18 + *   You should have received a copy of the GNU General Public License
  144.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  144.20 + */
  144.21 +
  144.22 +package org.sonews.util;
  144.23 +
  144.24 +import java.util.HashMap;
  144.25 +import java.util.HashSet;
  144.26 +import java.util.Map;
  144.27 +import java.util.Set;
  144.28 +import java.util.concurrent.ConcurrentHashMap;
  144.29 +
  144.30 +/**
  144.31 + * Implementation of a Map that will loose its stored values after a 
  144.32 + * configurable amount of time.
  144.33 + * This class may be used to cache config values for example.
  144.34 + * @author Christian Lins
  144.35 + * @since sonews/0.5.0
  144.36 + */
  144.37 +public class TimeoutMap<K,V> extends ConcurrentHashMap<K, V>
  144.38 +{
  144.39 +  
  144.40 +  private static final long serialVersionUID = 453453467700345L;
  144.41 +
  144.42 +  private int                    timeout     = 60000; // 60 sec
  144.43 +  private transient Map<K, Long> timeoutMap  = new HashMap<K, Long>();
  144.44 +  
  144.45 +  /**
  144.46 +   * Constructor.
  144.47 +   * @param timeout Timeout in milliseconds
  144.48 +   */
  144.49 +  public TimeoutMap(final int timeout)
  144.50 +  {
  144.51 +    this.timeout = timeout;
  144.52 +  }
  144.53 +  
  144.54 +  /**
  144.55 +   * Uses default timeout (60 sec).
  144.56 +   */
  144.57 +  public TimeoutMap()
  144.58 +  {
  144.59 +  }
  144.60 +  
  144.61 +  /**
  144.62 +   * 
  144.63 +   * @param key
  144.64 +   * @return true if key is still valid.
  144.65 +   */
  144.66 +  protected boolean checkTimeOut(Object key)
  144.67 +  {
  144.68 +    synchronized(this.timeoutMap)
  144.69 +    {
  144.70 +      if(this.timeoutMap.containsKey(key))
  144.71 +      {
  144.72 +        long keytime = this.timeoutMap.get(key);
  144.73 +        if((System.currentTimeMillis() - keytime) < this.timeout)
  144.74 +        {
  144.75 +          return true;
  144.76 +        }
  144.77 +        else
  144.78 +        {
  144.79 +          remove(key);
  144.80 +          return false;
  144.81 +        }
  144.82 +      }
  144.83 +      else
  144.84 +      {
  144.85 +        return false;
  144.86 +      }
  144.87 +    }
  144.88 +  }
  144.89 +  
  144.90 +  @Override
  144.91 +  public boolean containsKey(Object key)
  144.92 +  {
  144.93 +    return checkTimeOut(key);
  144.94 +  }
  144.95 +
  144.96 +  @Override
  144.97 +  public synchronized V get(Object key)
  144.98 +  {
  144.99 +    if(checkTimeOut(key))
 144.100 +    {
 144.101 +      return super.get(key);
 144.102 +    }
 144.103 +    else
 144.104 +    {
 144.105 +      return null;
 144.106 +    }
 144.107 +  }
 144.108 +
 144.109 +  @Override
 144.110 +  public V put(K key, V value)
 144.111 +  {
 144.112 +    synchronized(this.timeoutMap)
 144.113 +    {
 144.114 +      removeStaleKeys();
 144.115 +      this.timeoutMap.put(key, System.currentTimeMillis());
 144.116 +      return super.put(key, value);
 144.117 +    }
 144.118 +  }
 144.119 +
 144.120 +  /**
 144.121 +   * @param arg0
 144.122 +   * @return
 144.123 +   */
 144.124 +  @Override
 144.125 +  public V remove(Object arg0)
 144.126 +  {
 144.127 +    synchronized(this.timeoutMap)
 144.128 +    {
 144.129 +      this.timeoutMap.remove(arg0);
 144.130 +      V val = super.remove(arg0);
 144.131 +      return val;
 144.132 +    }
 144.133 +  }
 144.134 +
 144.135 +  protected void removeStaleKeys()
 144.136 +  {
 144.137 +    synchronized(this.timeoutMap)
 144.138 +    {
 144.139 +      Set<Object> keySet = new HashSet<Object>(this.timeoutMap.keySet());
 144.140 +      for(Object key : keySet)
 144.141 +      {
 144.142 +        // The key/value is removed by the checkTimeOut() method if true
 144.143 +        checkTimeOut(key);
 144.144 +      }
 144.145 +    }
 144.146 +  }
 144.147 +  
 144.148 +}
   145.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   145.2 +++ b/src/org/sonews/util/io/ArticleInputStream.java	Sun Aug 29 17:28:58 2010 +0200
   145.3 @@ -0,0 +1,71 @@
   145.4 +/*
   145.5 + *   SONEWS News Server
   145.6 + *   see AUTHORS for the list of contributors
   145.7 + *
   145.8 + *   This program is free software: you can redistribute it and/or modify
   145.9 + *   it under the terms of the GNU General Public License as published by
  145.10 + *   the Free Software Foundation, either version 3 of the License, or
  145.11 + *   (at your option) any later version.
  145.12 + *
  145.13 + *   This program is distributed in the hope that it will be useful,
  145.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  145.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  145.16 + *   GNU General Public License for more details.
  145.17 + *
  145.18 + *   You should have received a copy of the GNU General Public License
  145.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  145.20 + */
  145.21 +
  145.22 +package org.sonews.util.io;
  145.23 +
  145.24 +import java.io.ByteArrayOutputStream;
  145.25 +import java.io.IOException;
  145.26 +import java.io.InputStream;
  145.27 +import java.io.UnsupportedEncodingException;
  145.28 +import org.sonews.storage.Article;
  145.29 +
  145.30 +/**
  145.31 + * Capsulates an Article to provide a raw InputStream.
  145.32 + * @author Christian Lins
  145.33 + * @since sonews/0.5.0
  145.34 + */
  145.35 +public class ArticleInputStream extends InputStream
  145.36 +{
  145.37 +
  145.38 +  private byte[] buf;
  145.39 +  private int    pos = 0;
  145.40 +  
  145.41 +  public ArticleInputStream(final Article art)
  145.42 +    throws IOException, UnsupportedEncodingException
  145.43 +  {
  145.44 +    final ByteArrayOutputStream out = new ByteArrayOutputStream();
  145.45 +    out.write(art.getHeaderSource().getBytes("UTF-8"));
  145.46 +    out.write("\r\n\r\n".getBytes());
  145.47 +    out.write(art.getBody()); // Without CRLF
  145.48 +    out.flush();
  145.49 +    this.buf = out.toByteArray();
  145.50 +  }
  145.51 +
  145.52 +  /**
  145.53 +   * This method reads one byte from the stream.  The <code>pos</code>
  145.54 +   * counter is advanced to the next byte to be read.  The byte read is
  145.55 +   * returned as an int in the range of 0-255.  If the stream position
  145.56 +   * is already at the end of the buffer, no byte is read and a -1 is
  145.57 +   * returned in order to indicate the end of the stream.
  145.58 +   *
  145.59 +   * @return The byte read, or -1 if end of stream
  145.60 +   */
  145.61 +  @Override
  145.62 +  public synchronized int read()
  145.63 +  {
  145.64 +    if(pos < buf.length)
  145.65 +    {
  145.66 +      return ((int)buf[pos++]) & 0xFF;
  145.67 +    }
  145.68 +    else
  145.69 +    {
  145.70 +      return -1;
  145.71 +    }
  145.72 +  }
  145.73 +  
  145.74 +}
   146.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   146.2 +++ b/src/org/sonews/util/io/ArticleReader.java	Sun Aug 29 17:28:58 2010 +0200
   146.3 @@ -0,0 +1,135 @@
   146.4 +/*
   146.5 + *   SONEWS News Server
   146.6 + *   see AUTHORS for the list of contributors
   146.7 + *
   146.8 + *   This program is free software: you can redistribute it and/or modify
   146.9 + *   it under the terms of the GNU General Public License as published by
  146.10 + *   the Free Software Foundation, either version 3 of the License, or
  146.11 + *   (at your option) any later version.
  146.12 + *
  146.13 + *   This program is distributed in the hope that it will be useful,
  146.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  146.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  146.16 + *   GNU General Public License for more details.
  146.17 + *
  146.18 + *   You should have received a copy of the GNU General Public License
  146.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  146.20 + */
  146.21 +
  146.22 +package org.sonews.util.io;
  146.23 +
  146.24 +import java.io.BufferedInputStream;
  146.25 +import java.io.BufferedOutputStream;
  146.26 +import java.io.ByteArrayOutputStream;
  146.27 +import java.io.IOException;
  146.28 +import java.io.InputStream;
  146.29 +import java.io.UnsupportedEncodingException;
  146.30 +import java.net.Socket;
  146.31 +import java.net.UnknownHostException;
  146.32 +import org.sonews.config.Config;
  146.33 +import org.sonews.util.Log;
  146.34 +
  146.35 +/**
  146.36 + * Reads an news article from a NNTP server.
  146.37 + * @author Christian Lins
  146.38 + * @since sonews/0.5.0
  146.39 + */
  146.40 +public class ArticleReader 
  146.41 +{
  146.42 +
  146.43 +  private BufferedOutputStream out;
  146.44 +  private BufferedInputStream  in;
  146.45 +  private String               messageID;
  146.46 +  
  146.47 +  public ArticleReader(String host, int port, String messageID)
  146.48 +    throws IOException, UnknownHostException
  146.49 +  {
  146.50 +    this.messageID = messageID;
  146.51 +
  146.52 +    // Connect to NNTP server
  146.53 +    Socket socket = new Socket(host, port);
  146.54 +    this.out = new BufferedOutputStream(socket.getOutputStream());
  146.55 +    this.in  = new BufferedInputStream(socket.getInputStream());
  146.56 +    String line = readln(this.in);
  146.57 +    if(!line.startsWith("200 "))
  146.58 +    {
  146.59 +      throw new IOException("Invalid hello from server: " + line);
  146.60 +    }
  146.61 +  }
  146.62 +  
  146.63 +  private boolean eofArticle(byte[] buf)
  146.64 +  {
  146.65 +    if(buf.length < 4)
  146.66 +    {
  146.67 +      return false;
  146.68 +    }
  146.69 +    
  146.70 +    int l = buf.length - 1;
  146.71 +    return buf[l-3] == 10 // '*\n'
  146.72 +        && buf[l-2] == '.'                   // '.'
  146.73 +        && buf[l-1] == 13 && buf[l] == 10;  // '\r\n'
  146.74 +  }
  146.75 +  
  146.76 +  public byte[] getArticleData()
  146.77 +    throws IOException, UnsupportedEncodingException
  146.78 +  {
  146.79 +    long maxSize = Config.inst().get(Config.ARTICLE_MAXSIZE, 1024) * 1024L;
  146.80 +
  146.81 +    try
  146.82 +    {
  146.83 +      this.out.write(("ARTICLE " + this.messageID + "\r\n").getBytes("UTF-8"));
  146.84 +      this.out.flush();
  146.85 +
  146.86 +      String line = readln(this.in);
  146.87 +      if(line.startsWith("220 "))
  146.88 +      {
  146.89 +        ByteArrayOutputStream buf = new ByteArrayOutputStream();
  146.90 +        
  146.91 +        while(!eofArticle(buf.toByteArray()))
  146.92 +        {
  146.93 +          for(int b = in.read(); b != 10; b = in.read())
  146.94 +          {
  146.95 +            buf.write(b);
  146.96 +          }
  146.97 +
  146.98 +          buf.write(10);
  146.99 +          if(buf.size() > maxSize)
 146.100 +          {
 146.101 +            Log.get().warning("Skipping message that is too large: " + buf.size());
 146.102 +            return null;
 146.103 +          }
 146.104 +        }
 146.105 +        
 146.106 +        return buf.toByteArray();
 146.107 +      }
 146.108 +      else
 146.109 +      {
 146.110 +        Log.get().warning("ArticleReader: " + line);
 146.111 +        return null;
 146.112 +      }
 146.113 +    }
 146.114 +    catch(IOException ex)
 146.115 +    {
 146.116 +      throw ex;
 146.117 +    }
 146.118 +    finally
 146.119 +    {
 146.120 +      this.out.write("QUIT\r\n".getBytes("UTF-8"));
 146.121 +      this.out.flush();
 146.122 +      this.out.close();
 146.123 +    }
 146.124 +  }
 146.125 +  
 146.126 +  private String readln(InputStream in)
 146.127 +    throws IOException
 146.128 +  {
 146.129 +    ByteArrayOutputStream buf = new ByteArrayOutputStream();
 146.130 +    for(int b = in.read(); b != 10 /* \n */; b = in.read())
 146.131 +    {
 146.132 +      buf.write(b);
 146.133 +    }
 146.134 +    
 146.135 +    return new String(buf.toByteArray());
 146.136 +  }
 146.137 +
 146.138 +}
   147.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   147.2 +++ b/src/org/sonews/util/io/ArticleWriter.java	Sun Aug 29 17:28:58 2010 +0200
   147.3 @@ -0,0 +1,133 @@
   147.4 +/*
   147.5 + *   SONEWS News Server
   147.6 + *   see AUTHORS for the list of contributors
   147.7 + *
   147.8 + *   This program is free software: you can redistribute it and/or modify
   147.9 + *   it under the terms of the GNU General Public License as published by
  147.10 + *   the Free Software Foundation, either version 3 of the License, or
  147.11 + *   (at your option) any later version.
  147.12 + *
  147.13 + *   This program is distributed in the hope that it will be useful,
  147.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  147.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  147.16 + *   GNU General Public License for more details.
  147.17 + *
  147.18 + *   You should have received a copy of the GNU General Public License
  147.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  147.20 + */
  147.21 +
  147.22 +package org.sonews.util.io;
  147.23 +
  147.24 +import java.io.BufferedOutputStream;
  147.25 +import java.io.BufferedReader;
  147.26 +import java.io.IOException;
  147.27 +import java.io.InputStreamReader;
  147.28 +import java.io.UnsupportedEncodingException;
  147.29 +import java.net.Socket;
  147.30 +import java.net.UnknownHostException;
  147.31 +import org.sonews.storage.Article;
  147.32 +
  147.33 +/**
  147.34 + * Posts an Article to a NNTP server using the POST command.
  147.35 + * @author Christian Lins
  147.36 + * @since sonews/0.5.0
  147.37 + */
  147.38 +public class ArticleWriter 
  147.39 +{
  147.40 +  
  147.41 +  private BufferedOutputStream out;
  147.42 +  private BufferedReader       inr;
  147.43 +
  147.44 +  public ArticleWriter(String host, int port)
  147.45 +    throws IOException, UnknownHostException
  147.46 +  {
  147.47 +    // Connect to NNTP server
  147.48 +    Socket socket = new Socket(host, port);
  147.49 +    this.out = new BufferedOutputStream(socket.getOutputStream());
  147.50 +    this.inr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  147.51 +    String line = inr.readLine();
  147.52 +    if(line == null || !line.startsWith("200 "))
  147.53 +    {
  147.54 +      throw new IOException("Invalid hello from server: " + line);
  147.55 +    }
  147.56 +  }
  147.57 +  
  147.58 +  public void close()
  147.59 +    throws IOException, UnsupportedEncodingException
  147.60 +  {
  147.61 +    this.out.write("QUIT\r\n".getBytes("UTF-8"));
  147.62 +    this.out.flush();
  147.63 +  }
  147.64 +
  147.65 +  protected void finishPOST()
  147.66 +    throws IOException
  147.67 +  {
  147.68 +    this.out.write("\r\n.\r\n".getBytes());
  147.69 +    this.out.flush();
  147.70 +    String line = inr.readLine();
  147.71 +    if(line == null || !line.startsWith("240 ") || !line.startsWith("441 "))
  147.72 +    {
  147.73 +      throw new IOException(line);
  147.74 +    }
  147.75 +  }
  147.76 +
  147.77 +  protected void preparePOST()
  147.78 +    throws IOException
  147.79 +  {
  147.80 +    this.out.write("POST\r\n".getBytes("UTF-8"));
  147.81 +    this.out.flush();
  147.82 +
  147.83 +    String line = this.inr.readLine();
  147.84 +    if(line == null || !line.startsWith("340 "))
  147.85 +    {
  147.86 +      throw new IOException(line);
  147.87 +    }
  147.88 +  }
  147.89 +
  147.90 +  public void writeArticle(Article article)
  147.91 +    throws IOException, UnsupportedEncodingException
  147.92 +  {
  147.93 +    byte[] buf = new byte[512];
  147.94 +    ArticleInputStream in = new ArticleInputStream(article);
  147.95 +
  147.96 +    preparePOST();
  147.97 +    
  147.98 +    int len = in.read(buf);
  147.99 +    while(len != -1)
 147.100 +    {
 147.101 +      writeLine(buf, len);
 147.102 +      len = in.read(buf);
 147.103 +    }
 147.104 +
 147.105 +    finishPOST();
 147.106 +  }
 147.107 +
 147.108 +  /**
 147.109 +   * Writes the raw content of an article to the remote server. This method
 147.110 +   * does no charset conversion/handling of any kind so its the preferred
 147.111 +   * method for sending an article to remote peers.
 147.112 +   * @param rawArticle
 147.113 +   * @throws IOException
 147.114 +   */
 147.115 +  public void writeArticle(byte[] rawArticle)
 147.116 +    throws IOException
 147.117 +  {
 147.118 +    preparePOST();
 147.119 +    writeLine(rawArticle, rawArticle.length);
 147.120 +    finishPOST();
 147.121 +  }
 147.122 +
 147.123 +  /**
 147.124 +   * Writes the given buffer to the connect remote server.
 147.125 +   * @param buffer
 147.126 +   * @param len
 147.127 +   * @throws IOException
 147.128 +   */
 147.129 +  protected void writeLine(byte[] buffer, int len)
 147.130 +    throws IOException
 147.131 +  {
 147.132 +    this.out.write(buffer, 0, len);
 147.133 +    this.out.flush();
 147.134 +  }
 147.135 +
 147.136 +}
   148.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   148.2 +++ b/src/org/sonews/util/io/Resource.java	Sun Aug 29 17:28:58 2010 +0200
   148.3 @@ -0,0 +1,132 @@
   148.4 +/*
   148.5 + *   SONEWS News Server
   148.6 + *   see AUTHORS for the list of contributors
   148.7 + *
   148.8 + *   This program is free software: you can redistribute it and/or modify
   148.9 + *   it under the terms of the GNU General Public License as published by
  148.10 + *   the Free Software Foundation, either version 3 of the License, or
  148.11 + *   (at your option) any later version.
  148.12 + *
  148.13 + *   This program is distributed in the hope that it will be useful,
  148.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  148.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  148.16 + *   GNU General Public License for more details.
  148.17 + *
  148.18 + *   You should have received a copy of the GNU General Public License
  148.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  148.20 + */
  148.21 +
  148.22 +package org.sonews.util.io;
  148.23 +
  148.24 +import java.io.BufferedReader;
  148.25 +import java.io.IOException;
  148.26 +import java.io.InputStream;
  148.27 +import java.io.InputStreamReader;
  148.28 +import java.net.URL;
  148.29 +import java.nio.charset.Charset;
  148.30 +
  148.31 +/**
  148.32 + * Provides method for loading of resources.
  148.33 + * @author Christian Lins
  148.34 + * @since sonews/0.5.0
  148.35 + */
  148.36 +public final class Resource
  148.37 +{
  148.38 +  
  148.39 +  /**
  148.40 +   * Loads a resource and returns it as URL reference.
  148.41 +   * The Resource's classloader is used to load the resource, not
  148.42 +   * the System's ClassLoader so it may be safe to use this method
  148.43 +   * in a sandboxed environment.
  148.44 +   * @return
  148.45 +   */
  148.46 +  public static URL getAsURL(final String name)
  148.47 +  {
  148.48 +    if(name == null)
  148.49 +    {
  148.50 +      return null;
  148.51 +    }
  148.52 +
  148.53 +    return Resource.class.getClassLoader().getResource(name);
  148.54 +  }
  148.55 +  
  148.56 +  /**
  148.57 +   * Loads a resource and returns an InputStream to it.
  148.58 +   * @param name
  148.59 +   * @return
  148.60 +   */
  148.61 +  public static InputStream getAsStream(String name)
  148.62 +  {
  148.63 +    try
  148.64 +    {
  148.65 +      URL url = getAsURL(name);
  148.66 +      if(url == null)
  148.67 +      {
  148.68 +        return null;
  148.69 +      }
  148.70 +      else
  148.71 +      {
  148.72 +        return url.openStream();
  148.73 +      }
  148.74 +    }
  148.75 +    catch(IOException e)
  148.76 +    {
  148.77 +      e.printStackTrace();
  148.78 +      return null;
  148.79 +    }
  148.80 +  }
  148.81 +
  148.82 +  /**
  148.83 +   * Loads a plain text resource.
  148.84 +   * @param withNewline If false all newlines are removed from the 
  148.85 +   * return String
  148.86 +   */
  148.87 +  public static String getAsString(String name, boolean withNewline)
  148.88 +  {
  148.89 +    if(name == null)
  148.90 +      return null;
  148.91 +
  148.92 +    BufferedReader in = null;
  148.93 +    try
  148.94 +    {
  148.95 +      InputStream ins = getAsStream(name);
  148.96 +      if(ins == null)
  148.97 +        return null;
  148.98 +
  148.99 +      in = new BufferedReader(
 148.100 +        new InputStreamReader(ins, Charset.forName("UTF-8")));
 148.101 +      StringBuffer buf = new StringBuffer();
 148.102 +
 148.103 +      for(;;)
 148.104 +      {
 148.105 +        String line = in.readLine();
 148.106 +        if(line == null)
 148.107 +          break;
 148.108 +
 148.109 +        buf.append(line);
 148.110 +        if(withNewline)
 148.111 +          buf.append('\n');
 148.112 +      }
 148.113 +
 148.114 +      return buf.toString();
 148.115 +    }
 148.116 +    catch(Exception e)
 148.117 +    {
 148.118 +      e.printStackTrace();
 148.119 +      return null;
 148.120 +    }
 148.121 +    finally
 148.122 +    {
 148.123 +      try
 148.124 +      {
 148.125 +        if(in != null)
 148.126 +          in.close();
 148.127 +      }
 148.128 +      catch(IOException ex)
 148.129 +      {
 148.130 +        ex.printStackTrace();
 148.131 +      }
 148.132 +    }
 148.133 +  }
 148.134 +
 148.135 +}
   149.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   149.2 +++ b/src/org/sonews/util/io/package.html	Sun Aug 29 17:28:58 2010 +0200
   149.3 @@ -0,0 +1,1 @@
   149.4 +Contains I/O utilitiy classes.
   149.5 \ No newline at end of file
   150.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   150.2 +++ b/src/org/sonews/util/package.html	Sun Aug 29 17:28:58 2010 +0200
   150.3 @@ -0,0 +1,1 @@
   150.4 +Contains various utility classes.
   150.5 \ No newline at end of file