Switch intent style to Original K&R / Linux / Kernel.
1.1 --- a/src/org/sonews/Main.java Sun Aug 29 17:43:58 2010 +0200
1.2 +++ b/src/org/sonews/Main.java Sun Aug 29 18:17:37 2010 +0200
1.3 @@ -44,155 +44,123 @@
1.4 */
1.5 public final class Main
1.6 {
1.7 -
1.8 - private Main()
1.9 - {
1.10 - }
1.11
1.12 - /** Version information of the sonews daemon */
1.13 - public static final String VERSION = "sonews/1.1.0";
1.14 - public static final Date STARTDATE = new Date();
1.15 -
1.16 - /**
1.17 - * The main entrypoint.
1.18 - * @param args
1.19 - * @throws Exception
1.20 - */
1.21 - public static void main(String[] args) throws Exception
1.22 - {
1.23 - System.out.println(VERSION);
1.24 - Thread.currentThread().setName("Mainthread");
1.25 + /** Version information of the sonews daemon */
1.26 + public static final String VERSION = "sonews/1.1.0";
1.27 + public static final Date STARTDATE = new Date();
1.28
1.29 - // Command line arguments
1.30 - boolean feed = false; // Enable feeding?
1.31 - boolean mlgw = false; // Enable Mailinglist gateway?
1.32 - int port = -1;
1.33 -
1.34 - for(int n = 0; n < args.length; n++)
1.35 - {
1.36 - if(args[n].equals("-c") || args[n].equals("-config"))
1.37 - {
1.38 - Config.inst().set(Config.LEVEL_CLI, Config.CONFIGFILE, args[++n]);
1.39 - System.out.println("Using config file " + args[n]);
1.40 - }
1.41 - else if(args[n].equals("-dumpjdbcdriver"))
1.42 - {
1.43 - System.out.println("Available JDBC drivers:");
1.44 - Enumeration<Driver> drvs = DriverManager.getDrivers();
1.45 - while(drvs.hasMoreElements())
1.46 - {
1.47 - System.out.println(drvs.nextElement());
1.48 - }
1.49 - return;
1.50 - }
1.51 - else if(args[n].equals("-feed"))
1.52 - {
1.53 - feed = true;
1.54 - }
1.55 - else if(args[n].equals("-h") || args[n].equals("-help"))
1.56 - {
1.57 - printArguments();
1.58 - return;
1.59 - }
1.60 - else if(args[n].equals("-mlgw"))
1.61 - {
1.62 - mlgw = true;
1.63 - }
1.64 - else if(args[n].equals("-p"))
1.65 - {
1.66 - port = Integer.parseInt(args[++n]);
1.67 - }
1.68 - else if(args[n].equals("-plugin"))
1.69 - {
1.70 - System.out.println("Warning: -plugin-storage is not implemented!");
1.71 - }
1.72 - else if(args[n].equals("-plugin-command"))
1.73 - {
1.74 - try
1.75 - {
1.76 - CommandSelector.addCommandHandler(args[++n]);
1.77 - }
1.78 - catch(Exception ex)
1.79 - {
1.80 - Log.get().warning("Could not load command plugin: " + args[n]);
1.81 - Log.get().log(Level.INFO, "Main.java", ex);
1.82 - }
1.83 - }
1.84 - else if(args[n].equals("-plugin-storage"))
1.85 - {
1.86 - System.out.println("Warning: -plugin-storage is not implemented!");
1.87 - }
1.88 - else if(args[n].equals("-v") || args[n].equals("-version"))
1.89 - {
1.90 - // Simply return as the version info is already printed above
1.91 - return;
1.92 - }
1.93 - }
1.94 -
1.95 - // Try to load the JDBCDatabase;
1.96 - // Do NOT USE BackendConfig or Log classes before this point because they require
1.97 - // a working JDBCDatabase connection.
1.98 - try
1.99 - {
1.100 - StorageProvider sprov =
1.101 - StorageManager.loadProvider("org.sonews.storage.impl.JDBCDatabaseProvider");
1.102 - StorageManager.enableProvider(sprov);
1.103 -
1.104 - // Make sure some elementary groups are existing
1.105 - if(!StorageManager.current().isGroupExisting("control"))
1.106 - {
1.107 - StorageManager.current().addGroup("control", 0);
1.108 - Log.get().info("Group 'control' created.");
1.109 - }
1.110 - }
1.111 - catch(StorageBackendException ex)
1.112 - {
1.113 - ex.printStackTrace();
1.114 - System.err.println("Database initialization failed with " + ex.toString());
1.115 - System.err.println("Make sure you have specified the correct database" +
1.116 - " settings in sonews.conf!");
1.117 - return;
1.118 - }
1.119 -
1.120 - ChannelLineBuffers.allocateDirect();
1.121 -
1.122 - // Add shutdown hook
1.123 - Runtime.getRuntime().addShutdownHook(new ShutdownHook());
1.124 -
1.125 - // Start the listening daemon
1.126 - if(port <= 0)
1.127 - {
1.128 - port = Config.inst().get(Config.PORT, 119);
1.129 - }
1.130 - final NNTPDaemon daemon = NNTPDaemon.createInstance(port);
1.131 - daemon.start();
1.132 -
1.133 - // Start Connections purger thread...
1.134 - Connections.getInstance().start();
1.135 -
1.136 - // Start mailinglist gateway...
1.137 - if(mlgw)
1.138 - {
1.139 - new MailPoller().start();
1.140 - }
1.141 -
1.142 - // Start feeds
1.143 - if(feed)
1.144 - {
1.145 - FeedManager.startFeeding();
1.146 - }
1.147 + /**
1.148 + * The main entrypoint.
1.149 + * @param args
1.150 + * @throws Exception
1.151 + */
1.152 + public static void main(String[] args) throws Exception
1.153 + {
1.154 + System.out.println(VERSION);
1.155 + Thread.currentThread().setName("Mainthread");
1.156
1.157 - Purger purger = new Purger();
1.158 - purger.start();
1.159 -
1.160 - // Wait for main thread to exit (setDaemon(false))
1.161 - daemon.join();
1.162 - }
1.163 -
1.164 - private static void printArguments()
1.165 - {
1.166 - String usage = Resource.getAsString("helpers/usage", true);
1.167 - System.out.println(usage);
1.168 - }
1.169 + // Command line arguments
1.170 + boolean feed = false; // Enable feeding?
1.171 + boolean mlgw = false; // Enable Mailinglist gateway?
1.172 + int port = -1;
1.173
1.174 + for (int n = 0; n < args.length; n++) {
1.175 + if (args[n].equals("-c") || args[n].equals("-config")) {
1.176 + Config.inst().set(Config.LEVEL_CLI, Config.CONFIGFILE, args[++n]);
1.177 + System.out.println("Using config file " + args[n]);
1.178 + } else if (args[n].equals("-dumpjdbcdriver")) {
1.179 + System.out.println("Available JDBC drivers:");
1.180 + Enumeration<Driver> drvs = DriverManager.getDrivers();
1.181 + while (drvs.hasMoreElements()) {
1.182 + System.out.println(drvs.nextElement());
1.183 + }
1.184 + return;
1.185 + } else if (args[n].equals("-feed")) {
1.186 + feed = true;
1.187 + } else if (args[n].equals("-h") || args[n].equals("-help")) {
1.188 + printArguments();
1.189 + return;
1.190 + } else if (args[n].equals("-mlgw")) {
1.191 + mlgw = true;
1.192 + } else if (args[n].equals("-p")) {
1.193 + port = Integer.parseInt(args[++n]);
1.194 + } else if (args[n].equals("-plugin")) {
1.195 + System.out.println("Warning: -plugin-storage is not implemented!");
1.196 + } else if (args[n].equals("-plugin-command")) {
1.197 + try {
1.198 + CommandSelector.addCommandHandler(args[++n]);
1.199 + } catch (Exception ex) {
1.200 + Log.get().warning("Could not load command plugin: " + args[n]);
1.201 + Log.get().log(Level.INFO, "Main.java", ex);
1.202 + }
1.203 + } else if (args[n].equals("-plugin-storage")) {
1.204 + System.out.println("Warning: -plugin-storage is not implemented!");
1.205 + } else if (args[n].equals("-v") || args[n].equals("-version")) {
1.206 + // Simply return as the version info is already printed above
1.207 + return;
1.208 + }
1.209 + }
1.210 +
1.211 + // Try to load the JDBCDatabase;
1.212 + // Do NOT USE BackendConfig or Log classes before this point because they require
1.213 + // a working JDBCDatabase connection.
1.214 + try {
1.215 + StorageProvider sprov =
1.216 + StorageManager.loadProvider("org.sonews.storage.impl.JDBCDatabaseProvider");
1.217 + StorageManager.enableProvider(sprov);
1.218 +
1.219 + // Make sure some elementary groups are existing
1.220 + if (!StorageManager.current().isGroupExisting("control")) {
1.221 + StorageManager.current().addGroup("control", 0);
1.222 + Log.get().info("Group 'control' created.");
1.223 + }
1.224 + } catch (StorageBackendException ex) {
1.225 + ex.printStackTrace();
1.226 + System.err.println("Database initialization failed with " + ex.toString());
1.227 + System.err.println("Make sure you have specified the correct database"
1.228 + + " settings in sonews.conf!");
1.229 + return;
1.230 + }
1.231 +
1.232 + ChannelLineBuffers.allocateDirect();
1.233 +
1.234 + // Add shutdown hook
1.235 + Runtime.getRuntime().addShutdownHook(new ShutdownHook());
1.236 +
1.237 + // Start the listening daemon
1.238 + if (port <= 0) {
1.239 + port = Config.inst().get(Config.PORT, 119);
1.240 + }
1.241 + final NNTPDaemon daemon = NNTPDaemon.createInstance(port);
1.242 + daemon.start();
1.243 +
1.244 + // Start Connections purger thread...
1.245 + Connections.getInstance().start();
1.246 +
1.247 + // Start mailinglist gateway...
1.248 + if (mlgw) {
1.249 + new MailPoller().start();
1.250 + }
1.251 +
1.252 + // Start feeds
1.253 + if (feed) {
1.254 + FeedManager.startFeeding();
1.255 + }
1.256 +
1.257 + Purger purger = new Purger();
1.258 + purger.start();
1.259 +
1.260 + // Wait for main thread to exit (setDaemon(false))
1.261 + daemon.join();
1.262 + }
1.263 +
1.264 + private static void printArguments()
1.265 + {
1.266 + String usage = Resource.getAsString("helpers/usage", true);
1.267 + System.out.println(usage);
1.268 + }
1.269 +
1.270 + private Main()
1.271 + {
1.272 + }
1.273 }
2.1 --- a/src/org/sonews/ShutdownHook.java Sun Aug 29 17:43:58 2010 +0200
2.2 +++ b/src/org/sonews/ShutdownHook.java Sun Aug 29 18:17:37 2010 +0200
2.3 @@ -30,55 +30,44 @@
2.4 class ShutdownHook extends Thread
2.5 {
2.6
2.7 - /**
2.8 - * Called when the JVM exits.
2.9 - */
2.10 - @Override
2.11 - public void run()
2.12 - {
2.13 - System.out.println("sonews: Trying to shutdown all threads...");
2.14 + /**
2.15 + * Called when the JVM exits.
2.16 + */
2.17 + @Override
2.18 + public void run()
2.19 + {
2.20 + System.out.println("sonews: Trying to shutdown all threads...");
2.21
2.22 - Map<Thread, StackTraceElement[]> threadsMap = Thread.getAllStackTraces();
2.23 - for(Thread thread : threadsMap.keySet())
2.24 - {
2.25 - // Interrupt the thread if it's a AbstractDaemon
2.26 - AbstractDaemon daemon;
2.27 - if(thread instanceof AbstractDaemon && thread.isAlive())
2.28 - {
2.29 - try
2.30 - {
2.31 - daemon = (AbstractDaemon)thread;
2.32 - daemon.shutdownNow();
2.33 - }
2.34 - catch(SQLException ex)
2.35 - {
2.36 - System.out.println("sonews: " + ex);
2.37 - }
2.38 - }
2.39 - }
2.40 -
2.41 - for(Thread thread : threadsMap.keySet())
2.42 - {
2.43 - AbstractDaemon daemon;
2.44 - if(thread instanceof AbstractDaemon && thread.isAlive())
2.45 - {
2.46 - daemon = (AbstractDaemon)thread;
2.47 - System.out.println("sonews: Waiting for " + daemon + " to exit...");
2.48 - try
2.49 - {
2.50 - daemon.join(500);
2.51 - }
2.52 - catch(InterruptedException ex)
2.53 - {
2.54 - System.out.println(ex.getLocalizedMessage());
2.55 - }
2.56 - }
2.57 - }
2.58 -
2.59 - // We have notified all not-sleeping AbstractDaemons of the shutdown;
2.60 - // all other threads can be simply purged on VM shutdown
2.61 -
2.62 - System.out.println("sonews: Clean shutdown.");
2.63 - }
2.64 -
2.65 + Map<Thread, StackTraceElement[]> threadsMap = Thread.getAllStackTraces();
2.66 + for (Thread thread : threadsMap.keySet()) {
2.67 + // Interrupt the thread if it's a AbstractDaemon
2.68 + AbstractDaemon daemon;
2.69 + if (thread instanceof AbstractDaemon && thread.isAlive()) {
2.70 + try {
2.71 + daemon = (AbstractDaemon) thread;
2.72 + daemon.shutdownNow();
2.73 + } catch (SQLException ex) {
2.74 + System.out.println("sonews: " + ex);
2.75 + }
2.76 + }
2.77 + }
2.78 +
2.79 + for (Thread thread : threadsMap.keySet()) {
2.80 + AbstractDaemon daemon;
2.81 + if (thread instanceof AbstractDaemon && thread.isAlive()) {
2.82 + daemon = (AbstractDaemon) thread;
2.83 + System.out.println("sonews: Waiting for " + daemon + " to exit...");
2.84 + try {
2.85 + daemon.join(500);
2.86 + } catch (InterruptedException ex) {
2.87 + System.out.println(ex.getLocalizedMessage());
2.88 + }
2.89 + }
2.90 + }
2.91 +
2.92 + // We have notified all not-sleeping AbstractDaemons of the shutdown;
2.93 + // all other threads can be simply purged on VM shutdown
2.94 +
2.95 + System.out.println("sonews: Clean shutdown.");
2.96 + }
2.97 }
3.1 --- a/src/org/sonews/acl/AccessControl.java Sun Aug 29 17:43:58 2010 +0200
3.2 +++ b/src/org/sonews/acl/AccessControl.java Sun Aug 29 18:17:37 2010 +0200
3.3 @@ -26,6 +26,5 @@
3.4 public interface AccessControl
3.5 {
3.6
3.7 - boolean hasPermission(String user, char[] secret, String permission);
3.8 -
3.9 + boolean hasPermission(String user, char[] secret, String permission);
3.10 }
4.1 --- a/src/org/sonews/acl/AuthInfoCommand.java Sun Aug 29 17:43:58 2010 +0200
4.2 +++ b/src/org/sonews/acl/AuthInfoCommand.java Sun Aug 29 18:17:37 2010 +0200
4.3 @@ -31,34 +31,34 @@
4.4 public class AuthInfoCommand implements Command
4.5 {
4.6
4.7 - @Override
4.8 - public String[] getSupportedCommandStrings()
4.9 - {
4.10 - throw new UnsupportedOperationException("Not supported yet.");
4.11 - }
4.12 + @Override
4.13 + public String[] getSupportedCommandStrings()
4.14 + {
4.15 + throw new UnsupportedOperationException("Not supported yet.");
4.16 + }
4.17
4.18 - @Override
4.19 - public boolean hasFinished()
4.20 - {
4.21 - throw new UnsupportedOperationException("Not supported yet.");
4.22 - }
4.23 + @Override
4.24 + public boolean hasFinished()
4.25 + {
4.26 + throw new UnsupportedOperationException("Not supported yet.");
4.27 + }
4.28
4.29 - @Override
4.30 - public String impliedCapability()
4.31 - {
4.32 - throw new UnsupportedOperationException("Not supported yet.");
4.33 - }
4.34 + @Override
4.35 + public String impliedCapability()
4.36 + {
4.37 + throw new UnsupportedOperationException("Not supported yet.");
4.38 + }
4.39
4.40 - @Override
4.41 - public boolean isStateful()
4.42 - {
4.43 - throw new UnsupportedOperationException("Not supported yet.");
4.44 - }
4.45 + @Override
4.46 + public boolean isStateful()
4.47 + {
4.48 + throw new UnsupportedOperationException("Not supported yet.");
4.49 + }
4.50
4.51 - @Override
4.52 - public void processLine(NNTPConnection conn, String line, byte[] rawLine) throws IOException, StorageBackendException
4.53 - {
4.54 - throw new UnsupportedOperationException("Not supported yet.");
4.55 - }
4.56 -
4.57 + @Override
4.58 + public void processLine(NNTPConnection conn, String line, byte[] rawLine)
4.59 + throws IOException, StorageBackendException
4.60 + {
4.61 + throw new UnsupportedOperationException("Not supported yet.");
4.62 + }
4.63 }
5.1 --- a/src/org/sonews/config/AbstractConfig.java Sun Aug 29 17:43:58 2010 +0200
5.2 +++ b/src/org/sonews/config/AbstractConfig.java Sun Aug 29 18:17:37 2010 +0200
5.3 @@ -23,35 +23,34 @@
5.4 * @author Christian Lins
5.5 * @since sonews/0.5.0
5.6 */
5.7 -public abstract class AbstractConfig
5.8 +public abstract class AbstractConfig
5.9 {
5.10 -
5.11 - public abstract String get(String key, String defVal);
5.12 -
5.13 - public int get(final String key, final int defVal)
5.14 - {
5.15 - return Integer.parseInt(
5.16 - get(key, Integer.toString(defVal)));
5.17 - }
5.18 -
5.19 - public boolean get(String key, boolean defVal)
5.20 - {
5.21 - String val = get(key, Boolean.toString(defVal));
5.22 - return Boolean.parseBoolean(val);
5.23 - }
5.24
5.25 - /**
5.26 - * Returns a long config value specified via the given key.
5.27 - * @param key
5.28 - * @param defVal
5.29 - * @return
5.30 - */
5.31 - public long get(String key, long defVal)
5.32 - {
5.33 - String val = get(key, Long.toString(defVal));
5.34 - return Long.parseLong(val);
5.35 - }
5.36 + public abstract String get(String key, String defVal);
5.37
5.38 - protected abstract void set(String key, String val);
5.39 -
5.40 + public int get(final String key, final int defVal)
5.41 + {
5.42 + return Integer.parseInt(
5.43 + get(key, Integer.toString(defVal)));
5.44 + }
5.45 +
5.46 + public boolean get(String key, boolean defVal)
5.47 + {
5.48 + String val = get(key, Boolean.toString(defVal));
5.49 + return Boolean.parseBoolean(val);
5.50 + }
5.51 +
5.52 + /**
5.53 + * Returns a long config value specified via the given key.
5.54 + * @param key
5.55 + * @param defVal
5.56 + * @return
5.57 + */
5.58 + public long get(String key, long defVal)
5.59 + {
5.60 + String val = get(key, Long.toString(defVal));
5.61 + return Long.parseLong(val);
5.62 + }
5.63 +
5.64 + protected abstract void set(String key, String val);
5.65 }
6.1 --- a/src/org/sonews/config/BackendConfig.java Sun Aug 29 17:43:58 2010 +0200
6.2 +++ b/src/org/sonews/config/BackendConfig.java Sun Aug 29 18:17:37 2010 +0200
6.3 @@ -33,83 +33,67 @@
6.4 class BackendConfig extends AbstractConfig
6.5 {
6.6
6.7 - private static BackendConfig instance = new BackendConfig();
6.8 -
6.9 - public static BackendConfig getInstance()
6.10 - {
6.11 - return instance;
6.12 - }
6.13 -
6.14 - private final TimeoutMap<String, String> values
6.15 - = new TimeoutMap<String, String>();
6.16 -
6.17 - private BackendConfig()
6.18 - {
6.19 - super();
6.20 - }
6.21 -
6.22 - /**
6.23 - * Returns the config value for the given key or the defaultValue if the
6.24 - * key is not found in config.
6.25 - * @param key
6.26 - * @param defaultValue
6.27 - * @return
6.28 - */
6.29 - @Override
6.30 - public String get(String key, String defaultValue)
6.31 - {
6.32 - try
6.33 - {
6.34 - String configValue = values.get(key);
6.35 - if(configValue == null)
6.36 - {
6.37 - if(StorageManager.current() == null)
6.38 - {
6.39 - Log.get().warning("BackendConfig not available, using default.");
6.40 - return defaultValue;
6.41 - }
6.42 + private static BackendConfig instance = new BackendConfig();
6.43
6.44 - configValue = StorageManager.current().getConfigValue(key);
6.45 - if(configValue == null)
6.46 - {
6.47 - return defaultValue;
6.48 - }
6.49 - else
6.50 - {
6.51 - values.put(key, configValue);
6.52 - return configValue;
6.53 - }
6.54 - }
6.55 - else
6.56 - {
6.57 - return configValue;
6.58 - }
6.59 - }
6.60 - catch(StorageBackendException ex)
6.61 - {
6.62 - Log.get().log(Level.SEVERE, "Storage backend problem", ex);
6.63 - return defaultValue;
6.64 - }
6.65 - }
6.66 -
6.67 - /**
6.68 - * Sets the config value which is identified by the given key.
6.69 - * @param key
6.70 - * @param value
6.71 - */
6.72 - public void set(String key, String value)
6.73 - {
6.74 - values.put(key, value);
6.75 -
6.76 - try
6.77 - {
6.78 - // Write values to database
6.79 - StorageManager.current().setConfigValue(key, value);
6.80 - }
6.81 - catch(StorageBackendException ex)
6.82 - {
6.83 - ex.printStackTrace();
6.84 - }
6.85 - }
6.86 -
6.87 + public static BackendConfig getInstance()
6.88 + {
6.89 + return instance;
6.90 + }
6.91 + private final TimeoutMap<String, String> values = new TimeoutMap<String, String>();
6.92 +
6.93 + private BackendConfig()
6.94 + {
6.95 + super();
6.96 + }
6.97 +
6.98 + /**
6.99 + * Returns the config value for the given key or the defaultValue if the
6.100 + * key is not found in config.
6.101 + * @param key
6.102 + * @param defaultValue
6.103 + * @return
6.104 + */
6.105 + @Override
6.106 + public String get(String key, String defaultValue)
6.107 + {
6.108 + try {
6.109 + String configValue = values.get(key);
6.110 + if (configValue == null) {
6.111 + if (StorageManager.current() == null) {
6.112 + Log.get().warning("BackendConfig not available, using default.");
6.113 + return defaultValue;
6.114 + }
6.115 +
6.116 + configValue = StorageManager.current().getConfigValue(key);
6.117 + if (configValue == null) {
6.118 + return defaultValue;
6.119 + } else {
6.120 + values.put(key, configValue);
6.121 + return configValue;
6.122 + }
6.123 + } else {
6.124 + return configValue;
6.125 + }
6.126 + } catch (StorageBackendException ex) {
6.127 + Log.get().log(Level.SEVERE, "Storage backend problem", ex);
6.128 + return defaultValue;
6.129 + }
6.130 + }
6.131 +
6.132 + /**
6.133 + * Sets the config value which is identified by the given key.
6.134 + * @param key
6.135 + * @param value
6.136 + */
6.137 + public void set(String key, String value)
6.138 + {
6.139 + values.put(key, value);
6.140 +
6.141 + try {
6.142 + // Write values to database
6.143 + StorageManager.current().setConfigValue(key, value);
6.144 + } catch (StorageBackendException ex) {
6.145 + ex.printStackTrace();
6.146 + }
6.147 + }
6.148 }
7.1 --- a/src/org/sonews/config/CommandLineConfig.java Sun Aug 29 17:43:58 2010 +0200
7.2 +++ b/src/org/sonews/config/CommandLineConfig.java Sun Aug 29 18:17:37 2010 +0200
7.3 @@ -28,37 +28,34 @@
7.4 class CommandLineConfig extends AbstractConfig
7.5 {
7.6
7.7 - private static final CommandLineConfig instance = new CommandLineConfig();
7.8 + private static final CommandLineConfig instance = new CommandLineConfig();
7.9
7.10 - public static CommandLineConfig getInstance()
7.11 - {
7.12 - return instance;
7.13 - }
7.14 + public static CommandLineConfig getInstance()
7.15 + {
7.16 + return instance;
7.17 + }
7.18 + private final Map<String, String> values = new HashMap<String, String>();
7.19
7.20 - private final Map<String, String> values = new HashMap<String, String>();
7.21 -
7.22 - private CommandLineConfig() {}
7.23 + private CommandLineConfig()
7.24 + {
7.25 + }
7.26
7.27 - @Override
7.28 - public String get(String key, String def)
7.29 - {
7.30 - synchronized(this.values)
7.31 - {
7.32 - if(this.values.containsKey(key))
7.33 - {
7.34 - def = this.values.get(key);
7.35 - }
7.36 - }
7.37 - return def;
7.38 - }
7.39 + @Override
7.40 + public String get(String key, String def)
7.41 + {
7.42 + synchronized (this.values) {
7.43 + if (this.values.containsKey(key)) {
7.44 + def = this.values.get(key);
7.45 + }
7.46 + }
7.47 + return def;
7.48 + }
7.49
7.50 - @Override
7.51 - public void set(String key, String val)
7.52 - {
7.53 - synchronized(this.values)
7.54 - {
7.55 - this.values.put(key, val);
7.56 - }
7.57 - }
7.58 -
7.59 + @Override
7.60 + public void set(String key, String val)
7.61 + {
7.62 + synchronized (this.values) {
7.63 + this.values.put(key, val);
7.64 + }
7.65 + }
7.66 }
8.1 --- a/src/org/sonews/config/Config.java Sun Aug 29 17:43:58 2010 +0200
8.2 +++ b/src/org/sonews/config/Config.java Sun Aug 29 18:17:37 2010 +0200
8.3 @@ -25,151 +25,132 @@
8.4 */
8.5 public class Config extends AbstractConfig
8.6 {
8.7 -
8.8 - public static final int LEVEL_CLI = 1;
8.9 - public static final int LEVEL_FILE = 2;
8.10 - public static final int LEVEL_BACKEND = 3;
8.11
8.12 - public static final String CONFIGFILE = "sonews.configfile";
8.13 -
8.14 - /** BackendConfig key constant. Value is the maximum article size in kilobytes. */
8.15 - public static final String ARTICLE_MAXSIZE = "sonews.article.maxsize";
8.16 + public static final int LEVEL_CLI = 1;
8.17 + public static final int LEVEL_FILE = 2;
8.18 + public static final int LEVEL_BACKEND = 3;
8.19 + public static final String CONFIGFILE = "sonews.configfile";
8.20 + /** BackendConfig key constant. Value is the maximum article size in kilobytes. */
8.21 + public static final String ARTICLE_MAXSIZE = "sonews.article.maxsize";
8.22 + /** BackendConfig key constant. Value: Amount of news that are feeded per run. */
8.23 + public static final String EVENTLOG = "sonews.eventlog";
8.24 + public static final String FEED_NEWSPERRUN = "sonews.feed.newsperrun";
8.25 + public static final String FEED_PULLINTERVAL = "sonews.feed.pullinterval";
8.26 + public static final String HOSTNAME = "sonews.hostname";
8.27 + public static final String PORT = "sonews.port";
8.28 + public static final String TIMEOUT = "sonews.timeout";
8.29 + public static final String LOGLEVEL = "sonews.loglevel";
8.30 + public static final String MLPOLL_DELETEUNKNOWN = "sonews.mlpoll.deleteunknown";
8.31 + public static final String MLPOLL_HOST = "sonews.mlpoll.host";
8.32 + public static final String MLPOLL_PASSWORD = "sonews.mlpoll.password";
8.33 + public static final String MLPOLL_USER = "sonews.mlpoll.user";
8.34 + public static final String MLSEND_ADDRESS = "sonews.mlsend.address";
8.35 + public static final String MLSEND_RW_FROM = "sonews.mlsend.rewrite.from";
8.36 + public static final String MLSEND_RW_SENDER = "sonews.mlsend.rewrite.sender";
8.37 + public static final String MLSEND_HOST = "sonews.mlsend.host";
8.38 + public static final String MLSEND_PASSWORD = "sonews.mlsend.password";
8.39 + public static final String MLSEND_PORT = "sonews.mlsend.port";
8.40 + public static final String MLSEND_USER = "sonews.mlsend.user";
8.41 + /** Key constant. If value is "true" every I/O is written to logfile
8.42 + * (which is a lot!)
8.43 + */
8.44 + public static final String DEBUG = "sonews.debug";
8.45 + /** Key constant. Value is classname of the JDBC driver */
8.46 + public static final String STORAGE_DBMSDRIVER = "sonews.storage.dbmsdriver";
8.47 + /** Key constant. Value is JDBC connect String to the database. */
8.48 + public static final String STORAGE_DATABASE = "sonews.storage.database";
8.49 + /** Key constant. Value is the username for the DBMS. */
8.50 + public static final String STORAGE_USER = "sonews.storage.user";
8.51 + /** Key constant. Value is the password for the DBMS. */
8.52 + public static final String STORAGE_PASSWORD = "sonews.storage.password";
8.53 + /** Key constant. Value is the name of the host which is allowed to use the
8.54 + * XDAEMON command; default: "localhost" */
8.55 + public static final String XDAEMON_HOST = "sonews.xdaemon.host";
8.56 + /** The config key for the filename of the logfile */
8.57 + public static final String LOGFILE = "sonews.log";
8.58 + public static final String[] AVAILABLE_KEYS = {
8.59 + ARTICLE_MAXSIZE,
8.60 + EVENTLOG,
8.61 + FEED_NEWSPERRUN,
8.62 + FEED_PULLINTERVAL,
8.63 + HOSTNAME,
8.64 + MLPOLL_DELETEUNKNOWN,
8.65 + MLPOLL_HOST,
8.66 + MLPOLL_PASSWORD,
8.67 + MLPOLL_USER,
8.68 + MLSEND_ADDRESS,
8.69 + MLSEND_HOST,
8.70 + MLSEND_PASSWORD,
8.71 + MLSEND_PORT,
8.72 + MLSEND_RW_FROM,
8.73 + MLSEND_RW_SENDER,
8.74 + MLSEND_USER,
8.75 + PORT,
8.76 + TIMEOUT,
8.77 + XDAEMON_HOST
8.78 + };
8.79 + private static Config instance = new Config();
8.80
8.81 - /** BackendConfig key constant. Value: Amount of news that are feeded per run. */
8.82 - public static final String EVENTLOG = "sonews.eventlog";
8.83 - public static final String FEED_NEWSPERRUN = "sonews.feed.newsperrun";
8.84 - public static final String FEED_PULLINTERVAL = "sonews.feed.pullinterval";
8.85 - public static final String HOSTNAME = "sonews.hostname";
8.86 - public static final String PORT = "sonews.port";
8.87 - public static final String TIMEOUT = "sonews.timeout";
8.88 - public static final String LOGLEVEL = "sonews.loglevel";
8.89 - public static final String MLPOLL_DELETEUNKNOWN = "sonews.mlpoll.deleteunknown";
8.90 - public static final String MLPOLL_HOST = "sonews.mlpoll.host";
8.91 - public static final String MLPOLL_PASSWORD = "sonews.mlpoll.password";
8.92 - public static final String MLPOLL_USER = "sonews.mlpoll.user";
8.93 - public static final String MLSEND_ADDRESS = "sonews.mlsend.address";
8.94 - public static final String MLSEND_RW_FROM = "sonews.mlsend.rewrite.from";
8.95 - public static final String MLSEND_RW_SENDER = "sonews.mlsend.rewrite.sender";
8.96 - public static final String MLSEND_HOST = "sonews.mlsend.host";
8.97 - public static final String MLSEND_PASSWORD = "sonews.mlsend.password";
8.98 - public static final String MLSEND_PORT = "sonews.mlsend.port";
8.99 - public static final String MLSEND_USER = "sonews.mlsend.user";
8.100 -
8.101 - /** Key constant. If value is "true" every I/O is written to logfile
8.102 - * (which is a lot!)
8.103 - */
8.104 - public static final String DEBUG = "sonews.debug";
8.105 + public static Config inst()
8.106 + {
8.107 + return instance;
8.108 + }
8.109
8.110 - /** Key constant. Value is classname of the JDBC driver */
8.111 - public static final String STORAGE_DBMSDRIVER = "sonews.storage.dbmsdriver";
8.112 + private Config()
8.113 + {
8.114 + }
8.115
8.116 - /** Key constant. Value is JDBC connect String to the database. */
8.117 - public static final String STORAGE_DATABASE = "sonews.storage.database";
8.118 + @Override
8.119 + public String get(String key, String def)
8.120 + {
8.121 + String val = CommandLineConfig.getInstance().get(key, null);
8.122
8.123 - /** Key constant. Value is the username for the DBMS. */
8.124 - public static final String STORAGE_USER = "sonews.storage.user";
8.125 + if (val == null) {
8.126 + val = FileConfig.getInstance().get(key, null);
8.127 + }
8.128
8.129 - /** Key constant. Value is the password for the DBMS. */
8.130 - public static final String STORAGE_PASSWORD = "sonews.storage.password";
8.131 + if (val == null) {
8.132 + val = BackendConfig.getInstance().get(key, def);
8.133 + }
8.134
8.135 - /** Key constant. Value is the name of the host which is allowed to use the
8.136 - * XDAEMON command; default: "localhost" */
8.137 - public static final String XDAEMON_HOST = "sonews.xdaemon.host";
8.138 + return val;
8.139 + }
8.140
8.141 - /** The config key for the filename of the logfile */
8.142 - public static final String LOGFILE = "sonews.log";
8.143 + public String get(int maxLevel, String key, String def)
8.144 + {
8.145 + String val = CommandLineConfig.getInstance().get(key, null);
8.146
8.147 - public static final String[] AVAILABLE_KEYS = {
8.148 - ARTICLE_MAXSIZE,
8.149 - EVENTLOG,
8.150 - FEED_NEWSPERRUN,
8.151 - FEED_PULLINTERVAL,
8.152 - HOSTNAME,
8.153 - MLPOLL_DELETEUNKNOWN,
8.154 - MLPOLL_HOST,
8.155 - MLPOLL_PASSWORD,
8.156 - MLPOLL_USER,
8.157 - MLSEND_ADDRESS,
8.158 - MLSEND_HOST,
8.159 - MLSEND_PASSWORD,
8.160 - MLSEND_PORT,
8.161 - MLSEND_RW_FROM,
8.162 - MLSEND_RW_SENDER,
8.163 - MLSEND_USER,
8.164 - PORT,
8.165 - TIMEOUT,
8.166 - XDAEMON_HOST
8.167 - };
8.168 + if (val == null && maxLevel >= LEVEL_FILE) {
8.169 + val = FileConfig.getInstance().get(key, null);
8.170 + if (val == null && maxLevel >= LEVEL_BACKEND) {
8.171 + val = BackendConfig.getInstance().get(key, def);
8.172 + }
8.173 + }
8.174
8.175 - private static Config instance = new Config();
8.176 -
8.177 - public static Config inst()
8.178 - {
8.179 - return instance;
8.180 - }
8.181 -
8.182 - private Config(){}
8.183 + return val != null ? val : def;
8.184 + }
8.185
8.186 - @Override
8.187 - public String get(String key, String def)
8.188 - {
8.189 - String val = CommandLineConfig.getInstance().get(key, null);
8.190 -
8.191 - if(val == null)
8.192 - {
8.193 - val = FileConfig.getInstance().get(key, null);
8.194 - }
8.195 + @Override
8.196 + public void set(String key, String val)
8.197 + {
8.198 + set(LEVEL_BACKEND, key, val);
8.199 + }
8.200
8.201 - if(val == null)
8.202 - {
8.203 - val = BackendConfig.getInstance().get(key, def);
8.204 - }
8.205 -
8.206 - return val;
8.207 - }
8.208 -
8.209 - public String get(int maxLevel, String key, String def)
8.210 - {
8.211 - String val = CommandLineConfig.getInstance().get(key, null);
8.212 -
8.213 - if(val == null && maxLevel >= LEVEL_FILE)
8.214 - {
8.215 - val = FileConfig.getInstance().get(key, null);
8.216 - if(val == null && maxLevel >= LEVEL_BACKEND)
8.217 - {
8.218 - val = BackendConfig.getInstance().get(key, def);
8.219 - }
8.220 - }
8.221 -
8.222 - return val != null ? val : def;
8.223 - }
8.224 -
8.225 - @Override
8.226 - public void set(String key, String val)
8.227 - {
8.228 - set(LEVEL_BACKEND, key, val);
8.229 - }
8.230 -
8.231 - public void set(int level, String key, String val)
8.232 - {
8.233 - switch(level)
8.234 - {
8.235 - case LEVEL_CLI:
8.236 - {
8.237 - CommandLineConfig.getInstance().set(key, val);
8.238 - break;
8.239 - }
8.240 - case LEVEL_FILE:
8.241 - {
8.242 - FileConfig.getInstance().set(key, val);
8.243 - break;
8.244 - }
8.245 - case LEVEL_BACKEND:
8.246 - {
8.247 - BackendConfig.getInstance().set(key, val);
8.248 - break;
8.249 - }
8.250 - }
8.251 - }
8.252 -
8.253 + public void set(int level, String key, String val)
8.254 + {
8.255 + switch (level) {
8.256 + case LEVEL_CLI: {
8.257 + CommandLineConfig.getInstance().set(key, val);
8.258 + break;
8.259 + }
8.260 + case LEVEL_FILE: {
8.261 + FileConfig.getInstance().set(key, val);
8.262 + break;
8.263 + }
8.264 + case LEVEL_BACKEND: {
8.265 + BackendConfig.getInstance().set(key, val);
8.266 + break;
8.267 + }
8.268 + }
8.269 + }
8.270 }
9.1 --- a/src/org/sonews/config/FileConfig.java Sun Aug 29 17:43:58 2010 +0200
9.2 +++ b/src/org/sonews/config/FileConfig.java Sun Aug 29 18:17:37 2010 +0200
9.3 @@ -35,136 +35,120 @@
9.4 class FileConfig extends AbstractConfig
9.5 {
9.6
9.7 - private static final Properties defaultConfig = new Properties();
9.8 -
9.9 - private static FileConfig instance = null;
9.10 -
9.11 - static
9.12 - {
9.13 - // Set some default values
9.14 - defaultConfig.setProperty(Config.STORAGE_DATABASE, "jdbc:mysql://localhost/sonews");
9.15 - defaultConfig.setProperty(Config.STORAGE_DBMSDRIVER, "com.mysql.jdbc.Driver");
9.16 - defaultConfig.setProperty(Config.STORAGE_USER, "sonews_user");
9.17 - defaultConfig.setProperty(Config.STORAGE_PASSWORD, "mysecret");
9.18 - defaultConfig.setProperty(Config.DEBUG, "false");
9.19 - }
9.20 -
9.21 - /**
9.22 - * Note: this method is not thread-safe
9.23 - * @return A Config instance
9.24 - */
9.25 - public static synchronized FileConfig getInstance()
9.26 - {
9.27 - if(instance == null)
9.28 - {
9.29 - instance = new FileConfig();
9.30 - }
9.31 - return instance;
9.32 - }
9.33 + private static final Properties defaultConfig = new Properties();
9.34 + private static FileConfig instance = null;
9.35
9.36 - // Every config instance is initialized with the default values.
9.37 - private final Properties settings = (Properties)defaultConfig.clone();
9.38 + static {
9.39 + // Set some default values
9.40 + defaultConfig.setProperty(Config.STORAGE_DATABASE, "jdbc:mysql://localhost/sonews");
9.41 + defaultConfig.setProperty(Config.STORAGE_DBMSDRIVER, "com.mysql.jdbc.Driver");
9.42 + defaultConfig.setProperty(Config.STORAGE_USER, "sonews_user");
9.43 + defaultConfig.setProperty(Config.STORAGE_PASSWORD, "mysecret");
9.44 + defaultConfig.setProperty(Config.DEBUG, "false");
9.45 + }
9.46
9.47 - /**
9.48 - * Config is a singelton class with only one instance at time.
9.49 - * So the constructor is private to prevent the creation of more
9.50 - * then one Config instance.
9.51 - * @see Config.getInstance() to retrieve an instance of Config
9.52 - */
9.53 - private FileConfig()
9.54 - {
9.55 - try
9.56 - {
9.57 - // Load settings from file
9.58 - load();
9.59 - }
9.60 - catch(IOException ex)
9.61 - {
9.62 - ex.printStackTrace();
9.63 - }
9.64 - }
9.65 + /**
9.66 + * Note: this method is not thread-safe
9.67 + * @return A Config instance
9.68 + */
9.69 + public static synchronized FileConfig getInstance()
9.70 + {
9.71 + if (instance == null) {
9.72 + instance = new FileConfig();
9.73 + }
9.74 + return instance;
9.75 + }
9.76 + // Every config instance is initialized with the default values.
9.77 + private final Properties settings = (Properties) defaultConfig.clone();
9.78
9.79 - /**
9.80 - * Loads the configuration from the config file. By default this is done
9.81 - * by the (private) constructor but it can be useful to reload the config
9.82 - * by invoking this method.
9.83 - * @throws IOException
9.84 - */
9.85 - public void load()
9.86 - throws IOException
9.87 - {
9.88 - FileInputStream in = null;
9.89 -
9.90 - try
9.91 - {
9.92 - in = new FileInputStream(
9.93 - Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
9.94 - settings.load(in);
9.95 - }
9.96 - catch (FileNotFoundException e)
9.97 - {
9.98 - // MUST NOT use Log otherwise endless loop
9.99 - System.err.println(e.getMessage());
9.100 - save();
9.101 - }
9.102 - finally
9.103 - {
9.104 - if(in != null)
9.105 - in.close();
9.106 - }
9.107 - }
9.108 + /**
9.109 + * Config is a singelton class with only one instance at time.
9.110 + * So the constructor is private to prevent the creation of more
9.111 + * then one Config instance.
9.112 + * @see Config.getInstance() to retrieve an instance of Config
9.113 + */
9.114 + private FileConfig()
9.115 + {
9.116 + try {
9.117 + // Load settings from file
9.118 + load();
9.119 + } catch (IOException ex) {
9.120 + ex.printStackTrace();
9.121 + }
9.122 + }
9.123
9.124 - /**
9.125 - * Saves this Config to the config file. By default this is done
9.126 - * at program end.
9.127 - * @throws FileNotFoundException
9.128 - * @throws IOException
9.129 - */
9.130 - public void save() throws FileNotFoundException, IOException
9.131 - {
9.132 - FileOutputStream out = null;
9.133 - try
9.134 - {
9.135 - out = new FileOutputStream(
9.136 - Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
9.137 - settings.store(out, "SONEWS Config File");
9.138 - out.flush();
9.139 - }
9.140 - catch(IOException ex)
9.141 - {
9.142 - throw ex;
9.143 - }
9.144 - finally
9.145 - {
9.146 - if(out != null)
9.147 - out.close();
9.148 - }
9.149 - }
9.150 -
9.151 - /**
9.152 - * Returns the value that is stored within this config
9.153 - * identified by the given key. If the key cannot be found
9.154 - * the default value is returned.
9.155 - * @param key Key to identify the value.
9.156 - * @param def The default value that is returned if the key
9.157 - * is not found in this Config.
9.158 - * @return
9.159 - */
9.160 - @Override
9.161 - public String get(String key, String def)
9.162 - {
9.163 - return settings.getProperty(key, def);
9.164 - }
9.165 + /**
9.166 + * Loads the configuration from the config file. By default this is done
9.167 + * by the (private) constructor but it can be useful to reload the config
9.168 + * by invoking this method.
9.169 + * @throws IOException
9.170 + */
9.171 + public void load()
9.172 + throws IOException
9.173 + {
9.174 + FileInputStream in = null;
9.175
9.176 - /**
9.177 - * Sets the value for a given key.
9.178 - * @param key
9.179 - * @param value
9.180 - */
9.181 - @Override
9.182 - public void set(final String key, final String value)
9.183 - {
9.184 - settings.setProperty(key, value);
9.185 - }
9.186 + try {
9.187 + in = new FileInputStream(
9.188 + Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
9.189 + settings.load(in);
9.190 + } catch (FileNotFoundException e) {
9.191 + // MUST NOT use Log otherwise endless loop
9.192 + System.err.println(e.getMessage());
9.193 + save();
9.194 + } finally {
9.195 + if (in != null) {
9.196 + in.close();
9.197 + }
9.198 + }
9.199 + }
9.200
9.201 + /**
9.202 + * Saves this Config to the config file. By default this is done
9.203 + * at program end.
9.204 + * @throws FileNotFoundException
9.205 + * @throws IOException
9.206 + */
9.207 + public void save() throws FileNotFoundException, IOException
9.208 + {
9.209 + FileOutputStream out = null;
9.210 + try {
9.211 + out = new FileOutputStream(
9.212 + Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
9.213 + settings.store(out, "SONEWS Config File");
9.214 + out.flush();
9.215 + } catch (IOException ex) {
9.216 + throw ex;
9.217 + } finally {
9.218 + if (out != null) {
9.219 + out.close();
9.220 + }
9.221 + }
9.222 + }
9.223 +
9.224 + /**
9.225 + * Returns the value that is stored within this config
9.226 + * identified by the given key. If the key cannot be found
9.227 + * the default value is returned.
9.228 + * @param key Key to identify the value.
9.229 + * @param def The default value that is returned if the key
9.230 + * is not found in this Config.
9.231 + * @return
9.232 + */
9.233 + @Override
9.234 + public String get(String key, String def)
9.235 + {
9.236 + return settings.getProperty(key, def);
9.237 + }
9.238 +
9.239 + /**
9.240 + * Sets the value for a given key.
9.241 + * @param key
9.242 + * @param value
9.243 + */
9.244 + @Override
9.245 + public void set(final String key, final String value)
9.246 + {
9.247 + settings.setProperty(key, value);
9.248 + }
9.249 }
10.1 --- a/src/org/sonews/daemon/AbstractDaemon.java Sun Aug 29 17:43:58 2010 +0200
10.2 +++ b/src/org/sonews/daemon/AbstractDaemon.java Sun Aug 29 18:17:37 2010 +0200
10.3 @@ -32,70 +32,63 @@
10.4 public abstract class AbstractDaemon extends Thread
10.5 {
10.6
10.7 - /** This variable is write synchronized through setRunning */
10.8 - private boolean isRunning = false;
10.9 + /** This variable is write synchronized through setRunning */
10.10 + private boolean isRunning = false;
10.11
10.12 - /**
10.13 - * Protected constructor. Will be called by derived classes.
10.14 - */
10.15 - protected AbstractDaemon()
10.16 - {
10.17 - setDaemon(true); // VM will exit when all threads are daemons
10.18 - setName(getClass().getSimpleName());
10.19 - }
10.20 -
10.21 - /**
10.22 - * @return true if shutdown() was not yet called.
10.23 - */
10.24 - public boolean isRunning()
10.25 - {
10.26 - synchronized(this)
10.27 - {
10.28 - return this.isRunning;
10.29 - }
10.30 - }
10.31 -
10.32 - /**
10.33 - * Marks this thread to exit soon. Closes the associated JDBCDatabase connection
10.34 - * if available.
10.35 - * @throws java.sql.SQLException
10.36 - */
10.37 - public void shutdownNow()
10.38 - throws SQLException
10.39 - {
10.40 - synchronized(this)
10.41 - {
10.42 - this.isRunning = false;
10.43 - StorageManager.disableProvider();
10.44 - }
10.45 - }
10.46 -
10.47 - /**
10.48 - * Calls shutdownNow() but catches SQLExceptions if occurring.
10.49 - */
10.50 - public void shutdown()
10.51 - {
10.52 - try
10.53 - {
10.54 - shutdownNow();
10.55 - }
10.56 - catch(SQLException ex)
10.57 - {
10.58 - Log.get().warning(ex.toString());
10.59 - }
10.60 - }
10.61 -
10.62 - /**
10.63 - * Starts this daemon.
10.64 - */
10.65 - @Override
10.66 - public void start()
10.67 - {
10.68 - synchronized(this)
10.69 - {
10.70 - this.isRunning = true;
10.71 - }
10.72 - super.start();
10.73 - }
10.74 -
10.75 + /**
10.76 + * Protected constructor. Will be called by derived classes.
10.77 + */
10.78 + protected AbstractDaemon()
10.79 + {
10.80 + setDaemon(true); // VM will exit when all threads are daemons
10.81 + setName(getClass().getSimpleName());
10.82 + }
10.83 +
10.84 + /**
10.85 + * @return true if shutdown() was not yet called.
10.86 + */
10.87 + public boolean isRunning()
10.88 + {
10.89 + synchronized (this) {
10.90 + return this.isRunning;
10.91 + }
10.92 + }
10.93 +
10.94 + /**
10.95 + * Marks this thread to exit soon. Closes the associated JDBCDatabase connection
10.96 + * if available.
10.97 + * @throws java.sql.SQLException
10.98 + */
10.99 + public void shutdownNow()
10.100 + throws SQLException
10.101 + {
10.102 + synchronized (this) {
10.103 + this.isRunning = false;
10.104 + StorageManager.disableProvider();
10.105 + }
10.106 + }
10.107 +
10.108 + /**
10.109 + * Calls shutdownNow() but catches SQLExceptions if occurring.
10.110 + */
10.111 + public void shutdown()
10.112 + {
10.113 + try {
10.114 + shutdownNow();
10.115 + } catch (SQLException ex) {
10.116 + Log.get().warning(ex.toString());
10.117 + }
10.118 + }
10.119 +
10.120 + /**
10.121 + * Starts this daemon.
10.122 + */
10.123 + @Override
10.124 + public void start()
10.125 + {
10.126 + synchronized (this) {
10.127 + this.isRunning = true;
10.128 + }
10.129 + super.start();
10.130 + }
10.131 }
11.1 --- a/src/org/sonews/daemon/ChannelLineBuffers.java Sun Aug 29 17:43:58 2010 +0200
11.2 +++ b/src/org/sonews/daemon/ChannelLineBuffers.java Sun Aug 29 18:17:37 2010 +0200
11.3 @@ -30,254 +30,223 @@
11.4 * @author Christian Lins
11.5 * @since sonews/0.5.0
11.6 */
11.7 -public class ChannelLineBuffers
11.8 +public class ChannelLineBuffers
11.9 {
11.10 -
11.11 - /**
11.12 - * Size of one small buffer;
11.13 - * per default this is 512 bytes to fit one standard line.
11.14 - */
11.15 - public static final int BUFFER_SIZE = 512;
11.16 -
11.17 - private static int maxCachedBuffers = 2048; // Cached buffers maximum
11.18 -
11.19 - private static final List<ByteBuffer> freeSmallBuffers
11.20 - = new ArrayList<ByteBuffer>(maxCachedBuffers);
11.21 -
11.22 - /**
11.23 - * Allocates a predefined number of direct ByteBuffers (allocated via
11.24 - * ByteBuffer.allocateDirect()). This method is Thread-safe, but should only
11.25 - * called at startup.
11.26 - */
11.27 - public static void allocateDirect()
11.28 - {
11.29 - synchronized(freeSmallBuffers)
11.30 - {
11.31 - for(int n = 0; n < maxCachedBuffers; n++)
11.32 - {
11.33 - ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
11.34 - freeSmallBuffers.add(buffer);
11.35 - }
11.36 - }
11.37 - }
11.38 -
11.39 - private ByteBuffer inputBuffer = newLineBuffer();
11.40 - private List<ByteBuffer> outputBuffers = new ArrayList<ByteBuffer>();
11.41 -
11.42 - /**
11.43 - * Add the given ByteBuffer to the list of buffers to be send to the client.
11.44 - * This method is Thread-safe.
11.45 - * @param buffer
11.46 - * @throws java.nio.channels.ClosedChannelException If the client channel was
11.47 - * already closed.
11.48 - */
11.49 - public void addOutputBuffer(ByteBuffer buffer)
11.50 - throws ClosedChannelException
11.51 - {
11.52 - if(outputBuffers == null)
11.53 - {
11.54 - throw new ClosedChannelException();
11.55 - }
11.56 -
11.57 - synchronized(outputBuffers)
11.58 - {
11.59 - outputBuffers.add(buffer);
11.60 - }
11.61 - }
11.62 -
11.63 - /**
11.64 - * Currently a channel has only one input buffer. This *may* be a bottleneck
11.65 - * and should investigated in the future.
11.66 - * @param channel
11.67 - * @return The input buffer associated with given channel.
11.68 - */
11.69 - public ByteBuffer getInputBuffer()
11.70 - {
11.71 - return inputBuffer;
11.72 - }
11.73 -
11.74 - /**
11.75 - * Returns the current output buffer for writing(!) to SocketChannel.
11.76 - * @param channel
11.77 - * @return The next input buffer that contains unprocessed data or null
11.78 - * if the connection was closed or there are no more unprocessed buffers.
11.79 - */
11.80 - public ByteBuffer getOutputBuffer()
11.81 - {
11.82 - synchronized(outputBuffers)
11.83 - {
11.84 - if(outputBuffers == null || outputBuffers.isEmpty())
11.85 - {
11.86 - return null;
11.87 - }
11.88 - else
11.89 - {
11.90 - ByteBuffer buffer = outputBuffers.get(0);
11.91 - if(buffer.remaining() == 0)
11.92 - {
11.93 - outputBuffers.remove(0);
11.94 - // Add old buffers to the list of free buffers
11.95 - recycleBuffer(buffer);
11.96 - buffer = getOutputBuffer();
11.97 - }
11.98 - return buffer;
11.99 - }
11.100 - }
11.101 - }
11.102
11.103 - /**
11.104 - * @return false if there are output buffers pending to be written to the
11.105 - * client.
11.106 - */
11.107 - boolean isOutputBufferEmpty()
11.108 - {
11.109 - synchronized(outputBuffers)
11.110 - {
11.111 - return outputBuffers.isEmpty();
11.112 - }
11.113 - }
11.114 -
11.115 - /**
11.116 - * Goes through the input buffer of the given channel and searches
11.117 - * for next line terminator. If a '\n' is found, the bytes up to the
11.118 - * line terminator are returned as array of bytes (the line terminator
11.119 - * is omitted). If none is found the method returns null.
11.120 - * @param channel
11.121 - * @return A ByteBuffer wrapping the line.
11.122 - */
11.123 - ByteBuffer nextInputLine()
11.124 - {
11.125 - if(inputBuffer == null)
11.126 - {
11.127 - return null;
11.128 - }
11.129 -
11.130 - synchronized(inputBuffer)
11.131 - {
11.132 - ByteBuffer buffer = inputBuffer;
11.133 + /**
11.134 + * Size of one small buffer;
11.135 + * per default this is 512 bytes to fit one standard line.
11.136 + */
11.137 + public static final int BUFFER_SIZE = 512;
11.138 + private static int maxCachedBuffers = 2048; // Cached buffers maximum
11.139 + private static final List<ByteBuffer> freeSmallBuffers = new ArrayList<ByteBuffer>(maxCachedBuffers);
11.140
11.141 - // Mark the current write position
11.142 - int mark = buffer.position();
11.143 + /**
11.144 + * Allocates a predefined number of direct ByteBuffers (allocated via
11.145 + * ByteBuffer.allocateDirect()). This method is Thread-safe, but should only
11.146 + * called at startup.
11.147 + */
11.148 + public static void allocateDirect()
11.149 + {
11.150 + synchronized (freeSmallBuffers) {
11.151 + for (int n = 0; n < maxCachedBuffers; n++) {
11.152 + ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
11.153 + freeSmallBuffers.add(buffer);
11.154 + }
11.155 + }
11.156 + }
11.157 + private ByteBuffer inputBuffer = newLineBuffer();
11.158 + private List<ByteBuffer> outputBuffers = new ArrayList<ByteBuffer>();
11.159
11.160 - // Set position to 0 and limit to current position
11.161 - buffer.flip();
11.162 + /**
11.163 + * Add the given ByteBuffer to the list of buffers to be send to the client.
11.164 + * This method is Thread-safe.
11.165 + * @param buffer
11.166 + * @throws java.nio.channels.ClosedChannelException If the client channel was
11.167 + * already closed.
11.168 + */
11.169 + public void addOutputBuffer(ByteBuffer buffer)
11.170 + throws ClosedChannelException
11.171 + {
11.172 + if (outputBuffers == null) {
11.173 + throw new ClosedChannelException();
11.174 + }
11.175
11.176 - ByteBuffer lineBuffer = newLineBuffer();
11.177 + synchronized (outputBuffers) {
11.178 + outputBuffers.add(buffer);
11.179 + }
11.180 + }
11.181
11.182 - while (buffer.position() < buffer.limit())
11.183 - {
11.184 - byte b = buffer.get();
11.185 - if (b == 10) // '\n'
11.186 - {
11.187 - // The bytes between the buffer's current position and its limit,
11.188 - // if any, are copied to the beginning of the buffer. That is, the
11.189 - // byte at index p = position() is copied to index zero, the byte at
11.190 - // index p + 1 is copied to index one, and so forth until the byte
11.191 - // at index limit() - 1 is copied to index n = limit() - 1 - p.
11.192 - // The buffer's position is then set to n+1 and its limit is set to
11.193 - // its capacity.
11.194 - buffer.compact();
11.195 + /**
11.196 + * Currently a channel has only one input buffer. This *may* be a bottleneck
11.197 + * and should investigated in the future.
11.198 + * @param channel
11.199 + * @return The input buffer associated with given channel.
11.200 + */
11.201 + public ByteBuffer getInputBuffer()
11.202 + {
11.203 + return inputBuffer;
11.204 + }
11.205
11.206 - lineBuffer.flip(); // limit to position, position to 0
11.207 - return lineBuffer;
11.208 - }
11.209 - else
11.210 - {
11.211 - lineBuffer.put(b);
11.212 - }
11.213 - }
11.214 + /**
11.215 + * Returns the current output buffer for writing(!) to SocketChannel.
11.216 + * @param channel
11.217 + * @return The next input buffer that contains unprocessed data or null
11.218 + * if the connection was closed or there are no more unprocessed buffers.
11.219 + */
11.220 + public ByteBuffer getOutputBuffer()
11.221 + {
11.222 + synchronized (outputBuffers) {
11.223 + if (outputBuffers == null || outputBuffers.isEmpty()) {
11.224 + return null;
11.225 + } else {
11.226 + ByteBuffer buffer = outputBuffers.get(0);
11.227 + if (buffer.remaining() == 0) {
11.228 + outputBuffers.remove(0);
11.229 + // Add old buffers to the list of free buffers
11.230 + recycleBuffer(buffer);
11.231 + buffer = getOutputBuffer();
11.232 + }
11.233 + return buffer;
11.234 + }
11.235 + }
11.236 + }
11.237
11.238 - buffer.limit(BUFFER_SIZE);
11.239 - buffer.position(mark);
11.240 + /**
11.241 + * @return false if there are output buffers pending to be written to the
11.242 + * client.
11.243 + */
11.244 + boolean isOutputBufferEmpty()
11.245 + {
11.246 + synchronized (outputBuffers) {
11.247 + return outputBuffers.isEmpty();
11.248 + }
11.249 + }
11.250
11.251 - if(buffer.hasRemaining())
11.252 - {
11.253 - return null;
11.254 - }
11.255 - else
11.256 - {
11.257 - // In the first 512 was no newline found, so the input is not standard
11.258 - // compliant. We return the current buffer as new line and add a space
11.259 - // to the beginning of the next line which corrects some overlong header
11.260 - // lines.
11.261 - inputBuffer = newLineBuffer();
11.262 - inputBuffer.put((byte)' ');
11.263 - buffer.flip();
11.264 - return buffer;
11.265 - }
11.266 - }
11.267 - }
11.268 -
11.269 - /**
11.270 - * Returns a at least 512 bytes long ByteBuffer ready for usage.
11.271 - * The method first try to reuse an already allocated (cached) buffer but
11.272 - * if that fails returns a newly allocated direct buffer.
11.273 - * Use recycleBuffer() method when you do not longer use the allocated buffer.
11.274 - */
11.275 - static ByteBuffer newLineBuffer()
11.276 - {
11.277 - ByteBuffer buf = null;
11.278 - synchronized(freeSmallBuffers)
11.279 - {
11.280 - if(!freeSmallBuffers.isEmpty())
11.281 - {
11.282 - buf = freeSmallBuffers.remove(0);
11.283 - }
11.284 - }
11.285 -
11.286 - if(buf == null)
11.287 - {
11.288 - // Allocate a non-direct buffer
11.289 - buf = ByteBuffer.allocate(BUFFER_SIZE);
11.290 - }
11.291 -
11.292 - assert buf.position() == 0;
11.293 - assert buf.limit() >= BUFFER_SIZE;
11.294 -
11.295 - return buf;
11.296 - }
11.297 -
11.298 - /**
11.299 - * Adds the given buffer to the list of free buffers if it is a valuable
11.300 - * direct allocated buffer.
11.301 - * @param buffer
11.302 - */
11.303 - public static void recycleBuffer(ByteBuffer buffer)
11.304 - {
11.305 - assert buffer != null;
11.306 + /**
11.307 + * Goes through the input buffer of the given channel and searches
11.308 + * for next line terminator. If a '\n' is found, the bytes up to the
11.309 + * line terminator are returned as array of bytes (the line terminator
11.310 + * is omitted). If none is found the method returns null.
11.311 + * @param channel
11.312 + * @return A ByteBuffer wrapping the line.
11.313 + */
11.314 + ByteBuffer nextInputLine()
11.315 + {
11.316 + if (inputBuffer == null) {
11.317 + return null;
11.318 + }
11.319
11.320 - if(buffer.isDirect())
11.321 - {
11.322 - assert buffer.capacity() >= BUFFER_SIZE;
11.323 -
11.324 - // Add old buffers to the list of free buffers
11.325 - synchronized(freeSmallBuffers)
11.326 - {
11.327 - buffer.clear(); // Set position to 0 and limit to capacity
11.328 - freeSmallBuffers.add(buffer);
11.329 - }
11.330 - } // if(buffer.isDirect())
11.331 - }
11.332 -
11.333 - /**
11.334 - * Recycles all buffers of this ChannelLineBuffers object.
11.335 - */
11.336 - public void recycleBuffers()
11.337 - {
11.338 - synchronized(inputBuffer)
11.339 - {
11.340 - recycleBuffer(inputBuffer);
11.341 - this.inputBuffer = null;
11.342 - }
11.343 -
11.344 - synchronized(outputBuffers)
11.345 - {
11.346 - for(ByteBuffer buf : outputBuffers)
11.347 - {
11.348 - recycleBuffer(buf);
11.349 - }
11.350 - outputBuffers = null;
11.351 - }
11.352 - }
11.353 -
11.354 + synchronized (inputBuffer) {
11.355 + ByteBuffer buffer = inputBuffer;
11.356 +
11.357 + // Mark the current write position
11.358 + int mark = buffer.position();
11.359 +
11.360 + // Set position to 0 and limit to current position
11.361 + buffer.flip();
11.362 +
11.363 + ByteBuffer lineBuffer = newLineBuffer();
11.364 +
11.365 + while (buffer.position() < buffer.limit()) {
11.366 + byte b = buffer.get();
11.367 + if (b == 10) // '\n'
11.368 + {
11.369 + // The bytes between the buffer's current position and its limit,
11.370 + // if any, are copied to the beginning of the buffer. That is, the
11.371 + // byte at index p = position() is copied to index zero, the byte at
11.372 + // index p + 1 is copied to index one, and so forth until the byte
11.373 + // at index limit() - 1 is copied to index n = limit() - 1 - p.
11.374 + // The buffer's position is then set to n+1 and its limit is set to
11.375 + // its capacity.
11.376 + buffer.compact();
11.377 +
11.378 + lineBuffer.flip(); // limit to position, position to 0
11.379 + return lineBuffer;
11.380 + } else {
11.381 + lineBuffer.put(b);
11.382 + }
11.383 + }
11.384 +
11.385 + buffer.limit(BUFFER_SIZE);
11.386 + buffer.position(mark);
11.387 +
11.388 + if (buffer.hasRemaining()) {
11.389 + return null;
11.390 + } else {
11.391 + // In the first 512 was no newline found, so the input is not standard
11.392 + // compliant. We return the current buffer as new line and add a space
11.393 + // to the beginning of the next line which corrects some overlong header
11.394 + // lines.
11.395 + inputBuffer = newLineBuffer();
11.396 + inputBuffer.put((byte) ' ');
11.397 + buffer.flip();
11.398 + return buffer;
11.399 + }
11.400 + }
11.401 + }
11.402 +
11.403 + /**
11.404 + * Returns a at least 512 bytes long ByteBuffer ready for usage.
11.405 + * The method first try to reuse an already allocated (cached) buffer but
11.406 + * if that fails returns a newly allocated direct buffer.
11.407 + * Use recycleBuffer() method when you do not longer use the allocated buffer.
11.408 + */
11.409 + static ByteBuffer newLineBuffer()
11.410 + {
11.411 + ByteBuffer buf = null;
11.412 + synchronized (freeSmallBuffers) {
11.413 + if (!freeSmallBuffers.isEmpty()) {
11.414 + buf = freeSmallBuffers.remove(0);
11.415 + }
11.416 + }
11.417 +
11.418 + if (buf == null) {
11.419 + // Allocate a non-direct buffer
11.420 + buf = ByteBuffer.allocate(BUFFER_SIZE);
11.421 + }
11.422 +
11.423 + assert buf.position() == 0;
11.424 + assert buf.limit() >= BUFFER_SIZE;
11.425 +
11.426 + return buf;
11.427 + }
11.428 +
11.429 + /**
11.430 + * Adds the given buffer to the list of free buffers if it is a valuable
11.431 + * direct allocated buffer.
11.432 + * @param buffer
11.433 + */
11.434 + public static void recycleBuffer(ByteBuffer buffer)
11.435 + {
11.436 + assert buffer != null;
11.437 +
11.438 + if (buffer.isDirect()) {
11.439 + assert buffer.capacity() >= BUFFER_SIZE;
11.440 +
11.441 + // Add old buffers to the list of free buffers
11.442 + synchronized (freeSmallBuffers) {
11.443 + buffer.clear(); // Set position to 0 and limit to capacity
11.444 + freeSmallBuffers.add(buffer);
11.445 + }
11.446 + } // if(buffer.isDirect())
11.447 + }
11.448 +
11.449 + /**
11.450 + * Recycles all buffers of this ChannelLineBuffers object.
11.451 + */
11.452 + public void recycleBuffers()
11.453 + {
11.454 + synchronized (inputBuffer) {
11.455 + recycleBuffer(inputBuffer);
11.456 + this.inputBuffer = null;
11.457 + }
11.458 +
11.459 + synchronized (outputBuffers) {
11.460 + for (ByteBuffer buf : outputBuffers) {
11.461 + recycleBuffer(buf);
11.462 + }
11.463 + outputBuffers = null;
11.464 + }
11.465 + }
11.466 }
12.1 --- a/src/org/sonews/daemon/ChannelReader.java Sun Aug 29 17:43:58 2010 +0200
12.2 +++ b/src/org/sonews/daemon/ChannelReader.java Sun Aug 29 18:17:37 2010 +0200
12.3 @@ -37,166 +37,141 @@
12.4 class ChannelReader extends AbstractDaemon
12.5 {
12.6
12.7 - private static ChannelReader instance = new ChannelReader();
12.8 + private static ChannelReader instance = new ChannelReader();
12.9
12.10 - /**
12.11 - * @return Active ChannelReader instance.
12.12 - */
12.13 - public static ChannelReader getInstance()
12.14 - {
12.15 - return instance;
12.16 - }
12.17 -
12.18 - private Selector selector = null;
12.19 -
12.20 - protected ChannelReader()
12.21 - {
12.22 - }
12.23 -
12.24 - /**
12.25 - * Sets the selector which is used by this reader to determine the channel
12.26 - * to read from.
12.27 - * @param selector
12.28 - */
12.29 - public void setSelector(final Selector selector)
12.30 - {
12.31 - this.selector = selector;
12.32 - }
12.33 -
12.34 - /**
12.35 - * Run loop. Blocks until some data is available in a channel.
12.36 - */
12.37 - @Override
12.38 - public void run()
12.39 - {
12.40 - assert selector != null;
12.41 + /**
12.42 + * @return Active ChannelReader instance.
12.43 + */
12.44 + public static ChannelReader getInstance()
12.45 + {
12.46 + return instance;
12.47 + }
12.48 + private Selector selector = null;
12.49
12.50 - while(isRunning())
12.51 - {
12.52 - try
12.53 - {
12.54 - // select() blocks until some SelectableChannels are ready for
12.55 - // processing. There is no need to lock the selector as we have only
12.56 - // one thread per selector.
12.57 - selector.select();
12.58 + protected ChannelReader()
12.59 + {
12.60 + }
12.61
12.62 - // Get list of selection keys with pending events.
12.63 - // Note: the selected key set is not thread-safe
12.64 - SocketChannel channel = null;
12.65 - NNTPConnection conn = null;
12.66 - final Set<SelectionKey> selKeys = selector.selectedKeys();
12.67 - SelectionKey selKey = null;
12.68 + /**
12.69 + * Sets the selector which is used by this reader to determine the channel
12.70 + * to read from.
12.71 + * @param selector
12.72 + */
12.73 + public void setSelector(final Selector selector)
12.74 + {
12.75 + this.selector = selector;
12.76 + }
12.77
12.78 - synchronized (selKeys)
12.79 - {
12.80 - Iterator it = selKeys.iterator();
12.81 + /**
12.82 + * Run loop. Blocks until some data is available in a channel.
12.83 + */
12.84 + @Override
12.85 + public void run()
12.86 + {
12.87 + assert selector != null;
12.88
12.89 - // Process the first pending event
12.90 - while (it.hasNext())
12.91 - {
12.92 - selKey = (SelectionKey) it.next();
12.93 - channel = (SocketChannel) selKey.channel();
12.94 - conn = Connections.getInstance().get(channel);
12.95 + while (isRunning()) {
12.96 + try {
12.97 + // select() blocks until some SelectableChannels are ready for
12.98 + // processing. There is no need to lock the selector as we have only
12.99 + // one thread per selector.
12.100 + selector.select();
12.101
12.102 - // Because we cannot lock the selKey as that would cause a deadlock
12.103 - // we lock the connection. To preserve the order of the received
12.104 - // byte blocks a selection key for a connection that has pending
12.105 - // read events is skipped.
12.106 - if (conn == null || conn.tryReadLock())
12.107 - {
12.108 - // Remove from set to indicate that it's being processed
12.109 - it.remove();
12.110 - if (conn != null)
12.111 - {
12.112 - break; // End while loop
12.113 - }
12.114 - }
12.115 - else
12.116 - {
12.117 - selKey = null;
12.118 - channel = null;
12.119 - conn = null;
12.120 - }
12.121 - }
12.122 - }
12.123 + // Get list of selection keys with pending events.
12.124 + // Note: the selected key set is not thread-safe
12.125 + SocketChannel channel = null;
12.126 + NNTPConnection conn = null;
12.127 + final Set<SelectionKey> selKeys = selector.selectedKeys();
12.128 + SelectionKey selKey = null;
12.129
12.130 - // Do not lock the selKeys while processing because this causes
12.131 - // a deadlock in sun.nio.ch.SelectorImpl.lockAndDoSelect()
12.132 - if (selKey != null && channel != null && conn != null)
12.133 - {
12.134 - processSelectionKey(conn, channel, selKey);
12.135 - conn.unlockReadLock();
12.136 - }
12.137 + synchronized (selKeys) {
12.138 + Iterator it = selKeys.iterator();
12.139
12.140 - }
12.141 - catch(CancelledKeyException ex)
12.142 - {
12.143 - Log.get().warning("ChannelReader.run(): " + ex);
12.144 - Log.get().log(Level.INFO, "", ex);
12.145 - }
12.146 - catch(Exception ex)
12.147 - {
12.148 - ex.printStackTrace();
12.149 - }
12.150 -
12.151 - // Eventually wait for a register operation
12.152 - synchronized (NNTPDaemon.RegisterGate)
12.153 - {
12.154 - // Do nothing; FindBugs may warn about an empty synchronized
12.155 - // statement, but we cannot use a wait()/notify() mechanism here.
12.156 - // If we used something like RegisterGate.wait() we block here
12.157 - // until the NNTPDaemon calls notify(). But the daemon only
12.158 - // calls notify() if itself is NOT blocked in the listening socket.
12.159 - }
12.160 - } // while(isRunning())
12.161 - }
12.162 -
12.163 - private void processSelectionKey(final NNTPConnection connection,
12.164 - final SocketChannel socketChannel, final SelectionKey selKey)
12.165 - throws InterruptedException, IOException
12.166 - {
12.167 - assert selKey != null;
12.168 - assert selKey.isReadable();
12.169 -
12.170 - // Some bytes are available for reading
12.171 - if(selKey.isValid())
12.172 - {
12.173 - // Lock the channel
12.174 - //synchronized(socketChannel)
12.175 - {
12.176 - // Read the data into the appropriate buffer
12.177 - ByteBuffer buf = connection.getInputBuffer();
12.178 - int read = -1;
12.179 - try
12.180 - {
12.181 - read = socketChannel.read(buf);
12.182 - }
12.183 - catch(IOException ex)
12.184 - {
12.185 - // The connection was probably closed by the remote host
12.186 - // in a non-clean fashion
12.187 - Log.get().info("ChannelReader.processSelectionKey(): " + ex);
12.188 - }
12.189 - catch(Exception ex)
12.190 - {
12.191 - Log.get().warning("ChannelReader.processSelectionKey(): " + ex);
12.192 - }
12.193 -
12.194 - if(read == -1) // End of stream
12.195 - {
12.196 - selKey.cancel();
12.197 - }
12.198 - else if(read > 0) // If some data was read
12.199 - {
12.200 - ConnectionWorker.addChannel(socketChannel);
12.201 - }
12.202 - }
12.203 - }
12.204 - else
12.205 - {
12.206 - // Should not happen
12.207 - Log.get().severe("Should not happen: " + selKey.toString());
12.208 - }
12.209 - }
12.210 -
12.211 + // Process the first pending event
12.212 + while (it.hasNext()) {
12.213 + selKey = (SelectionKey) it.next();
12.214 + channel = (SocketChannel) selKey.channel();
12.215 + conn = Connections.getInstance().get(channel);
12.216 +
12.217 + // Because we cannot lock the selKey as that would cause a deadlock
12.218 + // we lock the connection. To preserve the order of the received
12.219 + // byte blocks a selection key for a connection that has pending
12.220 + // read events is skipped.
12.221 + if (conn == null || conn.tryReadLock()) {
12.222 + // Remove from set to indicate that it's being processed
12.223 + it.remove();
12.224 + if (conn != null) {
12.225 + break; // End while loop
12.226 + }
12.227 + } else {
12.228 + selKey = null;
12.229 + channel = null;
12.230 + conn = null;
12.231 + }
12.232 + }
12.233 + }
12.234 +
12.235 + // Do not lock the selKeys while processing because this causes
12.236 + // a deadlock in sun.nio.ch.SelectorImpl.lockAndDoSelect()
12.237 + if (selKey != null && channel != null && conn != null) {
12.238 + processSelectionKey(conn, channel, selKey);
12.239 + conn.unlockReadLock();
12.240 + }
12.241 +
12.242 + } catch (CancelledKeyException ex) {
12.243 + Log.get().warning("ChannelReader.run(): " + ex);
12.244 + Log.get().log(Level.INFO, "", ex);
12.245 + } catch (Exception ex) {
12.246 + ex.printStackTrace();
12.247 + }
12.248 +
12.249 + // Eventually wait for a register operation
12.250 + synchronized (NNTPDaemon.RegisterGate) {
12.251 + // Do nothing; FindBugs may warn about an empty synchronized
12.252 + // statement, but we cannot use a wait()/notify() mechanism here.
12.253 + // If we used something like RegisterGate.wait() we block here
12.254 + // until the NNTPDaemon calls notify(). But the daemon only
12.255 + // calls notify() if itself is NOT blocked in the listening socket.
12.256 + }
12.257 + } // while(isRunning())
12.258 + }
12.259 +
12.260 + private void processSelectionKey(final NNTPConnection connection,
12.261 + final SocketChannel socketChannel, final SelectionKey selKey)
12.262 + throws InterruptedException, IOException
12.263 + {
12.264 + assert selKey != null;
12.265 + assert selKey.isReadable();
12.266 +
12.267 + // Some bytes are available for reading
12.268 + if (selKey.isValid()) {
12.269 + // Lock the channel
12.270 + //synchronized(socketChannel)
12.271 + {
12.272 + // Read the data into the appropriate buffer
12.273 + ByteBuffer buf = connection.getInputBuffer();
12.274 + int read = -1;
12.275 + try {
12.276 + read = socketChannel.read(buf);
12.277 + } catch (IOException ex) {
12.278 + // The connection was probably closed by the remote host
12.279 + // in a non-clean fashion
12.280 + Log.get().info("ChannelReader.processSelectionKey(): " + ex);
12.281 + } catch (Exception ex) {
12.282 + Log.get().warning("ChannelReader.processSelectionKey(): " + ex);
12.283 + }
12.284 +
12.285 + if (read == -1) // End of stream
12.286 + {
12.287 + selKey.cancel();
12.288 + } else if (read > 0) // If some data was read
12.289 + {
12.290 + ConnectionWorker.addChannel(socketChannel);
12.291 + }
12.292 + }
12.293 + } else {
12.294 + // Should not happen
12.295 + Log.get().severe("Should not happen: " + selKey.toString());
12.296 + }
12.297 + }
12.298 }
13.1 --- a/src/org/sonews/daemon/ChannelWriter.java Sun Aug 29 17:43:58 2010 +0200
13.2 +++ b/src/org/sonews/daemon/ChannelWriter.java Sun Aug 29 18:17:37 2010 +0200
13.3 @@ -35,176 +35,151 @@
13.4 class ChannelWriter extends AbstractDaemon
13.5 {
13.6
13.7 - private static ChannelWriter instance = new ChannelWriter();
13.8 + private static ChannelWriter instance = new ChannelWriter();
13.9
13.10 - /**
13.11 - * @return Returns the active ChannelWriter instance.
13.12 - */
13.13 - public static ChannelWriter getInstance()
13.14 - {
13.15 - return instance;
13.16 - }
13.17 -
13.18 - private Selector selector = null;
13.19 -
13.20 - protected ChannelWriter()
13.21 - {
13.22 - }
13.23 -
13.24 - /**
13.25 - * @return Selector associated with this instance.
13.26 - */
13.27 - public Selector getSelector()
13.28 - {
13.29 - return this.selector;
13.30 - }
13.31 -
13.32 - /**
13.33 - * Sets the selector that is used by this ChannelWriter.
13.34 - * @param selector
13.35 - */
13.36 - public void setSelector(final Selector selector)
13.37 - {
13.38 - this.selector = selector;
13.39 - }
13.40 -
13.41 - /**
13.42 - * Run loop.
13.43 - */
13.44 - @Override
13.45 - public void run()
13.46 - {
13.47 - assert selector != null;
13.48 + /**
13.49 + * @return Returns the active ChannelWriter instance.
13.50 + */
13.51 + public static ChannelWriter getInstance()
13.52 + {
13.53 + return instance;
13.54 + }
13.55 + private Selector selector = null;
13.56
13.57 - while(isRunning())
13.58 - {
13.59 - try
13.60 - {
13.61 - SelectionKey selKey = null;
13.62 - SocketChannel socketChannel = null;
13.63 - NNTPConnection connection = null;
13.64 + protected ChannelWriter()
13.65 + {
13.66 + }
13.67
13.68 - // select() blocks until some SelectableChannels are ready for
13.69 - // processing. There is no need to synchronize the selector as we
13.70 - // have only one thread per selector.
13.71 - selector.select(); // The return value of select can be ignored
13.72 + /**
13.73 + * @return Selector associated with this instance.
13.74 + */
13.75 + public Selector getSelector()
13.76 + {
13.77 + return this.selector;
13.78 + }
13.79
13.80 - // Get list of selection keys with pending OP_WRITE events.
13.81 - // The keySET is not thread-safe whereas the keys itself are.
13.82 - Iterator it = selector.selectedKeys().iterator();
13.83 + /**
13.84 + * Sets the selector that is used by this ChannelWriter.
13.85 + * @param selector
13.86 + */
13.87 + public void setSelector(final Selector selector)
13.88 + {
13.89 + this.selector = selector;
13.90 + }
13.91
13.92 - while (it.hasNext())
13.93 - {
13.94 - // We remove the first event from the set and store it for
13.95 - // later processing.
13.96 - selKey = (SelectionKey) it.next();
13.97 - socketChannel = (SocketChannel) selKey.channel();
13.98 - connection = Connections.getInstance().get(socketChannel);
13.99 + /**
13.100 + * Run loop.
13.101 + */
13.102 + @Override
13.103 + public void run()
13.104 + {
13.105 + assert selector != null;
13.106
13.107 - it.remove();
13.108 - if (connection != null)
13.109 - {
13.110 - break;
13.111 - }
13.112 - else
13.113 - {
13.114 - selKey = null;
13.115 - }
13.116 - }
13.117 -
13.118 - if (selKey != null)
13.119 - {
13.120 - try
13.121 - {
13.122 - // Process the selected key.
13.123 - // As there is only one OP_WRITE key for a given channel, we need
13.124 - // not to synchronize this processing to retain the order.
13.125 - processSelectionKey(connection, socketChannel, selKey);
13.126 - }
13.127 - catch (IOException ex)
13.128 - {
13.129 - Log.get().warning("Error writing to channel: " + ex);
13.130 + while (isRunning()) {
13.131 + try {
13.132 + SelectionKey selKey = null;
13.133 + SocketChannel socketChannel = null;
13.134 + NNTPConnection connection = null;
13.135
13.136 - // Cancel write events for this channel
13.137 - selKey.cancel();
13.138 - connection.shutdownInput();
13.139 - connection.shutdownOutput();
13.140 - }
13.141 - }
13.142 -
13.143 - // Eventually wait for a register operation
13.144 - synchronized(NNTPDaemon.RegisterGate) { /* do nothing */ }
13.145 - }
13.146 - catch(CancelledKeyException ex)
13.147 - {
13.148 - Log.get().info("ChannelWriter.run(): " + ex);
13.149 - }
13.150 - catch(Exception ex)
13.151 - {
13.152 - ex.printStackTrace();
13.153 - }
13.154 - } // while(isRunning())
13.155 - }
13.156 -
13.157 - private void processSelectionKey(final NNTPConnection connection,
13.158 - final SocketChannel socketChannel, final SelectionKey selKey)
13.159 - throws InterruptedException, IOException
13.160 - {
13.161 - assert connection != null;
13.162 - assert socketChannel != null;
13.163 - assert selKey != null;
13.164 - assert selKey.isWritable();
13.165 + // select() blocks until some SelectableChannels are ready for
13.166 + // processing. There is no need to synchronize the selector as we
13.167 + // have only one thread per selector.
13.168 + selector.select(); // The return value of select can be ignored
13.169
13.170 - // SocketChannel is ready for writing
13.171 - if(selKey.isValid())
13.172 - {
13.173 - // Lock the socket channel
13.174 - synchronized(socketChannel)
13.175 - {
13.176 - // Get next output buffer
13.177 - ByteBuffer buf = connection.getOutputBuffer();
13.178 - if(buf == null)
13.179 - {
13.180 - // Currently we have nothing to write, so we stop the writeable
13.181 - // events until we have something to write to the socket channel
13.182 - //selKey.cancel();
13.183 - selKey.interestOps(0);
13.184 - // Update activity timestamp to prevent too early disconnects
13.185 - // on slow client connections
13.186 - connection.setLastActivity(System.currentTimeMillis());
13.187 - return;
13.188 - }
13.189 -
13.190 - while(buf != null) // There is data to be send
13.191 - {
13.192 - // Write buffer to socket channel; this method does not block
13.193 - if(socketChannel.write(buf) <= 0)
13.194 - {
13.195 - // Perhaps there is data to be written, but the SocketChannel's
13.196 - // buffer is full, so we stop writing to until the next event.
13.197 - break;
13.198 - }
13.199 - else
13.200 - {
13.201 - // Retrieve next buffer if available; method may return the same
13.202 - // buffer instance if it still have some bytes remaining
13.203 - buf = connection.getOutputBuffer();
13.204 - }
13.205 - }
13.206 - }
13.207 - }
13.208 - else
13.209 - {
13.210 - Log.get().warning("Invalid OP_WRITE key: " + selKey);
13.211 + // Get list of selection keys with pending OP_WRITE events.
13.212 + // The keySET is not thread-safe whereas the keys itself are.
13.213 + Iterator it = selector.selectedKeys().iterator();
13.214
13.215 - if(socketChannel.socket().isClosed())
13.216 - {
13.217 - connection.shutdownInput();
13.218 - connection.shutdownOutput();
13.219 - socketChannel.close();
13.220 - Log.get().info("Connection closed.");
13.221 - }
13.222 - }
13.223 - }
13.224 -
13.225 + while (it.hasNext()) {
13.226 + // We remove the first event from the set and store it for
13.227 + // later processing.
13.228 + selKey = (SelectionKey) it.next();
13.229 + socketChannel = (SocketChannel) selKey.channel();
13.230 + connection = Connections.getInstance().get(socketChannel);
13.231 +
13.232 + it.remove();
13.233 + if (connection != null) {
13.234 + break;
13.235 + } else {
13.236 + selKey = null;
13.237 + }
13.238 + }
13.239 +
13.240 + if (selKey != null) {
13.241 + try {
13.242 + // Process the selected key.
13.243 + // As there is only one OP_WRITE key for a given channel, we need
13.244 + // not to synchronize this processing to retain the order.
13.245 + processSelectionKey(connection, socketChannel, selKey);
13.246 + } catch (IOException ex) {
13.247 + Log.get().warning("Error writing to channel: " + ex);
13.248 +
13.249 + // Cancel write events for this channel
13.250 + selKey.cancel();
13.251 + connection.shutdownInput();
13.252 + connection.shutdownOutput();
13.253 + }
13.254 + }
13.255 +
13.256 + // Eventually wait for a register operation
13.257 + synchronized (NNTPDaemon.RegisterGate) { /* do nothing */ }
13.258 + } catch (CancelledKeyException ex) {
13.259 + Log.get().info("ChannelWriter.run(): " + ex);
13.260 + } catch (Exception ex) {
13.261 + ex.printStackTrace();
13.262 + }
13.263 + } // while(isRunning())
13.264 + }
13.265 +
13.266 + private void processSelectionKey(final NNTPConnection connection,
13.267 + final SocketChannel socketChannel, final SelectionKey selKey)
13.268 + throws InterruptedException, IOException
13.269 + {
13.270 + assert connection != null;
13.271 + assert socketChannel != null;
13.272 + assert selKey != null;
13.273 + assert selKey.isWritable();
13.274 +
13.275 + // SocketChannel is ready for writing
13.276 + if (selKey.isValid()) {
13.277 + // Lock the socket channel
13.278 + synchronized (socketChannel) {
13.279 + // Get next output buffer
13.280 + ByteBuffer buf = connection.getOutputBuffer();
13.281 + if (buf == null) {
13.282 + // Currently we have nothing to write, so we stop the writeable
13.283 + // events until we have something to write to the socket channel
13.284 + //selKey.cancel();
13.285 + selKey.interestOps(0);
13.286 + // Update activity timestamp to prevent too early disconnects
13.287 + // on slow client connections
13.288 + connection.setLastActivity(System.currentTimeMillis());
13.289 + return;
13.290 + }
13.291 +
13.292 + while (buf != null) // There is data to be send
13.293 + {
13.294 + // Write buffer to socket channel; this method does not block
13.295 + if (socketChannel.write(buf) <= 0) {
13.296 + // Perhaps there is data to be written, but the SocketChannel's
13.297 + // buffer is full, so we stop writing to until the next event.
13.298 + break;
13.299 + } else {
13.300 + // Retrieve next buffer if available; method may return the same
13.301 + // buffer instance if it still have some bytes remaining
13.302 + buf = connection.getOutputBuffer();
13.303 + }
13.304 + }
13.305 + }
13.306 + } else {
13.307 + Log.get().warning("Invalid OP_WRITE key: " + selKey);
13.308 +
13.309 + if (socketChannel.socket().isClosed()) {
13.310 + connection.shutdownInput();
13.311 + connection.shutdownOutput();
13.312 + socketChannel.close();
13.313 + Log.get().info("Connection closed.");
13.314 + }
13.315 + }
13.316 + }
13.317 }
14.1 --- a/src/org/sonews/daemon/CommandSelector.java Sun Aug 29 17:43:58 2010 +0200
14.2 +++ b/src/org/sonews/daemon/CommandSelector.java Sun Aug 29 18:17:37 2010 +0200
14.3 @@ -35,107 +35,84 @@
14.4 public class CommandSelector
14.5 {
14.6
14.7 - private static Map<Thread, CommandSelector> instances
14.8 - = new ConcurrentHashMap<Thread, CommandSelector>();
14.9 - private static Map<String, Class<?>> commandClassesMapping
14.10 - = new ConcurrentHashMap<String, Class<?>>();
14.11 + private static Map<Thread, CommandSelector> instances = new ConcurrentHashMap<Thread, CommandSelector>();
14.12 + private static Map<String, Class<?>> commandClassesMapping = new ConcurrentHashMap<String, Class<?>>();
14.13
14.14 - static
14.15 - {
14.16 - String[] classes = Resource.getAsString("helpers/commands.list", true).split("\n");
14.17 - for(String className : classes)
14.18 - {
14.19 - if(className.charAt(0) == '#')
14.20 - {
14.21 - // Skip comments
14.22 - continue;
14.23 - }
14.24 + static {
14.25 + String[] classes = Resource.getAsString("helpers/commands.list", true).split("\n");
14.26 + for (String className : classes) {
14.27 + if (className.charAt(0) == '#') {
14.28 + // Skip comments
14.29 + continue;
14.30 + }
14.31
14.32 - try
14.33 - {
14.34 - addCommandHandler(className);
14.35 - }
14.36 - catch(ClassNotFoundException ex)
14.37 - {
14.38 - Log.get().warning("Could not load command class: " + ex);
14.39 - }
14.40 - catch(InstantiationException ex)
14.41 - {
14.42 - Log.get().severe("Could not instantiate command class: " + ex);
14.43 - }
14.44 - catch(IllegalAccessException ex)
14.45 - {
14.46 - Log.get().severe("Could not access command class: " + ex);
14.47 - }
14.48 - }
14.49 - }
14.50 + try {
14.51 + addCommandHandler(className);
14.52 + } catch (ClassNotFoundException ex) {
14.53 + Log.get().warning("Could not load command class: " + ex);
14.54 + } catch (InstantiationException ex) {
14.55 + Log.get().severe("Could not instantiate command class: " + ex);
14.56 + } catch (IllegalAccessException ex) {
14.57 + Log.get().severe("Could not access command class: " + ex);
14.58 + }
14.59 + }
14.60 + }
14.61
14.62 - public static void addCommandHandler(String className)
14.63 - throws ClassNotFoundException, InstantiationException, IllegalAccessException
14.64 - {
14.65 - Class<?> clazz = Class.forName(className);
14.66 - Command cmd = (Command)clazz.newInstance();
14.67 - String[] cmdStrs = cmd.getSupportedCommandStrings();
14.68 - for (String cmdStr : cmdStrs)
14.69 - {
14.70 - commandClassesMapping.put(cmdStr, clazz);
14.71 - }
14.72 - }
14.73 + public static void addCommandHandler(String className)
14.74 + throws ClassNotFoundException, InstantiationException,
14.75 + IllegalAccessException
14.76 + {
14.77 + Class<?> clazz = Class.forName(className);
14.78 + Command cmd = (Command) clazz.newInstance();
14.79 + String[] cmdStrs = cmd.getSupportedCommandStrings();
14.80 + for (String cmdStr : cmdStrs) {
14.81 + commandClassesMapping.put(cmdStr, clazz);
14.82 + }
14.83 + }
14.84
14.85 - public static Set<String> getCommandNames()
14.86 - {
14.87 - return commandClassesMapping.keySet();
14.88 - }
14.89 + public static Set<String> getCommandNames()
14.90 + {
14.91 + return commandClassesMapping.keySet();
14.92 + }
14.93
14.94 - public static CommandSelector getInstance()
14.95 - {
14.96 - CommandSelector csel = instances.get(Thread.currentThread());
14.97 - if(csel == null)
14.98 - {
14.99 - csel = new CommandSelector();
14.100 - instances.put(Thread.currentThread(), csel);
14.101 - }
14.102 - return csel;
14.103 - }
14.104 + public static CommandSelector getInstance()
14.105 + {
14.106 + CommandSelector csel = instances.get(Thread.currentThread());
14.107 + if (csel == null) {
14.108 + csel = new CommandSelector();
14.109 + instances.put(Thread.currentThread(), csel);
14.110 + }
14.111 + return csel;
14.112 + }
14.113 + private Map<String, Command> commandMapping = new HashMap<String, Command>();
14.114 + private Command unsupportedCmd = new UnsupportedCommand();
14.115
14.116 - private Map<String, Command> commandMapping = new HashMap<String, Command>();
14.117 - private Command unsupportedCmd = new UnsupportedCommand();
14.118 + private CommandSelector()
14.119 + {
14.120 + }
14.121
14.122 - private CommandSelector()
14.123 - {}
14.124 + public Command get(String commandName)
14.125 + {
14.126 + try {
14.127 + commandName = commandName.toUpperCase();
14.128 + Command cmd = this.commandMapping.get(commandName);
14.129
14.130 - public Command get(String commandName)
14.131 - {
14.132 - try
14.133 - {
14.134 - commandName = commandName.toUpperCase();
14.135 - Command cmd = this.commandMapping.get(commandName);
14.136 + if (cmd == null) {
14.137 + Class<?> clazz = commandClassesMapping.get(commandName);
14.138 + if (clazz == null) {
14.139 + cmd = this.unsupportedCmd;
14.140 + } else {
14.141 + cmd = (Command) clazz.newInstance();
14.142 + this.commandMapping.put(commandName, cmd);
14.143 + }
14.144 + } else if (cmd.isStateful()) {
14.145 + cmd = cmd.getClass().newInstance();
14.146 + }
14.147
14.148 - if(cmd == null)
14.149 - {
14.150 - Class<?> clazz = commandClassesMapping.get(commandName);
14.151 - if(clazz == null)
14.152 - {
14.153 - cmd = this.unsupportedCmd;
14.154 - }
14.155 - else
14.156 - {
14.157 - cmd = (Command)clazz.newInstance();
14.158 - this.commandMapping.put(commandName, cmd);
14.159 - }
14.160 - }
14.161 - else if(cmd.isStateful())
14.162 - {
14.163 - cmd = cmd.getClass().newInstance();
14.164 - }
14.165 -
14.166 - return cmd;
14.167 - }
14.168 - catch(Exception ex)
14.169 - {
14.170 - ex.printStackTrace();
14.171 - return this.unsupportedCmd;
14.172 - }
14.173 - }
14.174 -
14.175 + return cmd;
14.176 + } catch (Exception ex) {
14.177 + ex.printStackTrace();
14.178 + return this.unsupportedCmd;
14.179 + }
14.180 + }
14.181 }
15.1 --- a/src/org/sonews/daemon/ConnectionWorker.java Sun Aug 29 17:43:58 2010 +0200
15.2 +++ b/src/org/sonews/daemon/ConnectionWorker.java Sun Aug 29 18:17:37 2010 +0200
15.3 @@ -31,72 +31,60 @@
15.4 class ConnectionWorker extends AbstractDaemon
15.5 {
15.6
15.7 - // 256 pending events should be enough
15.8 - private static ArrayBlockingQueue<SocketChannel> pendingChannels
15.9 - = new ArrayBlockingQueue<SocketChannel>(256, true);
15.10 -
15.11 - /**
15.12 - * Registers the given channel for further event processing.
15.13 - * @param channel
15.14 - */
15.15 - public static void addChannel(SocketChannel channel)
15.16 - throws InterruptedException
15.17 - {
15.18 - pendingChannels.put(channel);
15.19 - }
15.20 -
15.21 - /**
15.22 - * Processing loop.
15.23 - */
15.24 - @Override
15.25 - public void run()
15.26 - {
15.27 - while(isRunning())
15.28 - {
15.29 - try
15.30 - {
15.31 - // Retrieve and remove if available, otherwise wait.
15.32 - SocketChannel channel = pendingChannels.take();
15.33 + // 256 pending events should be enough
15.34 + private static ArrayBlockingQueue<SocketChannel> pendingChannels = new ArrayBlockingQueue<SocketChannel>(256, true);
15.35
15.36 - if(channel != null)
15.37 - {
15.38 - // Connections.getInstance().get() MAY return null
15.39 - NNTPConnection conn = Connections.getInstance().get(channel);
15.40 -
15.41 - // Try to lock the connection object
15.42 - if(conn != null && conn.tryReadLock())
15.43 - {
15.44 - ByteBuffer buf = conn.getBuffers().nextInputLine();
15.45 - while(buf != null) // Complete line was received
15.46 - {
15.47 - final byte[] line = new byte[buf.limit()];
15.48 - buf.get(line);
15.49 - ChannelLineBuffers.recycleBuffer(buf);
15.50 -
15.51 - // Here is the actual work done
15.52 - conn.lineReceived(line);
15.53 + /**
15.54 + * Registers the given channel for further event processing.
15.55 + * @param channel
15.56 + */
15.57 + public static void addChannel(SocketChannel channel)
15.58 + throws InterruptedException
15.59 + {
15.60 + pendingChannels.put(channel);
15.61 + }
15.62
15.63 - // Read next line as we could have already received the next line
15.64 - buf = conn.getBuffers().nextInputLine();
15.65 - }
15.66 - conn.unlockReadLock();
15.67 - }
15.68 - else
15.69 - {
15.70 - addChannel(channel);
15.71 - }
15.72 - }
15.73 - }
15.74 - catch(InterruptedException ex)
15.75 - {
15.76 - Log.get().info("ConnectionWorker interrupted: " + ex);
15.77 - }
15.78 - catch(Exception ex)
15.79 - {
15.80 - Log.get().severe("Exception in ConnectionWorker: " + ex);
15.81 - ex.printStackTrace();
15.82 - }
15.83 - } // end while(isRunning())
15.84 - }
15.85 -
15.86 + /**
15.87 + * Processing loop.
15.88 + */
15.89 + @Override
15.90 + public void run()
15.91 + {
15.92 + while (isRunning()) {
15.93 + try {
15.94 + // Retrieve and remove if available, otherwise wait.
15.95 + SocketChannel channel = pendingChannels.take();
15.96 +
15.97 + if (channel != null) {
15.98 + // Connections.getInstance().get() MAY return null
15.99 + NNTPConnection conn = Connections.getInstance().get(channel);
15.100 +
15.101 + // Try to lock the connection object
15.102 + if (conn != null && conn.tryReadLock()) {
15.103 + ByteBuffer buf = conn.getBuffers().nextInputLine();
15.104 + while (buf != null) // Complete line was received
15.105 + {
15.106 + final byte[] line = new byte[buf.limit()];
15.107 + buf.get(line);
15.108 + ChannelLineBuffers.recycleBuffer(buf);
15.109 +
15.110 + // Here is the actual work done
15.111 + conn.lineReceived(line);
15.112 +
15.113 + // Read next line as we could have already received the next line
15.114 + buf = conn.getBuffers().nextInputLine();
15.115 + }
15.116 + conn.unlockReadLock();
15.117 + } else {
15.118 + addChannel(channel);
15.119 + }
15.120 + }
15.121 + } catch (InterruptedException ex) {
15.122 + Log.get().info("ConnectionWorker interrupted: " + ex);
15.123 + } catch (Exception ex) {
15.124 + Log.get().severe("Exception in ConnectionWorker: " + ex);
15.125 + ex.printStackTrace();
15.126 + }
15.127 + } // end while(isRunning())
15.128 + }
15.129 }
16.1 --- a/src/org/sonews/daemon/Connections.java Sun Aug 29 17:43:58 2010 +0200
16.2 +++ b/src/org/sonews/daemon/Connections.java Sun Aug 29 18:17:37 2010 +0200
16.3 @@ -41,141 +41,120 @@
16.4 public final class Connections extends AbstractDaemon
16.5 {
16.6
16.7 - private static final Connections instance = new Connections();
16.8 -
16.9 - /**
16.10 - * @return Active Connections instance.
16.11 - */
16.12 - public static Connections getInstance()
16.13 - {
16.14 - return Connections.instance;
16.15 - }
16.16 -
16.17 - private final List<NNTPConnection> connections
16.18 - = new ArrayList<NNTPConnection>();
16.19 - private final Map<SocketChannel, NNTPConnection> connByChannel
16.20 - = new HashMap<SocketChannel, NNTPConnection>();
16.21 -
16.22 - private Connections()
16.23 - {
16.24 - setName("Connections");
16.25 - }
16.26 -
16.27 - /**
16.28 - * Adds the given NNTPConnection to the Connections management.
16.29 - * @param conn
16.30 - * @see org.sonews.daemon.NNTPConnection
16.31 - */
16.32 - public void add(final NNTPConnection conn)
16.33 - {
16.34 - synchronized(this.connections)
16.35 - {
16.36 - this.connections.add(conn);
16.37 - this.connByChannel.put(conn.getSocketChannel(), conn);
16.38 - }
16.39 - }
16.40 -
16.41 - /**
16.42 - * @param channel
16.43 - * @return NNTPConnection instance that is associated with the given
16.44 - * SocketChannel.
16.45 - */
16.46 - public NNTPConnection get(final SocketChannel channel)
16.47 - {
16.48 - synchronized(this.connections)
16.49 - {
16.50 - return this.connByChannel.get(channel);
16.51 - }
16.52 - }
16.53 + private static final Connections instance = new Connections();
16.54
16.55 - int getConnectionCount(String remote)
16.56 - {
16.57 - int cnt = 0;
16.58 - synchronized(this.connections)
16.59 - {
16.60 - for(NNTPConnection conn : this.connections)
16.61 - {
16.62 - assert conn != null;
16.63 - assert conn.getSocketChannel() != null;
16.64 + /**
16.65 + * @return Active Connections instance.
16.66 + */
16.67 + public static Connections getInstance()
16.68 + {
16.69 + return Connections.instance;
16.70 + }
16.71 + private final List<NNTPConnection> connections = new ArrayList<NNTPConnection>();
16.72 + private final Map<SocketChannel, NNTPConnection> connByChannel = new HashMap<SocketChannel, NNTPConnection>();
16.73
16.74 - Socket socket = conn.getSocketChannel().socket();
16.75 - if(socket != null)
16.76 - {
16.77 - InetSocketAddress sockAddr = (InetSocketAddress)socket.getRemoteSocketAddress();
16.78 - if(sockAddr != null)
16.79 - {
16.80 - if(sockAddr.getHostName().equals(remote))
16.81 - {
16.82 - cnt++;
16.83 - }
16.84 - }
16.85 - } // if(socket != null)
16.86 - }
16.87 - }
16.88 - return cnt;
16.89 - }
16.90 -
16.91 - /**
16.92 - * Run loops. Checks periodically for timed out connections and purged them
16.93 - * from the lists.
16.94 - */
16.95 - @Override
16.96 - public void run()
16.97 - {
16.98 - while(isRunning())
16.99 - {
16.100 - int timeoutMillis = 1000 * Config.inst().get(Config.TIMEOUT, 180);
16.101 -
16.102 - synchronized (this.connections)
16.103 - {
16.104 - final ListIterator<NNTPConnection> iter = this.connections.listIterator();
16.105 - NNTPConnection conn;
16.106 + private Connections()
16.107 + {
16.108 + setName("Connections");
16.109 + }
16.110
16.111 - while (iter.hasNext())
16.112 - {
16.113 - conn = iter.next();
16.114 - if((System.currentTimeMillis() - conn.getLastActivity()) > timeoutMillis
16.115 - && conn.getBuffers().isOutputBufferEmpty())
16.116 - {
16.117 - // A connection timeout has occurred so purge the connection
16.118 - iter.remove();
16.119 + /**
16.120 + * Adds the given NNTPConnection to the Connections management.
16.121 + * @param conn
16.122 + * @see org.sonews.daemon.NNTPConnection
16.123 + */
16.124 + public void add(final NNTPConnection conn)
16.125 + {
16.126 + synchronized (this.connections) {
16.127 + this.connections.add(conn);
16.128 + this.connByChannel.put(conn.getSocketChannel(), conn);
16.129 + }
16.130 + }
16.131
16.132 - // Close and remove the channel
16.133 - SocketChannel channel = conn.getSocketChannel();
16.134 - connByChannel.remove(channel);
16.135 -
16.136 - try
16.137 - {
16.138 - assert channel != null;
16.139 - assert channel.socket() != null;
16.140 -
16.141 - // Close the channel; implicitely cancels all selectionkeys
16.142 - channel.close();
16.143 - Log.get().info("Disconnected: " + channel.socket().getRemoteSocketAddress() +
16.144 - " (timeout)");
16.145 - }
16.146 - catch(IOException ex)
16.147 - {
16.148 - Log.get().warning("Connections.run(): " + ex);
16.149 - }
16.150 + /**
16.151 + * @param channel
16.152 + * @return NNTPConnection instance that is associated with the given
16.153 + * SocketChannel.
16.154 + */
16.155 + public NNTPConnection get(final SocketChannel channel)
16.156 + {
16.157 + synchronized (this.connections) {
16.158 + return this.connByChannel.get(channel);
16.159 + }
16.160 + }
16.161
16.162 - // Recycle the used buffers
16.163 - conn.getBuffers().recycleBuffers();
16.164 -
16.165 - Stats.getInstance().clientDisconnect();
16.166 - }
16.167 - }
16.168 - }
16.169 + int getConnectionCount(String remote)
16.170 + {
16.171 + int cnt = 0;
16.172 + synchronized (this.connections) {
16.173 + for (NNTPConnection conn : this.connections) {
16.174 + assert conn != null;
16.175 + assert conn.getSocketChannel() != null;
16.176
16.177 - try
16.178 - {
16.179 - Thread.sleep(10000); // Sleep ten seconds
16.180 - }
16.181 - catch(InterruptedException ex)
16.182 - {
16.183 - Log.get().warning("Connections Thread was interrupted: " + ex.getMessage());
16.184 - }
16.185 - }
16.186 - }
16.187 -
16.188 + Socket socket = conn.getSocketChannel().socket();
16.189 + if (socket != null) {
16.190 + InetSocketAddress sockAddr = (InetSocketAddress) socket.getRemoteSocketAddress();
16.191 + if (sockAddr != null) {
16.192 + if (sockAddr.getHostName().equals(remote)) {
16.193 + cnt++;
16.194 + }
16.195 + }
16.196 + } // if(socket != null)
16.197 + }
16.198 + }
16.199 + return cnt;
16.200 + }
16.201 +
16.202 + /**
16.203 + * Run loops. Checks periodically for timed out connections and purged them
16.204 + * from the lists.
16.205 + */
16.206 + @Override
16.207 + public void run()
16.208 + {
16.209 + while (isRunning()) {
16.210 + int timeoutMillis = 1000 * Config.inst().get(Config.TIMEOUT, 180);
16.211 +
16.212 + synchronized (this.connections) {
16.213 + final ListIterator<NNTPConnection> iter = this.connections.listIterator();
16.214 + NNTPConnection conn;
16.215 +
16.216 + while (iter.hasNext()) {
16.217 + conn = iter.next();
16.218 + if ((System.currentTimeMillis() - conn.getLastActivity()) > timeoutMillis
16.219 + && conn.getBuffers().isOutputBufferEmpty()) {
16.220 + // A connection timeout has occurred so purge the connection
16.221 + iter.remove();
16.222 +
16.223 + // Close and remove the channel
16.224 + SocketChannel channel = conn.getSocketChannel();
16.225 + connByChannel.remove(channel);
16.226 +
16.227 + try {
16.228 + assert channel != null;
16.229 + assert channel.socket() != null;
16.230 +
16.231 + // Close the channel; implicitely cancels all selectionkeys
16.232 + channel.close();
16.233 + Log.get().info("Disconnected: " + channel.socket().getRemoteSocketAddress()
16.234 + + " (timeout)");
16.235 + } catch (IOException ex) {
16.236 + Log.get().warning("Connections.run(): " + ex);
16.237 + }
16.238 +
16.239 + // Recycle the used buffers
16.240 + conn.getBuffers().recycleBuffers();
16.241 +
16.242 + Stats.getInstance().clientDisconnect();
16.243 + }
16.244 + }
16.245 + }
16.246 +
16.247 + try {
16.248 + Thread.sleep(10000); // Sleep ten seconds
16.249 + } catch (InterruptedException ex) {
16.250 + Log.get().warning("Connections Thread was interrupted: " + ex.getMessage());
16.251 + }
16.252 + }
16.253 + }
16.254 }
17.1 --- a/src/org/sonews/daemon/LineEncoder.java Sun Aug 29 17:43:58 2010 +0200
17.2 +++ b/src/org/sonews/daemon/LineEncoder.java Sun Aug 29 18:17:37 2010 +0200
17.3 @@ -33,48 +33,46 @@
17.4 class LineEncoder
17.5 {
17.6
17.7 - private CharBuffer characters;
17.8 - private Charset charset;
17.9 -
17.10 - /**
17.11 - * Constructs new LineEncoder.
17.12 - * @param characters
17.13 - * @param charset
17.14 - */
17.15 - public LineEncoder(CharBuffer characters, Charset charset)
17.16 - {
17.17 - this.characters = characters;
17.18 - this.charset = charset;
17.19 - }
17.20 -
17.21 - /**
17.22 - * Encodes the characters of this instance to the given ChannelLineBuffers
17.23 - * using the Charset of this instance.
17.24 - * @param buffer
17.25 - * @throws java.nio.channels.ClosedChannelException
17.26 - */
17.27 - public void encode(ChannelLineBuffers buffer)
17.28 - throws ClosedChannelException
17.29 - {
17.30 - CharsetEncoder encoder = charset.newEncoder();
17.31 - while (characters.hasRemaining())
17.32 - {
17.33 - ByteBuffer buf = ChannelLineBuffers.newLineBuffer();
17.34 - assert buf.position() == 0;
17.35 - assert buf.capacity() >= 512;
17.36 + private CharBuffer characters;
17.37 + private Charset charset;
17.38
17.39 - CoderResult res = encoder.encode(characters, buf, true);
17.40 + /**
17.41 + * Constructs new LineEncoder.
17.42 + * @param characters
17.43 + * @param charset
17.44 + */
17.45 + public LineEncoder(CharBuffer characters, Charset charset)
17.46 + {
17.47 + this.characters = characters;
17.48 + this.charset = charset;
17.49 + }
17.50
17.51 - // Set limit to current position and current position to 0;
17.52 - // means make ready for read from buffer
17.53 - buf.flip();
17.54 - buffer.addOutputBuffer(buf);
17.55 + /**
17.56 + * Encodes the characters of this instance to the given ChannelLineBuffers
17.57 + * using the Charset of this instance.
17.58 + * @param buffer
17.59 + * @throws java.nio.channels.ClosedChannelException
17.60 + */
17.61 + public void encode(ChannelLineBuffers buffer)
17.62 + throws ClosedChannelException
17.63 + {
17.64 + CharsetEncoder encoder = charset.newEncoder();
17.65 + while (characters.hasRemaining()) {
17.66 + ByteBuffer buf = ChannelLineBuffers.newLineBuffer();
17.67 + assert buf.position() == 0;
17.68 + assert buf.capacity() >= 512;
17.69
17.70 - if (res.isUnderflow()) // All input processed
17.71 - {
17.72 - break;
17.73 - }
17.74 - }
17.75 - }
17.76 -
17.77 + CoderResult res = encoder.encode(characters, buf, true);
17.78 +
17.79 + // Set limit to current position and current position to 0;
17.80 + // means make ready for read from buffer
17.81 + buf.flip();
17.82 + buffer.addOutputBuffer(buf);
17.83 +
17.84 + if (res.isUnderflow()) // All input processed
17.85 + {
17.86 + break;
17.87 + }
17.88 + }
17.89 + }
17.90 }
18.1 --- a/src/org/sonews/daemon/NNTPConnection.java Sun Aug 29 17:43:58 2010 +0200
18.2 +++ b/src/org/sonews/daemon/NNTPConnection.java Sun Aug 29 18:17:37 2010 +0200
18.3 @@ -46,383 +46,341 @@
18.4 public final class NNTPConnection
18.5 {
18.6
18.7 - public static final String NEWLINE = "\r\n"; // RFC defines this as newline
18.8 - public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
18.9 -
18.10 - private static final Timer cancelTimer = new Timer(true); // Thread-safe? True for run as daemon
18.11 -
18.12 - /** SocketChannel is generally thread-safe */
18.13 - private SocketChannel channel = null;
18.14 - private Charset charset = Charset.forName("UTF-8");
18.15 - private Command command = null;
18.16 - private Article currentArticle = null;
18.17 - private Channel currentGroup = null;
18.18 - private volatile long lastActivity = System.currentTimeMillis();
18.19 - private ChannelLineBuffers lineBuffers = new ChannelLineBuffers();
18.20 - private int readLock = 0;
18.21 - private final Object readLockGate = new Object();
18.22 - private SelectionKey writeSelKey = null;
18.23 -
18.24 - public NNTPConnection(final SocketChannel channel)
18.25 - throws IOException
18.26 - {
18.27 - if(channel == null)
18.28 - {
18.29 - throw new IllegalArgumentException("channel is null");
18.30 - }
18.31 + public static final String NEWLINE = "\r\n"; // RFC defines this as newline
18.32 + public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
18.33 + private static final Timer cancelTimer = new Timer(true); // Thread-safe? True for run as daemon
18.34 + /** SocketChannel is generally thread-safe */
18.35 + private SocketChannel channel = null;
18.36 + private Charset charset = Charset.forName("UTF-8");
18.37 + private Command command = null;
18.38 + private Article currentArticle = null;
18.39 + private Channel currentGroup = null;
18.40 + private volatile long lastActivity = System.currentTimeMillis();
18.41 + private ChannelLineBuffers lineBuffers = new ChannelLineBuffers();
18.42 + private int readLock = 0;
18.43 + private final Object readLockGate = new Object();
18.44 + private SelectionKey writeSelKey = null;
18.45
18.46 - this.channel = channel;
18.47 - Stats.getInstance().clientConnect();
18.48 - }
18.49 -
18.50 - /**
18.51 - * Tries to get the read lock for this NNTPConnection. This method is Thread-
18.52 - * safe and returns true of the read lock was successfully set. If the lock
18.53 - * is still hold by another Thread the method returns false.
18.54 - */
18.55 - boolean tryReadLock()
18.56 - {
18.57 - // As synchronizing simple types may cause deadlocks,
18.58 - // we use a gate object.
18.59 - synchronized(readLockGate)
18.60 - {
18.61 - if(readLock != 0)
18.62 - {
18.63 - return false;
18.64 - }
18.65 - else
18.66 - {
18.67 - readLock = Thread.currentThread().hashCode();
18.68 - return true;
18.69 - }
18.70 - }
18.71 - }
18.72 -
18.73 - /**
18.74 - * Releases the read lock in a Thread-safe way.
18.75 - * @throws IllegalMonitorStateException if a Thread not holding the lock
18.76 - * tries to release it.
18.77 - */
18.78 - void unlockReadLock()
18.79 - {
18.80 - synchronized(readLockGate)
18.81 - {
18.82 - if(readLock == Thread.currentThread().hashCode())
18.83 - {
18.84 - readLock = 0;
18.85 - }
18.86 - else
18.87 - {
18.88 - throw new IllegalMonitorStateException();
18.89 - }
18.90 - }
18.91 - }
18.92 -
18.93 - /**
18.94 - * @return Current input buffer of this NNTPConnection instance.
18.95 - */
18.96 - public ByteBuffer getInputBuffer()
18.97 - {
18.98 - return this.lineBuffers.getInputBuffer();
18.99 - }
18.100 -
18.101 - /**
18.102 - * @return Output buffer of this NNTPConnection which has at least one byte
18.103 - * free storage.
18.104 - */
18.105 - public ByteBuffer getOutputBuffer()
18.106 - {
18.107 - return this.lineBuffers.getOutputBuffer();
18.108 - }
18.109 -
18.110 - /**
18.111 - * @return ChannelLineBuffers instance associated with this NNTPConnection.
18.112 - */
18.113 - public ChannelLineBuffers getBuffers()
18.114 - {
18.115 - return this.lineBuffers;
18.116 - }
18.117 -
18.118 - /**
18.119 - * @return true if this connection comes from a local remote address.
18.120 - */
18.121 - public boolean isLocalConnection()
18.122 - {
18.123 - return ((InetSocketAddress)this.channel.socket().getRemoteSocketAddress())
18.124 - .getHostName().equalsIgnoreCase("localhost");
18.125 - }
18.126 + public NNTPConnection(final SocketChannel channel)
18.127 + throws IOException
18.128 + {
18.129 + if (channel == null) {
18.130 + throw new IllegalArgumentException("channel is null");
18.131 + }
18.132
18.133 - void setWriteSelectionKey(SelectionKey selKey)
18.134 - {
18.135 - this.writeSelKey = selKey;
18.136 - }
18.137 + this.channel = channel;
18.138 + Stats.getInstance().clientConnect();
18.139 + }
18.140
18.141 - public void shutdownInput()
18.142 - {
18.143 - try
18.144 - {
18.145 - // Closes the input line of the channel's socket, so no new data
18.146 - // will be received and a timeout can be triggered.
18.147 - this.channel.socket().shutdownInput();
18.148 - }
18.149 - catch(IOException ex)
18.150 - {
18.151 - Log.get().warning("Exception in NNTPConnection.shutdownInput(): " + ex);
18.152 - }
18.153 - }
18.154 -
18.155 - public void shutdownOutput()
18.156 - {
18.157 - cancelTimer.schedule(new TimerTask()
18.158 - {
18.159 - @Override
18.160 - public void run()
18.161 - {
18.162 - try
18.163 - {
18.164 - // Closes the output line of the channel's socket.
18.165 - channel.socket().shutdownOutput();
18.166 - channel.close();
18.167 - }
18.168 - catch(SocketException ex)
18.169 - {
18.170 - // Socket was already disconnected
18.171 - Log.get().info("NNTPConnection.shutdownOutput(): " + ex);
18.172 - }
18.173 - catch(Exception ex)
18.174 - {
18.175 - Log.get().warning("NNTPConnection.shutdownOutput(): " + ex);
18.176 - }
18.177 - }
18.178 - }, 3000);
18.179 - }
18.180 -
18.181 - public SocketChannel getSocketChannel()
18.182 - {
18.183 - return this.channel;
18.184 - }
18.185 -
18.186 - public Article getCurrentArticle()
18.187 - {
18.188 - return this.currentArticle;
18.189 - }
18.190 -
18.191 - public Charset getCurrentCharset()
18.192 - {
18.193 - return this.charset;
18.194 - }
18.195 + /**
18.196 + * Tries to get the read lock for this NNTPConnection. This method is Thread-
18.197 + * safe and returns true of the read lock was successfully set. If the lock
18.198 + * is still hold by another Thread the method returns false.
18.199 + */
18.200 + boolean tryReadLock()
18.201 + {
18.202 + // As synchronizing simple types may cause deadlocks,
18.203 + // we use a gate object.
18.204 + synchronized (readLockGate) {
18.205 + if (readLock != 0) {
18.206 + return false;
18.207 + } else {
18.208 + readLock = Thread.currentThread().hashCode();
18.209 + return true;
18.210 + }
18.211 + }
18.212 + }
18.213
18.214 - /**
18.215 - * @return The currently selected communication channel (not SocketChannel)
18.216 - */
18.217 - public Channel getCurrentChannel()
18.218 - {
18.219 - return this.currentGroup;
18.220 - }
18.221 -
18.222 - public void setCurrentArticle(final Article article)
18.223 - {
18.224 - this.currentArticle = article;
18.225 - }
18.226 -
18.227 - public void setCurrentGroup(final Channel group)
18.228 - {
18.229 - this.currentGroup = group;
18.230 - }
18.231 -
18.232 - public long getLastActivity()
18.233 - {
18.234 - return this.lastActivity;
18.235 - }
18.236 -
18.237 - /**
18.238 - * Due to the readLockGate there is no need to synchronize this method.
18.239 - * @param raw
18.240 - * @throws IllegalArgumentException if raw is null.
18.241 - * @throws IllegalStateException if calling thread does not own the readLock.
18.242 - */
18.243 - void lineReceived(byte[] raw)
18.244 - {
18.245 - if(raw == null)
18.246 - {
18.247 - throw new IllegalArgumentException("raw is null");
18.248 - }
18.249 -
18.250 - if(readLock == 0 || readLock != Thread.currentThread().hashCode())
18.251 - {
18.252 - throw new IllegalStateException("readLock not properly set");
18.253 - }
18.254 + /**
18.255 + * Releases the read lock in a Thread-safe way.
18.256 + * @throws IllegalMonitorStateException if a Thread not holding the lock
18.257 + * tries to release it.
18.258 + */
18.259 + void unlockReadLock()
18.260 + {
18.261 + synchronized (readLockGate) {
18.262 + if (readLock == Thread.currentThread().hashCode()) {
18.263 + readLock = 0;
18.264 + } else {
18.265 + throw new IllegalMonitorStateException();
18.266 + }
18.267 + }
18.268 + }
18.269
18.270 - this.lastActivity = System.currentTimeMillis();
18.271 -
18.272 - String line = new String(raw, this.charset);
18.273 -
18.274 - // There might be a trailing \r, but trim() is a bad idea
18.275 - // as it removes also leading spaces from long header lines.
18.276 - if(line.endsWith("\r"))
18.277 - {
18.278 - line = line.substring(0, line.length() - 1);
18.279 - raw = Arrays.copyOf(raw, raw.length - 1);
18.280 - }
18.281 -
18.282 - Log.get().fine("<< " + line);
18.283 -
18.284 - if(command == null)
18.285 - {
18.286 - command = parseCommandLine(line);
18.287 - assert command != null;
18.288 - }
18.289 + /**
18.290 + * @return Current input buffer of this NNTPConnection instance.
18.291 + */
18.292 + public ByteBuffer getInputBuffer()
18.293 + {
18.294 + return this.lineBuffers.getInputBuffer();
18.295 + }
18.296
18.297 - try
18.298 - {
18.299 - // The command object will process the line we just received
18.300 - try
18.301 - {
18.302 - command.processLine(this, line, raw);
18.303 - }
18.304 - catch(StorageBackendException ex)
18.305 - {
18.306 - Log.get().info("Retry command processing after StorageBackendException");
18.307 + /**
18.308 + * @return Output buffer of this NNTPConnection which has at least one byte
18.309 + * free storage.
18.310 + */
18.311 + public ByteBuffer getOutputBuffer()
18.312 + {
18.313 + return this.lineBuffers.getOutputBuffer();
18.314 + }
18.315
18.316 - // Try it a second time, so that the backend has time to recover
18.317 - command.processLine(this, line, raw);
18.318 - }
18.319 - }
18.320 - catch(ClosedChannelException ex0)
18.321 - {
18.322 - try
18.323 - {
18.324 - Log.get().info("Connection to " + channel.socket().getRemoteSocketAddress()
18.325 - + " closed: " + ex0);
18.326 - }
18.327 - catch(Exception ex0a)
18.328 - {
18.329 - ex0a.printStackTrace();
18.330 - }
18.331 - }
18.332 - catch(Exception ex1) // This will catch a second StorageBackendException
18.333 - {
18.334 - try
18.335 - {
18.336 - command = null;
18.337 - ex1.printStackTrace();
18.338 - println("500 Internal server error");
18.339 - }
18.340 - catch(Exception ex2)
18.341 - {
18.342 - ex2.printStackTrace();
18.343 - }
18.344 - }
18.345 + /**
18.346 + * @return ChannelLineBuffers instance associated with this NNTPConnection.
18.347 + */
18.348 + public ChannelLineBuffers getBuffers()
18.349 + {
18.350 + return this.lineBuffers;
18.351 + }
18.352
18.353 - if(command == null || command.hasFinished())
18.354 - {
18.355 - command = null;
18.356 - charset = Charset.forName("UTF-8"); // Reset to default
18.357 - }
18.358 - }
18.359 -
18.360 - /**
18.361 - * This method determines the fitting command processing class.
18.362 - * @param line
18.363 - * @return
18.364 - */
18.365 - private Command parseCommandLine(String line)
18.366 - {
18.367 - String cmdStr = line.split(" ")[0];
18.368 - return CommandSelector.getInstance().get(cmdStr);
18.369 - }
18.370 -
18.371 - /**
18.372 - * Puts the given line into the output buffer, adds a newline character
18.373 - * and returns. The method returns immediately and does not block until
18.374 - * the line was sent. If line is longer than 510 octets it is split up in
18.375 - * several lines. Each line is terminated by \r\n (NNTPConnection.NEWLINE).
18.376 - * @param line
18.377 - */
18.378 - public void println(final CharSequence line, final Charset charset)
18.379 - throws IOException
18.380 - {
18.381 - writeToChannel(CharBuffer.wrap(line), charset, line);
18.382 - writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
18.383 - }
18.384 + /**
18.385 + * @return true if this connection comes from a local remote address.
18.386 + */
18.387 + public boolean isLocalConnection()
18.388 + {
18.389 + return ((InetSocketAddress) this.channel.socket().getRemoteSocketAddress()).getHostName().equalsIgnoreCase("localhost");
18.390 + }
18.391
18.392 - /**
18.393 - * Writes the given raw lines to the output buffers and finishes with
18.394 - * a newline character (\r\n).
18.395 - * @param rawLines
18.396 - */
18.397 - public void println(final byte[] rawLines)
18.398 - throws IOException
18.399 - {
18.400 - this.lineBuffers.addOutputBuffer(ByteBuffer.wrap(rawLines));
18.401 - writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
18.402 - }
18.403 -
18.404 - /**
18.405 - * Encodes the given CharBuffer using the given Charset to a bunch of
18.406 - * ByteBuffers (each 512 bytes large) and enqueues them for writing at the
18.407 - * connected SocketChannel.
18.408 - * @throws java.io.IOException
18.409 - */
18.410 - private void writeToChannel(CharBuffer characters, final Charset charset,
18.411 - CharSequence debugLine)
18.412 - throws IOException
18.413 - {
18.414 - if(!charset.canEncode())
18.415 - {
18.416 - Log.get().severe("FATAL: Charset " + charset + " cannot encode!");
18.417 - return;
18.418 - }
18.419 -
18.420 - // Write characters to output buffers
18.421 - LineEncoder lenc = new LineEncoder(characters, charset);
18.422 - lenc.encode(lineBuffers);
18.423 -
18.424 - enableWriteEvents(debugLine);
18.425 - }
18.426 + void setWriteSelectionKey(SelectionKey selKey)
18.427 + {
18.428 + this.writeSelKey = selKey;
18.429 + }
18.430
18.431 - private void enableWriteEvents(CharSequence debugLine)
18.432 - {
18.433 - // Enable OP_WRITE events so that the buffers are processed
18.434 - try
18.435 - {
18.436 - this.writeSelKey.interestOps(SelectionKey.OP_WRITE);
18.437 - ChannelWriter.getInstance().getSelector().wakeup();
18.438 - }
18.439 - catch(Exception ex) // CancelledKeyException and ChannelCloseException
18.440 - {
18.441 - Log.get().warning("NNTPConnection.writeToChannel(): " + ex);
18.442 - return;
18.443 - }
18.444 + public void shutdownInput()
18.445 + {
18.446 + try {
18.447 + // Closes the input line of the channel's socket, so no new data
18.448 + // will be received and a timeout can be triggered.
18.449 + this.channel.socket().shutdownInput();
18.450 + } catch (IOException ex) {
18.451 + Log.get().warning("Exception in NNTPConnection.shutdownInput(): " + ex);
18.452 + }
18.453 + }
18.454
18.455 - // Update last activity timestamp
18.456 - this.lastActivity = System.currentTimeMillis();
18.457 - if(debugLine != null)
18.458 - {
18.459 - Log.get().fine(">> " + debugLine);
18.460 - }
18.461 - }
18.462 -
18.463 - public void println(final CharSequence line)
18.464 - throws IOException
18.465 - {
18.466 - println(line, charset);
18.467 - }
18.468 -
18.469 - public void print(final String line)
18.470 - throws IOException
18.471 - {
18.472 - writeToChannel(CharBuffer.wrap(line), charset, line);
18.473 - }
18.474 -
18.475 - public void setCurrentCharset(final Charset charset)
18.476 - {
18.477 - this.charset = charset;
18.478 - }
18.479 + public void shutdownOutput()
18.480 + {
18.481 + cancelTimer.schedule(new TimerTask()
18.482 + {
18.483
18.484 - void setLastActivity(long timestamp)
18.485 - {
18.486 - this.lastActivity = timestamp;
18.487 - }
18.488 -
18.489 + @Override
18.490 + public void run()
18.491 + {
18.492 + try {
18.493 + // Closes the output line of the channel's socket.
18.494 + channel.socket().shutdownOutput();
18.495 + channel.close();
18.496 + } catch (SocketException ex) {
18.497 + // Socket was already disconnected
18.498 + Log.get().info("NNTPConnection.shutdownOutput(): " + ex);
18.499 + } catch (Exception ex) {
18.500 + Log.get().warning("NNTPConnection.shutdownOutput(): " + ex);
18.501 + }
18.502 + }
18.503 + }, 3000);
18.504 + }
18.505 +
18.506 + public SocketChannel getSocketChannel()
18.507 + {
18.508 + return this.channel;
18.509 + }
18.510 +
18.511 + public Article getCurrentArticle()
18.512 + {
18.513 + return this.currentArticle;
18.514 + }
18.515 +
18.516 + public Charset getCurrentCharset()
18.517 + {
18.518 + return this.charset;
18.519 + }
18.520 +
18.521 + /**
18.522 + * @return The currently selected communication channel (not SocketChannel)
18.523 + */
18.524 + public Channel getCurrentChannel()
18.525 + {
18.526 + return this.currentGroup;
18.527 + }
18.528 +
18.529 + public void setCurrentArticle(final Article article)
18.530 + {
18.531 + this.currentArticle = article;
18.532 + }
18.533 +
18.534 + public void setCurrentGroup(final Channel group)
18.535 + {
18.536 + this.currentGroup = group;
18.537 + }
18.538 +
18.539 + public long getLastActivity()
18.540 + {
18.541 + return this.lastActivity;
18.542 + }
18.543 +
18.544 + /**
18.545 + * Due to the readLockGate there is no need to synchronize this method.
18.546 + * @param raw
18.547 + * @throws IllegalArgumentException if raw is null.
18.548 + * @throws IllegalStateException if calling thread does not own the readLock.
18.549 + */
18.550 + void lineReceived(byte[] raw)
18.551 + {
18.552 + if (raw == null) {
18.553 + throw new IllegalArgumentException("raw is null");
18.554 + }
18.555 +
18.556 + if (readLock == 0 || readLock != Thread.currentThread().hashCode()) {
18.557 + throw new IllegalStateException("readLock not properly set");
18.558 + }
18.559 +
18.560 + this.lastActivity = System.currentTimeMillis();
18.561 +
18.562 + String line = new String(raw, this.charset);
18.563 +
18.564 + // There might be a trailing \r, but trim() is a bad idea
18.565 + // as it removes also leading spaces from long header lines.
18.566 + if (line.endsWith("\r")) {
18.567 + line = line.substring(0, line.length() - 1);
18.568 + raw = Arrays.copyOf(raw, raw.length - 1);
18.569 + }
18.570 +
18.571 + Log.get().fine("<< " + line);
18.572 +
18.573 + if (command == null) {
18.574 + command = parseCommandLine(line);
18.575 + assert command != null;
18.576 + }
18.577 +
18.578 + try {
18.579 + // The command object will process the line we just received
18.580 + try {
18.581 + command.processLine(this, line, raw);
18.582 + } catch (StorageBackendException ex) {
18.583 + Log.get().info("Retry command processing after StorageBackendException");
18.584 +
18.585 + // Try it a second time, so that the backend has time to recover
18.586 + command.processLine(this, line, raw);
18.587 + }
18.588 + } catch (ClosedChannelException ex0) {
18.589 + try {
18.590 + Log.get().info("Connection to " + channel.socket().getRemoteSocketAddress()
18.591 + + " closed: " + ex0);
18.592 + } catch (Exception ex0a) {
18.593 + ex0a.printStackTrace();
18.594 + }
18.595 + } catch (Exception ex1) // This will catch a second StorageBackendException
18.596 + {
18.597 + try {
18.598 + command = null;
18.599 + ex1.printStackTrace();
18.600 + println("500 Internal server error");
18.601 + } catch (Exception ex2) {
18.602 + ex2.printStackTrace();
18.603 + }
18.604 + }
18.605 +
18.606 + if (command == null || command.hasFinished()) {
18.607 + command = null;
18.608 + charset = Charset.forName("UTF-8"); // Reset to default
18.609 + }
18.610 + }
18.611 +
18.612 + /**
18.613 + * This method determines the fitting command processing class.
18.614 + * @param line
18.615 + * @return
18.616 + */
18.617 + private Command parseCommandLine(String line)
18.618 + {
18.619 + String cmdStr = line.split(" ")[0];
18.620 + return CommandSelector.getInstance().get(cmdStr);
18.621 + }
18.622 +
18.623 + /**
18.624 + * Puts the given line into the output buffer, adds a newline character
18.625 + * and returns. The method returns immediately and does not block until
18.626 + * the line was sent. If line is longer than 510 octets it is split up in
18.627 + * several lines. Each line is terminated by \r\n (NNTPConnection.NEWLINE).
18.628 + * @param line
18.629 + */
18.630 + public void println(final CharSequence line, final Charset charset)
18.631 + throws IOException
18.632 + {
18.633 + writeToChannel(CharBuffer.wrap(line), charset, line);
18.634 + writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
18.635 + }
18.636 +
18.637 + /**
18.638 + * Writes the given raw lines to the output buffers and finishes with
18.639 + * a newline character (\r\n).
18.640 + * @param rawLines
18.641 + */
18.642 + public void println(final byte[] rawLines)
18.643 + throws IOException
18.644 + {
18.645 + this.lineBuffers.addOutputBuffer(ByteBuffer.wrap(rawLines));
18.646 + writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
18.647 + }
18.648 +
18.649 + /**
18.650 + * Encodes the given CharBuffer using the given Charset to a bunch of
18.651 + * ByteBuffers (each 512 bytes large) and enqueues them for writing at the
18.652 + * connected SocketChannel.
18.653 + * @throws java.io.IOException
18.654 + */
18.655 + private void writeToChannel(CharBuffer characters, final Charset charset,
18.656 + CharSequence debugLine)
18.657 + throws IOException
18.658 + {
18.659 + if (!charset.canEncode()) {
18.660 + Log.get().severe("FATAL: Charset " + charset + " cannot encode!");
18.661 + return;
18.662 + }
18.663 +
18.664 + // Write characters to output buffers
18.665 + LineEncoder lenc = new LineEncoder(characters, charset);
18.666 + lenc.encode(lineBuffers);
18.667 +
18.668 + enableWriteEvents(debugLine);
18.669 + }
18.670 +
18.671 + private void enableWriteEvents(CharSequence debugLine)
18.672 + {
18.673 + // Enable OP_WRITE events so that the buffers are processed
18.674 + try {
18.675 + this.writeSelKey.interestOps(SelectionKey.OP_WRITE);
18.676 + ChannelWriter.getInstance().getSelector().wakeup();
18.677 + } catch (Exception ex) // CancelledKeyException and ChannelCloseException
18.678 + {
18.679 + Log.get().warning("NNTPConnection.writeToChannel(): " + ex);
18.680 + return;
18.681 + }
18.682 +
18.683 + // Update last activity timestamp
18.684 + this.lastActivity = System.currentTimeMillis();
18.685 + if (debugLine != null) {
18.686 + Log.get().fine(">> " + debugLine);
18.687 + }
18.688 + }
18.689 +
18.690 + public void println(final CharSequence line)
18.691 + throws IOException
18.692 + {
18.693 + println(line, charset);
18.694 + }
18.695 +
18.696 + public void print(final String line)
18.697 + throws IOException
18.698 + {
18.699 + writeToChannel(CharBuffer.wrap(line), charset, line);
18.700 + }
18.701 +
18.702 + public void setCurrentCharset(final Charset charset)
18.703 + {
18.704 + this.charset = charset;
18.705 + }
18.706 +
18.707 + void setLastActivity(long timestamp)
18.708 + {
18.709 + this.lastActivity = timestamp;
18.710 + }
18.711 }
19.1 --- a/src/org/sonews/daemon/NNTPDaemon.java Sun Aug 29 17:43:58 2010 +0200
19.2 +++ b/src/org/sonews/daemon/NNTPDaemon.java Sun Aug 29 18:17:37 2010 +0200
19.3 @@ -40,158 +40,130 @@
19.4 public final class NNTPDaemon extends AbstractDaemon
19.5 {
19.6
19.7 - public static final Object RegisterGate = new Object();
19.8 -
19.9 - private static NNTPDaemon instance = null;
19.10 -
19.11 - public static synchronized NNTPDaemon createInstance(int port)
19.12 - {
19.13 - if(instance == null)
19.14 - {
19.15 - instance = new NNTPDaemon(port);
19.16 - return instance;
19.17 - }
19.18 - else
19.19 - {
19.20 - throw new RuntimeException("NNTPDaemon.createInstance() called twice");
19.21 - }
19.22 - }
19.23 -
19.24 - private int port;
19.25 -
19.26 - private NNTPDaemon(final int port)
19.27 - {
19.28 - Log.get().info("Server listening on port " + port);
19.29 - this.port = port;
19.30 - }
19.31 + public static final Object RegisterGate = new Object();
19.32 + private static NNTPDaemon instance = null;
19.33
19.34 - @Override
19.35 - public void run()
19.36 - {
19.37 - try
19.38 - {
19.39 - // Create a Selector that handles the SocketChannel multiplexing
19.40 - final Selector readSelector = Selector.open();
19.41 - final Selector writeSelector = Selector.open();
19.42 -
19.43 - // Start working threads
19.44 - final int workerThreads = Runtime.getRuntime().availableProcessors() * 4;
19.45 - ConnectionWorker[] cworkers = new ConnectionWorker[workerThreads];
19.46 - for(int n = 0; n < workerThreads; n++)
19.47 - {
19.48 - cworkers[n] = new ConnectionWorker();
19.49 - cworkers[n].start();
19.50 - }
19.51 -
19.52 - ChannelWriter.getInstance().setSelector(writeSelector);
19.53 - ChannelReader.getInstance().setSelector(readSelector);
19.54 - ChannelWriter.getInstance().start();
19.55 - ChannelReader.getInstance().start();
19.56 -
19.57 - final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
19.58 - serverSocketChannel.configureBlocking(true); // Set to blocking mode
19.59 -
19.60 - // Configure ServerSocket; bind to socket...
19.61 - final ServerSocket serverSocket = serverSocketChannel.socket();
19.62 - serverSocket.bind(new InetSocketAddress(this.port));
19.63 -
19.64 - while(isRunning())
19.65 - {
19.66 - SocketChannel socketChannel;
19.67 -
19.68 - try
19.69 - {
19.70 - // As we set the server socket channel to blocking mode the accept()
19.71 - // method will block.
19.72 - socketChannel = serverSocketChannel.accept();
19.73 - socketChannel.configureBlocking(false);
19.74 - assert socketChannel.isConnected();
19.75 - assert socketChannel.finishConnect();
19.76 - }
19.77 - catch(IOException ex)
19.78 - {
19.79 - // Under heavy load an IOException "Too many open files may
19.80 - // be thrown. It most cases we should slow down the connection
19.81 - // accepting, to give the worker threads some time to process work.
19.82 - Log.get().severe("IOException while accepting connection: " + ex.getMessage());
19.83 - Log.get().info("Connection accepting sleeping for seconds...");
19.84 - Thread.sleep(5000); // 5 seconds
19.85 - continue;
19.86 - }
19.87 -
19.88 - final NNTPConnection conn;
19.89 - try
19.90 - {
19.91 - conn = new NNTPConnection(socketChannel);
19.92 - Connections.getInstance().add(conn);
19.93 - }
19.94 - catch(IOException ex)
19.95 - {
19.96 - Log.get().warning(ex.toString());
19.97 - socketChannel.close();
19.98 - continue;
19.99 - }
19.100 -
19.101 - try
19.102 - {
19.103 - SelectionKey selKeyWrite =
19.104 - registerSelector(writeSelector, socketChannel, SelectionKey.OP_WRITE);
19.105 - registerSelector(readSelector, socketChannel, SelectionKey.OP_READ);
19.106 -
19.107 - Log.get().info("Connected: " + socketChannel.socket().getRemoteSocketAddress());
19.108 + public static synchronized NNTPDaemon createInstance(int port)
19.109 + {
19.110 + if (instance == null) {
19.111 + instance = new NNTPDaemon(port);
19.112 + return instance;
19.113 + } else {
19.114 + throw new RuntimeException("NNTPDaemon.createInstance() called twice");
19.115 + }
19.116 + }
19.117 + private int port;
19.118
19.119 - // Set write selection key and send hello to client
19.120 - conn.setWriteSelectionKey(selKeyWrite);
19.121 - conn.println("200 " + Config.inst().get(Config.HOSTNAME, "localhost")
19.122 - + " " + Main.VERSION + " news server ready - (posting ok).");
19.123 - }
19.124 - catch(CancelledKeyException cke)
19.125 - {
19.126 - Log.get().warning("CancelledKeyException " + cke.getMessage() + " was thrown: "
19.127 - + socketChannel.socket());
19.128 - }
19.129 - catch(ClosedChannelException cce)
19.130 - {
19.131 - Log.get().warning("ClosedChannelException " + cce.getMessage() + " was thrown: "
19.132 - + socketChannel.socket());
19.133 - }
19.134 - }
19.135 - }
19.136 - catch(BindException ex)
19.137 - {
19.138 - // Could not bind to socket; this is a fatal problem; so perform shutdown
19.139 - ex.printStackTrace();
19.140 - System.exit(1);
19.141 - }
19.142 - catch(IOException ex)
19.143 - {
19.144 - ex.printStackTrace();
19.145 - }
19.146 - catch(Exception ex)
19.147 - {
19.148 - ex.printStackTrace();
19.149 - }
19.150 - }
19.151 -
19.152 - public static SelectionKey registerSelector(final Selector selector,
19.153 - final SocketChannel channel, final int op)
19.154 - throws CancelledKeyException, ClosedChannelException
19.155 - {
19.156 - // Register the selector at the channel, so that it will be notified
19.157 - // on the socket's events
19.158 - synchronized(RegisterGate)
19.159 - {
19.160 - // Wakeup the currently blocking reader/writer thread; we have locked
19.161 - // the RegisterGate to prevent the awakened thread to block again
19.162 - selector.wakeup();
19.163 -
19.164 - // Lock the selector to prevent the waiting worker threads going into
19.165 - // selector.select() which would block the selector.
19.166 - synchronized (selector)
19.167 - {
19.168 - return channel.register(selector, op, null);
19.169 - }
19.170 - }
19.171 - }
19.172 -
19.173 + private NNTPDaemon(final int port)
19.174 + {
19.175 + Log.get().info("Server listening on port " + port);
19.176 + this.port = port;
19.177 + }
19.178 +
19.179 + @Override
19.180 + public void run()
19.181 + {
19.182 + try {
19.183 + // Create a Selector that handles the SocketChannel multiplexing
19.184 + final Selector readSelector = Selector.open();
19.185 + final Selector writeSelector = Selector.open();
19.186 +
19.187 + // Start working threads
19.188 + final int workerThreads = Runtime.getRuntime().availableProcessors() * 4;
19.189 + ConnectionWorker[] cworkers = new ConnectionWorker[workerThreads];
19.190 + for (int n = 0; n < workerThreads; n++) {
19.191 + cworkers[n] = new ConnectionWorker();
19.192 + cworkers[n].start();
19.193 + }
19.194 +
19.195 + ChannelWriter.getInstance().setSelector(writeSelector);
19.196 + ChannelReader.getInstance().setSelector(readSelector);
19.197 + ChannelWriter.getInstance().start();
19.198 + ChannelReader.getInstance().start();
19.199 +
19.200 + final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
19.201 + serverSocketChannel.configureBlocking(true); // Set to blocking mode
19.202 +
19.203 + // Configure ServerSocket; bind to socket...
19.204 + final ServerSocket serverSocket = serverSocketChannel.socket();
19.205 + serverSocket.bind(new InetSocketAddress(this.port));
19.206 +
19.207 + while (isRunning()) {
19.208 + SocketChannel socketChannel;
19.209 +
19.210 + try {
19.211 + // As we set the server socket channel to blocking mode the accept()
19.212 + // method will block.
19.213 + socketChannel = serverSocketChannel.accept();
19.214 + socketChannel.configureBlocking(false);
19.215 + assert socketChannel.isConnected();
19.216 + assert socketChannel.finishConnect();
19.217 + } catch (IOException ex) {
19.218 + // Under heavy load an IOException "Too many open files may
19.219 + // be thrown. It most cases we should slow down the connection
19.220 + // accepting, to give the worker threads some time to process work.
19.221 + Log.get().severe("IOException while accepting connection: " + ex.getMessage());
19.222 + Log.get().info("Connection accepting sleeping for seconds...");
19.223 + Thread.sleep(5000); // 5 seconds
19.224 + continue;
19.225 + }
19.226 +
19.227 + final NNTPConnection conn;
19.228 + try {
19.229 + conn = new NNTPConnection(socketChannel);
19.230 + Connections.getInstance().add(conn);
19.231 + } catch (IOException ex) {
19.232 + Log.get().warning(ex.toString());
19.233 + socketChannel.close();
19.234 + continue;
19.235 + }
19.236 +
19.237 + try {
19.238 + SelectionKey selKeyWrite =
19.239 + registerSelector(writeSelector, socketChannel, SelectionKey.OP_WRITE);
19.240 + registerSelector(readSelector, socketChannel, SelectionKey.OP_READ);
19.241 +
19.242 + Log.get().info("Connected: " + socketChannel.socket().getRemoteSocketAddress());
19.243 +
19.244 + // Set write selection key and send hello to client
19.245 + conn.setWriteSelectionKey(selKeyWrite);
19.246 + conn.println("200 " + Config.inst().get(Config.HOSTNAME, "localhost")
19.247 + + " " + Main.VERSION + " news server ready - (posting ok).");
19.248 + } catch (CancelledKeyException cke) {
19.249 + Log.get().warning("CancelledKeyException " + cke.getMessage() + " was thrown: "
19.250 + + socketChannel.socket());
19.251 + } catch (ClosedChannelException cce) {
19.252 + Log.get().warning("ClosedChannelException " + cce.getMessage() + " was thrown: "
19.253 + + socketChannel.socket());
19.254 + }
19.255 + }
19.256 + } catch (BindException ex) {
19.257 + // Could not bind to socket; this is a fatal problem; so perform shutdown
19.258 + ex.printStackTrace();
19.259 + System.exit(1);
19.260 + } catch (IOException ex) {
19.261 + ex.printStackTrace();
19.262 + } catch (Exception ex) {
19.263 + ex.printStackTrace();
19.264 + }
19.265 + }
19.266 +
19.267 + public static SelectionKey registerSelector(final Selector selector,
19.268 + final SocketChannel channel, final int op)
19.269 + throws CancelledKeyException, ClosedChannelException
19.270 + {
19.271 + // Register the selector at the channel, so that it will be notified
19.272 + // on the socket's events
19.273 + synchronized (RegisterGate) {
19.274 + // Wakeup the currently blocking reader/writer thread; we have locked
19.275 + // the RegisterGate to prevent the awakened thread to block again
19.276 + selector.wakeup();
19.277 +
19.278 + // Lock the selector to prevent the waiting worker threads going into
19.279 + // selector.select() which would block the selector.
19.280 + synchronized (selector) {
19.281 + return channel.register(selector, op, null);
19.282 + }
19.283 + }
19.284 + }
19.285 }
20.1 --- a/src/org/sonews/daemon/command/ArticleCommand.java Sun Aug 29 17:43:58 2010 +0200
20.2 +++ b/src/org/sonews/daemon/command/ArticleCommand.java Sun Aug 29 18:17:37 2010 +0200
20.3 @@ -33,142 +33,120 @@
20.4 public class ArticleCommand implements Command
20.5 {
20.6
20.7 - @Override
20.8 - public String[] getSupportedCommandStrings()
20.9 - {
20.10 - return new String[] {"ARTICLE", "BODY", "HEAD"};
20.11 - }
20.12 + @Override
20.13 + public String[] getSupportedCommandStrings()
20.14 + {
20.15 + return new String[] {"ARTICLE", "BODY", "HEAD"};
20.16 + }
20.17
20.18 - @Override
20.19 - public boolean hasFinished()
20.20 - {
20.21 - return true;
20.22 - }
20.23 + @Override
20.24 + public boolean hasFinished()
20.25 + {
20.26 + return true;
20.27 + }
20.28
20.29 - @Override
20.30 - public String impliedCapability()
20.31 - {
20.32 - return null;
20.33 - }
20.34 + @Override
20.35 + public String impliedCapability()
20.36 + {
20.37 + return null;
20.38 + }
20.39
20.40 - @Override
20.41 - public boolean isStateful()
20.42 - {
20.43 - return false;
20.44 - }
20.45 + @Override
20.46 + public boolean isStateful()
20.47 + {
20.48 + return false;
20.49 + }
20.50
20.51 - // TODO: Refactor this method to reduce its complexity!
20.52 - @Override
20.53 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
20.54 - throws IOException
20.55 - {
20.56 - final String[] command = line.split(" ");
20.57 -
20.58 - Article article = null;
20.59 - long artIndex = -1;
20.60 - if (command.length == 1)
20.61 - {
20.62 - article = conn.getCurrentArticle();
20.63 - if (article == null)
20.64 - {
20.65 - conn.println("420 no current article has been selected");
20.66 - return;
20.67 - }
20.68 - }
20.69 - else if (command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN))
20.70 - {
20.71 - // Message-ID
20.72 - article = Article.getByMessageID(command[1]);
20.73 - if (article == null)
20.74 - {
20.75 - conn.println("430 no such article found");
20.76 - return;
20.77 - }
20.78 - }
20.79 - else
20.80 - {
20.81 - // Message Number
20.82 - try
20.83 - {
20.84 - Channel currentGroup = conn.getCurrentChannel();
20.85 - if(currentGroup == null)
20.86 - {
20.87 - conn.println("400 no group selected");
20.88 - return;
20.89 - }
20.90 -
20.91 - artIndex = Long.parseLong(command[1]);
20.92 - article = currentGroup.getArticle(artIndex);
20.93 - }
20.94 - catch(NumberFormatException ex)
20.95 - {
20.96 - ex.printStackTrace();
20.97 - }
20.98 - catch(StorageBackendException ex)
20.99 - {
20.100 - ex.printStackTrace();
20.101 - }
20.102 + // TODO: Refactor this method to reduce its complexity!
20.103 + @Override
20.104 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
20.105 + throws IOException
20.106 + {
20.107 + final String[] command = line.split(" ");
20.108
20.109 - if (article == null)
20.110 - {
20.111 - conn.println("423 no such article number in this group");
20.112 - return;
20.113 - }
20.114 - conn.setCurrentArticle(article);
20.115 - }
20.116 + Article article = null;
20.117 + long artIndex = -1;
20.118 + if (command.length == 1) {
20.119 + article = conn.getCurrentArticle();
20.120 + if (article == null) {
20.121 + conn.println("420 no current article has been selected");
20.122 + return;
20.123 + }
20.124 + } else if (command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN)) {
20.125 + // Message-ID
20.126 + article = Article.getByMessageID(command[1]);
20.127 + if (article == null) {
20.128 + conn.println("430 no such article found");
20.129 + return;
20.130 + }
20.131 + } else {
20.132 + // Message Number
20.133 + try {
20.134 + Channel currentGroup = conn.getCurrentChannel();
20.135 + if (currentGroup == null) {
20.136 + conn.println("400 no group selected");
20.137 + return;
20.138 + }
20.139
20.140 - if(command[0].equalsIgnoreCase("ARTICLE"))
20.141 - {
20.142 - conn.println("220 " + artIndex + " " + article.getMessageID()
20.143 - + " article retrieved - head and body follow");
20.144 - conn.println(article.getHeaderSource());
20.145 - conn.println("");
20.146 - conn.println(article.getBody());
20.147 - conn.println(".");
20.148 - }
20.149 - else if(command[0].equalsIgnoreCase("BODY"))
20.150 - {
20.151 - conn.println("222 " + artIndex + " " + article.getMessageID() + " body");
20.152 - conn.println(article.getBody());
20.153 - conn.println(".");
20.154 - }
20.155 -
20.156 - /*
20.157 - * HEAD: This command is mandatory.
20.158 - *
20.159 - * Syntax
20.160 - * HEAD message-id
20.161 - * HEAD number
20.162 - * HEAD
20.163 - *
20.164 - * Responses
20.165 - *
20.166 - * First form (message-id specified)
20.167 - * 221 0|n message-id Headers follow (multi-line)
20.168 - * 430 No article with that message-id
20.169 - *
20.170 - * Second form (article number specified)
20.171 - * 221 n message-id Headers follow (multi-line)
20.172 - * 412 No newsgroup selected
20.173 - * 423 No article with that number
20.174 - *
20.175 - * Third form (current article number used)
20.176 - * 221 n message-id Headers follow (multi-line)
20.177 - * 412 No newsgroup selected
20.178 - * 420 Current article number is invalid
20.179 - *
20.180 - * Parameters
20.181 - * number Requested article number
20.182 - * n Returned article number
20.183 - * message-id Article message-id
20.184 - */
20.185 - else if(command[0].equalsIgnoreCase("HEAD"))
20.186 - {
20.187 - conn.println("221 " + artIndex + " " + article.getMessageID()
20.188 - + " Headers follow (multi-line)");
20.189 - conn.println(article.getHeaderSource());
20.190 - conn.println(".");
20.191 - }
20.192 - }
20.193 -
20.194 + artIndex = Long.parseLong(command[1]);
20.195 + article = currentGroup.getArticle(artIndex);
20.196 + } catch (NumberFormatException ex) {
20.197 + ex.printStackTrace();
20.198 + } catch (StorageBackendException ex) {
20.199 + ex.printStackTrace();
20.200 + }
20.201 +
20.202 + if (article == null) {
20.203 + conn.println("423 no such article number in this group");
20.204 + return;
20.205 + }
20.206 + conn.setCurrentArticle(article);
20.207 + }
20.208 +
20.209 + if (command[0].equalsIgnoreCase("ARTICLE")) {
20.210 + conn.println("220 " + artIndex + " " + article.getMessageID()
20.211 + + " article retrieved - head and body follow");
20.212 + conn.println(article.getHeaderSource());
20.213 + conn.println("");
20.214 + conn.println(article.getBody());
20.215 + conn.println(".");
20.216 + } else if (command[0].equalsIgnoreCase("BODY")) {
20.217 + conn.println("222 " + artIndex + " " + article.getMessageID() + " body");
20.218 + conn.println(article.getBody());
20.219 + conn.println(".");
20.220 + } /*
20.221 + * HEAD: This command is mandatory.
20.222 + *
20.223 + * Syntax
20.224 + * HEAD message-id
20.225 + * HEAD number
20.226 + * HEAD
20.227 + *
20.228 + * Responses
20.229 + *
20.230 + * First form (message-id specified)
20.231 + * 221 0|n message-id Headers follow (multi-line)
20.232 + * 430 No article with that message-id
20.233 + *
20.234 + * Second form (article number specified)
20.235 + * 221 n message-id Headers follow (multi-line)
20.236 + * 412 No newsgroup selected
20.237 + * 423 No article with that number
20.238 + *
20.239 + * Third form (current article number used)
20.240 + * 221 n message-id Headers follow (multi-line)
20.241 + * 412 No newsgroup selected
20.242 + * 420 Current article number is invalid
20.243 + *
20.244 + * Parameters
20.245 + * number Requested article number
20.246 + * n Returned article number
20.247 + * message-id Article message-id
20.248 + */ else if (command[0].equalsIgnoreCase("HEAD")) {
20.249 + conn.println("221 " + artIndex + " " + article.getMessageID()
20.250 + + " Headers follow (multi-line)");
20.251 + conn.println(article.getHeaderSource());
20.252 + conn.println(".");
20.253 + }
20.254 + }
20.255 }
21.1 --- a/src/org/sonews/daemon/command/CapabilitiesCommand.java Sun Aug 29 17:43:58 2010 +0200
21.2 +++ b/src/org/sonews/daemon/command/CapabilitiesCommand.java Sun Aug 29 18:17:37 2010 +0200
21.3 @@ -42,52 +42,49 @@
21.4 public class CapabilitiesCommand implements Command
21.5 {
21.6
21.7 - static final String[] CAPABILITIES = new String[]
21.8 - {
21.9 - "VERSION 2", // MUST be the first one; VERSION 2 refers to RFC3977
21.10 - "READER", // Server implements commands for reading
21.11 - "POST", // Server implements POST command
21.12 - "OVER" // Server implements OVER command
21.13 - };
21.14 + static final String[] CAPABILITIES = new String[] {
21.15 + "VERSION 2", // MUST be the first one; VERSION 2 refers to RFC3977
21.16 + "READER", // Server implements commands for reading
21.17 + "POST", // Server implements POST command
21.18 + "OVER" // Server implements OVER command
21.19 + };
21.20
21.21 - @Override
21.22 - public String[] getSupportedCommandStrings()
21.23 - {
21.24 - return new String[] {"CAPABILITIES"};
21.25 - }
21.26 + @Override
21.27 + public String[] getSupportedCommandStrings()
21.28 + {
21.29 + return new String[] {"CAPABILITIES"};
21.30 + }
21.31
21.32 - /**
21.33 - * First called after one call to processLine().
21.34 - * @return
21.35 - */
21.36 - @Override
21.37 - public boolean hasFinished()
21.38 - {
21.39 - return true;
21.40 - }
21.41 + /**
21.42 + * First called after one call to processLine().
21.43 + * @return
21.44 + */
21.45 + @Override
21.46 + public boolean hasFinished()
21.47 + {
21.48 + return true;
21.49 + }
21.50
21.51 - @Override
21.52 - public String impliedCapability()
21.53 - {
21.54 - return null;
21.55 - }
21.56 -
21.57 - @Override
21.58 - public boolean isStateful()
21.59 - {
21.60 - return false;
21.61 - }
21.62 + @Override
21.63 + public String impliedCapability()
21.64 + {
21.65 + return null;
21.66 + }
21.67
21.68 - @Override
21.69 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
21.70 - throws IOException
21.71 - {
21.72 - conn.println("101 Capabilities list:");
21.73 - for(String cap : CAPABILITIES)
21.74 - {
21.75 - conn.println(cap);
21.76 - }
21.77 - conn.println(".");
21.78 - }
21.79 + @Override
21.80 + public boolean isStateful()
21.81 + {
21.82 + return false;
21.83 + }
21.84
21.85 + @Override
21.86 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
21.87 + throws IOException
21.88 + {
21.89 + conn.println("101 Capabilities list:");
21.90 + for (String cap : CAPABILITIES) {
21.91 + conn.println(cap);
21.92 + }
21.93 + conn.println(".");
21.94 + }
21.95 }
22.1 --- a/src/org/sonews/daemon/command/Command.java Sun Aug 29 17:43:58 2010 +0200
22.2 +++ b/src/org/sonews/daemon/command/Command.java Sun Aug 29 18:17:37 2010 +0200
22.3 @@ -30,22 +30,21 @@
22.4 public interface Command
22.5 {
22.6
22.7 - /**
22.8 - * @return true if this instance can be reused.
22.9 - */
22.10 - boolean hasFinished();
22.11 + /**
22.12 + * @return true if this instance can be reused.
22.13 + */
22.14 + boolean hasFinished();
22.15
22.16 - /**
22.17 - * Returns capability string that is implied by this command class.
22.18 - * MAY return null if the command is required by the NNTP standard.
22.19 - */
22.20 - String impliedCapability();
22.21 + /**
22.22 + * Returns capability string that is implied by this command class.
22.23 + * MAY return null if the command is required by the NNTP standard.
22.24 + */
22.25 + String impliedCapability();
22.26
22.27 - boolean isStateful();
22.28 + boolean isStateful();
22.29
22.30 - String[] getSupportedCommandStrings();
22.31 + String[] getSupportedCommandStrings();
22.32
22.33 - void processLine(NNTPConnection conn, String line, byte[] rawLine)
22.34 - throws IOException, StorageBackendException;
22.35 -
22.36 + void processLine(NNTPConnection conn, String line, byte[] rawLine)
22.37 + throws IOException, StorageBackendException;
22.38 }
23.1 --- a/src/org/sonews/daemon/command/GroupCommand.java Sun Aug 29 17:43:58 2010 +0200
23.2 +++ b/src/org/sonews/daemon/command/GroupCommand.java Sun Aug 29 18:17:37 2010 +0200
23.3 @@ -48,55 +48,48 @@
23.4 public class GroupCommand implements Command
23.5 {
23.6
23.7 - @Override
23.8 - public String[] getSupportedCommandStrings()
23.9 - {
23.10 - return new String[]{"GROUP"};
23.11 - }
23.12 + @Override
23.13 + public String[] getSupportedCommandStrings()
23.14 + {
23.15 + return new String[] {"GROUP"};
23.16 + }
23.17
23.18 - @Override
23.19 - public boolean hasFinished()
23.20 - {
23.21 - return true;
23.22 - }
23.23 + @Override
23.24 + public boolean hasFinished()
23.25 + {
23.26 + return true;
23.27 + }
23.28
23.29 - @Override
23.30 - public String impliedCapability()
23.31 - {
23.32 - return null;
23.33 - }
23.34 + @Override
23.35 + public String impliedCapability()
23.36 + {
23.37 + return null;
23.38 + }
23.39
23.40 - @Override
23.41 - public boolean isStateful()
23.42 - {
23.43 - return true;
23.44 - }
23.45 -
23.46 - @Override
23.47 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
23.48 - throws IOException, StorageBackendException
23.49 - {
23.50 - final String[] command = line.split(" ");
23.51 + @Override
23.52 + public boolean isStateful()
23.53 + {
23.54 + return true;
23.55 + }
23.56
23.57 - Channel group;
23.58 - if(command.length >= 2)
23.59 - {
23.60 - group = Channel.getByName(command[1]);
23.61 - if(group == null || group.isDeleted())
23.62 - {
23.63 - conn.println("411 no such news group");
23.64 - }
23.65 - else
23.66 - {
23.67 - conn.setCurrentGroup(group);
23.68 - conn.println("211 " + group.getPostingsCount() + " " + group.getFirstArticleNumber()
23.69 - + " " + group.getLastArticleNumber() + " " + group.getName() + " group selected");
23.70 - }
23.71 - }
23.72 - else
23.73 - {
23.74 - conn.println("500 no group name given");
23.75 - }
23.76 - }
23.77 + @Override
23.78 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
23.79 + throws IOException, StorageBackendException
23.80 + {
23.81 + final String[] command = line.split(" ");
23.82
23.83 + Channel group;
23.84 + if (command.length >= 2) {
23.85 + group = Channel.getByName(command[1]);
23.86 + if (group == null || group.isDeleted()) {
23.87 + conn.println("411 no such news group");
23.88 + } else {
23.89 + conn.setCurrentGroup(group);
23.90 + conn.println("211 " + group.getPostingsCount() + " " + group.getFirstArticleNumber()
23.91 + + " " + group.getLastArticleNumber() + " " + group.getName() + " group selected");
23.92 + }
23.93 + } else {
23.94 + conn.println("500 no group name given");
23.95 + }
23.96 + }
23.97 }
24.1 --- a/src/org/sonews/daemon/command/HelpCommand.java Sun Aug 29 17:43:58 2010 +0200
24.2 +++ b/src/org/sonews/daemon/command/HelpCommand.java Sun Aug 29 18:17:37 2010 +0200
24.3 @@ -35,66 +35,56 @@
24.4 public class HelpCommand implements Command
24.5 {
24.6
24.7 - @Override
24.8 - public boolean hasFinished()
24.9 - {
24.10 - return true;
24.11 - }
24.12 + @Override
24.13 + public boolean hasFinished()
24.14 + {
24.15 + return true;
24.16 + }
24.17
24.18 - @Override
24.19 - public String impliedCapability()
24.20 - {
24.21 - return null;
24.22 - }
24.23 + @Override
24.24 + public String impliedCapability()
24.25 + {
24.26 + return null;
24.27 + }
24.28
24.29 - @Override
24.30 - public boolean isStateful()
24.31 - {
24.32 - return true;
24.33 - }
24.34 + @Override
24.35 + public boolean isStateful()
24.36 + {
24.37 + return true;
24.38 + }
24.39
24.40 - @Override
24.41 - public String[] getSupportedCommandStrings()
24.42 - {
24.43 - return new String[]{"HELP"};
24.44 - }
24.45 -
24.46 - @Override
24.47 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
24.48 - throws IOException
24.49 - {
24.50 - final String[] command = line.split(" ");
24.51 - conn.println("100 help text follows");
24.52 + @Override
24.53 + public String[] getSupportedCommandStrings()
24.54 + {
24.55 + return new String[] {"HELP"};
24.56 + }
24.57
24.58 - if(line.length() <= 1)
24.59 - {
24.60 - final String[] help = Resource
24.61 - .getAsString("helpers/helptext", true).split("\n");
24.62 - for(String hstr : help)
24.63 - {
24.64 - conn.println(hstr);
24.65 - }
24.66 + @Override
24.67 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
24.68 + throws IOException
24.69 + {
24.70 + final String[] command = line.split(" ");
24.71 + conn.println("100 help text follows");
24.72
24.73 - Set<String> commandNames = CommandSelector.getCommandNames();
24.74 - for(String cmdName : commandNames)
24.75 - {
24.76 - conn.println(cmdName);
24.77 - }
24.78 - }
24.79 - else
24.80 - {
24.81 - Command cmd = CommandSelector.getInstance().get(command[1]);
24.82 - if(cmd instanceof HelpfulCommand)
24.83 - {
24.84 - conn.println(((HelpfulCommand)cmd).getHelpString());
24.85 - }
24.86 - else
24.87 - {
24.88 - conn.println("No further help information available.");
24.89 - }
24.90 - }
24.91 -
24.92 - conn.println(".");
24.93 - }
24.94 -
24.95 + if (line.length() <= 1) {
24.96 + final String[] help = Resource.getAsString("helpers/helptext", true).split("\n");
24.97 + for (String hstr : help) {
24.98 + conn.println(hstr);
24.99 + }
24.100 +
24.101 + Set<String> commandNames = CommandSelector.getCommandNames();
24.102 + for (String cmdName : commandNames) {
24.103 + conn.println(cmdName);
24.104 + }
24.105 + } else {
24.106 + Command cmd = CommandSelector.getInstance().get(command[1]);
24.107 + if (cmd instanceof HelpfulCommand) {
24.108 + conn.println(((HelpfulCommand) cmd).getHelpString());
24.109 + } else {
24.110 + conn.println("No further help information available.");
24.111 + }
24.112 + }
24.113 +
24.114 + conn.println(".");
24.115 + }
24.116 }
25.1 --- a/src/org/sonews/daemon/command/HelpfulCommand.java Sun Aug 29 17:43:58 2010 +0200
25.2 +++ b/src/org/sonews/daemon/command/HelpfulCommand.java Sun Aug 29 18:17:37 2010 +0200
25.3 @@ -26,10 +26,9 @@
25.4 public interface HelpfulCommand extends Command
25.5 {
25.6
25.7 - /**
25.8 - * @return A short description of this command, that is
25.9 - * used within the output of the HELP command.
25.10 - */
25.11 - String getHelpString();
25.12 -
25.13 + /**
25.14 + * @return A short description of this command, that is
25.15 + * used within the output of the HELP command.
25.16 + */
25.17 + String getHelpString();
25.18 }
26.1 --- a/src/org/sonews/daemon/command/ListCommand.java Sun Aug 29 17:43:58 2010 +0200
26.2 +++ b/src/org/sonews/daemon/command/ListCommand.java Sun Aug 29 18:17:37 2010 +0200
26.3 @@ -37,117 +37,93 @@
26.4 public class ListCommand implements Command
26.5 {
26.6
26.7 - @Override
26.8 - public String[] getSupportedCommandStrings()
26.9 - {
26.10 - return new String[]{"LIST"};
26.11 - }
26.12 + @Override
26.13 + public String[] getSupportedCommandStrings()
26.14 + {
26.15 + return new String[] {"LIST"};
26.16 + }
26.17
26.18 - @Override
26.19 - public boolean hasFinished()
26.20 - {
26.21 - return true;
26.22 - }
26.23 + @Override
26.24 + public boolean hasFinished()
26.25 + {
26.26 + return true;
26.27 + }
26.28
26.29 - @Override
26.30 - public String impliedCapability()
26.31 - {
26.32 - return null;
26.33 - }
26.34 + @Override
26.35 + public String impliedCapability()
26.36 + {
26.37 + return null;
26.38 + }
26.39
26.40 - @Override
26.41 - public boolean isStateful()
26.42 - {
26.43 - return false;
26.44 - }
26.45 -
26.46 - @Override
26.47 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
26.48 - throws IOException, StorageBackendException
26.49 - {
26.50 - final String[] command = line.split(" ");
26.51 -
26.52 - if(command.length >= 2)
26.53 - {
26.54 - if(command[1].equalsIgnoreCase("OVERVIEW.FMT"))
26.55 - {
26.56 - conn.println("215 information follows");
26.57 - conn.println("Subject:\nFrom:\nDate:\nMessage-ID:\nReferences:\nBytes:\nLines:\nXref");
26.58 - conn.println(".");
26.59 - }
26.60 - else if(command[1].equalsIgnoreCase("NEWSGROUPS"))
26.61 - {
26.62 - conn.println("215 information follows");
26.63 - final List<Channel> list = Channel.getAll();
26.64 - for (Channel g : list)
26.65 - {
26.66 - conn.println(g.getName() + "\t" + "-");
26.67 - }
26.68 - conn.println(".");
26.69 - }
26.70 - else if(command[1].equalsIgnoreCase("SUBSCRIPTIONS"))
26.71 - {
26.72 - conn.println("215 information follows");
26.73 - conn.println(".");
26.74 - }
26.75 - else if(command[1].equalsIgnoreCase("EXTENSIONS"))
26.76 - {
26.77 - conn.println("202 Supported NNTP extensions.");
26.78 - conn.println("LISTGROUP");
26.79 - conn.println("XDAEMON");
26.80 - conn.println("XPAT");
26.81 - conn.println(".");
26.82 - }
26.83 - else if(command[1].equalsIgnoreCase("ACTIVE"))
26.84 - {
26.85 - String pattern = command.length == 2
26.86 - ? null : command[2].replace("*", "\\w*");
26.87 - printGroupInfo(conn, pattern);
26.88 - }
26.89 - else
26.90 - {
26.91 - conn.println("500 unknown argument to LIST command");
26.92 - }
26.93 - }
26.94 - else
26.95 - {
26.96 - printGroupInfo(conn, null);
26.97 - }
26.98 - }
26.99 + @Override
26.100 + public boolean isStateful()
26.101 + {
26.102 + return false;
26.103 + }
26.104
26.105 - private void printGroupInfo(NNTPConnection conn, String pattern)
26.106 - throws IOException, StorageBackendException
26.107 - {
26.108 - final List<Channel> groups = Channel.getAll();
26.109 - if(groups != null)
26.110 - {
26.111 - conn.println("215 list of newsgroups follows");
26.112 - for(Channel g : groups)
26.113 - {
26.114 - try
26.115 - {
26.116 - Matcher matcher = pattern == null ?
26.117 - null : Pattern.compile(pattern).matcher(g.getName());
26.118 - if(!g.isDeleted() &&
26.119 - (matcher == null || matcher.find()))
26.120 - {
26.121 - String writeable = g.isWriteable() ? " y" : " n";
26.122 - // Indeed first the higher article number then the lower
26.123 - conn.println(g.getName() + " " + g.getLastArticleNumber() + " "
26.124 - + g.getFirstArticleNumber() + writeable);
26.125 - }
26.126 - }
26.127 - catch(PatternSyntaxException ex)
26.128 - {
26.129 - Log.get().info(ex.toString());
26.130 - }
26.131 - }
26.132 - conn.println(".");
26.133 - }
26.134 - else
26.135 - {
26.136 - conn.println("500 server backend malfunction");
26.137 - }
26.138 - }
26.139 + @Override
26.140 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
26.141 + throws IOException, StorageBackendException
26.142 + {
26.143 + final String[] command = line.split(" ");
26.144
26.145 + if (command.length >= 2) {
26.146 + if (command[1].equalsIgnoreCase("OVERVIEW.FMT")) {
26.147 + conn.println("215 information follows");
26.148 + conn.println("Subject:\nFrom:\nDate:\nMessage-ID:\nReferences:\nBytes:\nLines:\nXref");
26.149 + conn.println(".");
26.150 + } else if (command[1].equalsIgnoreCase("NEWSGROUPS")) {
26.151 + conn.println("215 information follows");
26.152 + final List<Channel> list = Channel.getAll();
26.153 + for (Channel g : list) {
26.154 + conn.println(g.getName() + "\t" + "-");
26.155 + }
26.156 + conn.println(".");
26.157 + } else if (command[1].equalsIgnoreCase("SUBSCRIPTIONS")) {
26.158 + conn.println("215 information follows");
26.159 + conn.println(".");
26.160 + } else if (command[1].equalsIgnoreCase("EXTENSIONS")) {
26.161 + conn.println("202 Supported NNTP extensions.");
26.162 + conn.println("LISTGROUP");
26.163 + conn.println("XDAEMON");
26.164 + conn.println("XPAT");
26.165 + conn.println(".");
26.166 + } else if (command[1].equalsIgnoreCase("ACTIVE")) {
26.167 + String pattern = command.length == 2
26.168 + ? null : command[2].replace("*", "\\w*");
26.169 + printGroupInfo(conn, pattern);
26.170 + } else {
26.171 + conn.println("500 unknown argument to LIST command");
26.172 + }
26.173 + } else {
26.174 + printGroupInfo(conn, null);
26.175 + }
26.176 + }
26.177 +
26.178 + private void printGroupInfo(NNTPConnection conn, String pattern)
26.179 + throws IOException, StorageBackendException
26.180 + {
26.181 + final List<Channel> groups = Channel.getAll();
26.182 + if (groups != null) {
26.183 + conn.println("215 list of newsgroups follows");
26.184 + for (Channel g : groups) {
26.185 + try {
26.186 + Matcher matcher = pattern == null
26.187 + ? null : Pattern.compile(pattern).matcher(g.getName());
26.188 + if (!g.isDeleted()
26.189 + && (matcher == null || matcher.find())) {
26.190 + String writeable = g.isWriteable() ? " y" : " n";
26.191 + // Indeed first the higher article number then the lower
26.192 + conn.println(g.getName() + " " + g.getLastArticleNumber() + " "
26.193 + + g.getFirstArticleNumber() + writeable);
26.194 + }
26.195 + } catch (PatternSyntaxException ex) {
26.196 + Log.get().info(ex.toString());
26.197 + }
26.198 + }
26.199 + conn.println(".");
26.200 + } else {
26.201 + conn.println("500 server backend malfunction");
26.202 + }
26.203 + }
26.204 }
27.1 --- a/src/org/sonews/daemon/command/ListGroupCommand.java Sun Aug 29 17:43:58 2010 +0200
27.2 +++ b/src/org/sonews/daemon/command/ListGroupCommand.java Sun Aug 29 18:17:37 2010 +0200
27.3 @@ -33,62 +33,56 @@
27.4 public class ListGroupCommand implements Command
27.5 {
27.6
27.7 - @Override
27.8 - public String[] getSupportedCommandStrings()
27.9 - {
27.10 - return new String[]{"LISTGROUP"};
27.11 - }
27.12 + @Override
27.13 + public String[] getSupportedCommandStrings()
27.14 + {
27.15 + return new String[] {"LISTGROUP"};
27.16 + }
27.17
27.18 - @Override
27.19 - public boolean hasFinished()
27.20 - {
27.21 - return true;
27.22 - }
27.23 + @Override
27.24 + public boolean hasFinished()
27.25 + {
27.26 + return true;
27.27 + }
27.28
27.29 - @Override
27.30 - public String impliedCapability()
27.31 - {
27.32 - return null;
27.33 - }
27.34 + @Override
27.35 + public String impliedCapability()
27.36 + {
27.37 + return null;
27.38 + }
27.39
27.40 - @Override
27.41 - public boolean isStateful()
27.42 - {
27.43 - return false;
27.44 - }
27.45 + @Override
27.46 + public boolean isStateful()
27.47 + {
27.48 + return false;
27.49 + }
27.50
27.51 - @Override
27.52 - public void processLine(NNTPConnection conn, final String commandName, byte[] raw)
27.53 - throws IOException, StorageBackendException
27.54 - {
27.55 - final String[] command = commandName.split(" ");
27.56 + @Override
27.57 + public void processLine(NNTPConnection conn, final String commandName, byte[] raw)
27.58 + throws IOException, StorageBackendException
27.59 + {
27.60 + final String[] command = commandName.split(" ");
27.61
27.62 - Channel group;
27.63 - if(command.length >= 2)
27.64 - {
27.65 - group = Channel.getByName(command[1]);
27.66 - }
27.67 - else
27.68 - {
27.69 - group = conn.getCurrentChannel();
27.70 - }
27.71 + Channel group;
27.72 + if (command.length >= 2) {
27.73 + group = Channel.getByName(command[1]);
27.74 + } else {
27.75 + group = conn.getCurrentChannel();
27.76 + }
27.77
27.78 - if (group == null)
27.79 - {
27.80 - conn.println("412 no group selected; use GROUP <group> command");
27.81 - return;
27.82 - }
27.83 + if (group == null) {
27.84 + conn.println("412 no group selected; use GROUP <group> command");
27.85 + return;
27.86 + }
27.87
27.88 - List<Long> ids = group.getArticleNumbers();
27.89 - conn.println("211 " + ids.size() + " " +
27.90 - group.getFirstArticleNumber() + " " +
27.91 - group.getLastArticleNumber() + " list of article numbers follow");
27.92 - for(long id : ids)
27.93 - {
27.94 - // One index number per line
27.95 - conn.println(Long.toString(id));
27.96 - }
27.97 - conn.println(".");
27.98 - }
27.99 -
27.100 + List<Long> ids = group.getArticleNumbers();
27.101 + conn.println("211 " + ids.size() + " "
27.102 + + group.getFirstArticleNumber() + " "
27.103 + + group.getLastArticleNumber() + " list of article numbers follow");
27.104 + for (long id : ids) {
27.105 + // One index number per line
27.106 + conn.println(Long.toString(id));
27.107 + }
27.108 + conn.println(".");
27.109 + }
27.110 }
28.1 --- a/src/org/sonews/daemon/command/ModeReaderCommand.java Sun Aug 29 17:43:58 2010 +0200
28.2 +++ b/src/org/sonews/daemon/command/ModeReaderCommand.java Sun Aug 29 18:17:37 2010 +0200
28.3 @@ -30,43 +30,39 @@
28.4 */
28.5 public class ModeReaderCommand implements Command
28.6 {
28.7 -
28.8 - @Override
28.9 - public String[] getSupportedCommandStrings()
28.10 - {
28.11 - return new String[]{"MODE"};
28.12 - }
28.13
28.14 - @Override
28.15 - public boolean hasFinished()
28.16 - {
28.17 - return true;
28.18 - }
28.19 + @Override
28.20 + public String[] getSupportedCommandStrings()
28.21 + {
28.22 + return new String[] {"MODE"};
28.23 + }
28.24
28.25 - @Override
28.26 - public String impliedCapability()
28.27 - {
28.28 - return null;
28.29 - }
28.30 + @Override
28.31 + public boolean hasFinished()
28.32 + {
28.33 + return true;
28.34 + }
28.35
28.36 - @Override
28.37 - public boolean isStateful()
28.38 - {
28.39 - return false;
28.40 - }
28.41 + @Override
28.42 + public String impliedCapability()
28.43 + {
28.44 + return null;
28.45 + }
28.46
28.47 - @Override
28.48 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
28.49 - throws IOException, StorageBackendException
28.50 - {
28.51 - if(line.equalsIgnoreCase("MODE READER"))
28.52 - {
28.53 - conn.println("200 hello you can post");
28.54 - }
28.55 - else
28.56 - {
28.57 - conn.println("500 I do not know this mode command");
28.58 - }
28.59 - }
28.60 + @Override
28.61 + public boolean isStateful()
28.62 + {
28.63 + return false;
28.64 + }
28.65
28.66 + @Override
28.67 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
28.68 + throws IOException, StorageBackendException
28.69 + {
28.70 + if (line.equalsIgnoreCase("MODE READER")) {
28.71 + conn.println("200 hello you can post");
28.72 + } else {
28.73 + conn.println("500 I do not know this mode command");
28.74 + }
28.75 + }
28.76 }
29.1 --- a/src/org/sonews/daemon/command/NewGroupsCommand.java Sun Aug 29 17:43:58 2010 +0200
29.2 +++ b/src/org/sonews/daemon/command/NewGroupsCommand.java Sun Aug 29 18:17:37 2010 +0200
29.3 @@ -31,48 +31,44 @@
29.4 public class NewGroupsCommand implements Command
29.5 {
29.6
29.7 - @Override
29.8 - public String[] getSupportedCommandStrings()
29.9 - {
29.10 - return new String[]{"NEWGROUPS"};
29.11 - }
29.12 + @Override
29.13 + public String[] getSupportedCommandStrings()
29.14 + {
29.15 + return new String[] {"NEWGROUPS"};
29.16 + }
29.17
29.18 - @Override
29.19 - public boolean hasFinished()
29.20 - {
29.21 - return true;
29.22 - }
29.23 + @Override
29.24 + public boolean hasFinished()
29.25 + {
29.26 + return true;
29.27 + }
29.28
29.29 - @Override
29.30 - public String impliedCapability()
29.31 - {
29.32 - return null;
29.33 - }
29.34 + @Override
29.35 + public String impliedCapability()
29.36 + {
29.37 + return null;
29.38 + }
29.39
29.40 - @Override
29.41 - public boolean isStateful()
29.42 - {
29.43 - return false;
29.44 - }
29.45 + @Override
29.46 + public boolean isStateful()
29.47 + {
29.48 + return false;
29.49 + }
29.50
29.51 - @Override
29.52 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
29.53 - throws IOException, StorageBackendException
29.54 - {
29.55 - final String[] command = line.split(" ");
29.56 + @Override
29.57 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
29.58 + throws IOException, StorageBackendException
29.59 + {
29.60 + final String[] command = line.split(" ");
29.61
29.62 - if(command.length == 3)
29.63 - {
29.64 - conn.println("231 list of new newsgroups follows");
29.65 + if (command.length == 3) {
29.66 + conn.println("231 list of new newsgroups follows");
29.67
29.68 - // Currently we do not store a group's creation date;
29.69 - // so we return an empty list which is a valid response
29.70 - conn.println(".");
29.71 - }
29.72 - else
29.73 - {
29.74 - conn.println("500 invalid command usage");
29.75 - }
29.76 - }
29.77 -
29.78 + // Currently we do not store a group's creation date;
29.79 + // so we return an empty list which is a valid response
29.80 + conn.println(".");
29.81 + } else {
29.82 + conn.println("500 invalid command usage");
29.83 + }
29.84 + }
29.85 }
30.1 --- a/src/org/sonews/daemon/command/NextPrevCommand.java Sun Aug 29 17:43:58 2010 +0200
30.2 +++ b/src/org/sonews/daemon/command/NextPrevCommand.java Sun Aug 29 18:17:37 2010 +0200
30.3 @@ -33,84 +33,73 @@
30.4 public class NextPrevCommand implements Command
30.5 {
30.6
30.7 - @Override
30.8 - public String[] getSupportedCommandStrings()
30.9 - {
30.10 - return new String[]{"NEXT", "PREV"};
30.11 - }
30.12 + @Override
30.13 + public String[] getSupportedCommandStrings()
30.14 + {
30.15 + return new String[] {"NEXT", "PREV"};
30.16 + }
30.17
30.18 - @Override
30.19 - public boolean hasFinished()
30.20 - {
30.21 - return true;
30.22 - }
30.23 + @Override
30.24 + public boolean hasFinished()
30.25 + {
30.26 + return true;
30.27 + }
30.28
30.29 - @Override
30.30 - public String impliedCapability()
30.31 - {
30.32 - return null;
30.33 - }
30.34 + @Override
30.35 + public String impliedCapability()
30.36 + {
30.37 + return null;
30.38 + }
30.39
30.40 - @Override
30.41 - public boolean isStateful()
30.42 - {
30.43 - return false;
30.44 - }
30.45 + @Override
30.46 + public boolean isStateful()
30.47 + {
30.48 + return false;
30.49 + }
30.50
30.51 - @Override
30.52 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
30.53 - throws IOException, StorageBackendException
30.54 - {
30.55 - final Article currA = conn.getCurrentArticle();
30.56 - final Channel currG = conn.getCurrentChannel();
30.57 -
30.58 - if (currA == null)
30.59 - {
30.60 - conn.println("420 no current article has been selected");
30.61 - return;
30.62 - }
30.63 -
30.64 - if (currG == null)
30.65 - {
30.66 - conn.println("412 no newsgroup selected");
30.67 - return;
30.68 - }
30.69 -
30.70 - final String[] command = line.split(" ");
30.71 + @Override
30.72 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
30.73 + throws IOException, StorageBackendException
30.74 + {
30.75 + final Article currA = conn.getCurrentArticle();
30.76 + final Channel currG = conn.getCurrentChannel();
30.77
30.78 - if(command[0].equalsIgnoreCase("NEXT"))
30.79 - {
30.80 - selectNewArticle(conn, currA, currG, 1);
30.81 - }
30.82 - else if(command[0].equalsIgnoreCase("PREV"))
30.83 - {
30.84 - selectNewArticle(conn, currA, currG, -1);
30.85 - }
30.86 - else
30.87 - {
30.88 - conn.println("500 internal server error");
30.89 - }
30.90 - }
30.91 -
30.92 - private void selectNewArticle(NNTPConnection conn, Article article, Channel grp,
30.93 - final int delta)
30.94 - throws IOException, StorageBackendException
30.95 - {
30.96 - assert article != null;
30.97 + if (currA == null) {
30.98 + conn.println("420 no current article has been selected");
30.99 + return;
30.100 + }
30.101
30.102 - article = grp.getArticle(grp.getIndexOf(article) + delta);
30.103 + if (currG == null) {
30.104 + conn.println("412 no newsgroup selected");
30.105 + return;
30.106 + }
30.107
30.108 - if(article == null)
30.109 - {
30.110 - conn.println("421 no next article in this group");
30.111 - }
30.112 - else
30.113 - {
30.114 - conn.setCurrentArticle(article);
30.115 - conn.println("223 " + conn.getCurrentChannel().getIndexOf(article)
30.116 - + " " + article.getMessageID()
30.117 - + " article retrieved - request text separately");
30.118 - }
30.119 - }
30.120 + final String[] command = line.split(" ");
30.121
30.122 + if (command[0].equalsIgnoreCase("NEXT")) {
30.123 + selectNewArticle(conn, currA, currG, 1);
30.124 + } else if (command[0].equalsIgnoreCase("PREV")) {
30.125 + selectNewArticle(conn, currA, currG, -1);
30.126 + } else {
30.127 + conn.println("500 internal server error");
30.128 + }
30.129 + }
30.130 +
30.131 + private void selectNewArticle(NNTPConnection conn, Article article, Channel grp,
30.132 + final int delta)
30.133 + throws IOException, StorageBackendException
30.134 + {
30.135 + assert article != null;
30.136 +
30.137 + article = grp.getArticle(grp.getIndexOf(article) + delta);
30.138 +
30.139 + if (article == null) {
30.140 + conn.println("421 no next article in this group");
30.141 + } else {
30.142 + conn.setCurrentArticle(article);
30.143 + conn.println("223 " + conn.getCurrentChannel().getIndexOf(article)
30.144 + + " " + article.getMessageID()
30.145 + + " article retrieved - request text separately");
30.146 + }
30.147 + }
30.148 }
31.1 --- a/src/org/sonews/daemon/command/OverCommand.java Sun Aug 29 17:43:58 2010 +0200
31.2 +++ b/src/org/sonews/daemon/command/OverCommand.java Sun Aug 29 18:17:37 2010 +0200
31.3 @@ -109,186 +109,153 @@
31.4 public class OverCommand implements Command
31.5 {
31.6
31.7 - public static final int MAX_LINES_PER_DBREQUEST = 200;
31.8 + public static final int MAX_LINES_PER_DBREQUEST = 200;
31.9
31.10 - @Override
31.11 - public String[] getSupportedCommandStrings()
31.12 - {
31.13 - return new String[]{"OVER", "XOVER"};
31.14 - }
31.15 + @Override
31.16 + public String[] getSupportedCommandStrings()
31.17 + {
31.18 + return new String[] {"OVER", "XOVER"};
31.19 + }
31.20
31.21 - @Override
31.22 - public boolean hasFinished()
31.23 - {
31.24 - return true;
31.25 - }
31.26 + @Override
31.27 + public boolean hasFinished()
31.28 + {
31.29 + return true;
31.30 + }
31.31
31.32 - @Override
31.33 - public String impliedCapability()
31.34 - {
31.35 - return null;
31.36 - }
31.37 + @Override
31.38 + public String impliedCapability()
31.39 + {
31.40 + return null;
31.41 + }
31.42
31.43 - @Override
31.44 - public boolean isStateful()
31.45 - {
31.46 - return false;
31.47 - }
31.48 + @Override
31.49 + public boolean isStateful()
31.50 + {
31.51 + return false;
31.52 + }
31.53
31.54 - @Override
31.55 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
31.56 - throws IOException, StorageBackendException
31.57 - {
31.58 - if(conn.getCurrentChannel() == null)
31.59 - {
31.60 - conn.println("412 no newsgroup selected");
31.61 - }
31.62 - else
31.63 - {
31.64 - String[] command = line.split(" ");
31.65 + @Override
31.66 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
31.67 + throws IOException, StorageBackendException
31.68 + {
31.69 + if (conn.getCurrentChannel() == null) {
31.70 + conn.println("412 no newsgroup selected");
31.71 + } else {
31.72 + String[] command = line.split(" ");
31.73
31.74 - // If no parameter was specified, show information about
31.75 - // the currently selected article(s)
31.76 - if(command.length == 1)
31.77 - {
31.78 - final Article art = conn.getCurrentArticle();
31.79 - if(art == null)
31.80 - {
31.81 - conn.println("420 no article(s) selected");
31.82 - return;
31.83 - }
31.84 + // If no parameter was specified, show information about
31.85 + // the currently selected article(s)
31.86 + if (command.length == 1) {
31.87 + final Article art = conn.getCurrentArticle();
31.88 + if (art == null) {
31.89 + conn.println("420 no article(s) selected");
31.90 + return;
31.91 + }
31.92
31.93 - conn.println(buildOverview(art, -1));
31.94 - }
31.95 - // otherwise print information about the specified range
31.96 - else
31.97 - {
31.98 - long artStart;
31.99 - long artEnd = conn.getCurrentChannel().getLastArticleNumber();
31.100 - String[] nums = command[1].split("-");
31.101 - if(nums.length >= 1)
31.102 - {
31.103 - try
31.104 - {
31.105 - artStart = Integer.parseInt(nums[0]);
31.106 - }
31.107 - catch(NumberFormatException e)
31.108 - {
31.109 - Log.get().info(e.getMessage());
31.110 - artStart = Integer.parseInt(command[1]);
31.111 - }
31.112 - }
31.113 - else
31.114 - {
31.115 - artStart = conn.getCurrentChannel().getFirstArticleNumber();
31.116 - }
31.117 + conn.println(buildOverview(art, -1));
31.118 + } // otherwise print information about the specified range
31.119 + else {
31.120 + long artStart;
31.121 + long artEnd = conn.getCurrentChannel().getLastArticleNumber();
31.122 + String[] nums = command[1].split("-");
31.123 + if (nums.length >= 1) {
31.124 + try {
31.125 + artStart = Integer.parseInt(nums[0]);
31.126 + } catch (NumberFormatException e) {
31.127 + Log.get().info(e.getMessage());
31.128 + artStart = Integer.parseInt(command[1]);
31.129 + }
31.130 + } else {
31.131 + artStart = conn.getCurrentChannel().getFirstArticleNumber();
31.132 + }
31.133
31.134 - if(nums.length >=2)
31.135 - {
31.136 - try
31.137 - {
31.138 - artEnd = Integer.parseInt(nums[1]);
31.139 - }
31.140 - catch(NumberFormatException e)
31.141 - {
31.142 - e.printStackTrace();
31.143 - }
31.144 - }
31.145 + if (nums.length >= 2) {
31.146 + try {
31.147 + artEnd = Integer.parseInt(nums[1]);
31.148 + } catch (NumberFormatException e) {
31.149 + e.printStackTrace();
31.150 + }
31.151 + }
31.152
31.153 - if(artStart > artEnd)
31.154 - {
31.155 - if(command[0].equalsIgnoreCase("OVER"))
31.156 - {
31.157 - conn.println("423 no articles in that range");
31.158 - }
31.159 - else
31.160 - {
31.161 - conn.println("224 (empty) overview information follows:");
31.162 - conn.println(".");
31.163 - }
31.164 - }
31.165 - else
31.166 - {
31.167 - for(long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST)
31.168 - {
31.169 - long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
31.170 - List<Pair<Long, ArticleHead>> articleHeads = conn.getCurrentChannel()
31.171 - .getArticleHeads(n, nEnd);
31.172 - if(articleHeads.isEmpty() && n == artStart
31.173 - && command[0].equalsIgnoreCase("OVER"))
31.174 - {
31.175 - // This reply is only valid for OVER, not for XOVER command
31.176 - conn.println("423 no articles in that range");
31.177 - return;
31.178 - }
31.179 - else if(n == artStart)
31.180 - {
31.181 - // XOVER replies this although there is no data available
31.182 - conn.println("224 overview information follows");
31.183 - }
31.184 + if (artStart > artEnd) {
31.185 + if (command[0].equalsIgnoreCase("OVER")) {
31.186 + conn.println("423 no articles in that range");
31.187 + } else {
31.188 + conn.println("224 (empty) overview information follows:");
31.189 + conn.println(".");
31.190 + }
31.191 + } else {
31.192 + for (long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST) {
31.193 + long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
31.194 + List<Pair<Long, ArticleHead>> articleHeads = conn.getCurrentChannel().getArticleHeads(n, nEnd);
31.195 + if (articleHeads.isEmpty() && n == artStart
31.196 + && command[0].equalsIgnoreCase("OVER")) {
31.197 + // This reply is only valid for OVER, not for XOVER command
31.198 + conn.println("423 no articles in that range");
31.199 + return;
31.200 + } else if (n == artStart) {
31.201 + // XOVER replies this although there is no data available
31.202 + conn.println("224 overview information follows");
31.203 + }
31.204
31.205 - for(Pair<Long, ArticleHead> article : articleHeads)
31.206 - {
31.207 - String overview = buildOverview(article.getB(), article.getA());
31.208 - conn.println(overview);
31.209 - }
31.210 - } // for
31.211 - conn.println(".");
31.212 - }
31.213 - }
31.214 - }
31.215 - }
31.216 -
31.217 - private String buildOverview(ArticleHead art, long nr)
31.218 - {
31.219 - StringBuilder overview = new StringBuilder();
31.220 - overview.append(nr);
31.221 - overview.append('\t');
31.222 + for (Pair<Long, ArticleHead> article : articleHeads) {
31.223 + String overview = buildOverview(article.getB(), article.getA());
31.224 + conn.println(overview);
31.225 + }
31.226 + } // for
31.227 + conn.println(".");
31.228 + }
31.229 + }
31.230 + }
31.231 + }
31.232
31.233 - String subject = art.getHeader(Headers.SUBJECT)[0];
31.234 - if("".equals(subject))
31.235 - {
31.236 - subject = "<empty>";
31.237 - }
31.238 - overview.append(escapeString(subject));
31.239 - overview.append('\t');
31.240 + private String buildOverview(ArticleHead art, long nr)
31.241 + {
31.242 + StringBuilder overview = new StringBuilder();
31.243 + overview.append(nr);
31.244 + overview.append('\t');
31.245
31.246 - overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
31.247 - overview.append('\t');
31.248 - overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
31.249 - overview.append('\t');
31.250 - overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
31.251 - overview.append('\t');
31.252 - overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
31.253 - overview.append('\t');
31.254 + String subject = art.getHeader(Headers.SUBJECT)[0];
31.255 + if ("".equals(subject)) {
31.256 + subject = "<empty>";
31.257 + }
31.258 + overview.append(escapeString(subject));
31.259 + overview.append('\t');
31.260
31.261 - String bytes = art.getHeader(Headers.BYTES)[0];
31.262 - if("".equals(bytes))
31.263 - {
31.264 - bytes = "0";
31.265 - }
31.266 - overview.append(escapeString(bytes));
31.267 - overview.append('\t');
31.268 + overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
31.269 + overview.append('\t');
31.270 + overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
31.271 + overview.append('\t');
31.272 + overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
31.273 + overview.append('\t');
31.274 + overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
31.275 + overview.append('\t');
31.276
31.277 - String lines = art.getHeader(Headers.LINES)[0];
31.278 - if("".equals(lines))
31.279 - {
31.280 - lines = "0";
31.281 - }
31.282 - overview.append(escapeString(lines));
31.283 - overview.append('\t');
31.284 - overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
31.285 + String bytes = art.getHeader(Headers.BYTES)[0];
31.286 + if ("".equals(bytes)) {
31.287 + bytes = "0";
31.288 + }
31.289 + overview.append(escapeString(bytes));
31.290 + overview.append('\t');
31.291
31.292 - // Remove trailing tabs if some data is empty
31.293 - return overview.toString().trim();
31.294 - }
31.295 -
31.296 - private String escapeString(String str)
31.297 - {
31.298 - String nstr = str.replace("\r", "");
31.299 - nstr = nstr.replace('\n', ' ');
31.300 - nstr = nstr.replace('\t', ' ');
31.301 - return nstr.trim();
31.302 - }
31.303 -
31.304 + String lines = art.getHeader(Headers.LINES)[0];
31.305 + if ("".equals(lines)) {
31.306 + lines = "0";
31.307 + }
31.308 + overview.append(escapeString(lines));
31.309 + overview.append('\t');
31.310 + overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
31.311 +
31.312 + // Remove trailing tabs if some data is empty
31.313 + return overview.toString().trim();
31.314 + }
31.315 +
31.316 + private String escapeString(String str)
31.317 + {
31.318 + String nstr = str.replace("\r", "");
31.319 + nstr = nstr.replace('\n', ' ');
31.320 + nstr = nstr.replace('\t', ' ');
31.321 + return nstr.trim();
31.322 + }
31.323 }
32.1 --- a/src/org/sonews/daemon/command/PostCommand.java Sun Aug 29 17:43:58 2010 +0200
32.2 +++ b/src/org/sonews/daemon/command/PostCommand.java Sun Aug 29 18:17:37 2010 +0200
32.3 @@ -47,286 +47,237 @@
32.4 */
32.5 public class PostCommand implements Command
32.6 {
32.7 -
32.8 - private final Article article = new Article();
32.9 - private int lineCount = 0;
32.10 - private long bodySize = 0;
32.11 - private InternetHeaders headers = null;
32.12 - private long maxBodySize =
32.13 - Config.inst().get(Config.ARTICLE_MAXSIZE, 128) * 1024L; // Size in bytes
32.14 - private PostState state = PostState.WaitForLineOne;
32.15 - private final ByteArrayOutputStream bufBody = new ByteArrayOutputStream();
32.16 - private final StringBuilder strHead = new StringBuilder();
32.17
32.18 - @Override
32.19 - public String[] getSupportedCommandStrings()
32.20 - {
32.21 - return new String[]{"POST"};
32.22 - }
32.23 + private final Article article = new Article();
32.24 + private int lineCount = 0;
32.25 + private long bodySize = 0;
32.26 + private InternetHeaders headers = null;
32.27 + private long maxBodySize =
32.28 + Config.inst().get(Config.ARTICLE_MAXSIZE, 128) * 1024L; // Size in bytes
32.29 + private PostState state = PostState.WaitForLineOne;
32.30 + private final ByteArrayOutputStream bufBody = new ByteArrayOutputStream();
32.31 + private final StringBuilder strHead = new StringBuilder();
32.32
32.33 - @Override
32.34 - public boolean hasFinished()
32.35 - {
32.36 - return this.state == PostState.Finished;
32.37 - }
32.38 + @Override
32.39 + public String[] getSupportedCommandStrings()
32.40 + {
32.41 + return new String[] {"POST"};
32.42 + }
32.43
32.44 - @Override
32.45 - public String impliedCapability()
32.46 - {
32.47 - return null;
32.48 - }
32.49 + @Override
32.50 + public boolean hasFinished()
32.51 + {
32.52 + return this.state == PostState.Finished;
32.53 + }
32.54
32.55 - @Override
32.56 - public boolean isStateful()
32.57 - {
32.58 - return true;
32.59 - }
32.60 + @Override
32.61 + public String impliedCapability()
32.62 + {
32.63 + return null;
32.64 + }
32.65
32.66 - /**
32.67 - * Process the given line String. line.trim() was called by NNTPConnection.
32.68 - * @param line
32.69 - * @throws java.io.IOException
32.70 - * @throws java.sql.SQLException
32.71 - */
32.72 - @Override // TODO: Refactor this method to reduce complexity!
32.73 - public void processLine(NNTPConnection conn, String line, byte[] raw)
32.74 - throws IOException, StorageBackendException
32.75 - {
32.76 - switch(state)
32.77 - {
32.78 - case WaitForLineOne:
32.79 - {
32.80 - if(line.equalsIgnoreCase("POST"))
32.81 - {
32.82 - conn.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
32.83 - state = PostState.ReadingHeaders;
32.84 - }
32.85 - else
32.86 - {
32.87 - conn.println("500 invalid command usage");
32.88 - }
32.89 - break;
32.90 - }
32.91 - case ReadingHeaders:
32.92 - {
32.93 - strHead.append(line);
32.94 - strHead.append(NNTPConnection.NEWLINE);
32.95 -
32.96 - if("".equals(line) || ".".equals(line))
32.97 - {
32.98 - // we finally met the blank line
32.99 - // separating headers from body
32.100 -
32.101 - try
32.102 - {
32.103 - // Parse the header using the InternetHeader class from JavaMail API
32.104 - headers = new InternetHeaders(
32.105 - new ByteArrayInputStream(strHead.toString().trim()
32.106 - .getBytes(conn.getCurrentCharset())));
32.107 + @Override
32.108 + public boolean isStateful()
32.109 + {
32.110 + return true;
32.111 + }
32.112
32.113 - // add the header entries for the article
32.114 - article.setHeaders(headers);
32.115 - }
32.116 - catch (MessagingException e)
32.117 - {
32.118 - e.printStackTrace();
32.119 - conn.println("500 posting failed - invalid header");
32.120 - state = PostState.Finished;
32.121 - break;
32.122 - }
32.123 + /**
32.124 + * Process the given line String. line.trim() was called by NNTPConnection.
32.125 + * @param line
32.126 + * @throws java.io.IOException
32.127 + * @throws java.sql.SQLException
32.128 + */
32.129 + @Override // TODO: Refactor this method to reduce complexity!
32.130 + public void processLine(NNTPConnection conn, String line, byte[] raw)
32.131 + throws IOException, StorageBackendException
32.132 + {
32.133 + switch (state) {
32.134 + case WaitForLineOne: {
32.135 + if (line.equalsIgnoreCase("POST")) {
32.136 + conn.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
32.137 + state = PostState.ReadingHeaders;
32.138 + } else {
32.139 + conn.println("500 invalid command usage");
32.140 + }
32.141 + break;
32.142 + }
32.143 + case ReadingHeaders: {
32.144 + strHead.append(line);
32.145 + strHead.append(NNTPConnection.NEWLINE);
32.146
32.147 - // Change charset for reading body;
32.148 - // for multipart messages UTF-8 is returned
32.149 - //conn.setCurrentCharset(article.getBodyCharset());
32.150 -
32.151 - state = PostState.ReadingBody;
32.152 -
32.153 - if(".".equals(line))
32.154 - {
32.155 - // Post an article without body
32.156 - postArticle(conn, article);
32.157 - state = PostState.Finished;
32.158 - }
32.159 - }
32.160 - break;
32.161 - }
32.162 - case ReadingBody:
32.163 - {
32.164 - if(".".equals(line))
32.165 - {
32.166 - // Set some headers needed for Over command
32.167 - headers.setHeader(Headers.LINES, Integer.toString(lineCount));
32.168 - headers.setHeader(Headers.BYTES, Long.toString(bodySize));
32.169 + if ("".equals(line) || ".".equals(line)) {
32.170 + // we finally met the blank line
32.171 + // separating headers from body
32.172
32.173 - byte[] body = bufBody.toByteArray();
32.174 - if(body.length >= 2)
32.175 - {
32.176 - // Remove trailing CRLF
32.177 - body = Arrays.copyOf(body, body.length - 2);
32.178 - }
32.179 - article.setBody(body); // set the article body
32.180 -
32.181 - postArticle(conn, article);
32.182 - state = PostState.Finished;
32.183 - }
32.184 - else
32.185 - {
32.186 - bodySize += line.length() + 1;
32.187 - lineCount++;
32.188 -
32.189 - // Add line to body buffer
32.190 - bufBody.write(raw, 0, raw.length);
32.191 - bufBody.write(NNTPConnection.NEWLINE.getBytes());
32.192 -
32.193 - if(bodySize > maxBodySize)
32.194 - {
32.195 - conn.println("500 article is too long");
32.196 - state = PostState.Finished;
32.197 - break;
32.198 - }
32.199 - }
32.200 - break;
32.201 - }
32.202 - default:
32.203 - {
32.204 - // Should never happen
32.205 - Log.get().severe("PostCommand::processLine(): already finished...");
32.206 - }
32.207 - }
32.208 - }
32.209 -
32.210 - /**
32.211 - * Article is a control message and needs special handling.
32.212 - * @param article
32.213 - */
32.214 - private void controlMessage(NNTPConnection conn, Article article)
32.215 - throws IOException
32.216 - {
32.217 - String[] ctrl = article.getHeader(Headers.CONTROL)[0].split(" ");
32.218 - if(ctrl.length == 2) // "cancel <mid>"
32.219 - {
32.220 - try
32.221 - {
32.222 - StorageManager.current().delete(ctrl[1]);
32.223 -
32.224 - // Move cancel message to "control" group
32.225 - article.setHeader(Headers.NEWSGROUPS, "control");
32.226 - StorageManager.current().addArticle(article);
32.227 - conn.println("240 article cancelled");
32.228 - }
32.229 - catch(StorageBackendException ex)
32.230 - {
32.231 - Log.get().severe(ex.toString());
32.232 - conn.println("500 internal server error");
32.233 - }
32.234 - }
32.235 - else
32.236 - {
32.237 - conn.println("441 unknown control header");
32.238 - }
32.239 - }
32.240 -
32.241 - private void supersedeMessage(NNTPConnection conn, Article article)
32.242 - throws IOException
32.243 - {
32.244 - try
32.245 - {
32.246 - String oldMsg = article.getHeader(Headers.SUPERSEDES)[0];
32.247 - StorageManager.current().delete(oldMsg);
32.248 - StorageManager.current().addArticle(article);
32.249 - conn.println("240 article replaced");
32.250 - }
32.251 - catch(StorageBackendException ex)
32.252 - {
32.253 - Log.get().severe(ex.toString());
32.254 - conn.println("500 internal server error");
32.255 - }
32.256 - }
32.257 -
32.258 - private void postArticle(NNTPConnection conn, Article article)
32.259 - throws IOException
32.260 - {
32.261 - if(article.getHeader(Headers.CONTROL)[0].length() > 0)
32.262 - {
32.263 - controlMessage(conn, article);
32.264 - }
32.265 - else if(article.getHeader(Headers.SUPERSEDES)[0].length() > 0)
32.266 - {
32.267 - supersedeMessage(conn, article);
32.268 - }
32.269 - else // Post the article regularily
32.270 - {
32.271 - // Circle check; note that Path can already contain the hostname here
32.272 - String host = Config.inst().get(Config.HOSTNAME, "localhost");
32.273 - if(article.getHeader(Headers.PATH)[0].indexOf(host + "!", 1) > 0)
32.274 - {
32.275 - Log.get().info(article.getMessageID() + " skipped for host " + host);
32.276 - conn.println("441 I know this article already");
32.277 - return;
32.278 - }
32.279 + try {
32.280 + // Parse the header using the InternetHeader class from JavaMail API
32.281 + headers = new InternetHeaders(
32.282 + new ByteArrayInputStream(strHead.toString().trim().getBytes(conn.getCurrentCharset())));
32.283
32.284 - // Try to create the article in the database or post it to
32.285 - // appropriate mailing list
32.286 - try
32.287 - {
32.288 - boolean success = false;
32.289 - String[] groupnames = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
32.290 - for(String groupname : groupnames)
32.291 - {
32.292 - Group group = StorageManager.current().getGroup(groupname);
32.293 - if(group != null && !group.isDeleted())
32.294 - {
32.295 - if(group.isMailingList() && !conn.isLocalConnection())
32.296 - {
32.297 - // Send to mailing list; the Dispatcher writes
32.298 - // statistics to database
32.299 - Dispatcher.toList(article, group.getName());
32.300 - success = true;
32.301 - }
32.302 - else
32.303 - {
32.304 - // Store in database
32.305 - if(!StorageManager.current().isArticleExisting(article.getMessageID()))
32.306 - {
32.307 - StorageManager.current().addArticle(article);
32.308 + // add the header entries for the article
32.309 + article.setHeaders(headers);
32.310 + } catch (MessagingException e) {
32.311 + e.printStackTrace();
32.312 + conn.println("500 posting failed - invalid header");
32.313 + state = PostState.Finished;
32.314 + break;
32.315 + }
32.316
32.317 - // Log this posting to statistics
32.318 - Stats.getInstance().mailPosted(
32.319 - article.getHeader(Headers.NEWSGROUPS)[0]);
32.320 - }
32.321 - success = true;
32.322 - }
32.323 - }
32.324 - } // end for
32.325 + // Change charset for reading body;
32.326 + // for multipart messages UTF-8 is returned
32.327 + //conn.setCurrentCharset(article.getBodyCharset());
32.328
32.329 - if(success)
32.330 - {
32.331 - conn.println("240 article posted ok");
32.332 - FeedManager.queueForPush(article);
32.333 - }
32.334 - else
32.335 - {
32.336 - conn.println("441 newsgroup not found");
32.337 - }
32.338 - }
32.339 - catch(AddressException ex)
32.340 - {
32.341 - Log.get().warning(ex.getMessage());
32.342 - conn.println("441 invalid sender address");
32.343 - }
32.344 - catch(MessagingException ex)
32.345 - {
32.346 - // A MessageException is thrown when the sender email address is
32.347 - // invalid or something is wrong with the SMTP server.
32.348 - System.err.println(ex.getLocalizedMessage());
32.349 - conn.println("441 " + ex.getClass().getCanonicalName() + ": " + ex.getLocalizedMessage());
32.350 - }
32.351 - catch(StorageBackendException ex)
32.352 - {
32.353 - ex.printStackTrace();
32.354 - conn.println("500 internal server error");
32.355 - }
32.356 - }
32.357 - }
32.358 + state = PostState.ReadingBody;
32.359
32.360 + if (".".equals(line)) {
32.361 + // Post an article without body
32.362 + postArticle(conn, article);
32.363 + state = PostState.Finished;
32.364 + }
32.365 + }
32.366 + break;
32.367 + }
32.368 + case ReadingBody: {
32.369 + if (".".equals(line)) {
32.370 + // Set some headers needed for Over command
32.371 + headers.setHeader(Headers.LINES, Integer.toString(lineCount));
32.372 + headers.setHeader(Headers.BYTES, Long.toString(bodySize));
32.373 +
32.374 + byte[] body = bufBody.toByteArray();
32.375 + if (body.length >= 2) {
32.376 + // Remove trailing CRLF
32.377 + body = Arrays.copyOf(body, body.length - 2);
32.378 + }
32.379 + article.setBody(body); // set the article body
32.380 +
32.381 + postArticle(conn, article);
32.382 + state = PostState.Finished;
32.383 + } else {
32.384 + bodySize += line.length() + 1;
32.385 + lineCount++;
32.386 +
32.387 + // Add line to body buffer
32.388 + bufBody.write(raw, 0, raw.length);
32.389 + bufBody.write(NNTPConnection.NEWLINE.getBytes());
32.390 +
32.391 + if (bodySize > maxBodySize) {
32.392 + conn.println("500 article is too long");
32.393 + state = PostState.Finished;
32.394 + break;
32.395 + }
32.396 + }
32.397 + break;
32.398 + }
32.399 + default: {
32.400 + // Should never happen
32.401 + Log.get().severe("PostCommand::processLine(): already finished...");
32.402 + }
32.403 + }
32.404 + }
32.405 +
32.406 + /**
32.407 + * Article is a control message and needs special handling.
32.408 + * @param article
32.409 + */
32.410 + private void controlMessage(NNTPConnection conn, Article article)
32.411 + throws IOException
32.412 + {
32.413 + String[] ctrl = article.getHeader(Headers.CONTROL)[0].split(" ");
32.414 + if (ctrl.length == 2) // "cancel <mid>"
32.415 + {
32.416 + try {
32.417 + StorageManager.current().delete(ctrl[1]);
32.418 +
32.419 + // Move cancel message to "control" group
32.420 + article.setHeader(Headers.NEWSGROUPS, "control");
32.421 + StorageManager.current().addArticle(article);
32.422 + conn.println("240 article cancelled");
32.423 + } catch (StorageBackendException ex) {
32.424 + Log.get().severe(ex.toString());
32.425 + conn.println("500 internal server error");
32.426 + }
32.427 + } else {
32.428 + conn.println("441 unknown control header");
32.429 + }
32.430 + }
32.431 +
32.432 + private void supersedeMessage(NNTPConnection conn, Article article)
32.433 + throws IOException
32.434 + {
32.435 + try {
32.436 + String oldMsg = article.getHeader(Headers.SUPERSEDES)[0];
32.437 + StorageManager.current().delete(oldMsg);
32.438 + StorageManager.current().addArticle(article);
32.439 + conn.println("240 article replaced");
32.440 + } catch (StorageBackendException ex) {
32.441 + Log.get().severe(ex.toString());
32.442 + conn.println("500 internal server error");
32.443 + }
32.444 + }
32.445 +
32.446 + private void postArticle(NNTPConnection conn, Article article)
32.447 + throws IOException
32.448 + {
32.449 + if (article.getHeader(Headers.CONTROL)[0].length() > 0) {
32.450 + controlMessage(conn, article);
32.451 + } else if (article.getHeader(Headers.SUPERSEDES)[0].length() > 0) {
32.452 + supersedeMessage(conn, article);
32.453 + } else // Post the article regularily
32.454 + {
32.455 + // Circle check; note that Path can already contain the hostname here
32.456 + String host = Config.inst().get(Config.HOSTNAME, "localhost");
32.457 + if (article.getHeader(Headers.PATH)[0].indexOf(host + "!", 1) > 0) {
32.458 + Log.get().info(article.getMessageID() + " skipped for host " + host);
32.459 + conn.println("441 I know this article already");
32.460 + return;
32.461 + }
32.462 +
32.463 + // Try to create the article in the database or post it to
32.464 + // appropriate mailing list
32.465 + try {
32.466 + boolean success = false;
32.467 + String[] groupnames = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
32.468 + for (String groupname : groupnames) {
32.469 + Group group = StorageManager.current().getGroup(groupname);
32.470 + if (group != null && !group.isDeleted()) {
32.471 + if (group.isMailingList() && !conn.isLocalConnection()) {
32.472 + // Send to mailing list; the Dispatcher writes
32.473 + // statistics to database
32.474 + Dispatcher.toList(article, group.getName());
32.475 + success = true;
32.476 + } else {
32.477 + // Store in database
32.478 + if (!StorageManager.current().isArticleExisting(article.getMessageID())) {
32.479 + StorageManager.current().addArticle(article);
32.480 +
32.481 + // Log this posting to statistics
32.482 + Stats.getInstance().mailPosted(
32.483 + article.getHeader(Headers.NEWSGROUPS)[0]);
32.484 + }
32.485 + success = true;
32.486 + }
32.487 + }
32.488 + } // end for
32.489 +
32.490 + if (success) {
32.491 + conn.println("240 article posted ok");
32.492 + FeedManager.queueForPush(article);
32.493 + } else {
32.494 + conn.println("441 newsgroup not found");
32.495 + }
32.496 + } catch (AddressException ex) {
32.497 + Log.get().warning(ex.getMessage());
32.498 + conn.println("441 invalid sender address");
32.499 + } catch (MessagingException ex) {
32.500 + // A MessageException is thrown when the sender email address is
32.501 + // invalid or something is wrong with the SMTP server.
32.502 + System.err.println(ex.getLocalizedMessage());
32.503 + conn.println("441 " + ex.getClass().getCanonicalName() + ": " + ex.getLocalizedMessage());
32.504 + } catch (StorageBackendException ex) {
32.505 + ex.printStackTrace();
32.506 + conn.println("500 internal server error");
32.507 + }
32.508 + }
32.509 + }
32.510 }
33.1 --- a/src/org/sonews/daemon/command/PostState.java Sun Aug 29 17:43:58 2010 +0200
33.2 +++ b/src/org/sonews/daemon/command/PostState.java Sun Aug 29 18:17:37 2010 +0200
33.3 @@ -25,5 +25,6 @@
33.4 */
33.5 enum PostState
33.6 {
33.7 - WaitForLineOne, ReadingHeaders, ReadingBody, Finished
33.8 +
33.9 + WaitForLineOne, ReadingHeaders, ReadingBody, Finished
33.10 }
34.1 --- a/src/org/sonews/daemon/command/QuitCommand.java Sun Aug 29 17:43:58 2010 +0200
34.2 +++ b/src/org/sonews/daemon/command/QuitCommand.java Sun Aug 29 18:17:37 2010 +0200
34.3 @@ -30,38 +30,37 @@
34.4 public class QuitCommand implements Command
34.5 {
34.6
34.7 - @Override
34.8 - public String[] getSupportedCommandStrings()
34.9 - {
34.10 - return new String[]{"QUIT"};
34.11 - }
34.12 -
34.13 - @Override
34.14 - public boolean hasFinished()
34.15 - {
34.16 - return true;
34.17 - }
34.18 + @Override
34.19 + public String[] getSupportedCommandStrings()
34.20 + {
34.21 + return new String[] {"QUIT"};
34.22 + }
34.23
34.24 - @Override
34.25 - public String impliedCapability()
34.26 - {
34.27 - return null;
34.28 - }
34.29 + @Override
34.30 + public boolean hasFinished()
34.31 + {
34.32 + return true;
34.33 + }
34.34
34.35 - @Override
34.36 - public boolean isStateful()
34.37 - {
34.38 - return false;
34.39 - }
34.40 + @Override
34.41 + public String impliedCapability()
34.42 + {
34.43 + return null;
34.44 + }
34.45
34.46 - @Override
34.47 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
34.48 - throws IOException, StorageBackendException
34.49 - {
34.50 - conn.println("205 cya");
34.51 -
34.52 - conn.shutdownInput();
34.53 - conn.shutdownOutput();
34.54 - }
34.55 + @Override
34.56 + public boolean isStateful()
34.57 + {
34.58 + return false;
34.59 + }
34.60
34.61 + @Override
34.62 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
34.63 + throws IOException, StorageBackendException
34.64 + {
34.65 + conn.println("205 cya");
34.66 +
34.67 + conn.shutdownInput();
34.68 + conn.shutdownOutput();
34.69 + }
34.70 }
35.1 --- a/src/org/sonews/daemon/command/StatCommand.java Sun Aug 29 17:43:58 2010 +0200
35.2 +++ b/src/org/sonews/daemon/command/StatCommand.java Sun Aug 29 18:17:37 2010 +0200
35.3 @@ -31,84 +31,70 @@
35.4 public class StatCommand implements Command
35.5 {
35.6
35.7 - @Override
35.8 - public String[] getSupportedCommandStrings()
35.9 - {
35.10 - return new String[]{"STAT"};
35.11 - }
35.12 + @Override
35.13 + public String[] getSupportedCommandStrings()
35.14 + {
35.15 + return new String[] {"STAT"};
35.16 + }
35.17
35.18 - @Override
35.19 - public boolean hasFinished()
35.20 - {
35.21 - return true;
35.22 - }
35.23 + @Override
35.24 + public boolean hasFinished()
35.25 + {
35.26 + return true;
35.27 + }
35.28
35.29 - @Override
35.30 - public String impliedCapability()
35.31 - {
35.32 - return null;
35.33 - }
35.34 + @Override
35.35 + public String impliedCapability()
35.36 + {
35.37 + return null;
35.38 + }
35.39
35.40 - @Override
35.41 - public boolean isStateful()
35.42 - {
35.43 - return false;
35.44 - }
35.45 + @Override
35.46 + public boolean isStateful()
35.47 + {
35.48 + return false;
35.49 + }
35.50
35.51 - // TODO: Method has various exit points => Refactor!
35.52 - @Override
35.53 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
35.54 - throws IOException, StorageBackendException
35.55 - {
35.56 - final String[] command = line.split(" ");
35.57 + // TODO: Method has various exit points => Refactor!
35.58 + @Override
35.59 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
35.60 + throws IOException, StorageBackendException
35.61 + {
35.62 + final String[] command = line.split(" ");
35.63
35.64 - Article article = null;
35.65 - if(command.length == 1)
35.66 - {
35.67 - article = conn.getCurrentArticle();
35.68 - if(article == null)
35.69 - {
35.70 - conn.println("420 no current article has been selected");
35.71 - return;
35.72 - }
35.73 - }
35.74 - else if(command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN))
35.75 - {
35.76 - // Message-ID
35.77 - article = Article.getByMessageID(command[1]);
35.78 - if (article == null)
35.79 - {
35.80 - conn.println("430 no such article found");
35.81 - return;
35.82 - }
35.83 - }
35.84 - else
35.85 - {
35.86 - // Message Number
35.87 - try
35.88 - {
35.89 - long aid = Long.parseLong(command[1]);
35.90 - article = conn.getCurrentChannel().getArticle(aid);
35.91 - }
35.92 - catch(NumberFormatException ex)
35.93 - {
35.94 - ex.printStackTrace();
35.95 - }
35.96 - catch(StorageBackendException ex)
35.97 - {
35.98 - ex.printStackTrace();
35.99 - }
35.100 - if (article == null)
35.101 - {
35.102 - conn.println("423 no such article number in this group");
35.103 - return;
35.104 - }
35.105 - conn.setCurrentArticle(article);
35.106 - }
35.107 -
35.108 - conn.println("223 " + conn.getCurrentChannel().getIndexOf(article) + " "
35.109 - + article.getMessageID()
35.110 - + " article retrieved - request text separately");
35.111 - }
35.112 -
35.113 + Article article = null;
35.114 + if (command.length == 1) {
35.115 + article = conn.getCurrentArticle();
35.116 + if (article == null) {
35.117 + conn.println("420 no current article has been selected");
35.118 + return;
35.119 + }
35.120 + } else if (command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN)) {
35.121 + // Message-ID
35.122 + article = Article.getByMessageID(command[1]);
35.123 + if (article == null) {
35.124 + conn.println("430 no such article found");
35.125 + return;
35.126 + }
35.127 + } else {
35.128 + // Message Number
35.129 + try {
35.130 + long aid = Long.parseLong(command[1]);
35.131 + article = conn.getCurrentChannel().getArticle(aid);
35.132 + } catch (NumberFormatException ex) {
35.133 + ex.printStackTrace();
35.134 + } catch (StorageBackendException ex) {
35.135 + ex.printStackTrace();
35.136 + }
35.137 + if (article == null) {
35.138 + conn.println("423 no such article number in this group");
35.139 + return;
35.140 + }
35.141 + conn.setCurrentArticle(article);
35.142 + }
35.143 +
35.144 + conn.println("223 " + conn.getCurrentChannel().getIndexOf(article) + " "
35.145 + + article.getMessageID()
35.146 + + " article retrieved - request text separately");
35.147 + }
35.148 }
36.1 --- a/src/org/sonews/daemon/command/UnsupportedCommand.java Sun Aug 29 17:43:58 2010 +0200
36.2 +++ b/src/org/sonews/daemon/command/UnsupportedCommand.java Sun Aug 29 18:17:37 2010 +0200
36.3 @@ -29,39 +29,38 @@
36.4 */
36.5 public class UnsupportedCommand implements Command
36.6 {
36.7 -
36.8 - /**
36.9 - * @return Always returns null.
36.10 - */
36.11 - @Override
36.12 - public String[] getSupportedCommandStrings()
36.13 - {
36.14 - return null;
36.15 - }
36.16
36.17 - @Override
36.18 - public boolean hasFinished()
36.19 - {
36.20 - return true;
36.21 - }
36.22 + /**
36.23 + * @return Always returns null.
36.24 + */
36.25 + @Override
36.26 + public String[] getSupportedCommandStrings()
36.27 + {
36.28 + return null;
36.29 + }
36.30
36.31 - @Override
36.32 - public String impliedCapability()
36.33 - {
36.34 - return null;
36.35 - }
36.36 + @Override
36.37 + public boolean hasFinished()
36.38 + {
36.39 + return true;
36.40 + }
36.41
36.42 - @Override
36.43 - public boolean isStateful()
36.44 - {
36.45 - return false;
36.46 - }
36.47 + @Override
36.48 + public String impliedCapability()
36.49 + {
36.50 + return null;
36.51 + }
36.52
36.53 - @Override
36.54 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
36.55 - throws IOException
36.56 - {
36.57 - conn.println("500 command not supported");
36.58 - }
36.59 -
36.60 + @Override
36.61 + public boolean isStateful()
36.62 + {
36.63 + return false;
36.64 + }
36.65 +
36.66 + @Override
36.67 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
36.68 + throws IOException
36.69 + {
36.70 + conn.println("500 command not supported");
36.71 + }
36.72 }
37.1 --- a/src/org/sonews/daemon/command/XDaemonCommand.java Sun Aug 29 17:43:58 2010 +0200
37.2 +++ b/src/org/sonews/daemon/command/XDaemonCommand.java Sun Aug 29 18:17:37 2010 +0200
37.3 @@ -43,228 +43,162 @@
37.4 public class XDaemonCommand implements Command
37.5 {
37.6
37.7 - @Override
37.8 - public String[] getSupportedCommandStrings()
37.9 - {
37.10 - return new String[]{"XDAEMON"};
37.11 - }
37.12 + @Override
37.13 + public String[] getSupportedCommandStrings()
37.14 + {
37.15 + return new String[] {"XDAEMON"};
37.16 + }
37.17
37.18 - @Override
37.19 - public boolean hasFinished()
37.20 - {
37.21 - return true;
37.22 - }
37.23 + @Override
37.24 + public boolean hasFinished()
37.25 + {
37.26 + return true;
37.27 + }
37.28
37.29 - @Override
37.30 - public String impliedCapability()
37.31 - {
37.32 - return null;
37.33 - }
37.34 + @Override
37.35 + public String impliedCapability()
37.36 + {
37.37 + return null;
37.38 + }
37.39
37.40 - @Override
37.41 - public boolean isStateful()
37.42 - {
37.43 - return false;
37.44 - }
37.45 + @Override
37.46 + public boolean isStateful()
37.47 + {
37.48 + return false;
37.49 + }
37.50
37.51 - private void channelAdd(String[] commands, NNTPConnection conn)
37.52 - throws IOException, StorageBackendException
37.53 - {
37.54 - String groupName = commands[2];
37.55 - if(StorageManager.current().isGroupExisting(groupName))
37.56 - {
37.57 - conn.println("400 group " + groupName + " already existing!");
37.58 - }
37.59 - else
37.60 - {
37.61 - StorageManager.current().addGroup(groupName, Integer.parseInt(commands[3]));
37.62 - conn.println("200 group " + groupName + " created");
37.63 - }
37.64 - }
37.65 + private void channelAdd(String[] commands, NNTPConnection conn)
37.66 + throws IOException, StorageBackendException
37.67 + {
37.68 + String groupName = commands[2];
37.69 + if (StorageManager.current().isGroupExisting(groupName)) {
37.70 + conn.println("400 group " + groupName + " already existing!");
37.71 + } else {
37.72 + StorageManager.current().addGroup(groupName, Integer.parseInt(commands[3]));
37.73 + conn.println("200 group " + groupName + " created");
37.74 + }
37.75 + }
37.76
37.77 - // TODO: Refactor this method to reduce complexity!
37.78 - @Override
37.79 - public void processLine(NNTPConnection conn, String line, byte[] raw)
37.80 - throws IOException, StorageBackendException
37.81 - {
37.82 - InetSocketAddress addr = (InetSocketAddress)conn.getSocketChannel().socket()
37.83 - .getRemoteSocketAddress();
37.84 - if(addr.getHostName().equals(
37.85 - Config.inst().get(Config.XDAEMON_HOST, "localhost")))
37.86 - {
37.87 - String[] commands = line.split(" ", 4);
37.88 - if(commands.length == 3 && commands[1].equalsIgnoreCase("LIST"))
37.89 - {
37.90 - if(commands[2].equalsIgnoreCase("CONFIGKEYS"))
37.91 - {
37.92 - conn.println("100 list of available config keys follows");
37.93 - for(String key : Config.AVAILABLE_KEYS)
37.94 - {
37.95 - conn.println(key);
37.96 - }
37.97 - conn.println(".");
37.98 - }
37.99 - else if(commands[2].equalsIgnoreCase("PEERINGRULES"))
37.100 - {
37.101 - List<Subscription> pull =
37.102 - StorageManager.current().getSubscriptions(FeedManager.TYPE_PULL);
37.103 - List<Subscription> push =
37.104 - StorageManager.current().getSubscriptions(FeedManager.TYPE_PUSH);
37.105 - conn.println("100 list of peering rules follows");
37.106 - for(Subscription sub : pull)
37.107 - {
37.108 - conn.println("PULL " + sub.getHost() + ":" + sub.getPort()
37.109 - + " " + sub.getGroup());
37.110 - }
37.111 - for(Subscription sub : push)
37.112 - {
37.113 - conn.println("PUSH " + sub.getHost() + ":" + sub.getPort()
37.114 - + " " + sub.getGroup());
37.115 - }
37.116 - conn.println(".");
37.117 - }
37.118 - else
37.119 - {
37.120 - conn.println("401 unknown sub command");
37.121 - }
37.122 - }
37.123 - else if(commands.length == 3 && commands[1].equalsIgnoreCase("DELETE"))
37.124 - {
37.125 - StorageManager.current().delete(commands[2]);
37.126 - conn.println("200 article " + commands[2] + " deleted");
37.127 - }
37.128 - else if(commands.length == 4 && commands[1].equalsIgnoreCase("GROUPADD"))
37.129 - {
37.130 - channelAdd(commands, conn);
37.131 - }
37.132 - else if(commands.length == 3 && commands[1].equalsIgnoreCase("GROUPDEL"))
37.133 - {
37.134 - Group group = StorageManager.current().getGroup(commands[2]);
37.135 - if(group == null)
37.136 - {
37.137 - conn.println("400 group not found");
37.138 - }
37.139 - else
37.140 - {
37.141 - group.setFlag(Group.DELETED);
37.142 - group.update();
37.143 - conn.println("200 group " + commands[2] + " marked as deleted");
37.144 - }
37.145 - }
37.146 - else if(commands.length == 4 && commands[1].equalsIgnoreCase("SET"))
37.147 - {
37.148 - String key = commands[2];
37.149 - String val = commands[3];
37.150 - Config.inst().set(key, val);
37.151 - conn.println("200 new config value set");
37.152 - }
37.153 - else if(commands.length == 3 && commands[1].equalsIgnoreCase("GET"))
37.154 - {
37.155 - String key = commands[2];
37.156 - String val = Config.inst().get(key, null);
37.157 - if(val != null)
37.158 - {
37.159 - conn.println("100 config value for " + key + " follows");
37.160 - conn.println(val);
37.161 - conn.println(".");
37.162 - }
37.163 - else
37.164 - {
37.165 - conn.println("400 config value not set");
37.166 - }
37.167 - }
37.168 - else if(commands.length >= 3 && commands[1].equalsIgnoreCase("LOG"))
37.169 - {
37.170 - Group group = null;
37.171 - if(commands.length > 3)
37.172 - {
37.173 - group = (Group)Channel.getByName(commands[3]);
37.174 - }
37.175 + // TODO: Refactor this method to reduce complexity!
37.176 + @Override
37.177 + public void processLine(NNTPConnection conn, String line, byte[] raw)
37.178 + throws IOException, StorageBackendException
37.179 + {
37.180 + InetSocketAddress addr = (InetSocketAddress) conn.getSocketChannel().socket().getRemoteSocketAddress();
37.181 + if (addr.getHostName().equals(
37.182 + Config.inst().get(Config.XDAEMON_HOST, "localhost"))) {
37.183 + String[] commands = line.split(" ", 4);
37.184 + if (commands.length == 3 && commands[1].equalsIgnoreCase("LIST")) {
37.185 + if (commands[2].equalsIgnoreCase("CONFIGKEYS")) {
37.186 + conn.println("100 list of available config keys follows");
37.187 + for (String key : Config.AVAILABLE_KEYS) {
37.188 + conn.println(key);
37.189 + }
37.190 + conn.println(".");
37.191 + } else if (commands[2].equalsIgnoreCase("PEERINGRULES")) {
37.192 + List<Subscription> pull =
37.193 + StorageManager.current().getSubscriptions(FeedManager.TYPE_PULL);
37.194 + List<Subscription> push =
37.195 + StorageManager.current().getSubscriptions(FeedManager.TYPE_PUSH);
37.196 + conn.println("100 list of peering rules follows");
37.197 + for (Subscription sub : pull) {
37.198 + conn.println("PULL " + sub.getHost() + ":" + sub.getPort()
37.199 + + " " + sub.getGroup());
37.200 + }
37.201 + for (Subscription sub : push) {
37.202 + conn.println("PUSH " + sub.getHost() + ":" + sub.getPort()
37.203 + + " " + sub.getGroup());
37.204 + }
37.205 + conn.println(".");
37.206 + } else {
37.207 + conn.println("401 unknown sub command");
37.208 + }
37.209 + } else if (commands.length == 3 && commands[1].equalsIgnoreCase("DELETE")) {
37.210 + StorageManager.current().delete(commands[2]);
37.211 + conn.println("200 article " + commands[2] + " deleted");
37.212 + } else if (commands.length == 4 && commands[1].equalsIgnoreCase("GROUPADD")) {
37.213 + channelAdd(commands, conn);
37.214 + } else if (commands.length == 3 && commands[1].equalsIgnoreCase("GROUPDEL")) {
37.215 + Group group = StorageManager.current().getGroup(commands[2]);
37.216 + if (group == null) {
37.217 + conn.println("400 group not found");
37.218 + } else {
37.219 + group.setFlag(Group.DELETED);
37.220 + group.update();
37.221 + conn.println("200 group " + commands[2] + " marked as deleted");
37.222 + }
37.223 + } else if (commands.length == 4 && commands[1].equalsIgnoreCase("SET")) {
37.224 + String key = commands[2];
37.225 + String val = commands[3];
37.226 + Config.inst().set(key, val);
37.227 + conn.println("200 new config value set");
37.228 + } else if (commands.length == 3 && commands[1].equalsIgnoreCase("GET")) {
37.229 + String key = commands[2];
37.230 + String val = Config.inst().get(key, null);
37.231 + if (val != null) {
37.232 + conn.println("100 config value for " + key + " follows");
37.233 + conn.println(val);
37.234 + conn.println(".");
37.235 + } else {
37.236 + conn.println("400 config value not set");
37.237 + }
37.238 + } else if (commands.length >= 3 && commands[1].equalsIgnoreCase("LOG")) {
37.239 + Group group = null;
37.240 + if (commands.length > 3) {
37.241 + group = (Group) Channel.getByName(commands[3]);
37.242 + }
37.243
37.244 - if(commands[2].equalsIgnoreCase("CONNECTED_CLIENTS"))
37.245 - {
37.246 - conn.println("100 number of connections follow");
37.247 - conn.println(Integer.toString(Stats.getInstance().connectedClients()));
37.248 - conn.println(".");
37.249 - }
37.250 - else if(commands[2].equalsIgnoreCase("POSTED_NEWS"))
37.251 - {
37.252 - conn.println("100 hourly numbers of posted news yesterday");
37.253 - for(int n = 0; n < 24; n++)
37.254 - {
37.255 - conn.println(n + " " + Stats.getInstance()
37.256 - .getYesterdaysEvents(Stats.POSTED_NEWS, n, group));
37.257 - }
37.258 - conn.println(".");
37.259 - }
37.260 - else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS"))
37.261 - {
37.262 - conn.println("100 hourly numbers of gatewayed news yesterday");
37.263 - for(int n = 0; n < 24; n++)
37.264 - {
37.265 - conn.println(n + " " + Stats.getInstance()
37.266 - .getYesterdaysEvents(Stats.GATEWAYED_NEWS, n, group));
37.267 - }
37.268 - conn.println(".");
37.269 - }
37.270 - else if(commands[2].equalsIgnoreCase("TRANSMITTED_NEWS"))
37.271 - {
37.272 - conn.println("100 hourly numbers of news transmitted to peers yesterday");
37.273 - for(int n = 0; n < 24; n++)
37.274 - {
37.275 - conn.println(n + " " + Stats.getInstance()
37.276 - .getYesterdaysEvents(Stats.FEEDED_NEWS, n, group));
37.277 - }
37.278 - conn.println(".");
37.279 - }
37.280 - else if(commands[2].equalsIgnoreCase("HOSTED_NEWS"))
37.281 - {
37.282 - conn.println("100 number of overall hosted news");
37.283 - conn.println(Integer.toString(Stats.getInstance().getNumberOfNews()));
37.284 - conn.println(".");
37.285 - }
37.286 - else if(commands[2].equalsIgnoreCase("HOSTED_GROUPS"))
37.287 - {
37.288 - conn.println("100 number of hosted groups");
37.289 - conn.println(Integer.toString(Stats.getInstance().getNumberOfGroups()));
37.290 - conn.println(".");
37.291 - }
37.292 - else if(commands[2].equalsIgnoreCase("POSTED_NEWS_PER_HOUR"))
37.293 - {
37.294 - conn.println("100 posted news per hour");
37.295 - conn.println(Double.toString(Stats.getInstance().postedPerHour(-1)));
37.296 - conn.println(".");
37.297 - }
37.298 - else if(commands[2].equalsIgnoreCase("FEEDED_NEWS_PER_HOUR"))
37.299 - {
37.300 - conn.println("100 feeded news per hour");
37.301 - conn.println(Double.toString(Stats.getInstance().feededPerHour(-1)));
37.302 - conn.println(".");
37.303 - }
37.304 - else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS_PER_HOUR"))
37.305 - {
37.306 - conn.println("100 gatewayed news per hour");
37.307 - conn.println(Double.toString(Stats.getInstance().gatewayedPerHour(-1)));
37.308 - conn.println(".");
37.309 - }
37.310 - else
37.311 - {
37.312 - conn.println("401 unknown sub command");
37.313 - }
37.314 - }
37.315 - else if(commands.length >= 3 && commands[1].equalsIgnoreCase("PLUGIN"))
37.316 - {
37.317 -
37.318 - }
37.319 - else
37.320 - {
37.321 - conn.println("400 invalid command usage");
37.322 - }
37.323 - }
37.324 - else
37.325 - {
37.326 - conn.println("501 not allowed");
37.327 - }
37.328 - }
37.329 -
37.330 + if (commands[2].equalsIgnoreCase("CONNECTED_CLIENTS")) {
37.331 + conn.println("100 number of connections follow");
37.332 + conn.println(Integer.toString(Stats.getInstance().connectedClients()));
37.333 + conn.println(".");
37.334 + } else if (commands[2].equalsIgnoreCase("POSTED_NEWS")) {
37.335 + conn.println("100 hourly numbers of posted news yesterday");
37.336 + for (int n = 0; n < 24; n++) {
37.337 + conn.println(n + " " + Stats.getInstance().getYesterdaysEvents(Stats.POSTED_NEWS, n, group));
37.338 + }
37.339 + conn.println(".");
37.340 + } else if (commands[2].equalsIgnoreCase("GATEWAYED_NEWS")) {
37.341 + conn.println("100 hourly numbers of gatewayed news yesterday");
37.342 + for (int n = 0; n < 24; n++) {
37.343 + conn.println(n + " " + Stats.getInstance().getYesterdaysEvents(Stats.GATEWAYED_NEWS, n, group));
37.344 + }
37.345 + conn.println(".");
37.346 + } else if (commands[2].equalsIgnoreCase("TRANSMITTED_NEWS")) {
37.347 + conn.println("100 hourly numbers of news transmitted to peers yesterday");
37.348 + for (int n = 0; n < 24; n++) {
37.349 + conn.println(n + " " + Stats.getInstance().getYesterdaysEvents(Stats.FEEDED_NEWS, n, group));
37.350 + }
37.351 + conn.println(".");
37.352 + } else if (commands[2].equalsIgnoreCase("HOSTED_NEWS")) {
37.353 + conn.println("100 number of overall hosted news");
37.354 + conn.println(Integer.toString(Stats.getInstance().getNumberOfNews()));
37.355 + conn.println(".");
37.356 + } else if (commands[2].equalsIgnoreCase("HOSTED_GROUPS")) {
37.357 + conn.println("100 number of hosted groups");
37.358 + conn.println(Integer.toString(Stats.getInstance().getNumberOfGroups()));
37.359 + conn.println(".");
37.360 + } else if (commands[2].equalsIgnoreCase("POSTED_NEWS_PER_HOUR")) {
37.361 + conn.println("100 posted news per hour");
37.362 + conn.println(Double.toString(Stats.getInstance().postedPerHour(-1)));
37.363 + conn.println(".");
37.364 + } else if (commands[2].equalsIgnoreCase("FEEDED_NEWS_PER_HOUR")) {
37.365 + conn.println("100 feeded news per hour");
37.366 + conn.println(Double.toString(Stats.getInstance().feededPerHour(-1)));
37.367 + conn.println(".");
37.368 + } else if (commands[2].equalsIgnoreCase("GATEWAYED_NEWS_PER_HOUR")) {
37.369 + conn.println("100 gatewayed news per hour");
37.370 + conn.println(Double.toString(Stats.getInstance().gatewayedPerHour(-1)));
37.371 + conn.println(".");
37.372 + } else {
37.373 + conn.println("401 unknown sub command");
37.374 + }
37.375 + } else if (commands.length >= 3 && commands[1].equalsIgnoreCase("PLUGIN")) {
37.376 + } else {
37.377 + conn.println("400 invalid command usage");
37.378 + }
37.379 + } else {
37.380 + conn.println("501 not allowed");
37.381 + }
37.382 + }
37.383 }
38.1 --- a/src/org/sonews/daemon/command/XPatCommand.java Sun Aug 29 17:43:58 2010 +0200
38.2 +++ b/src/org/sonews/daemon/command/XPatCommand.java Sun Aug 29 18:17:37 2010 +0200
38.3 @@ -78,93 +78,79 @@
38.4 public class XPatCommand implements Command
38.5 {
38.6
38.7 - @Override
38.8 - public String[] getSupportedCommandStrings()
38.9 - {
38.10 - return new String[]{"XPAT"};
38.11 - }
38.12 -
38.13 - @Override
38.14 - public boolean hasFinished()
38.15 - {
38.16 - return true;
38.17 - }
38.18 + @Override
38.19 + public String[] getSupportedCommandStrings()
38.20 + {
38.21 + return new String[] {"XPAT"};
38.22 + }
38.23
38.24 - @Override
38.25 - public String impliedCapability()
38.26 - {
38.27 - return null;
38.28 - }
38.29 + @Override
38.30 + public boolean hasFinished()
38.31 + {
38.32 + return true;
38.33 + }
38.34
38.35 - @Override
38.36 - public boolean isStateful()
38.37 - {
38.38 - return false;
38.39 - }
38.40 + @Override
38.41 + public String impliedCapability()
38.42 + {
38.43 + return null;
38.44 + }
38.45
38.46 - @Override
38.47 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
38.48 - throws IOException, StorageBackendException
38.49 - {
38.50 - if(conn.getCurrentChannel() == null)
38.51 - {
38.52 - conn.println("430 no group selected");
38.53 - return;
38.54 - }
38.55 + @Override
38.56 + public boolean isStateful()
38.57 + {
38.58 + return false;
38.59 + }
38.60
38.61 - String[] command = line.split("\\p{Space}+");
38.62 + @Override
38.63 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
38.64 + throws IOException, StorageBackendException
38.65 + {
38.66 + if (conn.getCurrentChannel() == null) {
38.67 + conn.println("430 no group selected");
38.68 + return;
38.69 + }
38.70
38.71 - // There may be multiple patterns and Thunderbird produces
38.72 - // additional spaces between range and pattern
38.73 - if(command.length >= 4)
38.74 - {
38.75 - String header = command[1].toLowerCase(Locale.US);
38.76 - String range = command[2];
38.77 - String pattern = command[3];
38.78 + String[] command = line.split("\\p{Space}+");
38.79
38.80 - long start = -1;
38.81 - long end = -1;
38.82 - if(range.contains("-"))
38.83 - {
38.84 - String[] rsplit = range.split("-", 2);
38.85 - start = Long.parseLong(rsplit[0]);
38.86 - if(rsplit[1].length() > 0)
38.87 - {
38.88 - end = Long.parseLong(rsplit[1]);
38.89 - }
38.90 - }
38.91 - else // TODO: Handle Message-IDs
38.92 - {
38.93 - start = Long.parseLong(range);
38.94 - }
38.95 + // There may be multiple patterns and Thunderbird produces
38.96 + // additional spaces between range and pattern
38.97 + if (command.length >= 4) {
38.98 + String header = command[1].toLowerCase(Locale.US);
38.99 + String range = command[2];
38.100 + String pattern = command[3];
38.101
38.102 - try
38.103 - {
38.104 - List<Pair<Long, String>> heads = StorageManager.current().
38.105 - getArticleHeaders(conn.getCurrentChannel(), start, end, header, pattern);
38.106 -
38.107 - conn.println("221 header follows");
38.108 - for(Pair<Long, String> head : heads)
38.109 - {
38.110 - conn.println(head.getA() + " " + head.getB());
38.111 - }
38.112 - conn.println(".");
38.113 - }
38.114 - catch(PatternSyntaxException ex)
38.115 - {
38.116 - ex.printStackTrace();
38.117 - conn.println("500 invalid pattern syntax");
38.118 - }
38.119 - catch(StorageBackendException ex)
38.120 - {
38.121 - ex.printStackTrace();
38.122 - conn.println("500 internal server error");
38.123 - }
38.124 - }
38.125 - else
38.126 - {
38.127 - conn.println("430 invalid command usage");
38.128 - }
38.129 - }
38.130 + long start = -1;
38.131 + long end = -1;
38.132 + if (range.contains("-")) {
38.133 + String[] rsplit = range.split("-", 2);
38.134 + start = Long.parseLong(rsplit[0]);
38.135 + if (rsplit[1].length() > 0) {
38.136 + end = Long.parseLong(rsplit[1]);
38.137 + }
38.138 + } else // TODO: Handle Message-IDs
38.139 + {
38.140 + start = Long.parseLong(range);
38.141 + }
38.142
38.143 + try {
38.144 + List<Pair<Long, String>> heads = StorageManager.current().
38.145 + getArticleHeaders(conn.getCurrentChannel(), start, end, header, pattern);
38.146 +
38.147 + conn.println("221 header follows");
38.148 + for (Pair<Long, String> head : heads) {
38.149 + conn.println(head.getA() + " " + head.getB());
38.150 + }
38.151 + conn.println(".");
38.152 + } catch (PatternSyntaxException ex) {
38.153 + ex.printStackTrace();
38.154 + conn.println("500 invalid pattern syntax");
38.155 + } catch (StorageBackendException ex) {
38.156 + ex.printStackTrace();
38.157 + conn.println("500 internal server error");
38.158 + }
38.159 + } else {
38.160 + conn.println("430 invalid command usage");
38.161 + }
38.162 + }
38.163 }
39.1 --- a/src/org/sonews/feed/FeedManager.java Sun Aug 29 17:43:58 2010 +0200
39.2 +++ b/src/org/sonews/feed/FeedManager.java Sun Aug 29 18:17:37 2010 +0200
39.3 @@ -25,30 +25,30 @@
39.4 * @author Christian Lins
39.5 * @since sonews/0.5.0
39.6 */
39.7 -public final class FeedManager
39.8 +public final class FeedManager
39.9 {
39.10
39.11 - public static final int TYPE_PULL = 0;
39.12 - public static final int TYPE_PUSH = 1;
39.13 -
39.14 - private static PullFeeder pullFeeder = new PullFeeder();
39.15 - private static PushFeeder pushFeeder = new PushFeeder();
39.16 -
39.17 - /**
39.18 - * Reads the peer subscriptions from database and starts the appropriate
39.19 - * PullFeeder or PushFeeder.
39.20 - */
39.21 - public static synchronized void startFeeding()
39.22 - {
39.23 - pullFeeder.start();
39.24 - pushFeeder.start();
39.25 - }
39.26 -
39.27 - public static void queueForPush(Article article)
39.28 - {
39.29 - pushFeeder.queueForPush(article);
39.30 - }
39.31 -
39.32 - private FeedManager() {}
39.33 -
39.34 + public static final int TYPE_PULL = 0;
39.35 + public static final int TYPE_PUSH = 1;
39.36 + private static PullFeeder pullFeeder = new PullFeeder();
39.37 + private static PushFeeder pushFeeder = new PushFeeder();
39.38 +
39.39 + /**
39.40 + * Reads the peer subscriptions from database and starts the appropriate
39.41 + * PullFeeder or PushFeeder.
39.42 + */
39.43 + public static synchronized void startFeeding()
39.44 + {
39.45 + pullFeeder.start();
39.46 + pushFeeder.start();
39.47 + }
39.48 +
39.49 + public static void queueForPush(Article article)
39.50 + {
39.51 + pushFeeder.queueForPush(article);
39.52 + }
39.53 +
39.54 + private FeedManager()
39.55 + {
39.56 + }
39.57 }
40.1 --- a/src/org/sonews/feed/PullFeeder.java Sun Aug 29 17:43:58 2010 +0200
40.2 +++ b/src/org/sonews/feed/PullFeeder.java Sun Aug 29 18:17:37 2010 +0200
40.3 @@ -49,228 +49,192 @@
40.4 */
40.5 class PullFeeder extends AbstractDaemon
40.6 {
40.7 -
40.8 - private Map<Subscription, Integer> highMarks = new HashMap<Subscription, Integer>();
40.9 - private BufferedReader in;
40.10 - private PrintWriter out;
40.11 - private Set<Subscription> subscriptions = new HashSet<Subscription>();
40.12 -
40.13 - private void addSubscription(final Subscription sub)
40.14 - {
40.15 - subscriptions.add(sub);
40.16
40.17 - if(!highMarks.containsKey(sub))
40.18 - {
40.19 - // Set a initial highMark
40.20 - this.highMarks.put(sub, 0);
40.21 - }
40.22 - }
40.23 -
40.24 - /**
40.25 - * Changes to the given group and returns its high mark.
40.26 - * @param groupName
40.27 - * @return
40.28 - */
40.29 - private int changeGroup(String groupName)
40.30 - throws IOException
40.31 - {
40.32 - this.out.print("GROUP " + groupName + "\r\n");
40.33 - this.out.flush();
40.34 -
40.35 - String line = this.in.readLine();
40.36 - if(line.startsWith("211 "))
40.37 - {
40.38 - int highmark = Integer.parseInt(line.split(" ")[3]);
40.39 - return highmark;
40.40 - }
40.41 - else
40.42 - {
40.43 - throw new IOException("GROUP " + groupName + " returned: " + line);
40.44 - }
40.45 - }
40.46 -
40.47 - private void connectTo(final String host, final int port)
40.48 - throws IOException, UnknownHostException
40.49 - {
40.50 - Socket socket = new Socket(host, port);
40.51 - this.out = new PrintWriter(socket.getOutputStream());
40.52 - this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
40.53 + private Map<Subscription, Integer> highMarks = new HashMap<Subscription, Integer>();
40.54 + private BufferedReader in;
40.55 + private PrintWriter out;
40.56 + private Set<Subscription> subscriptions = new HashSet<Subscription>();
40.57
40.58 - String line = in.readLine();
40.59 - if(!(line.charAt(0) == '2')) // Could be 200 or 2xx if posting is not allowed
40.60 - {
40.61 - throw new IOException(line);
40.62 - }
40.63 + private void addSubscription(final Subscription sub)
40.64 + {
40.65 + subscriptions.add(sub);
40.66
40.67 - // Send MODE READER to peer, some newsservers are friendlier then
40.68 - this.out.println("MODE READER\r\n");
40.69 - this.out.flush();
40.70 - line = this.in.readLine();
40.71 - }
40.72 -
40.73 - private void disconnect()
40.74 - throws IOException
40.75 - {
40.76 - this.out.print("QUIT\r\n");
40.77 - this.out.flush();
40.78 - this.out.close();
40.79 - this.in.close();
40.80 -
40.81 - this.out = null;
40.82 - this.in = null;
40.83 - }
40.84 -
40.85 - /**
40.86 - * Uses the OVER or XOVER command to get a list of message overviews that
40.87 - * may be unknown to this feeder and are about to be peered.
40.88 - * @param start
40.89 - * @param end
40.90 - * @return A list of message ids with potentially interesting messages.
40.91 - */
40.92 - private List<String> over(int start, int end)
40.93 - throws IOException
40.94 - {
40.95 - this.out.print("OVER " + start + "-" + end + "\r\n");
40.96 - this.out.flush();
40.97 -
40.98 - String line = this.in.readLine();
40.99 - if(line.startsWith("500 ")) // OVER not supported
40.100 - {
40.101 - this.out.print("XOVER " + start + "-" + end + "\r\n");
40.102 - this.out.flush();
40.103 -
40.104 - line = this.in.readLine();
40.105 - }
40.106 -
40.107 - if(line.startsWith("224 "))
40.108 - {
40.109 - List<String> messages = new ArrayList<String>();
40.110 - line = this.in.readLine();
40.111 - while(!".".equals(line))
40.112 - {
40.113 - String mid = line.split("\t")[4]; // 5th should be the Message-ID
40.114 - messages.add(mid);
40.115 - line = this.in.readLine();
40.116 - }
40.117 - return messages;
40.118 - }
40.119 - else
40.120 - {
40.121 - throw new IOException("Server return for OVER/XOVER: " + line);
40.122 - }
40.123 - }
40.124 -
40.125 - @Override
40.126 - public void run()
40.127 - {
40.128 - while(isRunning())
40.129 - {
40.130 - int pullInterval = 1000 *
40.131 - Config.inst().get(Config.FEED_PULLINTERVAL, 3600);
40.132 - String host = "localhost";
40.133 - int port = 119;
40.134 -
40.135 - Log.get().info("Start PullFeeder run...");
40.136 + if (!highMarks.containsKey(sub)) {
40.137 + // Set a initial highMark
40.138 + this.highMarks.put(sub, 0);
40.139 + }
40.140 + }
40.141
40.142 - try
40.143 - {
40.144 - this.subscriptions.clear();
40.145 - List<Subscription> subsPull = StorageManager.current()
40.146 - .getSubscriptions(FeedManager.TYPE_PULL);
40.147 - for(Subscription sub : subsPull)
40.148 - {
40.149 - addSubscription(sub);
40.150 - }
40.151 - }
40.152 - catch(StorageBackendException ex)
40.153 - {
40.154 - Log.get().log(Level.SEVERE, host, ex);
40.155 - }
40.156 + /**
40.157 + * Changes to the given group and returns its high mark.
40.158 + * @param groupName
40.159 + * @return
40.160 + */
40.161 + private int changeGroup(String groupName)
40.162 + throws IOException
40.163 + {
40.164 + this.out.print("GROUP " + groupName + "\r\n");
40.165 + this.out.flush();
40.166
40.167 - try
40.168 - {
40.169 - for(Subscription sub : this.subscriptions)
40.170 - {
40.171 - host = sub.getHost();
40.172 - port = sub.getPort();
40.173 + String line = this.in.readLine();
40.174 + if (line.startsWith("211 ")) {
40.175 + int highmark = Integer.parseInt(line.split(" ")[3]);
40.176 + return highmark;
40.177 + } else {
40.178 + throw new IOException("GROUP " + groupName + " returned: " + line);
40.179 + }
40.180 + }
40.181
40.182 - try
40.183 - {
40.184 - Log.get().info("Feeding " + sub.getGroup() + " from " + sub.getHost());
40.185 - try
40.186 - {
40.187 - connectTo(host, port);
40.188 - }
40.189 - catch(SocketException ex)
40.190 - {
40.191 - Log.get().info("Skipping " + sub.getHost() + ": " + ex);
40.192 - continue;
40.193 - }
40.194 -
40.195 - int oldMark = this.highMarks.get(sub);
40.196 - int newMark = changeGroup(sub.getGroup());
40.197 -
40.198 - if(oldMark != newMark)
40.199 - {
40.200 - List<String> messageIDs = over(oldMark, newMark);
40.201 + private void connectTo(final String host, final int port)
40.202 + throws IOException, UnknownHostException
40.203 + {
40.204 + Socket socket = new Socket(host, port);
40.205 + this.out = new PrintWriter(socket.getOutputStream());
40.206 + this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
40.207
40.208 - for(String messageID : messageIDs)
40.209 - {
40.210 - if(!StorageManager.current().isArticleExisting(messageID))
40.211 - {
40.212 - try
40.213 - {
40.214 - // Post the message via common socket connection
40.215 - ArticleReader aread =
40.216 - new ArticleReader(sub.getHost(), sub.getPort(), messageID);
40.217 - byte[] abuf = aread.getArticleData();
40.218 - if(abuf == null)
40.219 - {
40.220 - Log.get().warning("Could not feed " + messageID
40.221 - + " from " + sub.getHost());
40.222 - }
40.223 - else
40.224 - {
40.225 - Log.get().info("Feeding " + messageID);
40.226 - ArticleWriter awrite = new ArticleWriter(
40.227 - "localhost", Config.inst().get(Config.PORT, 119));
40.228 - awrite.writeArticle(abuf);
40.229 - awrite.close();
40.230 - }
40.231 - Stats.getInstance().mailFeeded(sub.getGroup());
40.232 - }
40.233 - catch(IOException ex)
40.234 - {
40.235 - // There may be a temporary network failure
40.236 - ex.printStackTrace();
40.237 - Log.get().warning("Skipping mail " + messageID + " due to exception.");
40.238 - }
40.239 - }
40.240 - } // for(;;)
40.241 - this.highMarks.put(sub, newMark);
40.242 - }
40.243 -
40.244 - disconnect();
40.245 - }
40.246 - catch(StorageBackendException ex)
40.247 - {
40.248 - ex.printStackTrace();
40.249 - }
40.250 - catch(IOException ex)
40.251 - {
40.252 - ex.printStackTrace();
40.253 - Log.get().severe("PullFeeder run stopped due to exception.");
40.254 - }
40.255 - } // for(Subscription sub : subscriptions)
40.256 -
40.257 - Log.get().info("PullFeeder run ended. Waiting " + pullInterval / 1000 + "s");
40.258 - Thread.sleep(pullInterval);
40.259 - }
40.260 - catch(InterruptedException ex)
40.261 - {
40.262 - Log.get().warning(ex.getMessage());
40.263 - }
40.264 - }
40.265 - }
40.266 -
40.267 + String line = in.readLine();
40.268 + if (!(line.charAt(0) == '2')) // Could be 200 or 2xx if posting is not allowed
40.269 + {
40.270 + throw new IOException(line);
40.271 + }
40.272 +
40.273 + // Send MODE READER to peer, some newsservers are friendlier then
40.274 + this.out.println("MODE READER\r\n");
40.275 + this.out.flush();
40.276 + line = this.in.readLine();
40.277 + }
40.278 +
40.279 + private void disconnect()
40.280 + throws IOException
40.281 + {
40.282 + this.out.print("QUIT\r\n");
40.283 + this.out.flush();
40.284 + this.out.close();
40.285 + this.in.close();
40.286 +
40.287 + this.out = null;
40.288 + this.in = null;
40.289 + }
40.290 +
40.291 + /**
40.292 + * Uses the OVER or XOVER command to get a list of message overviews that
40.293 + * may be unknown to this feeder and are about to be peered.
40.294 + * @param start
40.295 + * @param end
40.296 + * @return A list of message ids with potentially interesting messages.
40.297 + */
40.298 + private List<String> over(int start, int end)
40.299 + throws IOException
40.300 + {
40.301 + this.out.print("OVER " + start + "-" + end + "\r\n");
40.302 + this.out.flush();
40.303 +
40.304 + String line = this.in.readLine();
40.305 + if (line.startsWith("500 ")) // OVER not supported
40.306 + {
40.307 + this.out.print("XOVER " + start + "-" + end + "\r\n");
40.308 + this.out.flush();
40.309 +
40.310 + line = this.in.readLine();
40.311 + }
40.312 +
40.313 + if (line.startsWith("224 ")) {
40.314 + List<String> messages = new ArrayList<String>();
40.315 + line = this.in.readLine();
40.316 + while (!".".equals(line)) {
40.317 + String mid = line.split("\t")[4]; // 5th should be the Message-ID
40.318 + messages.add(mid);
40.319 + line = this.in.readLine();
40.320 + }
40.321 + return messages;
40.322 + } else {
40.323 + throw new IOException("Server return for OVER/XOVER: " + line);
40.324 + }
40.325 + }
40.326 +
40.327 + @Override
40.328 + public void run()
40.329 + {
40.330 + while (isRunning()) {
40.331 + int pullInterval = 1000
40.332 + * Config.inst().get(Config.FEED_PULLINTERVAL, 3600);
40.333 + String host = "localhost";
40.334 + int port = 119;
40.335 +
40.336 + Log.get().info("Start PullFeeder run...");
40.337 +
40.338 + try {
40.339 + this.subscriptions.clear();
40.340 + List<Subscription> subsPull = StorageManager.current().getSubscriptions(FeedManager.TYPE_PULL);
40.341 + for (Subscription sub : subsPull) {
40.342 + addSubscription(sub);
40.343 + }
40.344 + } catch (StorageBackendException ex) {
40.345 + Log.get().log(Level.SEVERE, host, ex);
40.346 + }
40.347 +
40.348 + try {
40.349 + for (Subscription sub : this.subscriptions) {
40.350 + host = sub.getHost();
40.351 + port = sub.getPort();
40.352 +
40.353 + try {
40.354 + Log.get().info("Feeding " + sub.getGroup() + " from " + sub.getHost());
40.355 + try {
40.356 + connectTo(host, port);
40.357 + } catch (SocketException ex) {
40.358 + Log.get().info("Skipping " + sub.getHost() + ": " + ex);
40.359 + continue;
40.360 + }
40.361 +
40.362 + int oldMark = this.highMarks.get(sub);
40.363 + int newMark = changeGroup(sub.getGroup());
40.364 +
40.365 + if (oldMark != newMark) {
40.366 + List<String> messageIDs = over(oldMark, newMark);
40.367 +
40.368 + for (String messageID : messageIDs) {
40.369 + if (!StorageManager.current().isArticleExisting(messageID)) {
40.370 + try {
40.371 + // Post the message via common socket connection
40.372 + ArticleReader aread =
40.373 + new ArticleReader(sub.getHost(), sub.getPort(), messageID);
40.374 + byte[] abuf = aread.getArticleData();
40.375 + if (abuf == null) {
40.376 + Log.get().warning("Could not feed " + messageID
40.377 + + " from " + sub.getHost());
40.378 + } else {
40.379 + Log.get().info("Feeding " + messageID);
40.380 + ArticleWriter awrite = new ArticleWriter(
40.381 + "localhost", Config.inst().get(Config.PORT, 119));
40.382 + awrite.writeArticle(abuf);
40.383 + awrite.close();
40.384 + }
40.385 + Stats.getInstance().mailFeeded(sub.getGroup());
40.386 + } catch (IOException ex) {
40.387 + // There may be a temporary network failure
40.388 + ex.printStackTrace();
40.389 + Log.get().warning("Skipping mail " + messageID + " due to exception.");
40.390 + }
40.391 + }
40.392 + } // for(;;)
40.393 + this.highMarks.put(sub, newMark);
40.394 + }
40.395 +
40.396 + disconnect();
40.397 + } catch (StorageBackendException ex) {
40.398 + ex.printStackTrace();
40.399 + } catch (IOException ex) {
40.400 + ex.printStackTrace();
40.401 + Log.get().severe("PullFeeder run stopped due to exception.");
40.402 + }
40.403 + } // for(Subscription sub : subscriptions)
40.404 +
40.405 + Log.get().info("PullFeeder run ended. Waiting " + pullInterval / 1000 + "s");
40.406 + Thread.sleep(pullInterval);
40.407 + } catch (InterruptedException ex) {
40.408 + Log.get().warning(ex.getMessage());
40.409 + }
40.410 + }
40.411 + }
40.412 }
41.1 --- a/src/org/sonews/feed/PushFeeder.java Sun Aug 29 17:43:58 2010 +0200
41.2 +++ b/src/org/sonews/feed/PushFeeder.java Sun Aug 29 18:17:37 2010 +0200
41.3 @@ -37,82 +37,65 @@
41.4 */
41.5 class PushFeeder extends AbstractDaemon
41.6 {
41.7 -
41.8 - private ConcurrentLinkedQueue<Article> articleQueue =
41.9 - new ConcurrentLinkedQueue<Article>();
41.10 -
41.11 - @Override
41.12 - public void run()
41.13 - {
41.14 - while(isRunning())
41.15 - {
41.16 - try
41.17 - {
41.18 - synchronized(this)
41.19 - {
41.20 - this.wait();
41.21 - }
41.22 -
41.23 - List<Subscription> subscriptions = StorageManager.current()
41.24 - .getSubscriptions(FeedManager.TYPE_PUSH);
41.25
41.26 - Article article = this.articleQueue.poll();
41.27 - String[] groups = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
41.28 - Log.get().info("PushFeed: " + article.getMessageID());
41.29 - for(Subscription sub : subscriptions)
41.30 - {
41.31 - // Circle check
41.32 - if(article.getHeader(Headers.PATH)[0].contains(sub.getHost()))
41.33 - {
41.34 - Log.get().info(article.getMessageID() + " skipped for host "
41.35 - + sub.getHost());
41.36 - continue;
41.37 - }
41.38 + private ConcurrentLinkedQueue<Article> articleQueue =
41.39 + new ConcurrentLinkedQueue<Article>();
41.40
41.41 - try
41.42 - {
41.43 - for(String group : groups)
41.44 - {
41.45 - if(sub.getGroup().equals(group))
41.46 - {
41.47 - // Delete headers that may cause problems
41.48 - article.removeHeader(Headers.NNTP_POSTING_DATE);
41.49 - article.removeHeader(Headers.NNTP_POSTING_HOST);
41.50 - article.removeHeader(Headers.X_COMPLAINTS_TO);
41.51 - article.removeHeader(Headers.X_TRACE);
41.52 - article.removeHeader(Headers.XREF);
41.53 -
41.54 - // POST the message to remote server
41.55 - ArticleWriter awriter = new ArticleWriter(sub.getHost(), sub.getPort());
41.56 - awriter.writeArticle(article);
41.57 - break;
41.58 - }
41.59 - }
41.60 - }
41.61 - catch(IOException ex)
41.62 - {
41.63 - Log.get().warning(ex.toString());
41.64 - }
41.65 - }
41.66 - }
41.67 - catch(StorageBackendException ex)
41.68 - {
41.69 - Log.get().severe(ex.toString());
41.70 - }
41.71 - catch(InterruptedException ex)
41.72 - {
41.73 - Log.get().warning("PushFeeder interrupted: " + ex);
41.74 - }
41.75 - }
41.76 - }
41.77 -
41.78 - public void queueForPush(Article article)
41.79 - {
41.80 - this.articleQueue.add(article);
41.81 - synchronized(this)
41.82 - {
41.83 - this.notifyAll();
41.84 - }
41.85 - }
41.86 -
41.87 + @Override
41.88 + public void run()
41.89 + {
41.90 + while (isRunning()) {
41.91 + try {
41.92 + synchronized (this) {
41.93 + this.wait();
41.94 + }
41.95 +
41.96 + List<Subscription> subscriptions = StorageManager.current().getSubscriptions(FeedManager.TYPE_PUSH);
41.97 +
41.98 + Article article = this.articleQueue.poll();
41.99 + String[] groups = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
41.100 + Log.get().info("PushFeed: " + article.getMessageID());
41.101 + for (Subscription sub : subscriptions) {
41.102 + // Circle check
41.103 + if (article.getHeader(Headers.PATH)[0].contains(sub.getHost())) {
41.104 + Log.get().info(article.getMessageID() + " skipped for host "
41.105 + + sub.getHost());
41.106 + continue;
41.107 + }
41.108 +
41.109 + try {
41.110 + for (String group : groups) {
41.111 + if (sub.getGroup().equals(group)) {
41.112 + // Delete headers that may cause problems
41.113 + article.removeHeader(Headers.NNTP_POSTING_DATE);
41.114 + article.removeHeader(Headers.NNTP_POSTING_HOST);
41.115 + article.removeHeader(Headers.X_COMPLAINTS_TO);
41.116 + article.removeHeader(Headers.X_TRACE);
41.117 + article.removeHeader(Headers.XREF);
41.118 +
41.119 + // POST the message to remote server
41.120 + ArticleWriter awriter = new ArticleWriter(sub.getHost(), sub.getPort());
41.121 + awriter.writeArticle(article);
41.122 + break;
41.123 + }
41.124 + }
41.125 + } catch (IOException ex) {
41.126 + Log.get().warning(ex.toString());
41.127 + }
41.128 + }
41.129 + } catch (StorageBackendException ex) {
41.130 + Log.get().severe(ex.toString());
41.131 + } catch (InterruptedException ex) {
41.132 + Log.get().warning("PushFeeder interrupted: " + ex);
41.133 + }
41.134 + }
41.135 + }
41.136 +
41.137 + public void queueForPush(Article article)
41.138 + {
41.139 + this.articleQueue.add(article);
41.140 + synchronized (this) {
41.141 + this.notifyAll();
41.142 + }
41.143 + }
41.144 }
42.1 --- a/src/org/sonews/feed/Subscription.java Sun Aug 29 17:43:58 2010 +0200
42.2 +++ b/src/org/sonews/feed/Subscription.java Sun Aug 29 18:17:37 2010 +0200
42.3 @@ -24,61 +24,57 @@
42.4 * @author Christian Lins
42.5 * @since sonews/0.5.0
42.6 */
42.7 -public class Subscription
42.8 +public class Subscription
42.9 {
42.10
42.11 - private String host;
42.12 - private int port;
42.13 - private int feedtype;
42.14 - private String group;
42.15 -
42.16 - public Subscription(String host, int port, int feedtype, String group)
42.17 - {
42.18 - this.host = host;
42.19 - this.port = port;
42.20 - this.feedtype = feedtype;
42.21 - this.group = group;
42.22 - }
42.23 + private String host;
42.24 + private int port;
42.25 + private int feedtype;
42.26 + private String group;
42.27
42.28 - @Override
42.29 - public boolean equals(Object obj)
42.30 - {
42.31 - if(obj instanceof Subscription)
42.32 - {
42.33 - Subscription sub = (Subscription)obj;
42.34 - return sub.host.equals(host) && sub.group.equals(group)
42.35 - && sub.port == port && sub.feedtype == feedtype;
42.36 - }
42.37 - else
42.38 - {
42.39 - return false;
42.40 - }
42.41 - }
42.42 + public Subscription(String host, int port, int feedtype, String group)
42.43 + {
42.44 + this.host = host;
42.45 + this.port = port;
42.46 + this.feedtype = feedtype;
42.47 + this.group = group;
42.48 + }
42.49
42.50 - @Override
42.51 - public int hashCode()
42.52 - {
42.53 - return host.hashCode() + port + feedtype + group.hashCode();
42.54 - }
42.55 + @Override
42.56 + public boolean equals(Object obj)
42.57 + {
42.58 + if (obj instanceof Subscription) {
42.59 + Subscription sub = (Subscription) obj;
42.60 + return sub.host.equals(host) && sub.group.equals(group)
42.61 + && sub.port == port && sub.feedtype == feedtype;
42.62 + } else {
42.63 + return false;
42.64 + }
42.65 + }
42.66
42.67 - public int getFeedtype()
42.68 - {
42.69 - return feedtype;
42.70 - }
42.71 + @Override
42.72 + public int hashCode()
42.73 + {
42.74 + return host.hashCode() + port + feedtype + group.hashCode();
42.75 + }
42.76
42.77 - public String getGroup()
42.78 - {
42.79 - return group;
42.80 - }
42.81 + public int getFeedtype()
42.82 + {
42.83 + return feedtype;
42.84 + }
42.85
42.86 - public String getHost()
42.87 - {
42.88 - return host;
42.89 - }
42.90 + public String getGroup()
42.91 + {
42.92 + return group;
42.93 + }
42.94
42.95 - public int getPort()
42.96 - {
42.97 - return port;
42.98 - }
42.99 -
42.100 + public String getHost()
42.101 + {
42.102 + return host;
42.103 + }
42.104 +
42.105 + public int getPort()
42.106 + {
42.107 + return port;
42.108 + }
42.109 }
43.1 --- a/src/org/sonews/mlgw/Dispatcher.java Sun Aug 29 17:43:58 2010 +0200
43.2 +++ b/src/org/sonews/mlgw/Dispatcher.java Sun Aug 29 18:17:37 2010 +0200
43.3 @@ -43,267 +43,233 @@
43.4 * @author Christian Lins
43.5 * @since sonews/0.5.0
43.6 */
43.7 -public class Dispatcher
43.8 +public class Dispatcher
43.9 {
43.10
43.11 - static class PasswordAuthenticator extends Authenticator
43.12 - {
43.13 -
43.14 - @Override
43.15 - public PasswordAuthentication getPasswordAuthentication()
43.16 - {
43.17 - final String username =
43.18 - Config.inst().get(Config.MLSEND_USER, "user");
43.19 - final String password =
43.20 - Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
43.21 + static class PasswordAuthenticator extends Authenticator
43.22 + {
43.23
43.24 - return new PasswordAuthentication(username, password);
43.25 - }
43.26 -
43.27 - }
43.28 + @Override
43.29 + public PasswordAuthentication getPasswordAuthentication()
43.30 + {
43.31 + final String username =
43.32 + Config.inst().get(Config.MLSEND_USER, "user");
43.33 + final String password =
43.34 + Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
43.35
43.36 - /**
43.37 - * Chunks out the email address of the full List-Post header field.
43.38 - * @param listPostValue
43.39 - * @return The matching email address or null
43.40 - */
43.41 - private static String chunkListPost(String listPostValue)
43.42 - {
43.43 - // listPostValue is of form "<mailto:dev@openoffice.org>"
43.44 - Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
43.45 - Matcher mailMatcher = mailPattern.matcher(listPostValue);
43.46 - if(mailMatcher.find())
43.47 - {
43.48 - return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
43.49 - }
43.50 - else
43.51 - {
43.52 - return null;
43.53 - }
43.54 - }
43.55 + return new PasswordAuthentication(username, password);
43.56 + }
43.57 + }
43.58
43.59 - /**
43.60 - * This method inspects the header of the given message, trying
43.61 - * to find the most appropriate recipient.
43.62 - * @param msg
43.63 - * @param fallback If this is false only List-Post and X-List-Post headers
43.64 - * are examined.
43.65 - * @return null or fitting group name for the given message.
43.66 - */
43.67 - private static List<String> getGroupFor(final Message msg, final boolean fallback)
43.68 - throws MessagingException, StorageBackendException
43.69 - {
43.70 - List<String> groups = null;
43.71 + /**
43.72 + * Chunks out the email address of the full List-Post header field.
43.73 + * @param listPostValue
43.74 + * @return The matching email address or null
43.75 + */
43.76 + private static String chunkListPost(String listPostValue)
43.77 + {
43.78 + // listPostValue is of form "<mailto:dev@openoffice.org>"
43.79 + Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
43.80 + Matcher mailMatcher = mailPattern.matcher(listPostValue);
43.81 + if (mailMatcher.find()) {
43.82 + return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
43.83 + } else {
43.84 + return null;
43.85 + }
43.86 + }
43.87
43.88 - // Is there a List-Post header?
43.89 - String[] listPost = msg.getHeader(Headers.LIST_POST);
43.90 - InternetAddress listPostAddr;
43.91 + /**
43.92 + * This method inspects the header of the given message, trying
43.93 + * to find the most appropriate recipient.
43.94 + * @param msg
43.95 + * @param fallback If this is false only List-Post and X-List-Post headers
43.96 + * are examined.
43.97 + * @return null or fitting group name for the given message.
43.98 + */
43.99 + private static List<String> getGroupFor(final Message msg, final boolean fallback)
43.100 + throws MessagingException, StorageBackendException
43.101 + {
43.102 + List<String> groups = null;
43.103
43.104 - if(listPost == null || listPost.length == 0 || "".equals(listPost[0]))
43.105 - {
43.106 - // Is there a X-List-Post header?
43.107 - listPost = msg.getHeader(Headers.X_LIST_POST);
43.108 - }
43.109 + // Is there a List-Post header?
43.110 + String[] listPost = msg.getHeader(Headers.LIST_POST);
43.111 + InternetAddress listPostAddr;
43.112
43.113 - if(listPost != null && listPost.length > 0
43.114 - && !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null)
43.115 - {
43.116 - // listPost[0] is of form "<mailto:dev@openoffice.org>"
43.117 - listPost[0] = chunkListPost(listPost[0]);
43.118 - listPostAddr = new InternetAddress(listPost[0], false);
43.119 - groups = StorageManager.current().getGroupsForList(listPostAddr.getAddress());
43.120 - }
43.121 - else if(fallback)
43.122 - {
43.123 - Log.get().info("Using fallback recipient discovery for: " + msg.getSubject());
43.124 - groups = new ArrayList<String>();
43.125 - // Fallback to TO/CC/BCC addresses
43.126 - Address[] to = msg.getAllRecipients();
43.127 - for(Address toa : to) // Address can have '<' '>' around
43.128 - {
43.129 - if(toa instanceof InternetAddress)
43.130 - {
43.131 - List<String> g = StorageManager.current()
43.132 - .getGroupsForList(((InternetAddress)toa).getAddress());
43.133 - groups.addAll(g);
43.134 - }
43.135 - }
43.136 - }
43.137 -
43.138 - return groups;
43.139 - }
43.140 -
43.141 - /**
43.142 - * Posts a message that was received from a mailing list to the
43.143 - * appropriate newsgroup.
43.144 - * If the message already exists in the storage, this message checks
43.145 - * if it must be posted in an additional group. This can happen for
43.146 - * crosspostings in different mailing lists.
43.147 - * @param msg
43.148 - */
43.149 - public static boolean toGroup(final Message msg)
43.150 - {
43.151 - if(msg == null)
43.152 + if (listPost == null || listPost.length == 0 || "".equals(listPost[0])) {
43.153 + // Is there a X-List-Post header?
43.154 + listPost = msg.getHeader(Headers.X_LIST_POST);
43.155 + }
43.156 +
43.157 + if (listPost != null && listPost.length > 0
43.158 + && !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null) {
43.159 + // listPost[0] is of form "<mailto:dev@openoffice.org>"
43.160 + listPost[0] = chunkListPost(listPost[0]);
43.161 + listPostAddr = new InternetAddress(listPost[0], false);
43.162 + groups = StorageManager.current().getGroupsForList(listPostAddr.getAddress());
43.163 + } else if (fallback) {
43.164 + Log.get().info("Using fallback recipient discovery for: " + msg.getSubject());
43.165 + groups = new ArrayList<String>();
43.166 + // Fallback to TO/CC/BCC addresses
43.167 + Address[] to = msg.getAllRecipients();
43.168 + for (Address toa : to) // Address can have '<' '>' around
43.169 + {
43.170 + if (toa instanceof InternetAddress) {
43.171 + List<String> g = StorageManager.current().getGroupsForList(((InternetAddress) toa).getAddress());
43.172 + groups.addAll(g);
43.173 + }
43.174 + }
43.175 + }
43.176 +
43.177 + return groups;
43.178 + }
43.179 +
43.180 + /**
43.181 + * Posts a message that was received from a mailing list to the
43.182 + * appropriate newsgroup.
43.183 + * If the message already exists in the storage, this message checks
43.184 + * if it must be posted in an additional group. This can happen for
43.185 + * crosspostings in different mailing lists.
43.186 + * @param msg
43.187 + */
43.188 + public static boolean toGroup(final Message msg)
43.189 {
43.190 - throw new IllegalArgumentException("Argument 'msg' must not be null!");
43.191 - }
43.192 + if (msg == null) {
43.193 + throw new IllegalArgumentException("Argument 'msg' must not be null!");
43.194 + }
43.195
43.196 - try
43.197 - {
43.198 - // Create new Article object
43.199 - Article article = new Article(msg);
43.200 - boolean posted = false;
43.201 + try {
43.202 + // Create new Article object
43.203 + Article article = new Article(msg);
43.204 + boolean posted = false;
43.205
43.206 - // Check if this mail is already existing the storage
43.207 - boolean updateReq =
43.208 - StorageManager.current().isArticleExisting(article.getMessageID());
43.209 + // Check if this mail is already existing the storage
43.210 + boolean updateReq =
43.211 + StorageManager.current().isArticleExisting(article.getMessageID());
43.212
43.213 - List<String> newsgroups = getGroupFor(msg, !updateReq);
43.214 - List<String> oldgroups = new ArrayList<String>();
43.215 - if(updateReq)
43.216 - {
43.217 - // Check for duplicate entries of the same group
43.218 - Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
43.219 - if(oldArticle != null)
43.220 - {
43.221 - List<Group> oldGroups = oldArticle.getGroups();
43.222 - for(Group oldGroup : oldGroups)
43.223 - {
43.224 - if(!newsgroups.contains(oldGroup.getName()))
43.225 - {
43.226 - oldgroups.add(oldGroup.getName());
43.227 - }
43.228 - }
43.229 + List<String> newsgroups = getGroupFor(msg, !updateReq);
43.230 + List<String> oldgroups = new ArrayList<String>();
43.231 + if (updateReq) {
43.232 + // Check for duplicate entries of the same group
43.233 + Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
43.234 + if (oldArticle != null) {
43.235 + List<Group> oldGroups = oldArticle.getGroups();
43.236 + for (Group oldGroup : oldGroups) {
43.237 + if (!newsgroups.contains(oldGroup.getName())) {
43.238 + oldgroups.add(oldGroup.getName());
43.239 + }
43.240 + }
43.241 + }
43.242 + }
43.243 +
43.244 + if (newsgroups.size() > 0) {
43.245 + newsgroups.addAll(oldgroups);
43.246 + StringBuilder groups = new StringBuilder();
43.247 + for (int n = 0; n < newsgroups.size(); n++) {
43.248 + groups.append(newsgroups.get(n));
43.249 + if (n + 1 != newsgroups.size()) {
43.250 + groups.append(',');
43.251 + }
43.252 + }
43.253 + Log.get().info("Posting to group " + groups.toString());
43.254 +
43.255 + article.setGroup(groups.toString());
43.256 + //article.removeHeader(Headers.REPLY_TO);
43.257 + //article.removeHeader(Headers.TO);
43.258 +
43.259 + // Write article to database
43.260 + if (updateReq) {
43.261 + Log.get().info("Updating " + article.getMessageID()
43.262 + + " with additional groups");
43.263 + StorageManager.current().delete(article.getMessageID());
43.264 + StorageManager.current().addArticle(article);
43.265 + } else {
43.266 + Log.get().info("Gatewaying " + article.getMessageID() + " to "
43.267 + + article.getHeader(Headers.NEWSGROUPS)[0]);
43.268 + StorageManager.current().addArticle(article);
43.269 + Stats.getInstance().mailGatewayed(
43.270 + article.getHeader(Headers.NEWSGROUPS)[0]);
43.271 + }
43.272 + posted = true;
43.273 + } else {
43.274 + StringBuilder buf = new StringBuilder();
43.275 + for (Address toa : msg.getAllRecipients()) {
43.276 + buf.append(' ');
43.277 + buf.append(toa.toString());
43.278 + }
43.279 + buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
43.280 + Log.get().warning("No group for" + buf.toString());
43.281 + }
43.282 + return posted;
43.283 + } catch (Exception ex) {
43.284 + ex.printStackTrace();
43.285 + return false;
43.286 }
43.287 - }
43.288 + }
43.289
43.290 - if(newsgroups.size() > 0)
43.291 - {
43.292 - newsgroups.addAll(oldgroups);
43.293 - StringBuilder groups = new StringBuilder();
43.294 - for(int n = 0; n < newsgroups.size(); n++)
43.295 - {
43.296 - groups.append(newsgroups.get(n));
43.297 - if (n + 1 != newsgroups.size())
43.298 - {
43.299 - groups.append(',');
43.300 - }
43.301 - }
43.302 - Log.get().info("Posting to group " + groups.toString());
43.303 + /**
43.304 + * Mails a message received through NNTP to the appropriate mailing list.
43.305 + * This method MAY be called several times by PostCommand for the same
43.306 + * article.
43.307 + */
43.308 + public static void toList(Article article, String group)
43.309 + throws IOException, MessagingException, StorageBackendException
43.310 + {
43.311 + // Get mailing lists for the group of this article
43.312 + List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
43.313
43.314 - article.setGroup(groups.toString());
43.315 - //article.removeHeader(Headers.REPLY_TO);
43.316 - //article.removeHeader(Headers.TO);
43.317 + if (rcptAddresses == null || rcptAddresses.size() == 0) {
43.318 + Log.get().warning("No ML-address for " + group + " found.");
43.319 + return;
43.320 + }
43.321
43.322 - // Write article to database
43.323 - if(updateReq)
43.324 - {
43.325 - Log.get().info("Updating " + article.getMessageID()
43.326 - + " with additional groups");
43.327 - StorageManager.current().delete(article.getMessageID());
43.328 - StorageManager.current().addArticle(article);
43.329 - }
43.330 - else
43.331 - {
43.332 - Log.get().info("Gatewaying " + article.getMessageID() + " to "
43.333 - + article.getHeader(Headers.NEWSGROUPS)[0]);
43.334 - StorageManager.current().addArticle(article);
43.335 - Stats.getInstance().mailGatewayed(
43.336 - article.getHeader(Headers.NEWSGROUPS)[0]);
43.337 - }
43.338 - posted = true;
43.339 - }
43.340 - else
43.341 - {
43.342 - StringBuilder buf = new StringBuilder();
43.343 - for (Address toa : msg.getAllRecipients())
43.344 - {
43.345 - buf.append(' ');
43.346 - buf.append(toa.toString());
43.347 - }
43.348 - buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
43.349 - Log.get().warning("No group for" + buf.toString());
43.350 - }
43.351 - return posted;
43.352 - }
43.353 - catch(Exception ex)
43.354 - {
43.355 - ex.printStackTrace();
43.356 - return false;
43.357 - }
43.358 - }
43.359 -
43.360 - /**
43.361 - * Mails a message received through NNTP to the appropriate mailing list.
43.362 - * This method MAY be called several times by PostCommand for the same
43.363 - * article.
43.364 - */
43.365 - public static void toList(Article article, String group)
43.366 - throws IOException, MessagingException, StorageBackendException
43.367 - {
43.368 - // Get mailing lists for the group of this article
43.369 - List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
43.370 + for (String rcptAddress : rcptAddresses) {
43.371 + // Compose message and send it via given SMTP-Host
43.372 + String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
43.373 + int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
43.374 + String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
43.375 + String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
43.376 + String smtpFrom = Config.inst().get(
43.377 + Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
43.378
43.379 - if(rcptAddresses == null || rcptAddresses.size() == 0)
43.380 - {
43.381 - Log.get().warning("No ML-address for " + group + " found.");
43.382 - return;
43.383 - }
43.384 + // TODO: Make Article cloneable()
43.385 + article.getMessageID(); // Make sure an ID is existing
43.386 + article.removeHeader(Headers.NEWSGROUPS);
43.387 + article.removeHeader(Headers.PATH);
43.388 + article.removeHeader(Headers.LINES);
43.389 + article.removeHeader(Headers.BYTES);
43.390
43.391 - for(String rcptAddress : rcptAddresses)
43.392 - {
43.393 - // Compose message and send it via given SMTP-Host
43.394 - String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
43.395 - int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
43.396 - String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
43.397 - String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
43.398 - String smtpFrom = Config.inst().get(
43.399 - Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
43.400 + article.setHeader("To", rcptAddress);
43.401 + //article.setHeader("Reply-To", listAddress);
43.402
43.403 - // TODO: Make Article cloneable()
43.404 - article.getMessageID(); // Make sure an ID is existing
43.405 - article.removeHeader(Headers.NEWSGROUPS);
43.406 - article.removeHeader(Headers.PATH);
43.407 - article.removeHeader(Headers.LINES);
43.408 - article.removeHeader(Headers.BYTES);
43.409 + if (Config.inst().get(Config.MLSEND_RW_SENDER, false)) {
43.410 + rewriteSenderAddress(article); // Set the SENDER address
43.411 + }
43.412
43.413 - article.setHeader("To", rcptAddress);
43.414 - //article.setHeader("Reply-To", listAddress);
43.415 + SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
43.416 + smtpTransport.send(article, smtpFrom, rcptAddress);
43.417 + smtpTransport.close();
43.418
43.419 - if (Config.inst().get(Config.MLSEND_RW_SENDER, false))
43.420 - {
43.421 - rewriteSenderAddress(article); // Set the SENDER address
43.422 - }
43.423 + Stats.getInstance().mailGatewayed(group);
43.424 + Log.get().info("MLGateway: Mail " + article.getHeader("Subject")[0]
43.425 + + " was delivered to " + rcptAddress + ".");
43.426 + }
43.427 + }
43.428
43.429 - SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
43.430 - smtpTransport.send(article, smtpFrom, rcptAddress);
43.431 - smtpTransport.close();
43.432 + /**
43.433 + * Sets the SENDER header of the given MimeMessage. This might be necessary
43.434 + * for moderated groups that does not allow the "normal" FROM sender.
43.435 + * @param msg
43.436 + * @throws javax.mail.MessagingException
43.437 + */
43.438 + private static void rewriteSenderAddress(Article msg)
43.439 + throws MessagingException
43.440 + {
43.441 + String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
43.442
43.443 - Stats.getInstance().mailGatewayed(group);
43.444 - Log.get().info("MLGateway: Mail " + article.getHeader("Subject")[0]
43.445 - + " was delivered to " + rcptAddress + ".");
43.446 - }
43.447 - }
43.448 -
43.449 - /**
43.450 - * Sets the SENDER header of the given MimeMessage. This might be necessary
43.451 - * for moderated groups that does not allow the "normal" FROM sender.
43.452 - * @param msg
43.453 - * @throws javax.mail.MessagingException
43.454 - */
43.455 - private static void rewriteSenderAddress(Article msg)
43.456 - throws MessagingException
43.457 - {
43.458 - String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
43.459 -
43.460 - if(mlAddress != null)
43.461 - {
43.462 - msg.setHeader(Headers.SENDER, mlAddress);
43.463 - }
43.464 - else
43.465 - {
43.466 - throw new MessagingException("Cannot rewrite SENDER header!");
43.467 - }
43.468 - }
43.469 -
43.470 + if (mlAddress != null) {
43.471 + msg.setHeader(Headers.SENDER, mlAddress);
43.472 + } else {
43.473 + throw new MessagingException("Cannot rewrite SENDER header!");
43.474 + }
43.475 + }
43.476 }
44.1 --- a/src/org/sonews/mlgw/MailPoller.java Sun Aug 29 17:43:58 2010 +0200
44.2 +++ b/src/org/sonews/mlgw/MailPoller.java Sun Aug 29 18:17:37 2010 +0200
44.3 @@ -42,110 +42,94 @@
44.4 public class MailPoller extends AbstractDaemon
44.5 {
44.6
44.7 - static class PasswordAuthenticator extends Authenticator
44.8 - {
44.9 -
44.10 - @Override
44.11 - public PasswordAuthentication getPasswordAuthentication()
44.12 - {
44.13 - final String username =
44.14 - Config.inst().get(Config.MLPOLL_USER, "user");
44.15 - final String password =
44.16 - Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
44.17 + static class PasswordAuthenticator extends Authenticator
44.18 + {
44.19
44.20 - return new PasswordAuthentication(username, password);
44.21 - }
44.22 -
44.23 - }
44.24 -
44.25 - @Override
44.26 - public void run()
44.27 - {
44.28 - Log.get().info("Starting Mailinglist Poller...");
44.29 - int errors = 0;
44.30 - while(isRunning())
44.31 - {
44.32 - try
44.33 - {
44.34 - // Wait some time between runs. At the beginning has advantages,
44.35 - // because the wait is not skipped if an exception occurs.
44.36 - Thread.sleep(60000 * (errors + 1)); // one minute * errors
44.37 -
44.38 - final String host =
44.39 - Config.inst().get(Config.MLPOLL_HOST, "samplehost");
44.40 - final String username =
44.41 - Config.inst().get(Config.MLPOLL_USER, "user");
44.42 - final String password =
44.43 - Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
44.44 -
44.45 - Stats.getInstance().mlgwRunStart();
44.46 -
44.47 - // Create empty properties
44.48 - Properties props = System.getProperties();
44.49 - props.put("mail.pop3.host", host);
44.50 - props.put("mail.mime.address.strict", "false");
44.51 + @Override
44.52 + public PasswordAuthentication getPasswordAuthentication()
44.53 + {
44.54 + final String username =
44.55 + Config.inst().get(Config.MLPOLL_USER, "user");
44.56 + final String password =
44.57 + Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
44.58
44.59 - // Get session
44.60 - Session session = Session.getInstance(props);
44.61 + return new PasswordAuthentication(username, password);
44.62 + }
44.63 + }
44.64
44.65 - // Get the store
44.66 - Store store = session.getStore("pop3");
44.67 - store.connect(host, 110, username, password);
44.68 + @Override
44.69 + public void run()
44.70 + {
44.71 + Log.get().info("Starting Mailinglist Poller...");
44.72 + int errors = 0;
44.73 + while (isRunning()) {
44.74 + try {
44.75 + // Wait some time between runs. At the beginning has advantages,
44.76 + // because the wait is not skipped if an exception occurs.
44.77 + Thread.sleep(60000 * (errors + 1)); // one minute * errors
44.78
44.79 - // Get folder
44.80 - Folder folder = store.getFolder("INBOX");
44.81 - folder.open(Folder.READ_WRITE);
44.82 + final String host =
44.83 + Config.inst().get(Config.MLPOLL_HOST, "samplehost");
44.84 + final String username =
44.85 + Config.inst().get(Config.MLPOLL_USER, "user");
44.86 + final String password =
44.87 + Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
44.88
44.89 - // Get directory
44.90 - Message[] messages = folder.getMessages();
44.91 + Stats.getInstance().mlgwRunStart();
44.92
44.93 - // Dispatch messages and delete it afterwards on the inbox
44.94 - for(Message message : messages)
44.95 - {
44.96 - if(Dispatcher.toGroup(message)
44.97 - || Config.inst().get(Config.MLPOLL_DELETEUNKNOWN, false))
44.98 - {
44.99 - // Delete the message
44.100 - message.setFlag(Flag.DELETED, true);
44.101 - }
44.102 - }
44.103 + // Create empty properties
44.104 + Properties props = System.getProperties();
44.105 + props.put("mail.pop3.host", host);
44.106 + props.put("mail.mime.address.strict", "false");
44.107
44.108 - // Close connection
44.109 - folder.close(true); // true to expunge deleted messages
44.110 - store.close();
44.111 - errors = 0;
44.112 -
44.113 - Stats.getInstance().mlgwRunEnd();
44.114 - }
44.115 - catch(NoSuchProviderException ex)
44.116 - {
44.117 - Log.get().severe(ex.toString());
44.118 - shutdown();
44.119 - }
44.120 - catch(AuthenticationFailedException ex)
44.121 - {
44.122 - // AuthentificationFailedException may be thrown if credentials are
44.123 - // bad or if the Mailbox is in use (locked).
44.124 - ex.printStackTrace();
44.125 - errors = errors < 5 ? errors + 1 : errors;
44.126 - }
44.127 - catch(InterruptedException ex)
44.128 - {
44.129 - System.out.println("sonews: " + this + " returns: " + ex);
44.130 - return;
44.131 - }
44.132 - catch(MessagingException ex)
44.133 - {
44.134 - ex.printStackTrace();
44.135 - errors = errors < 5 ? errors + 1 : errors;
44.136 - }
44.137 - catch(Exception ex)
44.138 - {
44.139 - ex.printStackTrace();
44.140 - errors = errors < 5 ? errors + 1 : errors;
44.141 - }
44.142 - }
44.143 - Log.get().severe("MailPoller exited.");
44.144 - }
44.145 -
44.146 + // Get session
44.147 + Session session = Session.getInstance(props);
44.148 +
44.149 + // Get the store
44.150 + Store store = session.getStore("pop3");
44.151 + store.connect(host, 110, username, password);
44.152 +
44.153 + // Get folder
44.154 + Folder folder = store.getFolder("INBOX");
44.155 + folder.open(Folder.READ_WRITE);
44.156 +
44.157 + // Get directory
44.158 + Message[] messages = folder.getMessages();
44.159 +
44.160 + // Dispatch messages and delete it afterwards on the inbox
44.161 + for (Message message : messages) {
44.162 + if (Dispatcher.toGroup(message)
44.163 + || Config.inst().get(Config.MLPOLL_DELETEUNKNOWN, false)) {
44.164 + // Delete the message
44.165 + message.setFlag(Flag.DELETED, true);
44.166 + }
44.167 + }
44.168 +
44.169 + // Close connection
44.170 + folder.close(true); // true to expunge deleted messages
44.171 + store.close();
44.172 + errors = 0;
44.173 +
44.174 + Stats.getInstance().mlgwRunEnd();
44.175 + } catch (NoSuchProviderException ex) {
44.176 + Log.get().severe(ex.toString());
44.177 + shutdown();
44.178 + } catch (AuthenticationFailedException ex) {
44.179 + // AuthentificationFailedException may be thrown if credentials are
44.180 + // bad or if the Mailbox is in use (locked).
44.181 + ex.printStackTrace();
44.182 + errors = errors < 5 ? errors + 1 : errors;
44.183 + } catch (InterruptedException ex) {
44.184 + System.out.println("sonews: " + this + " returns: " + ex);
44.185 + return;
44.186 + } catch (MessagingException ex) {
44.187 + ex.printStackTrace();
44.188 + errors = errors < 5 ? errors + 1 : errors;
44.189 + } catch (Exception ex) {
44.190 + ex.printStackTrace();
44.191 + errors = errors < 5 ? errors + 1 : errors;
44.192 + }
44.193 + }
44.194 + Log.get().severe("MailPoller exited.");
44.195 + }
44.196 }
45.1 --- a/src/org/sonews/mlgw/SMTPTransport.java Sun Aug 29 17:43:58 2010 +0200
45.2 +++ b/src/org/sonews/mlgw/SMTPTransport.java Sun Aug 29 18:17:37 2010 +0200
45.3 @@ -36,98 +36,90 @@
45.4 class SMTPTransport
45.5 {
45.6
45.7 - protected BufferedReader in;
45.8 - protected BufferedOutputStream out;
45.9 - protected Socket socket;
45.10 + protected BufferedReader in;
45.11 + protected BufferedOutputStream out;
45.12 + protected Socket socket;
45.13
45.14 - public SMTPTransport(String host, int port)
45.15 - throws IOException, UnknownHostException
45.16 - {
45.17 - socket = new Socket(host, port);
45.18 - this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
45.19 - this.out = new BufferedOutputStream(socket.getOutputStream());
45.20 + public SMTPTransport(String host, int port)
45.21 + throws IOException, UnknownHostException
45.22 + {
45.23 + socket = new Socket(host, port);
45.24 + this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
45.25 + this.out = new BufferedOutputStream(socket.getOutputStream());
45.26
45.27 - // Read helo from server
45.28 - String line = this.in.readLine();
45.29 - if(line == null || !line.startsWith("220 "))
45.30 - {
45.31 - throw new IOException("Invalid helo from server: " + line);
45.32 - }
45.33 + // Read helo from server
45.34 + String line = this.in.readLine();
45.35 + if (line == null || !line.startsWith("220 ")) {
45.36 + throw new IOException("Invalid helo from server: " + line);
45.37 + }
45.38
45.39 - // Send HELO to server
45.40 - this.out.write(
45.41 - ("HELO " + Config.inst().get(Config.HOSTNAME, "localhost") + "\r\n").getBytes("UTF-8"));
45.42 - this.out.flush();
45.43 - line = this.in.readLine();
45.44 - if(line == null || !line.startsWith("250 "))
45.45 - {
45.46 - throw new IOException("Unexpected reply: " + line);
45.47 - }
45.48 - }
45.49 + // Send HELO to server
45.50 + this.out.write(
45.51 + ("HELO " + Config.inst().get(Config.HOSTNAME, "localhost") + "\r\n").getBytes("UTF-8"));
45.52 + this.out.flush();
45.53 + line = this.in.readLine();
45.54 + if (line == null || !line.startsWith("250 ")) {
45.55 + throw new IOException("Unexpected reply: " + line);
45.56 + }
45.57 + }
45.58
45.59 - public SMTPTransport(String host)
45.60 - throws IOException
45.61 - {
45.62 - this(host, 25);
45.63 - }
45.64 + public SMTPTransport(String host)
45.65 + throws IOException
45.66 + {
45.67 + this(host, 25);
45.68 + }
45.69
45.70 - public void close()
45.71 - throws IOException
45.72 - {
45.73 - this.out.write("QUIT".getBytes("UTF-8"));
45.74 - this.out.flush();
45.75 - this.in.readLine();
45.76 + public void close()
45.77 + throws IOException
45.78 + {
45.79 + this.out.write("QUIT".getBytes("UTF-8"));
45.80 + this.out.flush();
45.81 + this.in.readLine();
45.82
45.83 - this.socket.close();
45.84 - }
45.85 + this.socket.close();
45.86 + }
45.87
45.88 - public void send(Article article, String mailFrom, String rcptTo)
45.89 - throws IOException
45.90 - {
45.91 - assert(article != null);
45.92 - assert(mailFrom != null);
45.93 - assert(rcptTo != null);
45.94 + public void send(Article article, String mailFrom, String rcptTo)
45.95 + throws IOException
45.96 + {
45.97 + assert (article != null);
45.98 + assert (mailFrom != null);
45.99 + assert (rcptTo != null);
45.100
45.101 - this.out.write(("MAIL FROM: " + mailFrom).getBytes("UTF-8"));
45.102 - this.out.flush();
45.103 - String line = this.in.readLine();
45.104 - if(line == null || !line.startsWith("250 "))
45.105 - {
45.106 - throw new IOException("Unexpected reply: " + line);
45.107 - }
45.108 + this.out.write(("MAIL FROM: " + mailFrom).getBytes("UTF-8"));
45.109 + this.out.flush();
45.110 + String line = this.in.readLine();
45.111 + if (line == null || !line.startsWith("250 ")) {
45.112 + throw new IOException("Unexpected reply: " + line);
45.113 + }
45.114
45.115 - this.out.write(("RCPT TO: " + rcptTo).getBytes("UTF-8"));
45.116 - this.out.flush();
45.117 - line = this.in.readLine();
45.118 - if(line == null || !line.startsWith("250 "))
45.119 - {
45.120 - throw new IOException("Unexpected reply: " + line);
45.121 - }
45.122 + this.out.write(("RCPT TO: " + rcptTo).getBytes("UTF-8"));
45.123 + this.out.flush();
45.124 + line = this.in.readLine();
45.125 + if (line == null || !line.startsWith("250 ")) {
45.126 + throw new IOException("Unexpected reply: " + line);
45.127 + }
45.128
45.129 - this.out.write("DATA".getBytes("UTF-8"));
45.130 - this.out.flush();
45.131 - line = this.in.readLine();
45.132 - if(line == null || !line.startsWith("354 "))
45.133 - {
45.134 - throw new IOException("Unexpected reply: " + line);
45.135 - }
45.136 + this.out.write("DATA".getBytes("UTF-8"));
45.137 + this.out.flush();
45.138 + line = this.in.readLine();
45.139 + if (line == null || !line.startsWith("354 ")) {
45.140 + throw new IOException("Unexpected reply: " + line);
45.141 + }
45.142
45.143 - ArticleInputStream artStream = new ArticleInputStream(article);
45.144 - for(int b = artStream.read(); b >= 0; b = artStream.read())
45.145 - {
45.146 - this.out.write(b);
45.147 - }
45.148 + ArticleInputStream artStream = new ArticleInputStream(article);
45.149 + for (int b = artStream.read(); b >= 0; b = artStream.read()) {
45.150 + this.out.write(b);
45.151 + }
45.152
45.153 - // Flush the binary stream; important because otherwise the output
45.154 - // will be mixed with the PrintWriter.
45.155 - this.out.flush();
45.156 - this.out.write("\r\n.\r\n".getBytes("UTF-8"));
45.157 - this.out.flush();
45.158 - line = this.in.readLine();
45.159 - if(line == null || !line.startsWith("250 "))
45.160 - {
45.161 - throw new IOException("Unexpected reply: " + line);
45.162 - }
45.163 - }
45.164 -
45.165 + // Flush the binary stream; important because otherwise the output
45.166 + // will be mixed with the PrintWriter.
45.167 + this.out.flush();
45.168 + this.out.write("\r\n.\r\n".getBytes("UTF-8"));
45.169 + this.out.flush();
45.170 + line = this.in.readLine();
45.171 + if (line == null || !line.startsWith("250 ")) {
45.172 + throw new IOException("Unexpected reply: " + line);
45.173 + }
45.174 + }
45.175 }
46.1 --- a/src/org/sonews/plugin/Plugin.java Sun Aug 29 17:43:58 2010 +0200
46.2 +++ b/src/org/sonews/plugin/Plugin.java Sun Aug 29 18:17:37 2010 +0200
46.3 @@ -28,15 +28,14 @@
46.4 public interface Plugin
46.5 {
46.6
46.7 - /**
46.8 - * Called when the Plugin is loaded by sonews. This method can be used
46.9 - * by implementing classes to install additional or required plugins.
46.10 - */
46.11 - void load();
46.12 + /**
46.13 + * Called when the Plugin is loaded by sonews. This method can be used
46.14 + * by implementing classes to install additional or required plugins.
46.15 + */
46.16 + void load();
46.17
46.18 - /**
46.19 - * Called when the Plugin is unloaded by sonews.
46.20 - */
46.21 - void unload();
46.22 -
46.23 + /**
46.24 + * Called when the Plugin is unloaded by sonews.
46.25 + */
46.26 + void unload();
46.27 }
47.1 --- a/src/org/sonews/storage/Article.java Sun Aug 29 17:43:58 2010 +0200
47.2 +++ b/src/org/sonews/storage/Article.java Sun Aug 29 18:17:37 2010 +0200
47.3 @@ -41,213 +41,196 @@
47.4 */
47.5 public class Article extends ArticleHead
47.6 {
47.7 -
47.8 - /**
47.9 - * Loads the Article identified by the given ID from the JDBCDatabase.
47.10 - * @param messageID
47.11 - * @return null if Article is not found or if an error occurred.
47.12 - */
47.13 - public static Article getByMessageID(final String messageID)
47.14 - {
47.15 - try
47.16 - {
47.17 - return StorageManager.current().getArticle(messageID);
47.18 - }
47.19 - catch(StorageBackendException ex)
47.20 - {
47.21 - ex.printStackTrace();
47.22 - return null;
47.23 - }
47.24 - }
47.25 -
47.26 - private byte[] body = new byte[0];
47.27 -
47.28 - /**
47.29 - * Default constructor.
47.30 - */
47.31 - public Article()
47.32 - {
47.33 - }
47.34 -
47.35 - /**
47.36 - * Creates a new Article object using the date from the given
47.37 - * raw data.
47.38 - */
47.39 - public Article(String headers, byte[] body)
47.40 - {
47.41 - try
47.42 - {
47.43 - this.body = body;
47.44
47.45 - // Parse the header
47.46 - this.headers = new InternetHeaders(
47.47 - new ByteArrayInputStream(headers.getBytes()));
47.48 -
47.49 - this.headerSrc = headers;
47.50 - }
47.51 - catch(MessagingException ex)
47.52 - {
47.53 - ex.printStackTrace();
47.54 - }
47.55 - }
47.56 + /**
47.57 + * Loads the Article identified by the given ID from the JDBCDatabase.
47.58 + * @param messageID
47.59 + * @return null if Article is not found or if an error occurred.
47.60 + */
47.61 + public static Article getByMessageID(final String messageID)
47.62 + {
47.63 + try {
47.64 + return StorageManager.current().getArticle(messageID);
47.65 + } catch (StorageBackendException ex) {
47.66 + ex.printStackTrace();
47.67 + return null;
47.68 + }
47.69 + }
47.70 + private byte[] body = new byte[0];
47.71
47.72 - /**
47.73 - * Creates an Article instance using the data from the javax.mail.Message
47.74 - * object. This constructor is called by the Mailinglist gateway.
47.75 - * @see javax.mail.Message
47.76 - * @param msg
47.77 - * @throws IOException
47.78 - * @throws MessagingException
47.79 - */
47.80 - public Article(final Message msg)
47.81 - throws IOException, MessagingException
47.82 - {
47.83 - this.headers = new InternetHeaders();
47.84 + /**
47.85 + * Default constructor.
47.86 + */
47.87 + public Article()
47.88 + {
47.89 + }
47.90
47.91 - for(Enumeration e = msg.getAllHeaders() ; e.hasMoreElements();)
47.92 - {
47.93 - final Header header = (Header)e.nextElement();
47.94 - this.headers.addHeader(header.getName(), header.getValue());
47.95 - }
47.96 + /**
47.97 + * Creates a new Article object using the date from the given
47.98 + * raw data.
47.99 + */
47.100 + public Article(String headers, byte[] body)
47.101 + {
47.102 + try {
47.103 + this.body = body;
47.104
47.105 - // Reads the raw byte body using Message.writeTo(OutputStream out)
47.106 - this.body = readContent(msg);
47.107 -
47.108 - // Validate headers
47.109 - validateHeaders();
47.110 - }
47.111 + // Parse the header
47.112 + this.headers = new InternetHeaders(
47.113 + new ByteArrayInputStream(headers.getBytes()));
47.114
47.115 - /**
47.116 - * Reads from the given Message into a byte array.
47.117 - * @param in
47.118 - * @return
47.119 - * @throws IOException
47.120 - */
47.121 - private byte[] readContent(Message in)
47.122 - throws IOException, MessagingException
47.123 - {
47.124 - ByteArrayOutputStream out = new ByteArrayOutputStream();
47.125 - in.writeTo(out);
47.126 - return out.toByteArray();
47.127 - }
47.128 + this.headerSrc = headers;
47.129 + } catch (MessagingException ex) {
47.130 + ex.printStackTrace();
47.131 + }
47.132 + }
47.133
47.134 - /**
47.135 - * Removes the header identified by the given key.
47.136 - * @param headerKey
47.137 - */
47.138 - public void removeHeader(final String headerKey)
47.139 - {
47.140 - this.headers.removeHeader(headerKey);
47.141 - this.headerSrc = null;
47.142 - }
47.143 + /**
47.144 + * Creates an Article instance using the data from the javax.mail.Message
47.145 + * object. This constructor is called by the Mailinglist gateway.
47.146 + * @see javax.mail.Message
47.147 + * @param msg
47.148 + * @throws IOException
47.149 + * @throws MessagingException
47.150 + */
47.151 + public Article(final Message msg)
47.152 + throws IOException, MessagingException
47.153 + {
47.154 + this.headers = new InternetHeaders();
47.155
47.156 - /**
47.157 - * Generates a message id for this article and sets it into
47.158 - * the header object. You have to update the JDBCDatabase manually to make this
47.159 - * change persistent.
47.160 - * Note: a Message-ID should never be changed and only generated once.
47.161 - */
47.162 - private String generateMessageID()
47.163 - {
47.164 - String randomString;
47.165 - MessageDigest md5;
47.166 - try
47.167 - {
47.168 - md5 = MessageDigest.getInstance("MD5");
47.169 - md5.reset();
47.170 - md5.update(getBody());
47.171 - md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
47.172 - md5.update(getHeader(Headers.FROM)[0].getBytes());
47.173 - byte[] result = md5.digest();
47.174 - StringBuffer hexString = new StringBuffer();
47.175 - for (int i = 0; i < result.length; i++)
47.176 - {
47.177 - hexString.append(Integer.toHexString(0xFF & result[i]));
47.178 - }
47.179 - randomString = hexString.toString();
47.180 - }
47.181 - catch (NoSuchAlgorithmException e)
47.182 - {
47.183 - e.printStackTrace();
47.184 - randomString = UUID.randomUUID().toString();
47.185 - }
47.186 - String msgID = "<" + randomString + "@"
47.187 - + Config.inst().get(Config.HOSTNAME, "localhost") + ">";
47.188 -
47.189 - this.headers.setHeader(Headers.MESSAGE_ID, msgID);
47.190 -
47.191 - return msgID;
47.192 - }
47.193 + for (Enumeration e = msg.getAllHeaders(); e.hasMoreElements();) {
47.194 + final Header header = (Header) e.nextElement();
47.195 + this.headers.addHeader(header.getName(), header.getValue());
47.196 + }
47.197
47.198 - /**
47.199 - * Returns the body string.
47.200 - */
47.201 - public byte[] getBody()
47.202 - {
47.203 - return body;
47.204 - }
47.205 -
47.206 - /**
47.207 - * @return Numerical IDs of the newsgroups this Article belongs to.
47.208 - */
47.209 - public List<Group> getGroups()
47.210 - {
47.211 - String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
47.212 - ArrayList<Group> groups = new ArrayList<Group>();
47.213 + // Reads the raw byte body using Message.writeTo(OutputStream out)
47.214 + this.body = readContent(msg);
47.215
47.216 - try
47.217 - {
47.218 - for(String newsgroup : groupnames)
47.219 - {
47.220 - newsgroup = newsgroup.trim();
47.221 - Group group = StorageManager.current().getGroup(newsgroup);
47.222 - if(group != null && // If the server does not provide the group, ignore it
47.223 - !groups.contains(group)) // Yes, there may be duplicates
47.224 - {
47.225 - groups.add(group);
47.226 - }
47.227 - }
47.228 - }
47.229 - catch(StorageBackendException ex)
47.230 - {
47.231 - ex.printStackTrace();
47.232 - return null;
47.233 - }
47.234 - return groups;
47.235 - }
47.236 + // Validate headers
47.237 + validateHeaders();
47.238 + }
47.239
47.240 - public void setBody(byte[] body)
47.241 - {
47.242 - this.body = body;
47.243 - }
47.244 -
47.245 - /**
47.246 - *
47.247 - * @param groupname Name(s) of newsgroups
47.248 - */
47.249 - public void setGroup(String groupname)
47.250 - {
47.251 - this.headers.setHeader(Headers.NEWSGROUPS, groupname);
47.252 - }
47.253 + /**
47.254 + * Reads from the given Message into a byte array.
47.255 + * @param in
47.256 + * @return
47.257 + * @throws IOException
47.258 + */
47.259 + private byte[] readContent(Message in)
47.260 + throws IOException, MessagingException
47.261 + {
47.262 + ByteArrayOutputStream out = new ByteArrayOutputStream();
47.263 + in.writeTo(out);
47.264 + return out.toByteArray();
47.265 + }
47.266
47.267 - /**
47.268 - * Returns the Message-ID of this Article. If the appropriate header
47.269 - * is empty, a new Message-ID is created.
47.270 - * @return Message-ID of this Article.
47.271 - */
47.272 - public String getMessageID()
47.273 - {
47.274 - String[] msgID = getHeader(Headers.MESSAGE_ID);
47.275 - return msgID[0].equals("") ? generateMessageID() : msgID[0];
47.276 - }
47.277 -
47.278 - /**
47.279 - * @return String containing the Message-ID.
47.280 - */
47.281 - @Override
47.282 - public String toString()
47.283 - {
47.284 - return getMessageID();
47.285 - }
47.286 + /**
47.287 + * Removes the header identified by the given key.
47.288 + * @param headerKey
47.289 + */
47.290 + public void removeHeader(final String headerKey)
47.291 + {
47.292 + this.headers.removeHeader(headerKey);
47.293 + this.headerSrc = null;
47.294 + }
47.295
47.296 + /**
47.297 + * Generates a message id for this article and sets it into
47.298 + * the header object. You have to update the JDBCDatabase manually to make this
47.299 + * change persistent.
47.300 + * Note: a Message-ID should never be changed and only generated once.
47.301 + */
47.302 + private String generateMessageID()
47.303 + {
47.304 + String randomString;
47.305 + MessageDigest md5;
47.306 + try {
47.307 + md5 = MessageDigest.getInstance("MD5");
47.308 + md5.reset();
47.309 + md5.update(getBody());
47.310 + md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
47.311 + md5.update(getHeader(Headers.FROM)[0].getBytes());
47.312 + byte[] result = md5.digest();
47.313 + StringBuffer hexString = new StringBuffer();
47.314 + for (int i = 0; i < result.length; i++) {
47.315 + hexString.append(Integer.toHexString(0xFF & result[i]));
47.316 + }
47.317 + randomString = hexString.toString();
47.318 + } catch (NoSuchAlgorithmException e) {
47.319 + e.printStackTrace();
47.320 + randomString = UUID.randomUUID().toString();
47.321 + }
47.322 + String msgID = "<" + randomString + "@"
47.323 + + Config.inst().get(Config.HOSTNAME, "localhost") + ">";
47.324 +
47.325 + this.headers.setHeader(Headers.MESSAGE_ID, msgID);
47.326 +
47.327 + return msgID;
47.328 + }
47.329 +
47.330 + /**
47.331 + * Returns the body string.
47.332 + */
47.333 + public byte[] getBody()
47.334 + {
47.335 + return body;
47.336 + }
47.337 +
47.338 + /**
47.339 + * @return Numerical IDs of the newsgroups this Article belongs to.
47.340 + */
47.341 + public List<Group> getGroups()
47.342 + {
47.343 + String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
47.344 + ArrayList<Group> groups = new ArrayList<Group>();
47.345 +
47.346 + try {
47.347 + for (String newsgroup : groupnames) {
47.348 + newsgroup = newsgroup.trim();
47.349 + Group group = StorageManager.current().getGroup(newsgroup);
47.350 + if (group != null && // If the server does not provide the group, ignore it
47.351 + !groups.contains(group)) // Yes, there may be duplicates
47.352 + {
47.353 + groups.add(group);
47.354 + }
47.355 + }
47.356 + } catch (StorageBackendException ex) {
47.357 + ex.printStackTrace();
47.358 + return null;
47.359 + }
47.360 + return groups;
47.361 + }
47.362 +
47.363 + public void setBody(byte[] body)
47.364 + {
47.365 + this.body = body;
47.366 + }
47.367 +
47.368 + /**
47.369 + *
47.370 + * @param groupname Name(s) of newsgroups
47.371 + */
47.372 + public void setGroup(String groupname)
47.373 + {
47.374 + this.headers.setHeader(Headers.NEWSGROUPS, groupname);
47.375 + }
47.376 +
47.377 + /**
47.378 + * Returns the Message-ID of this Article. If the appropriate header
47.379 + * is empty, a new Message-ID is created.
47.380 + * @return Message-ID of this Article.
47.381 + */
47.382 + public String getMessageID()
47.383 + {
47.384 + String[] msgID = getHeader(Headers.MESSAGE_ID);
47.385 + return msgID[0].equals("") ? generateMessageID() : msgID[0];
47.386 + }
47.387 +
47.388 + /**
47.389 + * @return String containing the Message-ID.
47.390 + */
47.391 + @Override
47.392 + public String toString()
47.393 + {
47.394 + return getMessageID();
47.395 + }
47.396 }
48.1 --- a/src/org/sonews/storage/ArticleHead.java Sun Aug 29 17:43:58 2010 +0200
48.2 +++ b/src/org/sonews/storage/ArticleHead.java Sun Aug 29 18:17:37 2010 +0200
48.3 @@ -31,131 +31,122 @@
48.4 * @author Christian Lins
48.5 * @since sonews/0.5.0
48.6 */
48.7 -public class ArticleHead
48.8 +public class ArticleHead
48.9 {
48.10
48.11 - protected InternetHeaders headers = null;
48.12 - protected String headerSrc = null;
48.13 -
48.14 - protected ArticleHead()
48.15 - {
48.16 - }
48.17 -
48.18 - public ArticleHead(String headers)
48.19 - {
48.20 - try
48.21 - {
48.22 - // Parse the header
48.23 - this.headers = new InternetHeaders(
48.24 - new ByteArrayInputStream(headers.getBytes()));
48.25 - }
48.26 - catch(MessagingException ex)
48.27 - {
48.28 - ex.printStackTrace();
48.29 - }
48.30 - }
48.31 -
48.32 - /**
48.33 - * Returns the header field with given name.
48.34 - * @param name Name of the header field(s).
48.35 - * @param returnNull If set to true, this method will return null instead
48.36 - * of an empty array if there is no header field found.
48.37 - * @return Header values or empty string.
48.38 - */
48.39 - public String[] getHeader(String name, boolean returnNull)
48.40 - {
48.41 - String[] ret = this.headers.getHeader(name);
48.42 - if(ret == null && !returnNull)
48.43 - {
48.44 - ret = new String[]{""};
48.45 - }
48.46 - return ret;
48.47 - }
48.48 + protected InternetHeaders headers = null;
48.49 + protected String headerSrc = null;
48.50
48.51 - public String[] getHeader(String name)
48.52 - {
48.53 - return getHeader(name, false);
48.54 - }
48.55 -
48.56 - /**
48.57 - * Sets the header value identified through the header name.
48.58 - * @param name
48.59 - * @param value
48.60 - */
48.61 - public void setHeader(String name, String value)
48.62 - {
48.63 - this.headers.setHeader(name, value);
48.64 - this.headerSrc = null;
48.65 - }
48.66 + protected ArticleHead()
48.67 + {
48.68 + }
48.69
48.70 - public Enumeration getAllHeaders()
48.71 - {
48.72 - return this.headers.getAllHeaders();
48.73 - }
48.74 + public ArticleHead(String headers)
48.75 + {
48.76 + try {
48.77 + // Parse the header
48.78 + this.headers = new InternetHeaders(
48.79 + new ByteArrayInputStream(headers.getBytes()));
48.80 + } catch (MessagingException ex) {
48.81 + ex.printStackTrace();
48.82 + }
48.83 + }
48.84
48.85 - /**
48.86 - * @return Header source code of this Article.
48.87 - */
48.88 - public String getHeaderSource()
48.89 - {
48.90 - if(this.headerSrc != null)
48.91 - {
48.92 - return this.headerSrc;
48.93 - }
48.94 + /**
48.95 + * Returns the header field with given name.
48.96 + * @param name Name of the header field(s).
48.97 + * @param returnNull If set to true, this method will return null instead
48.98 + * of an empty array if there is no header field found.
48.99 + * @return Header values or empty string.
48.100 + */
48.101 + public String[] getHeader(String name, boolean returnNull)
48.102 + {
48.103 + String[] ret = this.headers.getHeader(name);
48.104 + if (ret == null && !returnNull) {
48.105 + ret = new String[] {""};
48.106 + }
48.107 + return ret;
48.108 + }
48.109
48.110 - StringBuffer buf = new StringBuffer();
48.111 + public String[] getHeader(String name)
48.112 + {
48.113 + return getHeader(name, false);
48.114 + }
48.115
48.116 - for(Enumeration en = this.headers.getAllHeaders(); en.hasMoreElements();)
48.117 - {
48.118 - Header entry = (Header)en.nextElement();
48.119 + /**
48.120 + * Sets the header value identified through the header name.
48.121 + * @param name
48.122 + * @param value
48.123 + */
48.124 + public void setHeader(String name, String value)
48.125 + {
48.126 + this.headers.setHeader(name, value);
48.127 + this.headerSrc = null;
48.128 + }
48.129
48.130 - String value = entry.getValue().replaceAll("[\r\n]", " ");
48.131 - buf.append(entry.getName());
48.132 - buf.append(": ");
48.133 - buf.append(MimeUtility.fold(entry.getName().length() + 2, value));
48.134 + public Enumeration getAllHeaders()
48.135 + {
48.136 + return this.headers.getAllHeaders();
48.137 + }
48.138
48.139 - if(en.hasMoreElements())
48.140 - {
48.141 - buf.append("\r\n");
48.142 - }
48.143 - }
48.144 + /**
48.145 + * @return Header source code of this Article.
48.146 + */
48.147 + public String getHeaderSource()
48.148 + {
48.149 + if (this.headerSrc != null) {
48.150 + return this.headerSrc;
48.151 + }
48.152
48.153 - this.headerSrc = buf.toString();
48.154 - return this.headerSrc;
48.155 - }
48.156 + StringBuffer buf = new StringBuffer();
48.157
48.158 - /**
48.159 - * Sets the headers of this Article. If headers contain no
48.160 - * Message-Id a new one is created.
48.161 - * @param headers
48.162 - */
48.163 - public void setHeaders(InternetHeaders headers)
48.164 - {
48.165 - this.headers = headers;
48.166 - this.headerSrc = null;
48.167 - validateHeaders();
48.168 - }
48.169 + for (Enumeration en = this.headers.getAllHeaders(); en.hasMoreElements();) {
48.170 + Header entry = (Header) en.nextElement();
48.171
48.172 - /**
48.173 - * Checks some headers for their validity and generates an
48.174 - * appropriate Path-header for this host if not yet existing.
48.175 - * This method is called by some Article constructors and the
48.176 - * method setHeaders().
48.177 - * @return true if something on the headers was changed.
48.178 - */
48.179 - protected void validateHeaders()
48.180 - {
48.181 - // Check for valid Path-header
48.182 - final String path = getHeader(Headers.PATH)[0];
48.183 - final String host = Config.inst().get(Config.HOSTNAME, "localhost");
48.184 - if(!path.startsWith(host))
48.185 - {
48.186 - StringBuffer pathBuf = new StringBuffer();
48.187 - pathBuf.append(host);
48.188 - pathBuf.append('!');
48.189 - pathBuf.append(path);
48.190 - this.headers.setHeader(Headers.PATH, pathBuf.toString());
48.191 - }
48.192 - }
48.193 -
48.194 + String value = entry.getValue().replaceAll("[\r\n]", " ");
48.195 + buf.append(entry.getName());
48.196 + buf.append(": ");
48.197 + buf.append(MimeUtility.fold(entry.getName().length() + 2, value));
48.198 +
48.199 + if (en.hasMoreElements()) {
48.200 + buf.append("\r\n");
48.201 + }
48.202 + }
48.203 +
48.204 + this.headerSrc = buf.toString();
48.205 + return this.headerSrc;
48.206 + }
48.207 +
48.208 + /**
48.209 + * Sets the headers of this Article. If headers contain no
48.210 + * Message-Id a new one is created.
48.211 + * @param headers
48.212 + */
48.213 + public void setHeaders(InternetHeaders headers)
48.214 + {
48.215 + this.headers = headers;
48.216 + this.headerSrc = null;
48.217 + validateHeaders();
48.218 + }
48.219 +
48.220 + /**
48.221 + * Checks some headers for their validity and generates an
48.222 + * appropriate Path-header for this host if not yet existing.
48.223 + * This method is called by some Article constructors and the
48.224 + * method setHeaders().
48.225 + * @return true if something on the headers was changed.
48.226 + */
48.227 + protected void validateHeaders()
48.228 + {
48.229 + // Check for valid Path-header
48.230 + final String path = getHeader(Headers.PATH)[0];
48.231 + final String host = Config.inst().get(Config.HOSTNAME, "localhost");
48.232 + if (!path.startsWith(host)) {
48.233 + StringBuffer pathBuf = new StringBuffer();
48.234 + pathBuf.append(host);
48.235 + pathBuf.append('!');
48.236 + pathBuf.append(path);
48.237 + this.headers.setHeader(Headers.PATH, pathBuf.toString());
48.238 + }
48.239 + }
48.240 }
49.1 --- a/src/org/sonews/storage/Channel.java Sun Aug 29 17:43:58 2010 +0200
49.2 +++ b/src/org/sonews/storage/Channel.java Sun Aug 29 18:17:37 2010 +0200
49.3 @@ -33,79 +33,75 @@
49.4 public abstract class Channel
49.5 {
49.6
49.7 - /**
49.8 - * If this flag is set the Group is no real newsgroup but a mailing list
49.9 - * mirror. In that case every posting and receiving mails must go through
49.10 - * the mailing list gateway.
49.11 - */
49.12 - public static final int MAILINGLIST = 0x1;
49.13 + /**
49.14 + * If this flag is set the Group is no real newsgroup but a mailing list
49.15 + * mirror. In that case every posting and receiving mails must go through
49.16 + * the mailing list gateway.
49.17 + */
49.18 + public static final int MAILINGLIST = 0x1;
49.19 + /**
49.20 + * If this flag is set the Group is marked as readonly and the posting
49.21 + * is prohibited. This can be useful for groups that are synced only in
49.22 + * one direction.
49.23 + */
49.24 + public static final int READONLY = 0x2;
49.25 + /**
49.26 + * If this flag is set the Group is marked as deleted and must not occur
49.27 + * in any output. The deletion is done lazily by a low priority daemon.
49.28 + */
49.29 + public static final int DELETED = 0x80;
49.30
49.31 - /**
49.32 - * If this flag is set the Group is marked as readonly and the posting
49.33 - * is prohibited. This can be useful for groups that are synced only in
49.34 - * one direction.
49.35 - */
49.36 - public static final int READONLY = 0x2;
49.37 + public static List<Channel> getAll()
49.38 + {
49.39 + List<Channel> all = new ArrayList<Channel>();
49.40
49.41 - /**
49.42 - * If this flag is set the Group is marked as deleted and must not occur
49.43 - * in any output. The deletion is done lazily by a low priority daemon.
49.44 - */
49.45 - public static final int DELETED = 0x80;
49.46 + /*List<Channel> agroups = AggregatedGroup.getAll();
49.47 + if(agroups != null)
49.48 + {
49.49 + all.addAll(agroups);
49.50 + }*/
49.51
49.52 - public static List<Channel> getAll()
49.53 - {
49.54 - List<Channel> all = new ArrayList<Channel>();
49.55 + List<Channel> groups = Group.getAll();
49.56 + if (groups != null) {
49.57 + all.addAll(groups);
49.58 + }
49.59
49.60 - /*List<Channel> agroups = AggregatedGroup.getAll();
49.61 - if(agroups != null)
49.62 - {
49.63 - all.addAll(agroups);
49.64 - }*/
49.65 + return all;
49.66 + }
49.67
49.68 - List<Channel> groups = Group.getAll();
49.69 - if(groups != null)
49.70 - {
49.71 - all.addAll(groups);
49.72 - }
49.73 + public static Channel getByName(String name)
49.74 + throws StorageBackendException
49.75 + {
49.76 + return StorageManager.current().getGroup(name);
49.77 + }
49.78
49.79 - return all;
49.80 - }
49.81 + public abstract Article getArticle(long idx)
49.82 + throws StorageBackendException;
49.83
49.84 - public static Channel getByName(String name)
49.85 - throws StorageBackendException
49.86 - {
49.87 - return StorageManager.current().getGroup(name);
49.88 - }
49.89 + public abstract List<Pair<Long, ArticleHead>> getArticleHeads(
49.90 + final long first, final long last)
49.91 + throws StorageBackendException;
49.92
49.93 - public abstract Article getArticle(long idx)
49.94 - throws StorageBackendException;
49.95 + public abstract List<Long> getArticleNumbers()
49.96 + throws StorageBackendException;
49.97
49.98 - public abstract List<Pair<Long, ArticleHead>> getArticleHeads(
49.99 - final long first, final long last)
49.100 - throws StorageBackendException;
49.101 + public abstract long getFirstArticleNumber()
49.102 + throws StorageBackendException;
49.103
49.104 - public abstract List<Long> getArticleNumbers()
49.105 - throws StorageBackendException;
49.106 + public abstract long getIndexOf(Article art)
49.107 + throws StorageBackendException;
49.108
49.109 - public abstract long getFirstArticleNumber()
49.110 - throws StorageBackendException;
49.111 + public abstract long getInternalID();
49.112
49.113 - public abstract long getIndexOf(Article art)
49.114 - throws StorageBackendException;
49.115 + public abstract long getLastArticleNumber()
49.116 + throws StorageBackendException;
49.117
49.118 - public abstract long getInternalID();
49.119 + public abstract String getName();
49.120
49.121 - public abstract long getLastArticleNumber()
49.122 - throws StorageBackendException;
49.123 + public abstract long getPostingsCount()
49.124 + throws StorageBackendException;
49.125
49.126 - public abstract String getName();
49.127 -
49.128 - public abstract long getPostingsCount()
49.129 - throws StorageBackendException;
49.130 + public abstract boolean isDeleted();
49.131
49.132 - public abstract boolean isDeleted();
49.133 -
49.134 - public abstract boolean isWriteable();
49.135 -
49.136 + public abstract boolean isWriteable();
49.137 }
50.1 --- a/src/org/sonews/storage/Group.java Sun Aug 29 17:43:58 2010 +0200
50.2 +++ b/src/org/sonews/storage/Group.java Sun Aug 29 18:17:37 2010 +0200
50.3 @@ -31,154 +31,147 @@
50.4 // TODO: This class should not be public!
50.5 public class Group extends Channel
50.6 {
50.7 -
50.8 - private long id = 0;
50.9 - private int flags = -1;
50.10 - private String name = null;
50.11
50.12 - /**
50.13 - * @return List of all groups this server handles.
50.14 - */
50.15 - public static List<Channel> getAll()
50.16 - {
50.17 - try
50.18 - {
50.19 - return StorageManager.current().getGroups();
50.20 - }
50.21 - catch(StorageBackendException ex)
50.22 - {
50.23 - Log.get().severe(ex.getMessage());
50.24 - return null;
50.25 - }
50.26 - }
50.27 -
50.28 - /**
50.29 - * @param name
50.30 - * @param id
50.31 - */
50.32 - public Group(final String name, final long id, final int flags)
50.33 - {
50.34 - this.id = id;
50.35 - this.flags = flags;
50.36 - this.name = name;
50.37 - }
50.38 + private long id = 0;
50.39 + private int flags = -1;
50.40 + private String name = null;
50.41
50.42 - @Override
50.43 - public boolean equals(Object obj)
50.44 - {
50.45 - if(obj instanceof Group)
50.46 - {
50.47 - return ((Group)obj).id == this.id;
50.48 - }
50.49 - else
50.50 - {
50.51 - return false;
50.52 - }
50.53 - }
50.54 + /**
50.55 + * @return List of all groups this server handles.
50.56 + */
50.57 + public static List<Channel> getAll()
50.58 + {
50.59 + try {
50.60 + return StorageManager.current().getGroups();
50.61 + } catch (StorageBackendException ex) {
50.62 + Log.get().severe(ex.getMessage());
50.63 + return null;
50.64 + }
50.65 + }
50.66
50.67 - public Article getArticle(long idx)
50.68 - throws StorageBackendException
50.69 - {
50.70 - return StorageManager.current().getArticle(idx, this.id);
50.71 - }
50.72 + /**
50.73 + * @param name
50.74 + * @param id
50.75 + */
50.76 + public Group(final String name, final long id, final int flags)
50.77 + {
50.78 + this.id = id;
50.79 + this.flags = flags;
50.80 + this.name = name;
50.81 + }
50.82
50.83 - public List<Pair<Long, ArticleHead>> getArticleHeads(final long first, final long last)
50.84 - throws StorageBackendException
50.85 - {
50.86 - return StorageManager.current().getArticleHeads(this, first, last);
50.87 - }
50.88 -
50.89 - public List<Long> getArticleNumbers()
50.90 - throws StorageBackendException
50.91 - {
50.92 - return StorageManager.current().getArticleNumbers(id);
50.93 - }
50.94 + @Override
50.95 + public boolean equals(Object obj)
50.96 + {
50.97 + if (obj instanceof Group) {
50.98 + return ((Group) obj).id == this.id;
50.99 + } else {
50.100 + return false;
50.101 + }
50.102 + }
50.103
50.104 - public long getFirstArticleNumber()
50.105 - throws StorageBackendException
50.106 - {
50.107 - return StorageManager.current().getFirstArticleNumber(this);
50.108 - }
50.109 + public Article getArticle(long idx)
50.110 + throws StorageBackendException
50.111 + {
50.112 + return StorageManager.current().getArticle(idx, this.id);
50.113 + }
50.114
50.115 - public int getFlags()
50.116 - {
50.117 - return this.flags;
50.118 - }
50.119 + public List<Pair<Long, ArticleHead>> getArticleHeads(final long first, final long last)
50.120 + throws StorageBackendException
50.121 + {
50.122 + return StorageManager.current().getArticleHeads(this, first, last);
50.123 + }
50.124
50.125 - public long getIndexOf(Article art)
50.126 - throws StorageBackendException
50.127 - {
50.128 - return StorageManager.current().getArticleIndex(art, this);
50.129 - }
50.130 + public List<Long> getArticleNumbers()
50.131 + throws StorageBackendException
50.132 + {
50.133 + return StorageManager.current().getArticleNumbers(id);
50.134 + }
50.135
50.136 - /**
50.137 - * Returns the group id.
50.138 - */
50.139 - public long getInternalID()
50.140 - {
50.141 - assert id > 0;
50.142 + public long getFirstArticleNumber()
50.143 + throws StorageBackendException
50.144 + {
50.145 + return StorageManager.current().getFirstArticleNumber(this);
50.146 + }
50.147
50.148 - return id;
50.149 - }
50.150 + public int getFlags()
50.151 + {
50.152 + return this.flags;
50.153 + }
50.154
50.155 - public boolean isDeleted()
50.156 - {
50.157 - return (this.flags & DELETED) != 0;
50.158 - }
50.159 + public long getIndexOf(Article art)
50.160 + throws StorageBackendException
50.161 + {
50.162 + return StorageManager.current().getArticleIndex(art, this);
50.163 + }
50.164
50.165 - public boolean isMailingList()
50.166 - {
50.167 - return (this.flags & MAILINGLIST) != 0;
50.168 - }
50.169 + /**
50.170 + * Returns the group id.
50.171 + */
50.172 + public long getInternalID()
50.173 + {
50.174 + assert id > 0;
50.175
50.176 - public boolean isWriteable()
50.177 - {
50.178 - return true;
50.179 - }
50.180 + return id;
50.181 + }
50.182
50.183 - public long getLastArticleNumber()
50.184 - throws StorageBackendException
50.185 - {
50.186 - return StorageManager.current().getLastArticleNumber(this);
50.187 - }
50.188 + public boolean isDeleted()
50.189 + {
50.190 + return (this.flags & DELETED) != 0;
50.191 + }
50.192
50.193 - public String getName()
50.194 - {
50.195 - return name;
50.196 - }
50.197 + public boolean isMailingList()
50.198 + {
50.199 + return (this.flags & MAILINGLIST) != 0;
50.200 + }
50.201
50.202 - /**
50.203 - * Performs this.flags |= flag to set a specified flag and updates the data
50.204 - * in the JDBCDatabase.
50.205 - * @param flag
50.206 - */
50.207 - public void setFlag(final int flag)
50.208 - {
50.209 - this.flags |= flag;
50.210 - }
50.211 + public boolean isWriteable()
50.212 + {
50.213 + return true;
50.214 + }
50.215
50.216 - public void setName(final String name)
50.217 - {
50.218 - this.name = name;
50.219 - }
50.220 + public long getLastArticleNumber()
50.221 + throws StorageBackendException
50.222 + {
50.223 + return StorageManager.current().getLastArticleNumber(this);
50.224 + }
50.225
50.226 - /**
50.227 - * @return Number of posted articles in this group.
50.228 - * @throws java.sql.SQLException
50.229 - */
50.230 - public long getPostingsCount()
50.231 - throws StorageBackendException
50.232 - {
50.233 - return StorageManager.current().getPostingsCount(this.name);
50.234 - }
50.235 + public String getName()
50.236 + {
50.237 + return name;
50.238 + }
50.239
50.240 - /**
50.241 - * Updates flags and name in the backend.
50.242 - */
50.243 - public void update()
50.244 - throws StorageBackendException
50.245 - {
50.246 - StorageManager.current().update(this);
50.247 - }
50.248 + /**
50.249 + * Performs this.flags |= flag to set a specified flag and updates the data
50.250 + * in the JDBCDatabase.
50.251 + * @param flag
50.252 + */
50.253 + public void setFlag(final int flag)
50.254 + {
50.255 + this.flags |= flag;
50.256 + }
50.257
50.258 + public void setName(final String name)
50.259 + {
50.260 + this.name = name;
50.261 + }
50.262 +
50.263 + /**
50.264 + * @return Number of posted articles in this group.
50.265 + * @throws java.sql.SQLException
50.266 + */
50.267 + public long getPostingsCount()
50.268 + throws StorageBackendException
50.269 + {
50.270 + return StorageManager.current().getPostingsCount(this.name);
50.271 + }
50.272 +
50.273 + /**
50.274 + * Updates flags and name in the backend.
50.275 + */
50.276 + public void update()
50.277 + throws StorageBackendException
50.278 + {
50.279 + StorageManager.current().update(this);
50.280 + }
50.281 }
51.1 --- a/src/org/sonews/storage/Headers.java Sun Aug 29 17:43:58 2010 +0200
51.2 +++ b/src/org/sonews/storage/Headers.java Sun Aug 29 18:17:37 2010 +0200
51.3 @@ -27,30 +27,30 @@
51.4 public final class Headers
51.5 {
51.6
51.7 - public static final String BYTES = "bytes";
51.8 - public static final String CONTENT_TYPE = "content-type";
51.9 - public static final String CONTROL = "control";
51.10 - public static final String DATE = "date";
51.11 - public static final String FROM = "from";
51.12 - public static final String LINES = "lines";
51.13 - public static final String LIST_POST = "list-post";
51.14 - public static final String MESSAGE_ID = "message-id";
51.15 - public static final String NEWSGROUPS = "newsgroups";
51.16 - public static final String NNTP_POSTING_DATE = "nntp-posting-date";
51.17 - public static final String NNTP_POSTING_HOST = "nntp-posting-host";
51.18 - public static final String PATH = "path";
51.19 - public static final String REFERENCES = "references";
51.20 - public static final String REPLY_TO = "reply-to";
51.21 - public static final String SENDER = "sender";
51.22 - public static final String SUBJECT = "subject";
51.23 - public static final String SUPERSEDES = "subersedes";
51.24 - public static final String TO = "to";
51.25 - public static final String X_COMPLAINTS_TO = "x-complaints-to";
51.26 - public static final String X_LIST_POST = "x-list-post";
51.27 - public static final String X_TRACE = "x-trace";
51.28 - public static final String XREF = "xref";
51.29 + public static final String BYTES = "bytes";
51.30 + public static final String CONTENT_TYPE = "content-type";
51.31 + public static final String CONTROL = "control";
51.32 + public static final String DATE = "date";
51.33 + public static final String FROM = "from";
51.34 + public static final String LINES = "lines";
51.35 + public static final String LIST_POST = "list-post";
51.36 + public static final String MESSAGE_ID = "message-id";
51.37 + public static final String NEWSGROUPS = "newsgroups";
51.38 + public static final String NNTP_POSTING_DATE = "nntp-posting-date";
51.39 + public static final String NNTP_POSTING_HOST = "nntp-posting-host";
51.40 + public static final String PATH = "path";
51.41 + public static final String REFERENCES = "references";
51.42 + public static final String REPLY_TO = "reply-to";
51.43 + public static final String SENDER = "sender";
51.44 + public static final String SUBJECT = "subject";
51.45 + public static final String SUPERSEDES = "subersedes";
51.46 + public static final String TO = "to";
51.47 + public static final String X_COMPLAINTS_TO = "x-complaints-to";
51.48 + public static final String X_LIST_POST = "x-list-post";
51.49 + public static final String X_TRACE = "x-trace";
51.50 + public static final String XREF = "xref";
51.51
51.52 - private Headers()
51.53 - {}
51.54 -
51.55 + private Headers()
51.56 + {
51.57 + }
51.58 }
52.1 --- a/src/org/sonews/storage/Storage.java Sun Aug 29 17:43:58 2010 +0200
52.2 +++ b/src/org/sonews/storage/Storage.java Sun Aug 29 18:17:37 2010 +0200
52.3 @@ -30,121 +30,120 @@
52.4 public interface Storage
52.5 {
52.6
52.7 - /**
52.8 - * Stores the given Article in the storage.
52.9 - * @param art
52.10 - * @throws StorageBackendException
52.11 - */
52.12 - void addArticle(Article art)
52.13 - throws StorageBackendException;
52.14 + /**
52.15 + * Stores the given Article in the storage.
52.16 + * @param art
52.17 + * @throws StorageBackendException
52.18 + */
52.19 + void addArticle(Article art)
52.20 + throws StorageBackendException;
52.21
52.22 - void addEvent(long timestamp, int type, long groupID)
52.23 - throws StorageBackendException;
52.24 + void addEvent(long timestamp, int type, long groupID)
52.25 + throws StorageBackendException;
52.26
52.27 - void addGroup(String groupname, int flags)
52.28 - throws StorageBackendException;
52.29 + void addGroup(String groupname, int flags)
52.30 + throws StorageBackendException;
52.31
52.32 - int countArticles()
52.33 - throws StorageBackendException;
52.34 + int countArticles()
52.35 + throws StorageBackendException;
52.36
52.37 - int countGroups()
52.38 - throws StorageBackendException;
52.39 + int countGroups()
52.40 + throws StorageBackendException;
52.41
52.42 - void delete(String messageID)
52.43 - throws StorageBackendException;
52.44 + void delete(String messageID)
52.45 + throws StorageBackendException;
52.46
52.47 - Article getArticle(String messageID)
52.48 - throws StorageBackendException;
52.49 + Article getArticle(String messageID)
52.50 + throws StorageBackendException;
52.51
52.52 - Article getArticle(long articleIndex, long groupID)
52.53 - throws StorageBackendException;
52.54 + Article getArticle(long articleIndex, long groupID)
52.55 + throws StorageBackendException;
52.56
52.57 - List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first, long last)
52.58 - throws StorageBackendException;
52.59 + List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first, long last)
52.60 + throws StorageBackendException;
52.61
52.62 - List<Pair<Long, String>> getArticleHeaders(Channel channel, long start, long end,
52.63 - String header, String pattern)
52.64 - throws StorageBackendException;
52.65 + List<Pair<Long, String>> getArticleHeaders(Channel channel, long start, long end,
52.66 + String header, String pattern)
52.67 + throws StorageBackendException;
52.68
52.69 - long getArticleIndex(Article art, Group group)
52.70 - throws StorageBackendException;
52.71 + long getArticleIndex(Article art, Group group)
52.72 + throws StorageBackendException;
52.73
52.74 - List<Long> getArticleNumbers(long groupID)
52.75 - throws StorageBackendException;
52.76 + List<Long> getArticleNumbers(long groupID)
52.77 + throws StorageBackendException;
52.78
52.79 - String getConfigValue(String key)
52.80 - throws StorageBackendException;
52.81 + String getConfigValue(String key)
52.82 + throws StorageBackendException;
52.83
52.84 - int getEventsCount(int eventType, long startTimestamp, long endTimestamp,
52.85 - Channel channel)
52.86 - throws StorageBackendException;
52.87 + int getEventsCount(int eventType, long startTimestamp, long endTimestamp,
52.88 + Channel channel)
52.89 + throws StorageBackendException;
52.90
52.91 - double getEventsPerHour(int key, long gid)
52.92 - throws StorageBackendException;
52.93 + double getEventsPerHour(int key, long gid)
52.94 + throws StorageBackendException;
52.95
52.96 - int getFirstArticleNumber(Group group)
52.97 - throws StorageBackendException;
52.98 + int getFirstArticleNumber(Group group)
52.99 + throws StorageBackendException;
52.100
52.101 - Group getGroup(String name)
52.102 - throws StorageBackendException;
52.103 + Group getGroup(String name)
52.104 + throws StorageBackendException;
52.105
52.106 - List<Channel> getGroups()
52.107 - throws StorageBackendException;
52.108 + List<Channel> getGroups()
52.109 + throws StorageBackendException;
52.110
52.111 - /**
52.112 - * Retrieves the collection of groupnames that are associated with the
52.113 - * given list address.
52.114 - * @param inetaddress
52.115 - * @return
52.116 - * @throws StorageBackendException
52.117 - */
52.118 - List<String> getGroupsForList(String listAddress)
52.119 - throws StorageBackendException;
52.120 + /**
52.121 + * Retrieves the collection of groupnames that are associated with the
52.122 + * given list address.
52.123 + * @param inetaddress
52.124 + * @return
52.125 + * @throws StorageBackendException
52.126 + */
52.127 + List<String> getGroupsForList(String listAddress)
52.128 + throws StorageBackendException;
52.129
52.130 - int getLastArticleNumber(Group group)
52.131 - throws StorageBackendException;
52.132 + int getLastArticleNumber(Group group)
52.133 + throws StorageBackendException;
52.134
52.135 - /**
52.136 - * Returns a list of email addresses that are related to the given
52.137 - * groupname. In most cases the list may contain only one entry.
52.138 - * @param groupname
52.139 - * @return
52.140 - * @throws StorageBackendException
52.141 - */
52.142 - List<String> getListsForGroup(String groupname)
52.143 - throws StorageBackendException;
52.144 + /**
52.145 + * Returns a list of email addresses that are related to the given
52.146 + * groupname. In most cases the list may contain only one entry.
52.147 + * @param groupname
52.148 + * @return
52.149 + * @throws StorageBackendException
52.150 + */
52.151 + List<String> getListsForGroup(String groupname)
52.152 + throws StorageBackendException;
52.153
52.154 - String getOldestArticle()
52.155 - throws StorageBackendException;
52.156 + String getOldestArticle()
52.157 + throws StorageBackendException;
52.158
52.159 - int getPostingsCount(String groupname)
52.160 - throws StorageBackendException;
52.161 + int getPostingsCount(String groupname)
52.162 + throws StorageBackendException;
52.163
52.164 - List<Subscription> getSubscriptions(int type)
52.165 - throws StorageBackendException;
52.166 + List<Subscription> getSubscriptions(int type)
52.167 + throws StorageBackendException;
52.168
52.169 - boolean isArticleExisting(String messageID)
52.170 - throws StorageBackendException;
52.171 + boolean isArticleExisting(String messageID)
52.172 + throws StorageBackendException;
52.173
52.174 - boolean isGroupExisting(String groupname)
52.175 - throws StorageBackendException;
52.176 + boolean isGroupExisting(String groupname)
52.177 + throws StorageBackendException;
52.178
52.179 - void purgeGroup(Group group)
52.180 - throws StorageBackendException;
52.181 + void purgeGroup(Group group)
52.182 + throws StorageBackendException;
52.183
52.184 - void setConfigValue(String key, String value)
52.185 - throws StorageBackendException;
52.186 + void setConfigValue(String key, String value)
52.187 + throws StorageBackendException;
52.188
52.189 - /**
52.190 - * Updates headers and channel references of the given article.
52.191 - * @param article
52.192 - * @return
52.193 - * @throws StorageBackendException
52.194 - */
52.195 - boolean update(Article article)
52.196 - throws StorageBackendException;
52.197 + /**
52.198 + * Updates headers and channel references of the given article.
52.199 + * @param article
52.200 + * @return
52.201 + * @throws StorageBackendException
52.202 + */
52.203 + boolean update(Article article)
52.204 + throws StorageBackendException;
52.205
52.206 - boolean update(Group group)
52.207 - throws StorageBackendException;
52.208 -
52.209 + boolean update(Group group)
52.210 + throws StorageBackendException;
52.211 }
53.1 --- a/src/org/sonews/storage/StorageBackendException.java Sun Aug 29 17:43:58 2010 +0200
53.2 +++ b/src/org/sonews/storage/StorageBackendException.java Sun Aug 29 18:17:37 2010 +0200
53.3 @@ -19,21 +19,20 @@
53.4 package org.sonews.storage;
53.5
53.6 /**
53.7 - *
53.8 + * Exception caused by the storage backend.
53.9 * @author Christian Lins
53.10 * @since sonews/1.0
53.11 */
53.12 public class StorageBackendException extends Exception
53.13 {
53.14
53.15 - public StorageBackendException(Throwable cause)
53.16 - {
53.17 - super(cause);
53.18 - }
53.19 + public StorageBackendException(Throwable cause)
53.20 + {
53.21 + super(cause);
53.22 + }
53.23
53.24 - public StorageBackendException(String msg)
53.25 - {
53.26 - super(msg);
53.27 - }
53.28 -
53.29 + public StorageBackendException(String msg)
53.30 + {
53.31 + super(msg);
53.32 + }
53.33 }
54.1 --- a/src/org/sonews/storage/StorageManager.java Sun Aug 29 17:43:58 2010 +0200
54.2 +++ b/src/org/sonews/storage/StorageManager.java Sun Aug 29 18:17:37 2010 +0200
54.3 @@ -26,64 +26,53 @@
54.4 public final class StorageManager
54.5 {
54.6
54.7 - private static StorageProvider provider;
54.8 + private static StorageProvider provider;
54.9
54.10 - public static Storage current()
54.11 - throws StorageBackendException
54.12 - {
54.13 - synchronized(StorageManager.class)
54.14 - {
54.15 - if(provider == null)
54.16 - {
54.17 - return null;
54.18 - }
54.19 - else
54.20 - {
54.21 - return provider.storage(Thread.currentThread());
54.22 - }
54.23 - }
54.24 - }
54.25 + public static Storage current()
54.26 + throws StorageBackendException
54.27 + {
54.28 + synchronized (StorageManager.class) {
54.29 + if (provider == null) {
54.30 + return null;
54.31 + } else {
54.32 + return provider.storage(Thread.currentThread());
54.33 + }
54.34 + }
54.35 + }
54.36
54.37 - public static StorageProvider loadProvider(String pluginClassName)
54.38 - {
54.39 - try
54.40 - {
54.41 - Class<?> clazz = Class.forName(pluginClassName);
54.42 - Object inst = clazz.newInstance();
54.43 - return (StorageProvider)inst;
54.44 - }
54.45 - catch(Exception ex)
54.46 - {
54.47 - System.err.println(ex);
54.48 - return null;
54.49 - }
54.50 - }
54.51 + public static StorageProvider loadProvider(String pluginClassName)
54.52 + {
54.53 + try {
54.54 + Class<?> clazz = Class.forName(pluginClassName);
54.55 + Object inst = clazz.newInstance();
54.56 + return (StorageProvider) inst;
54.57 + } catch (Exception ex) {
54.58 + System.err.println(ex);
54.59 + return null;
54.60 + }
54.61 + }
54.62
54.63 - /**
54.64 - * Sets the current storage provider.
54.65 - * @param provider
54.66 - */
54.67 - public static void enableProvider(StorageProvider provider)
54.68 - {
54.69 - synchronized(StorageManager.class)
54.70 - {
54.71 - if(StorageManager.provider != null)
54.72 - {
54.73 - disableProvider();
54.74 - }
54.75 - StorageManager.provider = provider;
54.76 - }
54.77 - }
54.78 + /**
54.79 + * Sets the current storage provider.
54.80 + * @param provider
54.81 + */
54.82 + public static void enableProvider(StorageProvider provider)
54.83 + {
54.84 + synchronized (StorageManager.class) {
54.85 + if (StorageManager.provider != null) {
54.86 + disableProvider();
54.87 + }
54.88 + StorageManager.provider = provider;
54.89 + }
54.90 + }
54.91
54.92 - /**
54.93 - * Disables the current provider.
54.94 - */
54.95 - public static void disableProvider()
54.96 - {
54.97 - synchronized(StorageManager.class)
54.98 - {
54.99 - provider = null;
54.100 - }
54.101 - }
54.102 -
54.103 + /**
54.104 + * Disables the current provider.
54.105 + */
54.106 + public static void disableProvider()
54.107 + {
54.108 + synchronized (StorageManager.class) {
54.109 + provider = null;
54.110 + }
54.111 + }
54.112 }
55.1 --- a/src/org/sonews/storage/StorageProvider.java Sun Aug 29 17:43:58 2010 +0200
55.2 +++ b/src/org/sonews/storage/StorageProvider.java Sun Aug 29 18:17:37 2010 +0200
55.3 @@ -26,15 +26,14 @@
55.4 public interface StorageProvider
55.5 {
55.6
55.7 - public boolean isSupported(String uri);
55.8 + public boolean isSupported(String uri);
55.9
55.10 - /**
55.11 - * This method returns the reference to the associated storage.
55.12 - * The reference MAY be unique for each thread. In any case it MUST be
55.13 - * thread-safe to use this method.
55.14 - * @return The reference to the associated Storage.
55.15 - */
55.16 - public Storage storage(Thread thread)
55.17 - throws StorageBackendException;
55.18 -
55.19 + /**
55.20 + * This method returns the reference to the associated storage.
55.21 + * The reference MAY be unique for each thread. In any case it MUST be
55.22 + * thread-safe to use this method.
55.23 + * @return The reference to the associated Storage.
55.24 + */
55.25 + public Storage storage(Thread thread)
55.26 + throws StorageBackendException;
55.27 }
56.1 --- a/src/org/sonews/storage/impl/JDBCDatabase.java Sun Aug 29 17:43:58 2010 +0200
56.2 +++ b/src/org/sonews/storage/impl/JDBCDatabase.java Sun Aug 29 18:17:37 2010 +0200
56.3 @@ -52,1731 +52,1378 @@
56.4 public class JDBCDatabase implements Storage
56.5 {
56.6
56.7 - public static final int MAX_RESTARTS = 2;
56.8 -
56.9 - private Connection conn = null;
56.10 - private PreparedStatement pstmtAddArticle1 = null;
56.11 - private PreparedStatement pstmtAddArticle2 = null;
56.12 - private PreparedStatement pstmtAddArticle3 = null;
56.13 - private PreparedStatement pstmtAddArticle4 = null;
56.14 - private PreparedStatement pstmtAddGroup0 = null;
56.15 - private PreparedStatement pstmtAddEvent = null;
56.16 - private PreparedStatement pstmtCountArticles = null;
56.17 - private PreparedStatement pstmtCountGroups = null;
56.18 - private PreparedStatement pstmtDeleteArticle0 = null;
56.19 - private PreparedStatement pstmtDeleteArticle1 = null;
56.20 - private PreparedStatement pstmtDeleteArticle2 = null;
56.21 - private PreparedStatement pstmtDeleteArticle3 = null;
56.22 - private PreparedStatement pstmtGetArticle0 = null;
56.23 - private PreparedStatement pstmtGetArticle1 = null;
56.24 - private PreparedStatement pstmtGetArticleHeaders0 = null;
56.25 - private PreparedStatement pstmtGetArticleHeaders1 = null;
56.26 - private PreparedStatement pstmtGetArticleHeads = null;
56.27 - private PreparedStatement pstmtGetArticleIDs = null;
56.28 - private PreparedStatement pstmtGetArticleIndex = null;
56.29 - private PreparedStatement pstmtGetConfigValue = null;
56.30 - private PreparedStatement pstmtGetEventsCount0 = null;
56.31 - private PreparedStatement pstmtGetEventsCount1 = null;
56.32 - private PreparedStatement pstmtGetGroupForList = null;
56.33 - private PreparedStatement pstmtGetGroup0 = null;
56.34 - private PreparedStatement pstmtGetGroup1 = null;
56.35 - private PreparedStatement pstmtGetFirstArticleNumber = null;
56.36 - private PreparedStatement pstmtGetListForGroup = null;
56.37 - private PreparedStatement pstmtGetLastArticleNumber = null;
56.38 - private PreparedStatement pstmtGetMaxArticleID = null;
56.39 - private PreparedStatement pstmtGetMaxArticleIndex = null;
56.40 - private PreparedStatement pstmtGetOldestArticle = null;
56.41 - private PreparedStatement pstmtGetPostingsCount = null;
56.42 - private PreparedStatement pstmtGetSubscriptions = null;
56.43 - private PreparedStatement pstmtIsArticleExisting = null;
56.44 - private PreparedStatement pstmtIsGroupExisting = null;
56.45 - private PreparedStatement pstmtPurgeGroup0 = null;
56.46 - private PreparedStatement pstmtPurgeGroup1 = null;
56.47 - private PreparedStatement pstmtSetConfigValue0 = null;
56.48 - private PreparedStatement pstmtSetConfigValue1 = null;
56.49 - private PreparedStatement pstmtUpdateGroup = null;
56.50 -
56.51 - /** How many times the database connection was reinitialized */
56.52 - private int restarts = 0;
56.53 -
56.54 - /**
56.55 - * Rises the database: reconnect and recreate all prepared statements.
56.56 - * @throws java.lang.SQLException
56.57 - */
56.58 - protected void arise()
56.59 - throws SQLException
56.60 - {
56.61 - try
56.62 - {
56.63 - // Load database driver
56.64 - Class.forName(
56.65 - Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DBMSDRIVER, "java.lang.Object"));
56.66 + public static final int MAX_RESTARTS = 2;
56.67 + private Connection conn = null;
56.68 + private PreparedStatement pstmtAddArticle1 = null;
56.69 + private PreparedStatement pstmtAddArticle2 = null;
56.70 + private PreparedStatement pstmtAddArticle3 = null;
56.71 + private PreparedStatement pstmtAddArticle4 = null;
56.72 + private PreparedStatement pstmtAddGroup0 = null;
56.73 + private PreparedStatement pstmtAddEvent = null;
56.74 + private PreparedStatement pstmtCountArticles = null;
56.75 + private PreparedStatement pstmtCountGroups = null;
56.76 + private PreparedStatement pstmtDeleteArticle0 = null;
56.77 + private PreparedStatement pstmtDeleteArticle1 = null;
56.78 + private PreparedStatement pstmtDeleteArticle2 = null;
56.79 + private PreparedStatement pstmtDeleteArticle3 = null;
56.80 + private PreparedStatement pstmtGetArticle0 = null;
56.81 + private PreparedStatement pstmtGetArticle1 = null;
56.82 + private PreparedStatement pstmtGetArticleHeaders0 = null;
56.83 + private PreparedStatement pstmtGetArticleHeaders1 = null;
56.84 + private PreparedStatement pstmtGetArticleHeads = null;
56.85 + private PreparedStatement pstmtGetArticleIDs = null;
56.86 + private PreparedStatement pstmtGetArticleIndex = null;
56.87 + private PreparedStatement pstmtGetConfigValue = null;
56.88 + private PreparedStatement pstmtGetEventsCount0 = null;
56.89 + private PreparedStatement pstmtGetEventsCount1 = null;
56.90 + private PreparedStatement pstmtGetGroupForList = null;
56.91 + private PreparedStatement pstmtGetGroup0 = null;
56.92 + private PreparedStatement pstmtGetGroup1 = null;
56.93 + private PreparedStatement pstmtGetFirstArticleNumber = null;
56.94 + private PreparedStatement pstmtGetListForGroup = null;
56.95 + private PreparedStatement pstmtGetLastArticleNumber = null;
56.96 + private PreparedStatement pstmtGetMaxArticleID = null;
56.97 + private PreparedStatement pstmtGetMaxArticleIndex = null;
56.98 + private PreparedStatement pstmtGetOldestArticle = null;
56.99 + private PreparedStatement pstmtGetPostingsCount = null;
56.100 + private PreparedStatement pstmtGetSubscriptions = null;
56.101 + private PreparedStatement pstmtIsArticleExisting = null;
56.102 + private PreparedStatement pstmtIsGroupExisting = null;
56.103 + private PreparedStatement pstmtPurgeGroup0 = null;
56.104 + private PreparedStatement pstmtPurgeGroup1 = null;
56.105 + private PreparedStatement pstmtSetConfigValue0 = null;
56.106 + private PreparedStatement pstmtSetConfigValue1 = null;
56.107 + private PreparedStatement pstmtUpdateGroup = null;
56.108 + /** How many times the database connection was reinitialized */
56.109 + private int restarts = 0;
56.110
56.111 - // Establish database connection
56.112 - this.conn = DriverManager.getConnection(
56.113 - Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DATABASE, "<not specified>"),
56.114 - Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_USER, "root"),
56.115 - Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_PASSWORD, ""));
56.116 + /**
56.117 + * Rises the database: reconnect and recreate all prepared statements.
56.118 + * @throws java.lang.SQLException
56.119 + */
56.120 + protected void arise()
56.121 + throws SQLException
56.122 + {
56.123 + try {
56.124 + // Load database driver
56.125 + Class.forName(
56.126 + Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DBMSDRIVER, "java.lang.Object"));
56.127
56.128 - this.conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
56.129 - if(this.conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE)
56.130 - {
56.131 - Log.get().warning("Database is NOT fully serializable!");
56.132 - }
56.133 + // Establish database connection
56.134 + this.conn = DriverManager.getConnection(
56.135 + Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DATABASE, "<not specified>"),
56.136 + Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_USER, "root"),
56.137 + Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_PASSWORD, ""));
56.138
56.139 - // Prepare statements for method addArticle()
56.140 - this.pstmtAddArticle1 = conn.prepareStatement(
56.141 - "INSERT INTO articles (article_id, body) VALUES(?, ?)");
56.142 - this.pstmtAddArticle2 = conn.prepareStatement(
56.143 - "INSERT INTO headers (article_id, header_key, header_value, header_index) " +
56.144 - "VALUES (?, ?, ?, ?)");
56.145 - this.pstmtAddArticle3 = conn.prepareStatement(
56.146 - "INSERT INTO postings (group_id, article_id, article_index)" +
56.147 - "VALUES (?, ?, ?)");
56.148 - this.pstmtAddArticle4 = conn.prepareStatement(
56.149 - "INSERT INTO article_ids (article_id, message_id) VALUES (?, ?)");
56.150 + this.conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
56.151 + if (this.conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE) {
56.152 + Log.get().warning("Database is NOT fully serializable!");
56.153 + }
56.154
56.155 - // Prepare statement for method addStatValue()
56.156 - this.pstmtAddEvent = conn.prepareStatement(
56.157 - "INSERT INTO events VALUES (?, ?, ?)");
56.158 -
56.159 - // Prepare statement for method addGroup()
56.160 - this.pstmtAddGroup0 = conn.prepareStatement(
56.161 - "INSERT INTO groups (name, flags) VALUES (?, ?)");
56.162 -
56.163 - // Prepare statement for method countArticles()
56.164 - this.pstmtCountArticles = conn.prepareStatement(
56.165 - "SELECT Count(article_id) FROM article_ids");
56.166 -
56.167 - // Prepare statement for method countGroups()
56.168 - this.pstmtCountGroups = conn.prepareStatement(
56.169 - "SELECT Count(group_id) FROM groups WHERE " +
56.170 - "flags & " + Channel.DELETED + " = 0");
56.171 -
56.172 - // Prepare statements for method delete(article)
56.173 - this.pstmtDeleteArticle0 = conn.prepareStatement(
56.174 - "DELETE FROM articles WHERE article_id = " +
56.175 - "(SELECT article_id FROM article_ids WHERE message_id = ?)");
56.176 - this.pstmtDeleteArticle1 = conn.prepareStatement(
56.177 - "DELETE FROM headers WHERE article_id = " +
56.178 - "(SELECT article_id FROM article_ids WHERE message_id = ?)");
56.179 - this.pstmtDeleteArticle2 = conn.prepareStatement(
56.180 - "DELETE FROM postings WHERE article_id = " +
56.181 - "(SELECT article_id FROM article_ids WHERE message_id = ?)");
56.182 - this.pstmtDeleteArticle3 = conn.prepareStatement(
56.183 - "DELETE FROM article_ids WHERE message_id = ?");
56.184 + // Prepare statements for method addArticle()
56.185 + this.pstmtAddArticle1 = conn.prepareStatement(
56.186 + "INSERT INTO articles (article_id, body) VALUES(?, ?)");
56.187 + this.pstmtAddArticle2 = conn.prepareStatement(
56.188 + "INSERT INTO headers (article_id, header_key, header_value, header_index) "
56.189 + + "VALUES (?, ?, ?, ?)");
56.190 + this.pstmtAddArticle3 = conn.prepareStatement(
56.191 + "INSERT INTO postings (group_id, article_id, article_index)"
56.192 + + "VALUES (?, ?, ?)");
56.193 + this.pstmtAddArticle4 = conn.prepareStatement(
56.194 + "INSERT INTO article_ids (article_id, message_id) VALUES (?, ?)");
56.195
56.196 - // Prepare statements for methods getArticle()
56.197 - this.pstmtGetArticle0 = conn.prepareStatement(
56.198 - "SELECT * FROM articles WHERE article_id = " +
56.199 - "(SELECT article_id FROM article_ids WHERE message_id = ?)");
56.200 - this.pstmtGetArticle1 = conn.prepareStatement(
56.201 - "SELECT * FROM articles WHERE article_id = " +
56.202 - "(SELECT article_id FROM postings WHERE " +
56.203 - "article_index = ? AND group_id = ?)");
56.204 -
56.205 - // Prepare statement for method getArticleHeaders()
56.206 - this.pstmtGetArticleHeaders0 = conn.prepareStatement(
56.207 - "SELECT header_key, header_value FROM headers WHERE article_id = ? " +
56.208 - "ORDER BY header_index ASC");
56.209 + // Prepare statement for method addStatValue()
56.210 + this.pstmtAddEvent = conn.prepareStatement(
56.211 + "INSERT INTO events VALUES (?, ?, ?)");
56.212
56.213 - // Prepare statement for method getArticleHeaders(regular expr pattern)
56.214 - this.pstmtGetArticleHeaders1 = conn.prepareStatement(
56.215 - "SELECT p.article_index, h.header_value FROM headers h " +
56.216 - "INNER JOIN postings p ON h.article_id = p.article_id " +
56.217 - "INNER JOIN groups g ON p.group_id = g.group_id " +
56.218 - "WHERE g.name = ? AND " +
56.219 - "h.header_key = ? AND " +
56.220 - "p.article_index >= ? " +
56.221 - "ORDER BY p.article_index ASC");
56.222 + // Prepare statement for method addGroup()
56.223 + this.pstmtAddGroup0 = conn.prepareStatement(
56.224 + "INSERT INTO groups (name, flags) VALUES (?, ?)");
56.225
56.226 - this.pstmtGetArticleIDs = conn.prepareStatement(
56.227 - "SELECT article_index FROM postings WHERE group_id = ?");
56.228 -
56.229 - // Prepare statement for method getArticleIndex
56.230 - this.pstmtGetArticleIndex = conn.prepareStatement(
56.231 - "SELECT article_index FROM postings WHERE " +
56.232 - "article_id = (SELECT article_id FROM article_ids " +
56.233 - "WHERE message_id = ?) " +
56.234 - " AND group_id = ?");
56.235 + // Prepare statement for method countArticles()
56.236 + this.pstmtCountArticles = conn.prepareStatement(
56.237 + "SELECT Count(article_id) FROM article_ids");
56.238
56.239 - // Prepare statements for method getArticleHeads()
56.240 - this.pstmtGetArticleHeads = conn.prepareStatement(
56.241 - "SELECT article_id, article_index FROM postings WHERE " +
56.242 - "postings.group_id = ? AND article_index >= ? AND " +
56.243 - "article_index <= ?");
56.244 + // Prepare statement for method countGroups()
56.245 + this.pstmtCountGroups = conn.prepareStatement(
56.246 + "SELECT Count(group_id) FROM groups WHERE "
56.247 + + "flags & " + Channel.DELETED + " = 0");
56.248
56.249 - // Prepare statements for method getConfigValue()
56.250 - this.pstmtGetConfigValue = conn.prepareStatement(
56.251 - "SELECT config_value FROM config WHERE config_key = ?");
56.252 + // Prepare statements for method delete(article)
56.253 + this.pstmtDeleteArticle0 = conn.prepareStatement(
56.254 + "DELETE FROM articles WHERE article_id = "
56.255 + + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
56.256 + this.pstmtDeleteArticle1 = conn.prepareStatement(
56.257 + "DELETE FROM headers WHERE article_id = "
56.258 + + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
56.259 + this.pstmtDeleteArticle2 = conn.prepareStatement(
56.260 + "DELETE FROM postings WHERE article_id = "
56.261 + + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
56.262 + this.pstmtDeleteArticle3 = conn.prepareStatement(
56.263 + "DELETE FROM article_ids WHERE message_id = ?");
56.264
56.265 - // Prepare statements for method getEventsCount()
56.266 - this.pstmtGetEventsCount0 = conn.prepareStatement(
56.267 - "SELECT Count(*) FROM events WHERE event_key = ? AND " +
56.268 - "event_time >= ? AND event_time < ?");
56.269 + // Prepare statements for methods getArticle()
56.270 + this.pstmtGetArticle0 = conn.prepareStatement(
56.271 + "SELECT * FROM articles WHERE article_id = "
56.272 + + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
56.273 + this.pstmtGetArticle1 = conn.prepareStatement(
56.274 + "SELECT * FROM articles WHERE article_id = "
56.275 + + "(SELECT article_id FROM postings WHERE "
56.276 + + "article_index = ? AND group_id = ?)");
56.277
56.278 - this.pstmtGetEventsCount1 = conn.prepareStatement(
56.279 - "SELECT Count(*) FROM events WHERE event_key = ? AND " +
56.280 - "event_time >= ? AND event_time < ? AND group_id = ?");
56.281 -
56.282 - // Prepare statement for method getGroupForList()
56.283 - this.pstmtGetGroupForList = conn.prepareStatement(
56.284 - "SELECT name FROM groups INNER JOIN groups2list " +
56.285 - "ON groups.group_id = groups2list.group_id " +
56.286 - "WHERE groups2list.listaddress = ?");
56.287 + // Prepare statement for method getArticleHeaders()
56.288 + this.pstmtGetArticleHeaders0 = conn.prepareStatement(
56.289 + "SELECT header_key, header_value FROM headers WHERE article_id = ? "
56.290 + + "ORDER BY header_index ASC");
56.291
56.292 - // Prepare statement for method getGroup()
56.293 - this.pstmtGetGroup0 = conn.prepareStatement(
56.294 - "SELECT group_id, flags FROM groups WHERE Name = ?");
56.295 - this.pstmtGetGroup1 = conn.prepareStatement(
56.296 - "SELECT name FROM groups WHERE group_id = ?");
56.297 + // Prepare statement for method getArticleHeaders(regular expr pattern)
56.298 + this.pstmtGetArticleHeaders1 = conn.prepareStatement(
56.299 + "SELECT p.article_index, h.header_value FROM headers h "
56.300 + + "INNER JOIN postings p ON h.article_id = p.article_id "
56.301 + + "INNER JOIN groups g ON p.group_id = g.group_id "
56.302 + + "WHERE g.name = ? AND "
56.303 + + "h.header_key = ? AND "
56.304 + + "p.article_index >= ? "
56.305 + + "ORDER BY p.article_index ASC");
56.306
56.307 - // Prepare statement for method getLastArticleNumber()
56.308 - this.pstmtGetLastArticleNumber = conn.prepareStatement(
56.309 - "SELECT Max(article_index) FROM postings WHERE group_id = ?");
56.310 + this.pstmtGetArticleIDs = conn.prepareStatement(
56.311 + "SELECT article_index FROM postings WHERE group_id = ?");
56.312
56.313 - // Prepare statement for method getListForGroup()
56.314 - this.pstmtGetListForGroup = conn.prepareStatement(
56.315 - "SELECT listaddress FROM groups2list INNER JOIN groups " +
56.316 - "ON groups.group_id = groups2list.group_id WHERE name = ?");
56.317 + // Prepare statement for method getArticleIndex
56.318 + this.pstmtGetArticleIndex = conn.prepareStatement(
56.319 + "SELECT article_index FROM postings WHERE "
56.320 + + "article_id = (SELECT article_id FROM article_ids "
56.321 + + "WHERE message_id = ?) "
56.322 + + " AND group_id = ?");
56.323
56.324 - // Prepare statement for method getMaxArticleID()
56.325 - this.pstmtGetMaxArticleID = conn.prepareStatement(
56.326 - "SELECT Max(article_id) FROM articles");
56.327 -
56.328 - // Prepare statement for method getMaxArticleIndex()
56.329 - this.pstmtGetMaxArticleIndex = conn.prepareStatement(
56.330 - "SELECT Max(article_index) FROM postings WHERE group_id = ?");
56.331 -
56.332 - // Prepare statement for method getOldestArticle()
56.333 - this.pstmtGetOldestArticle = conn.prepareStatement(
56.334 - "SELECT message_id FROM article_ids WHERE article_id = " +
56.335 - "(SELECT Min(article_id) FROM article_ids)");
56.336 + // Prepare statements for method getArticleHeads()
56.337 + this.pstmtGetArticleHeads = conn.prepareStatement(
56.338 + "SELECT article_id, article_index FROM postings WHERE "
56.339 + + "postings.group_id = ? AND article_index >= ? AND "
56.340 + + "article_index <= ?");
56.341
56.342 - // Prepare statement for method getFirstArticleNumber()
56.343 - this.pstmtGetFirstArticleNumber = conn.prepareStatement(
56.344 - "SELECT Min(article_index) FROM postings WHERE group_id = ?");
56.345 -
56.346 - // Prepare statement for method getPostingsCount()
56.347 - this.pstmtGetPostingsCount = conn.prepareStatement(
56.348 - "SELECT Count(*) FROM postings NATURAL JOIN groups " +
56.349 - "WHERE groups.name = ?");
56.350 -
56.351 - // Prepare statement for method getSubscriptions()
56.352 - this.pstmtGetSubscriptions = conn.prepareStatement(
56.353 - "SELECT host, port, name FROM peers NATURAL JOIN " +
56.354 - "peer_subscriptions NATURAL JOIN groups WHERE feedtype = ?");
56.355 -
56.356 - // Prepare statement for method isArticleExisting()
56.357 - this.pstmtIsArticleExisting = conn.prepareStatement(
56.358 - "SELECT Count(article_id) FROM article_ids WHERE message_id = ?");
56.359 -
56.360 - // Prepare statement for method isGroupExisting()
56.361 - this.pstmtIsGroupExisting = conn.prepareStatement(
56.362 - "SELECT * FROM groups WHERE name = ?");
56.363 -
56.364 - // Prepare statement for method setConfigValue()
56.365 - this.pstmtSetConfigValue0 = conn.prepareStatement(
56.366 - "DELETE FROM config WHERE config_key = ?");
56.367 - this.pstmtSetConfigValue1 = conn.prepareStatement(
56.368 - "INSERT INTO config VALUES(?, ?)");
56.369 + // Prepare statements for method getConfigValue()
56.370 + this.pstmtGetConfigValue = conn.prepareStatement(
56.371 + "SELECT config_value FROM config WHERE config_key = ?");
56.372
56.373 - // Prepare statements for method purgeGroup()
56.374 - this.pstmtPurgeGroup0 = conn.prepareStatement(
56.375 - "DELETE FROM peer_subscriptions WHERE group_id = ?");
56.376 - this.pstmtPurgeGroup1 = conn.prepareStatement(
56.377 - "DELETE FROM groups WHERE group_id = ?");
56.378 + // Prepare statements for method getEventsCount()
56.379 + this.pstmtGetEventsCount0 = conn.prepareStatement(
56.380 + "SELECT Count(*) FROM events WHERE event_key = ? AND "
56.381 + + "event_time >= ? AND event_time < ?");
56.382
56.383 - // Prepare statement for method update(Group)
56.384 - this.pstmtUpdateGroup = conn.prepareStatement(
56.385 - "UPDATE groups SET flags = ?, name = ? WHERE group_id = ?");
56.386 - }
56.387 - catch(ClassNotFoundException ex)
56.388 - {
56.389 - throw new Error("JDBC Driver not found!", ex);
56.390 - }
56.391 - }
56.392 -
56.393 - /**
56.394 - * Adds an article to the database.
56.395 - * @param article
56.396 - * @return
56.397 - * @throws java.sql.SQLException
56.398 - */
56.399 - @Override
56.400 - public void addArticle(final Article article)
56.401 - throws StorageBackendException
56.402 - {
56.403 - try
56.404 - {
56.405 - this.conn.setAutoCommit(false);
56.406 + this.pstmtGetEventsCount1 = conn.prepareStatement(
56.407 + "SELECT Count(*) FROM events WHERE event_key = ? AND "
56.408 + + "event_time >= ? AND event_time < ? AND group_id = ?");
56.409
56.410 - int newArticleID = getMaxArticleID() + 1;
56.411 + // Prepare statement for method getGroupForList()
56.412 + this.pstmtGetGroupForList = conn.prepareStatement(
56.413 + "SELECT name FROM groups INNER JOIN groups2list "
56.414 + + "ON groups.group_id = groups2list.group_id "
56.415 + + "WHERE groups2list.listaddress = ?");
56.416
56.417 - // Fill prepared statement with values;
56.418 - // writes body to article table
56.419 - pstmtAddArticle1.setInt(1, newArticleID);
56.420 - pstmtAddArticle1.setBytes(2, article.getBody());
56.421 - pstmtAddArticle1.execute();
56.422 + // Prepare statement for method getGroup()
56.423 + this.pstmtGetGroup0 = conn.prepareStatement(
56.424 + "SELECT group_id, flags FROM groups WHERE Name = ?");
56.425 + this.pstmtGetGroup1 = conn.prepareStatement(
56.426 + "SELECT name FROM groups WHERE group_id = ?");
56.427
56.428 - // Add headers
56.429 - Enumeration headers = article.getAllHeaders();
56.430 - for(int n = 0; headers.hasMoreElements(); n++)
56.431 - {
56.432 - Header header = (Header)headers.nextElement();
56.433 - pstmtAddArticle2.setInt(1, newArticleID);
56.434 - pstmtAddArticle2.setString(2, header.getName().toLowerCase());
56.435 - pstmtAddArticle2.setString(3,
56.436 - header.getValue().replaceAll("[\r\n]", ""));
56.437 - pstmtAddArticle2.setInt(4, n);
56.438 - pstmtAddArticle2.execute();
56.439 - }
56.440 -
56.441 - // For each newsgroup add a reference
56.442 - List<Group> groups = article.getGroups();
56.443 - for(Group group : groups)
56.444 - {
56.445 - pstmtAddArticle3.setLong(1, group.getInternalID());
56.446 - pstmtAddArticle3.setInt(2, newArticleID);
56.447 - pstmtAddArticle3.setLong(3, getMaxArticleIndex(group.getInternalID()) + 1);
56.448 - pstmtAddArticle3.execute();
56.449 - }
56.450 -
56.451 - // Write message-id to article_ids table
56.452 - this.pstmtAddArticle4.setInt(1, newArticleID);
56.453 - this.pstmtAddArticle4.setString(2, article.getMessageID());
56.454 - this.pstmtAddArticle4.execute();
56.455 + // Prepare statement for method getLastArticleNumber()
56.456 + this.pstmtGetLastArticleNumber = conn.prepareStatement(
56.457 + "SELECT Max(article_index) FROM postings WHERE group_id = ?");
56.458
56.459 - this.conn.commit();
56.460 - this.conn.setAutoCommit(true);
56.461 + // Prepare statement for method getListForGroup()
56.462 + this.pstmtGetListForGroup = conn.prepareStatement(
56.463 + "SELECT listaddress FROM groups2list INNER JOIN groups "
56.464 + + "ON groups.group_id = groups2list.group_id WHERE name = ?");
56.465
56.466 - this.restarts = 0; // Reset error count
56.467 - }
56.468 - catch(SQLException ex)
56.469 - {
56.470 - try
56.471 - {
56.472 - this.conn.rollback(); // Rollback changes
56.473 - }
56.474 - catch(SQLException ex2)
56.475 - {
56.476 - Log.get().severe("Rollback of addArticle() failed: " + ex2);
56.477 - }
56.478 -
56.479 - try
56.480 - {
56.481 - this.conn.setAutoCommit(true); // and release locks
56.482 - }
56.483 - catch(SQLException ex2)
56.484 - {
56.485 - Log.get().severe("setAutoCommit(true) of addArticle() failed: " + ex2);
56.486 - }
56.487 + // Prepare statement for method getMaxArticleID()
56.488 + this.pstmtGetMaxArticleID = conn.prepareStatement(
56.489 + "SELECT Max(article_id) FROM articles");
56.490
56.491 - restartConnection(ex);
56.492 - addArticle(article);
56.493 - }
56.494 - }
56.495 -
56.496 - /**
56.497 - * Adds a group to the JDBCDatabase. This method is not accessible via NNTP.
56.498 - * @param name
56.499 - * @throws java.sql.SQLException
56.500 - */
56.501 - @Override
56.502 - public void addGroup(String name, int flags)
56.503 - throws StorageBackendException
56.504 - {
56.505 - try
56.506 - {
56.507 - this.conn.setAutoCommit(false);
56.508 - pstmtAddGroup0.setString(1, name);
56.509 - pstmtAddGroup0.setInt(2, flags);
56.510 + // Prepare statement for method getMaxArticleIndex()
56.511 + this.pstmtGetMaxArticleIndex = conn.prepareStatement(
56.512 + "SELECT Max(article_index) FROM postings WHERE group_id = ?");
56.513
56.514 - pstmtAddGroup0.executeUpdate();
56.515 - this.conn.commit();
56.516 - this.conn.setAutoCommit(true);
56.517 - this.restarts = 0; // Reset error count
56.518 - }
56.519 - catch(SQLException ex)
56.520 - {
56.521 - try
56.522 - {
56.523 - this.conn.rollback();
56.524 - this.conn.setAutoCommit(true);
56.525 - }
56.526 - catch(SQLException ex2)
56.527 - {
56.528 - ex2.printStackTrace();
56.529 - }
56.530 + // Prepare statement for method getOldestArticle()
56.531 + this.pstmtGetOldestArticle = conn.prepareStatement(
56.532 + "SELECT message_id FROM article_ids WHERE article_id = "
56.533 + + "(SELECT Min(article_id) FROM article_ids)");
56.534
56.535 - restartConnection(ex);
56.536 - addGroup(name, flags);
56.537 - }
56.538 - }
56.539 + // Prepare statement for method getFirstArticleNumber()
56.540 + this.pstmtGetFirstArticleNumber = conn.prepareStatement(
56.541 + "SELECT Min(article_index) FROM postings WHERE group_id = ?");
56.542
56.543 - @Override
56.544 - public void addEvent(long time, int type, long gid)
56.545 - throws StorageBackendException
56.546 - {
56.547 - try
56.548 - {
56.549 - this.conn.setAutoCommit(false);
56.550 - this.pstmtAddEvent.setLong(1, time);
56.551 - this.pstmtAddEvent.setInt(2, type);
56.552 - this.pstmtAddEvent.setLong(3, gid);
56.553 - this.pstmtAddEvent.executeUpdate();
56.554 - this.conn.commit();
56.555 - this.conn.setAutoCommit(true);
56.556 - this.restarts = 0;
56.557 - }
56.558 - catch(SQLException ex)
56.559 - {
56.560 - try
56.561 - {
56.562 - this.conn.rollback();
56.563 - this.conn.setAutoCommit(true);
56.564 - }
56.565 - catch(SQLException ex2)
56.566 - {
56.567 - ex2.printStackTrace();
56.568 - }
56.569 + // Prepare statement for method getPostingsCount()
56.570 + this.pstmtGetPostingsCount = conn.prepareStatement(
56.571 + "SELECT Count(*) FROM postings NATURAL JOIN groups "
56.572 + + "WHERE groups.name = ?");
56.573
56.574 - restartConnection(ex);
56.575 - addEvent(time, type, gid);
56.576 - }
56.577 - }
56.578 + // Prepare statement for method getSubscriptions()
56.579 + this.pstmtGetSubscriptions = conn.prepareStatement(
56.580 + "SELECT host, port, name FROM peers NATURAL JOIN "
56.581 + + "peer_subscriptions NATURAL JOIN groups WHERE feedtype = ?");
56.582
56.583 - @Override
56.584 - public int countArticles()
56.585 - throws StorageBackendException
56.586 - {
56.587 - ResultSet rs = null;
56.588 + // Prepare statement for method isArticleExisting()
56.589 + this.pstmtIsArticleExisting = conn.prepareStatement(
56.590 + "SELECT Count(article_id) FROM article_ids WHERE message_id = ?");
56.591
56.592 - try
56.593 - {
56.594 - rs = this.pstmtCountArticles.executeQuery();
56.595 - if(rs.next())
56.596 - {
56.597 - return rs.getInt(1);
56.598 - }
56.599 - else
56.600 - {
56.601 - return -1;
56.602 - }
56.603 - }
56.604 - catch(SQLException ex)
56.605 - {
56.606 - restartConnection(ex);
56.607 - return countArticles();
56.608 - }
56.609 - finally
56.610 - {
56.611 - if(rs != null)
56.612 - {
56.613 - try
56.614 - {
56.615 - rs.close();
56.616 - }
56.617 - catch(SQLException ex)
56.618 - {
56.619 - ex.printStackTrace();
56.620 - }
56.621 - restarts = 0;
56.622 - }
56.623 - }
56.624 - }
56.625 + // Prepare statement for method isGroupExisting()
56.626 + this.pstmtIsGroupExisting = conn.prepareStatement(
56.627 + "SELECT * FROM groups WHERE name = ?");
56.628
56.629 - @Override
56.630 - public int countGroups()
56.631 - throws StorageBackendException
56.632 - {
56.633 - ResultSet rs = null;
56.634 + // Prepare statement for method setConfigValue()
56.635 + this.pstmtSetConfigValue0 = conn.prepareStatement(
56.636 + "DELETE FROM config WHERE config_key = ?");
56.637 + this.pstmtSetConfigValue1 = conn.prepareStatement(
56.638 + "INSERT INTO config VALUES(?, ?)");
56.639
56.640 - try
56.641 - {
56.642 - rs = this.pstmtCountGroups.executeQuery();
56.643 - if(rs.next())
56.644 - {
56.645 - return rs.getInt(1);
56.646 - }
56.647 - else
56.648 - {
56.649 - return -1;
56.650 - }
56.651 - }
56.652 - catch(SQLException ex)
56.653 - {
56.654 - restartConnection(ex);
56.655 - return countGroups();
56.656 - }
56.657 - finally
56.658 - {
56.659 - if(rs != null)
56.660 - {
56.661 - try
56.662 - {
56.663 - rs.close();
56.664 - }
56.665 - catch(SQLException ex)
56.666 - {
56.667 - ex.printStackTrace();
56.668 - }
56.669 - restarts = 0;
56.670 - }
56.671 - }
56.672 - }
56.673 + // Prepare statements for method purgeGroup()
56.674 + this.pstmtPurgeGroup0 = conn.prepareStatement(
56.675 + "DELETE FROM peer_subscriptions WHERE group_id = ?");
56.676 + this.pstmtPurgeGroup1 = conn.prepareStatement(
56.677 + "DELETE FROM groups WHERE group_id = ?");
56.678
56.679 - @Override
56.680 - public void delete(final String messageID)
56.681 - throws StorageBackendException
56.682 - {
56.683 - try
56.684 - {
56.685 - this.conn.setAutoCommit(false);
56.686 -
56.687 - this.pstmtDeleteArticle0.setString(1, messageID);
56.688 - int rs = this.pstmtDeleteArticle0.executeUpdate();
56.689 -
56.690 - // We do not trust the ON DELETE CASCADE functionality to delete
56.691 - // orphaned references...
56.692 - this.pstmtDeleteArticle1.setString(1, messageID);
56.693 - rs = this.pstmtDeleteArticle1.executeUpdate();
56.694 + // Prepare statement for method update(Group)
56.695 + this.pstmtUpdateGroup = conn.prepareStatement(
56.696 + "UPDATE groups SET flags = ?, name = ? WHERE group_id = ?");
56.697 + } catch (ClassNotFoundException ex) {
56.698 + throw new Error("JDBC Driver not found!", ex);
56.699 + }
56.700 + }
56.701
56.702 - this.pstmtDeleteArticle2.setString(1, messageID);
56.703 - rs = this.pstmtDeleteArticle2.executeUpdate();
56.704 + /**
56.705 + * Adds an article to the database.
56.706 + * @param article
56.707 + * @return
56.708 + * @throws java.sql.SQLException
56.709 + */
56.710 + @Override
56.711 + public void addArticle(final Article article)
56.712 + throws StorageBackendException
56.713 + {
56.714 + try {
56.715 + this.conn.setAutoCommit(false);
56.716
56.717 - this.pstmtDeleteArticle3.setString(1, messageID);
56.718 - rs = this.pstmtDeleteArticle3.executeUpdate();
56.719 -
56.720 - this.conn.commit();
56.721 - this.conn.setAutoCommit(true);
56.722 - }
56.723 - catch(SQLException ex)
56.724 - {
56.725 - throw new StorageBackendException(ex);
56.726 - }
56.727 - }
56.728 + int newArticleID = getMaxArticleID() + 1;
56.729
56.730 - @Override
56.731 - public Article getArticle(String messageID)
56.732 - throws StorageBackendException
56.733 - {
56.734 - ResultSet rs = null;
56.735 - try
56.736 - {
56.737 - pstmtGetArticle0.setString(1, messageID);
56.738 - rs = pstmtGetArticle0.executeQuery();
56.739 + // Fill prepared statement with values;
56.740 + // writes body to article table
56.741 + pstmtAddArticle1.setInt(1, newArticleID);
56.742 + pstmtAddArticle1.setBytes(2, article.getBody());
56.743 + pstmtAddArticle1.execute();
56.744
56.745 - if(!rs.next())
56.746 - {
56.747 - return null;
56.748 - }
56.749 - else
56.750 - {
56.751 - byte[] body = rs.getBytes("body");
56.752 - String headers = getArticleHeaders(rs.getInt("article_id"));
56.753 - return new Article(headers, body);
56.754 - }
56.755 - }
56.756 - catch(SQLException ex)
56.757 - {
56.758 - restartConnection(ex);
56.759 - return getArticle(messageID);
56.760 - }
56.761 - finally
56.762 - {
56.763 - if(rs != null)
56.764 - {
56.765 - try
56.766 - {
56.767 - rs.close();
56.768 - }
56.769 - catch(SQLException ex)
56.770 - {
56.771 - ex.printStackTrace();
56.772 - }
56.773 - restarts = 0; // Reset error count
56.774 - }
56.775 - }
56.776 - }
56.777 -
56.778 - /**
56.779 - * Retrieves an article by its ID.
56.780 - * @param articleID
56.781 - * @return
56.782 - * @throws StorageBackendException
56.783 - */
56.784 - @Override
56.785 - public Article getArticle(long articleIndex, long gid)
56.786 - throws StorageBackendException
56.787 - {
56.788 - ResultSet rs = null;
56.789 + // Add headers
56.790 + Enumeration headers = article.getAllHeaders();
56.791 + for (int n = 0; headers.hasMoreElements(); n++) {
56.792 + Header header = (Header) headers.nextElement();
56.793 + pstmtAddArticle2.setInt(1, newArticleID);
56.794 + pstmtAddArticle2.setString(2, header.getName().toLowerCase());
56.795 + pstmtAddArticle2.setString(3,
56.796 + header.getValue().replaceAll("[\r\n]", ""));
56.797 + pstmtAddArticle2.setInt(4, n);
56.798 + pstmtAddArticle2.execute();
56.799 + }
56.800
56.801 - try
56.802 - {
56.803 - this.pstmtGetArticle1.setLong(1, articleIndex);
56.804 - this.pstmtGetArticle1.setLong(2, gid);
56.805 + // For each newsgroup add a reference
56.806 + List<Group> groups = article.getGroups();
56.807 + for (Group group : groups) {
56.808 + pstmtAddArticle3.setLong(1, group.getInternalID());
56.809 + pstmtAddArticle3.setInt(2, newArticleID);
56.810 + pstmtAddArticle3.setLong(3, getMaxArticleIndex(group.getInternalID()) + 1);
56.811 + pstmtAddArticle3.execute();
56.812 + }
56.813
56.814 - rs = this.pstmtGetArticle1.executeQuery();
56.815 + // Write message-id to article_ids table
56.816 + this.pstmtAddArticle4.setInt(1, newArticleID);
56.817 + this.pstmtAddArticle4.setString(2, article.getMessageID());
56.818 + this.pstmtAddArticle4.execute();
56.819
56.820 - if(rs.next())
56.821 - {
56.822 - byte[] body = rs.getBytes("body");
56.823 - String headers = getArticleHeaders(rs.getInt("article_id"));
56.824 - return new Article(headers, body);
56.825 - }
56.826 - else
56.827 - {
56.828 - return null;
56.829 - }
56.830 - }
56.831 - catch(SQLException ex)
56.832 - {
56.833 - restartConnection(ex);
56.834 - return getArticle(articleIndex, gid);
56.835 - }
56.836 - finally
56.837 - {
56.838 - if(rs != null)
56.839 - {
56.840 - try
56.841 - {
56.842 - rs.close();
56.843 - }
56.844 - catch(SQLException ex)
56.845 - {
56.846 - ex.printStackTrace();
56.847 - }
56.848 - restarts = 0;
56.849 - }
56.850 - }
56.851 - }
56.852 + this.conn.commit();
56.853 + this.conn.setAutoCommit(true);
56.854
56.855 - /**
56.856 - * Searches for fitting header values using the given regular expression.
56.857 - * @param group
56.858 - * @param start
56.859 - * @param end
56.860 - * @param headerKey
56.861 - * @param pattern
56.862 - * @return
56.863 - * @throws StorageBackendException
56.864 - */
56.865 - @Override
56.866 - public List<Pair<Long, String>> getArticleHeaders(Channel group, long start,
56.867 - long end, String headerKey, String patStr)
56.868 - throws StorageBackendException, PatternSyntaxException
56.869 - {
56.870 - ResultSet rs = null;
56.871 - List<Pair<Long, String>> heads = new ArrayList<Pair<Long, String>>();
56.872 + this.restarts = 0; // Reset error count
56.873 + } catch (SQLException ex) {
56.874 + try {
56.875 + this.conn.rollback(); // Rollback changes
56.876 + } catch (SQLException ex2) {
56.877 + Log.get().severe("Rollback of addArticle() failed: " + ex2);
56.878 + }
56.879
56.880 - try
56.881 - {
56.882 - this.pstmtGetArticleHeaders1.setString(1, group.getName());
56.883 - this.pstmtGetArticleHeaders1.setString(2, headerKey);
56.884 - this.pstmtGetArticleHeaders1.setLong(3, start);
56.885 + try {
56.886 + this.conn.setAutoCommit(true); // and release locks
56.887 + } catch (SQLException ex2) {
56.888 + Log.get().severe("setAutoCommit(true) of addArticle() failed: " + ex2);
56.889 + }
56.890
56.891 - rs = this.pstmtGetArticleHeaders1.executeQuery();
56.892 + restartConnection(ex);
56.893 + addArticle(article);
56.894 + }
56.895 + }
56.896
56.897 - // Convert the "NNTP" regex to Java regex
56.898 - patStr = patStr.replace("*", ".*");
56.899 - Pattern pattern = Pattern.compile(patStr);
56.900 + /**
56.901 + * Adds a group to the JDBCDatabase. This method is not accessible via NNTP.
56.902 + * @param name
56.903 + * @throws java.sql.SQLException
56.904 + */
56.905 + @Override
56.906 + public void addGroup(String name, int flags)
56.907 + throws StorageBackendException
56.908 + {
56.909 + try {
56.910 + this.conn.setAutoCommit(false);
56.911 + pstmtAddGroup0.setString(1, name);
56.912 + pstmtAddGroup0.setInt(2, flags);
56.913
56.914 - while(rs.next())
56.915 - {
56.916 - Long articleIndex = rs.getLong(1);
56.917 - if(end < 0 || articleIndex <= end) // Match start is done via SQL
56.918 - {
56.919 - String headerValue = rs.getString(2);
56.920 - Matcher matcher = pattern.matcher(headerValue);
56.921 - if(matcher.matches())
56.922 - {
56.923 - heads.add(new Pair<Long, String>(articleIndex, headerValue));
56.924 - }
56.925 - }
56.926 - }
56.927 - }
56.928 - catch(SQLException ex)
56.929 - {
56.930 - restartConnection(ex);
56.931 - return getArticleHeaders(group, start, end, headerKey, patStr);
56.932 - }
56.933 - finally
56.934 - {
56.935 - if(rs != null)
56.936 - {
56.937 - try
56.938 - {
56.939 - rs.close();
56.940 - }
56.941 - catch(SQLException ex)
56.942 - {
56.943 - ex.printStackTrace();
56.944 - }
56.945 - }
56.946 - }
56.947 + pstmtAddGroup0.executeUpdate();
56.948 + this.conn.commit();
56.949 + this.conn.setAutoCommit(true);
56.950 + this.restarts = 0; // Reset error count
56.951 + } catch (SQLException ex) {
56.952 + try {
56.953 + this.conn.rollback();
56.954 + this.conn.setAutoCommit(true);
56.955 + } catch (SQLException ex2) {
56.956 + ex2.printStackTrace();
56.957 + }
56.958
56.959 - return heads;
56.960 - }
56.961 + restartConnection(ex);
56.962 + addGroup(name, flags);
56.963 + }
56.964 + }
56.965
56.966 - private String getArticleHeaders(long articleID)
56.967 - throws StorageBackendException
56.968 - {
56.969 - ResultSet rs = null;
56.970 -
56.971 - try
56.972 - {
56.973 - this.pstmtGetArticleHeaders0.setLong(1, articleID);
56.974 - rs = this.pstmtGetArticleHeaders0.executeQuery();
56.975 -
56.976 - StringBuilder buf = new StringBuilder();
56.977 - if(rs.next())
56.978 - {
56.979 - for(;;)
56.980 - {
56.981 - buf.append(rs.getString(1)); // key
56.982 - buf.append(": ");
56.983 - String foldedValue = MimeUtility.fold(0, rs.getString(2));
56.984 - buf.append(foldedValue); // value
56.985 - if(rs.next())
56.986 - {
56.987 - buf.append("\r\n");
56.988 - }
56.989 - else
56.990 - {
56.991 - break;
56.992 - }
56.993 - }
56.994 - }
56.995 -
56.996 - return buf.toString();
56.997 - }
56.998 - catch(SQLException ex)
56.999 - {
56.1000 - restartConnection(ex);
56.1001 - return getArticleHeaders(articleID);
56.1002 - }
56.1003 - finally
56.1004 - {
56.1005 - if(rs != null)
56.1006 - {
56.1007 - try
56.1008 - {
56.1009 - rs.close();
56.1010 - }
56.1011 - catch(SQLException ex)
56.1012 - {
56.1013 - ex.printStackTrace();
56.1014 - }
56.1015 - }
56.1016 - }
56.1017 - }
56.1018 + @Override
56.1019 + public void addEvent(long time, int type, long gid)
56.1020 + throws StorageBackendException
56.1021 + {
56.1022 + try {
56.1023 + this.conn.setAutoCommit(false);
56.1024 + this.pstmtAddEvent.setLong(1, time);
56.1025 + this.pstmtAddEvent.setInt(2, type);
56.1026 + this.pstmtAddEvent.setLong(3, gid);
56.1027 + this.pstmtAddEvent.executeUpdate();
56.1028 + this.conn.commit();
56.1029 + this.conn.setAutoCommit(true);
56.1030 + this.restarts = 0;
56.1031 + } catch (SQLException ex) {
56.1032 + try {
56.1033 + this.conn.rollback();
56.1034 + this.conn.setAutoCommit(true);
56.1035 + } catch (SQLException ex2) {
56.1036 + ex2.printStackTrace();
56.1037 + }
56.1038
56.1039 - @Override
56.1040 - public long getArticleIndex(Article article, Group group)
56.1041 - throws StorageBackendException
56.1042 - {
56.1043 - ResultSet rs = null;
56.1044 + restartConnection(ex);
56.1045 + addEvent(time, type, gid);
56.1046 + }
56.1047 + }
56.1048
56.1049 - try
56.1050 - {
56.1051 - this.pstmtGetArticleIndex.setString(1, article.getMessageID());
56.1052 - this.pstmtGetArticleIndex.setLong(2, group.getInternalID());
56.1053 -
56.1054 - rs = this.pstmtGetArticleIndex.executeQuery();
56.1055 - if(rs.next())
56.1056 - {
56.1057 - return rs.getLong(1);
56.1058 - }
56.1059 - else
56.1060 - {
56.1061 - return -1;
56.1062 - }
56.1063 - }
56.1064 - catch(SQLException ex)
56.1065 - {
56.1066 - restartConnection(ex);
56.1067 - return getArticleIndex(article, group);
56.1068 - }
56.1069 - finally
56.1070 - {
56.1071 - if(rs != null)
56.1072 - {
56.1073 - try
56.1074 - {
56.1075 - rs.close();
56.1076 - }
56.1077 - catch(SQLException ex)
56.1078 - {
56.1079 - ex.printStackTrace();
56.1080 - }
56.1081 - }
56.1082 - }
56.1083 - }
56.1084 -
56.1085 - /**
56.1086 - * Returns a list of Long/Article Pairs.
56.1087 - * @throws java.sql.SQLException
56.1088 - */
56.1089 - @Override
56.1090 - public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first,
56.1091 - long last)
56.1092 - throws StorageBackendException
56.1093 - {
56.1094 - ResultSet rs = null;
56.1095 + @Override
56.1096 + public int countArticles()
56.1097 + throws StorageBackendException
56.1098 + {
56.1099 + ResultSet rs = null;
56.1100
56.1101 - try
56.1102 - {
56.1103 - this.pstmtGetArticleHeads.setLong(1, group.getInternalID());
56.1104 - this.pstmtGetArticleHeads.setLong(2, first);
56.1105 - this.pstmtGetArticleHeads.setLong(3, last);
56.1106 - rs = pstmtGetArticleHeads.executeQuery();
56.1107 + try {
56.1108 + rs = this.pstmtCountArticles.executeQuery();
56.1109 + if (rs.next()) {
56.1110 + return rs.getInt(1);
56.1111 + } else {
56.1112 + return -1;
56.1113 + }
56.1114 + } catch (SQLException ex) {
56.1115 + restartConnection(ex);
56.1116 + return countArticles();
56.1117 + } finally {
56.1118 + if (rs != null) {
56.1119 + try {
56.1120 + rs.close();
56.1121 + } catch (SQLException ex) {
56.1122 + ex.printStackTrace();
56.1123 + }
56.1124 + restarts = 0;
56.1125 + }
56.1126 + }
56.1127 + }
56.1128
56.1129 - List<Pair<Long, ArticleHead>> articles
56.1130 - = new ArrayList<Pair<Long, ArticleHead>>();
56.1131 + @Override
56.1132 + public int countGroups()
56.1133 + throws StorageBackendException
56.1134 + {
56.1135 + ResultSet rs = null;
56.1136
56.1137 - while (rs.next())
56.1138 - {
56.1139 - long aid = rs.getLong("article_id");
56.1140 - long aidx = rs.getLong("article_index");
56.1141 - String headers = getArticleHeaders(aid);
56.1142 - articles.add(new Pair<Long, ArticleHead>(aidx,
56.1143 - new ArticleHead(headers)));
56.1144 - }
56.1145 + try {
56.1146 + rs = this.pstmtCountGroups.executeQuery();
56.1147 + if (rs.next()) {
56.1148 + return rs.getInt(1);
56.1149 + } else {
56.1150 + return -1;
56.1151 + }
56.1152 + } catch (SQLException ex) {
56.1153 + restartConnection(ex);
56.1154 + return countGroups();
56.1155 + } finally {
56.1156 + if (rs != null) {
56.1157 + try {
56.1158 + rs.close();
56.1159 + } catch (SQLException ex) {
56.1160 + ex.printStackTrace();
56.1161 + }
56.1162 + restarts = 0;
56.1163 + }
56.1164 + }
56.1165 + }
56.1166
56.1167 - return articles;
56.1168 - }
56.1169 - catch(SQLException ex)
56.1170 - {
56.1171 - restartConnection(ex);
56.1172 - return getArticleHeads(group, first, last);
56.1173 - }
56.1174 - finally
56.1175 - {
56.1176 - if(rs != null)
56.1177 - {
56.1178 - try
56.1179 - {
56.1180 - rs.close();
56.1181 - }
56.1182 - catch(SQLException ex)
56.1183 - {
56.1184 - ex.printStackTrace();
56.1185 - }
56.1186 - }
56.1187 - }
56.1188 - }
56.1189 + @Override
56.1190 + public void delete(final String messageID)
56.1191 + throws StorageBackendException
56.1192 + {
56.1193 + try {
56.1194 + this.conn.setAutoCommit(false);
56.1195
56.1196 - @Override
56.1197 - public List<Long> getArticleNumbers(long gid)
56.1198 - throws StorageBackendException
56.1199 - {
56.1200 - ResultSet rs = null;
56.1201 - try
56.1202 - {
56.1203 - List<Long> ids = new ArrayList<Long>();
56.1204 - this.pstmtGetArticleIDs.setLong(1, gid);
56.1205 - rs = this.pstmtGetArticleIDs.executeQuery();
56.1206 - while(rs.next())
56.1207 - {
56.1208 - ids.add(rs.getLong(1));
56.1209 - }
56.1210 - return ids;
56.1211 - }
56.1212 - catch(SQLException ex)
56.1213 - {
56.1214 - restartConnection(ex);
56.1215 - return getArticleNumbers(gid);
56.1216 - }
56.1217 - finally
56.1218 - {
56.1219 - if(rs != null)
56.1220 - {
56.1221 - try
56.1222 - {
56.1223 - rs.close();
56.1224 - restarts = 0; // Clear the restart count after successful request
56.1225 - }
56.1226 - catch(SQLException ex)
56.1227 - {
56.1228 - ex.printStackTrace();
56.1229 - }
56.1230 - }
56.1231 - }
56.1232 - }
56.1233 + this.pstmtDeleteArticle0.setString(1, messageID);
56.1234 + int rs = this.pstmtDeleteArticle0.executeUpdate();
56.1235
56.1236 - @Override
56.1237 - public String getConfigValue(String key)
56.1238 - throws StorageBackendException
56.1239 - {
56.1240 - ResultSet rs = null;
56.1241 - try
56.1242 - {
56.1243 - this.pstmtGetConfigValue.setString(1, key);
56.1244 + // We do not trust the ON DELETE CASCADE functionality to delete
56.1245 + // orphaned references...
56.1246 + this.pstmtDeleteArticle1.setString(1, messageID);
56.1247 + rs = this.pstmtDeleteArticle1.executeUpdate();
56.1248
56.1249 - rs = this.pstmtGetConfigValue.executeQuery();
56.1250 - if(rs.next())
56.1251 - {
56.1252 - return rs.getString(1); // First data on index 1 not 0
56.1253 - }
56.1254 - else
56.1255 - {
56.1256 - return null;
56.1257 - }
56.1258 - }
56.1259 - catch(SQLException ex)
56.1260 - {
56.1261 - restartConnection(ex);
56.1262 - return getConfigValue(key);
56.1263 - }
56.1264 - finally
56.1265 - {
56.1266 - if(rs != null)
56.1267 - {
56.1268 - try
56.1269 - {
56.1270 - rs.close();
56.1271 - }
56.1272 - catch(SQLException ex)
56.1273 - {
56.1274 - ex.printStackTrace();
56.1275 - }
56.1276 - restarts = 0; // Clear the restart count after successful request
56.1277 - }
56.1278 - }
56.1279 - }
56.1280 + this.pstmtDeleteArticle2.setString(1, messageID);
56.1281 + rs = this.pstmtDeleteArticle2.executeUpdate();
56.1282
56.1283 - @Override
56.1284 - public int getEventsCount(int type, long start, long end, Channel channel)
56.1285 - throws StorageBackendException
56.1286 - {
56.1287 - ResultSet rs = null;
56.1288 -
56.1289 - try
56.1290 - {
56.1291 - if(channel == null)
56.1292 - {
56.1293 - this.pstmtGetEventsCount0.setInt(1, type);
56.1294 - this.pstmtGetEventsCount0.setLong(2, start);
56.1295 - this.pstmtGetEventsCount0.setLong(3, end);
56.1296 - rs = this.pstmtGetEventsCount0.executeQuery();
56.1297 - }
56.1298 - else
56.1299 - {
56.1300 - this.pstmtGetEventsCount1.setInt(1, type);
56.1301 - this.pstmtGetEventsCount1.setLong(2, start);
56.1302 - this.pstmtGetEventsCount1.setLong(3, end);
56.1303 - this.pstmtGetEventsCount1.setLong(4, channel.getInternalID());
56.1304 - rs = this.pstmtGetEventsCount1.executeQuery();
56.1305 - }
56.1306 -
56.1307 - if(rs.next())
56.1308 - {
56.1309 - return rs.getInt(1);
56.1310 - }
56.1311 - else
56.1312 - {
56.1313 - return -1;
56.1314 - }
56.1315 - }
56.1316 - catch(SQLException ex)
56.1317 - {
56.1318 - restartConnection(ex);
56.1319 - return getEventsCount(type, start, end, channel);
56.1320 - }
56.1321 - finally
56.1322 - {
56.1323 - if(rs != null)
56.1324 - {
56.1325 - try
56.1326 - {
56.1327 - rs.close();
56.1328 - }
56.1329 - catch(SQLException ex)
56.1330 - {
56.1331 - ex.printStackTrace();
56.1332 - }
56.1333 - }
56.1334 - }
56.1335 - }
56.1336 -
56.1337 - /**
56.1338 - * Reads all Groups from the JDBCDatabase.
56.1339 - * @return
56.1340 - * @throws StorageBackendException
56.1341 - */
56.1342 - @Override
56.1343 - public List<Channel> getGroups()
56.1344 - throws StorageBackendException
56.1345 - {
56.1346 - ResultSet rs;
56.1347 - List<Channel> buffer = new ArrayList<Channel>();
56.1348 - Statement stmt = null;
56.1349 + this.pstmtDeleteArticle3.setString(1, messageID);
56.1350 + rs = this.pstmtDeleteArticle3.executeUpdate();
56.1351
56.1352 - try
56.1353 - {
56.1354 - stmt = conn.createStatement();
56.1355 - rs = stmt.executeQuery("SELECT * FROM groups ORDER BY name");
56.1356 + this.conn.commit();
56.1357 + this.conn.setAutoCommit(true);
56.1358 + } catch (SQLException ex) {
56.1359 + throw new StorageBackendException(ex);
56.1360 + }
56.1361 + }
56.1362
56.1363 - while(rs.next())
56.1364 - {
56.1365 - String name = rs.getString("name");
56.1366 - long id = rs.getLong("group_id");
56.1367 - int flags = rs.getInt("flags");
56.1368 -
56.1369 - Group group = new Group(name, id, flags);
56.1370 - buffer.add(group);
56.1371 - }
56.1372 + @Override
56.1373 + public Article getArticle(String messageID)
56.1374 + throws StorageBackendException
56.1375 + {
56.1376 + ResultSet rs = null;
56.1377 + try {
56.1378 + pstmtGetArticle0.setString(1, messageID);
56.1379 + rs = pstmtGetArticle0.executeQuery();
56.1380
56.1381 - return buffer;
56.1382 - }
56.1383 - catch(SQLException ex)
56.1384 - {
56.1385 - restartConnection(ex);
56.1386 - return getGroups();
56.1387 - }
56.1388 - finally
56.1389 - {
56.1390 - if(stmt != null)
56.1391 - {
56.1392 - try
56.1393 - {
56.1394 - stmt.close(); // Implicitely closes ResultSets
56.1395 - }
56.1396 - catch(SQLException ex)
56.1397 - {
56.1398 - ex.printStackTrace();
56.1399 - }
56.1400 - }
56.1401 - }
56.1402 - }
56.1403 + if (!rs.next()) {
56.1404 + return null;
56.1405 + } else {
56.1406 + byte[] body = rs.getBytes("body");
56.1407 + String headers = getArticleHeaders(rs.getInt("article_id"));
56.1408 + return new Article(headers, body);
56.1409 + }
56.1410 + } catch (SQLException ex) {
56.1411 + restartConnection(ex);
56.1412 + return getArticle(messageID);
56.1413 + } finally {
56.1414 + if (rs != null) {
56.1415 + try {
56.1416 + rs.close();
56.1417 + } catch (SQLException ex) {
56.1418 + ex.printStackTrace();
56.1419 + }
56.1420 + restarts = 0; // Reset error count
56.1421 + }
56.1422 + }
56.1423 + }
56.1424
56.1425 - @Override
56.1426 - public List<String> getGroupsForList(String listAddress)
56.1427 - throws StorageBackendException
56.1428 - {
56.1429 - ResultSet rs = null;
56.1430 -
56.1431 - try
56.1432 - {
56.1433 - this.pstmtGetGroupForList.setString(1, listAddress);
56.1434 + /**
56.1435 + * Retrieves an article by its ID.
56.1436 + * @param articleID
56.1437 + * @return
56.1438 + * @throws StorageBackendException
56.1439 + */
56.1440 + @Override
56.1441 + public Article getArticle(long articleIndex, long gid)
56.1442 + throws StorageBackendException
56.1443 + {
56.1444 + ResultSet rs = null;
56.1445
56.1446 - rs = this.pstmtGetGroupForList.executeQuery();
56.1447 - List<String> groups = new ArrayList<String>();
56.1448 - while(rs.next())
56.1449 - {
56.1450 - String group = rs.getString(1);
56.1451 - groups.add(group);
56.1452 - }
56.1453 - return groups;
56.1454 - }
56.1455 - catch(SQLException ex)
56.1456 - {
56.1457 - restartConnection(ex);
56.1458 - return getGroupsForList(listAddress);
56.1459 - }
56.1460 - finally
56.1461 - {
56.1462 - if(rs != null)
56.1463 - {
56.1464 - try
56.1465 - {
56.1466 - rs.close();
56.1467 - }
56.1468 - catch(SQLException ex)
56.1469 - {
56.1470 - ex.printStackTrace();
56.1471 - }
56.1472 - }
56.1473 - }
56.1474 - }
56.1475 -
56.1476 - /**
56.1477 - * Returns the Group that is identified by the name.
56.1478 - * @param name
56.1479 - * @return
56.1480 - * @throws StorageBackendException
56.1481 - */
56.1482 - @Override
56.1483 - public Group getGroup(String name)
56.1484 - throws StorageBackendException
56.1485 - {
56.1486 - ResultSet rs = null;
56.1487 -
56.1488 - try
56.1489 - {
56.1490 - this.pstmtGetGroup0.setString(1, name);
56.1491 - rs = this.pstmtGetGroup0.executeQuery();
56.1492 + try {
56.1493 + this.pstmtGetArticle1.setLong(1, articleIndex);
56.1494 + this.pstmtGetArticle1.setLong(2, gid);
56.1495
56.1496 - if (!rs.next())
56.1497 - {
56.1498 - return null;
56.1499 - }
56.1500 - else
56.1501 - {
56.1502 - long id = rs.getLong("group_id");
56.1503 - int flags = rs.getInt("flags");
56.1504 - return new Group(name, id, flags);
56.1505 - }
56.1506 - }
56.1507 - catch(SQLException ex)
56.1508 - {
56.1509 - restartConnection(ex);
56.1510 - return getGroup(name);
56.1511 - }
56.1512 - finally
56.1513 - {
56.1514 - if(rs != null)
56.1515 - {
56.1516 - try
56.1517 - {
56.1518 - rs.close();
56.1519 - }
56.1520 - catch(SQLException ex)
56.1521 - {
56.1522 - ex.printStackTrace();
56.1523 - }
56.1524 - }
56.1525 - }
56.1526 - }
56.1527 + rs = this.pstmtGetArticle1.executeQuery();
56.1528
56.1529 - @Override
56.1530 - public List<String> getListsForGroup(String group)
56.1531 - throws StorageBackendException
56.1532 - {
56.1533 - ResultSet rs = null;
56.1534 - List<String> lists = new ArrayList<String>();
56.1535 + if (rs.next()) {
56.1536 + byte[] body = rs.getBytes("body");
56.1537 + String headers = getArticleHeaders(rs.getInt("article_id"));
56.1538 + return new Article(headers, body);
56.1539 + } else {
56.1540 + return null;
56.1541 + }
56.1542 + } catch (SQLException ex) {
56.1543 + restartConnection(ex);
56.1544 + return getArticle(articleIndex, gid);
56.1545 + } finally {
56.1546 + if (rs != null) {
56.1547 + try {
56.1548 + rs.close();
56.1549 + } catch (SQLException ex) {
56.1550 + ex.printStackTrace();
56.1551 + }
56.1552 + restarts = 0;
56.1553 + }
56.1554 + }
56.1555 + }
56.1556
56.1557 - try
56.1558 - {
56.1559 - this.pstmtGetListForGroup.setString(1, group);
56.1560 - rs = this.pstmtGetListForGroup.executeQuery();
56.1561 + /**
56.1562 + * Searches for fitting header values using the given regular expression.
56.1563 + * @param group
56.1564 + * @param start
56.1565 + * @param end
56.1566 + * @param headerKey
56.1567 + * @param pattern
56.1568 + * @return
56.1569 + * @throws StorageBackendException
56.1570 + */
56.1571 + @Override
56.1572 + public List<Pair<Long, String>> getArticleHeaders(Channel group, long start,
56.1573 + long end, String headerKey, String patStr)
56.1574 + throws StorageBackendException, PatternSyntaxException
56.1575 + {
56.1576 + ResultSet rs = null;
56.1577 + List<Pair<Long, String>> heads = new ArrayList<Pair<Long, String>>();
56.1578
56.1579 - while(rs.next())
56.1580 - {
56.1581 - lists.add(rs.getString(1));
56.1582 - }
56.1583 - return lists;
56.1584 - }
56.1585 - catch(SQLException ex)
56.1586 - {
56.1587 - restartConnection(ex);
56.1588 - return getListsForGroup(group);
56.1589 - }
56.1590 - finally
56.1591 - {
56.1592 - if(rs != null)
56.1593 - {
56.1594 - try
56.1595 - {
56.1596 - rs.close();
56.1597 - }
56.1598 - catch(SQLException ex)
56.1599 - {
56.1600 - ex.printStackTrace();
56.1601 - }
56.1602 - }
56.1603 - }
56.1604 - }
56.1605 -
56.1606 - private int getMaxArticleIndex(long groupID)
56.1607 - throws StorageBackendException
56.1608 - {
56.1609 - ResultSet rs = null;
56.1610 + try {
56.1611 + this.pstmtGetArticleHeaders1.setString(1, group.getName());
56.1612 + this.pstmtGetArticleHeaders1.setString(2, headerKey);
56.1613 + this.pstmtGetArticleHeaders1.setLong(3, start);
56.1614
56.1615 - try
56.1616 - {
56.1617 - this.pstmtGetMaxArticleIndex.setLong(1, groupID);
56.1618 - rs = this.pstmtGetMaxArticleIndex.executeQuery();
56.1619 + rs = this.pstmtGetArticleHeaders1.executeQuery();
56.1620
56.1621 - int maxIndex = 0;
56.1622 - if (rs.next())
56.1623 - {
56.1624 - maxIndex = rs.getInt(1);
56.1625 - }
56.1626 + // Convert the "NNTP" regex to Java regex
56.1627 + patStr = patStr.replace("*", ".*");
56.1628 + Pattern pattern = Pattern.compile(patStr);
56.1629
56.1630 - return maxIndex;
56.1631 - }
56.1632 - catch(SQLException ex)
56.1633 - {
56.1634 - restartConnection(ex);
56.1635 - return getMaxArticleIndex(groupID);
56.1636 - }
56.1637 - finally
56.1638 - {
56.1639 - if(rs != null)
56.1640 - {
56.1641 - try
56.1642 - {
56.1643 - rs.close();
56.1644 - }
56.1645 - catch(SQLException ex)
56.1646 - {
56.1647 - ex.printStackTrace();
56.1648 - }
56.1649 - }
56.1650 - }
56.1651 - }
56.1652 -
56.1653 - private int getMaxArticleID()
56.1654 - throws StorageBackendException
56.1655 - {
56.1656 - ResultSet rs = null;
56.1657 + while (rs.next()) {
56.1658 + Long articleIndex = rs.getLong(1);
56.1659 + if (end < 0 || articleIndex <= end) // Match start is done via SQL
56.1660 + {
56.1661 + String headerValue = rs.getString(2);
56.1662 + Matcher matcher = pattern.matcher(headerValue);
56.1663 + if (matcher.matches()) {
56.1664 + heads.add(new Pair<Long, String>(articleIndex, headerValue));
56.1665 + }
56.1666 + }
56.1667 + }
56.1668 + } catch (SQLException ex) {
56.1669 + restartConnection(ex);
56.1670 + return getArticleHeaders(group, start, end, headerKey, patStr);
56.1671 + } finally {
56.1672 + if (rs != null) {
56.1673 + try {
56.1674 + rs.close();
56.1675 + } catch (SQLException ex) {
56.1676 + ex.printStackTrace();
56.1677 + }
56.1678 + }
56.1679 + }
56.1680
56.1681 - try
56.1682 - {
56.1683 - rs = this.pstmtGetMaxArticleID.executeQuery();
56.1684 + return heads;
56.1685 + }
56.1686
56.1687 - int maxIndex = 0;
56.1688 - if (rs.next())
56.1689 - {
56.1690 - maxIndex = rs.getInt(1);
56.1691 - }
56.1692 + private String getArticleHeaders(long articleID)
56.1693 + throws StorageBackendException
56.1694 + {
56.1695 + ResultSet rs = null;
56.1696
56.1697 - return maxIndex;
56.1698 - }
56.1699 - catch(SQLException ex)
56.1700 - {
56.1701 - restartConnection(ex);
56.1702 - return getMaxArticleID();
56.1703 - }
56.1704 - finally
56.1705 - {
56.1706 - if(rs != null)
56.1707 - {
56.1708 - try
56.1709 - {
56.1710 - rs.close();
56.1711 - }
56.1712 - catch(SQLException ex)
56.1713 - {
56.1714 - ex.printStackTrace();
56.1715 - }
56.1716 - }
56.1717 - }
56.1718 - }
56.1719 + try {
56.1720 + this.pstmtGetArticleHeaders0.setLong(1, articleID);
56.1721 + rs = this.pstmtGetArticleHeaders0.executeQuery();
56.1722
56.1723 - @Override
56.1724 - public int getLastArticleNumber(Group group)
56.1725 - throws StorageBackendException
56.1726 - {
56.1727 - ResultSet rs = null;
56.1728 + StringBuilder buf = new StringBuilder();
56.1729 + if (rs.next()) {
56.1730 + for (;;) {
56.1731 + buf.append(rs.getString(1)); // key
56.1732 + buf.append(": ");
56.1733 + String foldedValue = MimeUtility.fold(0, rs.getString(2));
56.1734 + buf.append(foldedValue); // value
56.1735 + if (rs.next()) {
56.1736 + buf.append("\r\n");
56.1737 + } else {
56.1738 + break;
56.1739 + }
56.1740 + }
56.1741 + }
56.1742
56.1743 - try
56.1744 - {
56.1745 - this.pstmtGetLastArticleNumber.setLong(1, group.getInternalID());
56.1746 - rs = this.pstmtGetLastArticleNumber.executeQuery();
56.1747 - if (rs.next())
56.1748 - {
56.1749 - return rs.getInt(1);
56.1750 - }
56.1751 - else
56.1752 - {
56.1753 - return 0;
56.1754 - }
56.1755 - }
56.1756 - catch(SQLException ex)
56.1757 - {
56.1758 - restartConnection(ex);
56.1759 - return getLastArticleNumber(group);
56.1760 - }
56.1761 - finally
56.1762 - {
56.1763 - if(rs != null)
56.1764 - {
56.1765 - try
56.1766 - {
56.1767 - rs.close();
56.1768 - }
56.1769 - catch(SQLException ex)
56.1770 - {
56.1771 - ex.printStackTrace();
56.1772 - }
56.1773 - }
56.1774 - }
56.1775 - }
56.1776 + return buf.toString();
56.1777 + } catch (SQLException ex) {
56.1778 + restartConnection(ex);
56.1779 + return getArticleHeaders(articleID);
56.1780 + } finally {
56.1781 + if (rs != null) {
56.1782 + try {
56.1783 + rs.close();
56.1784 + } catch (SQLException ex) {
56.1785 + ex.printStackTrace();
56.1786 + }
56.1787 + }
56.1788 + }
56.1789 + }
56.1790
56.1791 - @Override
56.1792 - public int getFirstArticleNumber(Group group)
56.1793 - throws StorageBackendException
56.1794 - {
56.1795 - ResultSet rs = null;
56.1796 - try
56.1797 - {
56.1798 - this.pstmtGetFirstArticleNumber.setLong(1, group.getInternalID());
56.1799 - rs = this.pstmtGetFirstArticleNumber.executeQuery();
56.1800 - if(rs.next())
56.1801 - {
56.1802 - return rs.getInt(1);
56.1803 - }
56.1804 - else
56.1805 - {
56.1806 - return 0;
56.1807 - }
56.1808 - }
56.1809 - catch(SQLException ex)
56.1810 - {
56.1811 - restartConnection(ex);
56.1812 - return getFirstArticleNumber(group);
56.1813 - }
56.1814 - finally
56.1815 - {
56.1816 - if(rs != null)
56.1817 - {
56.1818 - try
56.1819 - {
56.1820 - rs.close();
56.1821 - }
56.1822 - catch(SQLException ex)
56.1823 - {
56.1824 - ex.printStackTrace();
56.1825 - }
56.1826 - }
56.1827 - }
56.1828 - }
56.1829 -
56.1830 - /**
56.1831 - * Returns a group name identified by the given id.
56.1832 - * @param id
56.1833 - * @return
56.1834 - * @throws StorageBackendException
56.1835 - */
56.1836 - public String getGroup(int id)
56.1837 - throws StorageBackendException
56.1838 - {
56.1839 - ResultSet rs = null;
56.1840 + @Override
56.1841 + public long getArticleIndex(Article article, Group group)
56.1842 + throws StorageBackendException
56.1843 + {
56.1844 + ResultSet rs = null;
56.1845
56.1846 - try
56.1847 - {
56.1848 - this.pstmtGetGroup1.setInt(1, id);
56.1849 - rs = this.pstmtGetGroup1.executeQuery();
56.1850 + try {
56.1851 + this.pstmtGetArticleIndex.setString(1, article.getMessageID());
56.1852 + this.pstmtGetArticleIndex.setLong(2, group.getInternalID());
56.1853
56.1854 - if (rs.next())
56.1855 - {
56.1856 - return rs.getString(1);
56.1857 - }
56.1858 - else
56.1859 - {
56.1860 - return null;
56.1861 - }
56.1862 - }
56.1863 - catch(SQLException ex)
56.1864 - {
56.1865 - restartConnection(ex);
56.1866 - return getGroup(id);
56.1867 - }
56.1868 - finally
56.1869 - {
56.1870 - if(rs != null)
56.1871 - {
56.1872 - try
56.1873 - {
56.1874 - rs.close();
56.1875 - }
56.1876 - catch(SQLException ex)
56.1877 - {
56.1878 - ex.printStackTrace();
56.1879 - }
56.1880 - }
56.1881 - }
56.1882 - }
56.1883 + rs = this.pstmtGetArticleIndex.executeQuery();
56.1884 + if (rs.next()) {
56.1885 + return rs.getLong(1);
56.1886 + } else {
56.1887 + return -1;
56.1888 + }
56.1889 + } catch (SQLException ex) {
56.1890 + restartConnection(ex);
56.1891 + return getArticleIndex(article, group);
56.1892 + } finally {
56.1893 + if (rs != null) {
56.1894 + try {
56.1895 + rs.close();
56.1896 + } catch (SQLException ex) {
56.1897 + ex.printStackTrace();
56.1898 + }
56.1899 + }
56.1900 + }
56.1901 + }
56.1902
56.1903 - @Override
56.1904 - public double getEventsPerHour(int key, long gid)
56.1905 - throws StorageBackendException
56.1906 - {
56.1907 - String gidquery = "";
56.1908 - if(gid >= 0)
56.1909 - {
56.1910 - gidquery = " AND group_id = " + gid;
56.1911 - }
56.1912 -
56.1913 - Statement stmt = null;
56.1914 - ResultSet rs = null;
56.1915 -
56.1916 - try
56.1917 - {
56.1918 - stmt = this.conn.createStatement();
56.1919 - rs = stmt.executeQuery("SELECT Count(*) / (Max(event_time) - Min(event_time))" +
56.1920 - " * 1000 * 60 * 60 FROM events WHERE event_key = " + key + gidquery);
56.1921 -
56.1922 - if(rs.next())
56.1923 - {
56.1924 - restarts = 0; // reset error count
56.1925 - return rs.getDouble(1);
56.1926 - }
56.1927 - else
56.1928 - {
56.1929 - return Double.NaN;
56.1930 - }
56.1931 - }
56.1932 - catch(SQLException ex)
56.1933 - {
56.1934 - restartConnection(ex);
56.1935 - return getEventsPerHour(key, gid);
56.1936 - }
56.1937 - finally
56.1938 - {
56.1939 - try
56.1940 - {
56.1941 - if(stmt != null)
56.1942 - {
56.1943 - stmt.close(); // Implicitely closes the result sets
56.1944 - }
56.1945 - }
56.1946 - catch(SQLException ex)
56.1947 - {
56.1948 - ex.printStackTrace();
56.1949 - }
56.1950 - }
56.1951 - }
56.1952 + /**
56.1953 + * Returns a list of Long/Article Pairs.
56.1954 + * @throws java.sql.SQLException
56.1955 + */
56.1956 + @Override
56.1957 + public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first,
56.1958 + long last)
56.1959 + throws StorageBackendException
56.1960 + {
56.1961 + ResultSet rs = null;
56.1962
56.1963 - @Override
56.1964 - public String getOldestArticle()
56.1965 - throws StorageBackendException
56.1966 - {
56.1967 - ResultSet rs = null;
56.1968 + try {
56.1969 + this.pstmtGetArticleHeads.setLong(1, group.getInternalID());
56.1970 + this.pstmtGetArticleHeads.setLong(2, first);
56.1971 + this.pstmtGetArticleHeads.setLong(3, last);
56.1972 + rs = pstmtGetArticleHeads.executeQuery();
56.1973
56.1974 - try
56.1975 - {
56.1976 - rs = this.pstmtGetOldestArticle.executeQuery();
56.1977 - if(rs.next())
56.1978 - {
56.1979 - return rs.getString(1);
56.1980 - }
56.1981 - else
56.1982 - {
56.1983 - return null;
56.1984 - }
56.1985 - }
56.1986 - catch(SQLException ex)
56.1987 - {
56.1988 - restartConnection(ex);
56.1989 - return getOldestArticle();
56.1990 - }
56.1991 - finally
56.1992 - {
56.1993 - if(rs != null)
56.1994 - {
56.1995 - try
56.1996 - {
56.1997 - rs.close();
56.1998 - }
56.1999 - catch(SQLException ex)
56.2000 - {
56.2001 - ex.printStackTrace();
56.2002 - }
56.2003 - }
56.2004 - }
56.2005 - }
56.2006 + List<Pair<Long, ArticleHead>> articles = new ArrayList<Pair<Long, ArticleHead>>();
56.2007
56.2008 - @Override
56.2009 - public int getPostingsCount(String groupname)
56.2010 - throws StorageBackendException
56.2011 - {
56.2012 - ResultSet rs = null;
56.2013 -
56.2014 - try
56.2015 - {
56.2016 - this.pstmtGetPostingsCount.setString(1, groupname);
56.2017 - rs = this.pstmtGetPostingsCount.executeQuery();
56.2018 - if(rs.next())
56.2019 - {
56.2020 - return rs.getInt(1);
56.2021 - }
56.2022 - else
56.2023 - {
56.2024 - Log.get().warning("Count on postings return nothing!");
56.2025 - return 0;
56.2026 - }
56.2027 - }
56.2028 - catch(SQLException ex)
56.2029 - {
56.2030 - restartConnection(ex);
56.2031 - return getPostingsCount(groupname);
56.2032 - }
56.2033 - finally
56.2034 - {
56.2035 - if(rs != null)
56.2036 - {
56.2037 - try
56.2038 - {
56.2039 - rs.close();
56.2040 - }
56.2041 - catch(SQLException ex)
56.2042 - {
56.2043 - ex.printStackTrace();
56.2044 - }
56.2045 - }
56.2046 - }
56.2047 - }
56.2048 + while (rs.next()) {
56.2049 + long aid = rs.getLong("article_id");
56.2050 + long aidx = rs.getLong("article_index");
56.2051 + String headers = getArticleHeaders(aid);
56.2052 + articles.add(new Pair<Long, ArticleHead>(aidx,
56.2053 + new ArticleHead(headers)));
56.2054 + }
56.2055
56.2056 - @Override
56.2057 - public List<Subscription> getSubscriptions(int feedtype)
56.2058 - throws StorageBackendException
56.2059 - {
56.2060 - ResultSet rs = null;
56.2061 -
56.2062 - try
56.2063 - {
56.2064 - List<Subscription> subs = new ArrayList<Subscription>();
56.2065 - this.pstmtGetSubscriptions.setInt(1, feedtype);
56.2066 - rs = this.pstmtGetSubscriptions.executeQuery();
56.2067 -
56.2068 - while(rs.next())
56.2069 - {
56.2070 - String host = rs.getString("host");
56.2071 - String group = rs.getString("name");
56.2072 - int port = rs.getInt("port");
56.2073 - subs.add(new Subscription(host, port, feedtype, group));
56.2074 - }
56.2075 -
56.2076 - return subs;
56.2077 - }
56.2078 - catch(SQLException ex)
56.2079 - {
56.2080 - restartConnection(ex);
56.2081 - return getSubscriptions(feedtype);
56.2082 - }
56.2083 - finally
56.2084 - {
56.2085 - if(rs != null)
56.2086 - {
56.2087 - try
56.2088 - {
56.2089 - rs.close();
56.2090 - }
56.2091 - catch(SQLException ex)
56.2092 - {
56.2093 - ex.printStackTrace();
56.2094 - }
56.2095 - }
56.2096 - }
56.2097 - }
56.2098 + return articles;
56.2099 + } catch (SQLException ex) {
56.2100 + restartConnection(ex);
56.2101 + return getArticleHeads(group, first, last);
56.2102 + } finally {
56.2103 + if (rs != null) {
56.2104 + try {
56.2105 + rs.close();
56.2106 + } catch (SQLException ex) {
56.2107 + ex.printStackTrace();
56.2108 + }
56.2109 + }
56.2110 + }
56.2111 + }
56.2112
56.2113 - /**
56.2114 - * Checks if there is an article with the given messageid in the JDBCDatabase.
56.2115 - * @param name
56.2116 - * @return
56.2117 - * @throws StorageBackendException
56.2118 - */
56.2119 - @Override
56.2120 - public boolean isArticleExisting(String messageID)
56.2121 - throws StorageBackendException
56.2122 - {
56.2123 - ResultSet rs = null;
56.2124 -
56.2125 - try
56.2126 - {
56.2127 - this.pstmtIsArticleExisting.setString(1, messageID);
56.2128 - rs = this.pstmtIsArticleExisting.executeQuery();
56.2129 - return rs.next() && rs.getInt(1) == 1;
56.2130 - }
56.2131 - catch(SQLException ex)
56.2132 - {
56.2133 - restartConnection(ex);
56.2134 - return isArticleExisting(messageID);
56.2135 - }
56.2136 - finally
56.2137 - {
56.2138 - if(rs != null)
56.2139 - {
56.2140 - try
56.2141 - {
56.2142 - rs.close();
56.2143 - }
56.2144 - catch(SQLException ex)
56.2145 - {
56.2146 - ex.printStackTrace();
56.2147 - }
56.2148 - }
56.2149 - }
56.2150 - }
56.2151 -
56.2152 - /**
56.2153 - * Checks if there is a group with the given name in the JDBCDatabase.
56.2154 - * @param name
56.2155 - * @return
56.2156 - * @throws StorageBackendException
56.2157 - */
56.2158 - @Override
56.2159 - public boolean isGroupExisting(String name)
56.2160 - throws StorageBackendException
56.2161 - {
56.2162 - ResultSet rs = null;
56.2163 -
56.2164 - try
56.2165 - {
56.2166 - this.pstmtIsGroupExisting.setString(1, name);
56.2167 - rs = this.pstmtIsGroupExisting.executeQuery();
56.2168 - return rs.next();
56.2169 - }
56.2170 - catch(SQLException ex)
56.2171 - {
56.2172 - restartConnection(ex);
56.2173 - return isGroupExisting(name);
56.2174 - }
56.2175 - finally
56.2176 - {
56.2177 - if(rs != null)
56.2178 - {
56.2179 - try
56.2180 - {
56.2181 - rs.close();
56.2182 - }
56.2183 - catch(SQLException ex)
56.2184 - {
56.2185 - ex.printStackTrace();
56.2186 - }
56.2187 - }
56.2188 - }
56.2189 - }
56.2190 + @Override
56.2191 + public List<Long> getArticleNumbers(long gid)
56.2192 + throws StorageBackendException
56.2193 + {
56.2194 + ResultSet rs = null;
56.2195 + try {
56.2196 + List<Long> ids = new ArrayList<Long>();
56.2197 + this.pstmtGetArticleIDs.setLong(1, gid);
56.2198 + rs = this.pstmtGetArticleIDs.executeQuery();
56.2199 + while (rs.next()) {
56.2200 + ids.add(rs.getLong(1));
56.2201 + }
56.2202 + return ids;
56.2203 + } catch (SQLException ex) {
56.2204 + restartConnection(ex);
56.2205 + return getArticleNumbers(gid);
56.2206 + } finally {
56.2207 + if (rs != null) {
56.2208 + try {
56.2209 + rs.close();
56.2210 + restarts = 0; // Clear the restart count after successful request
56.2211 + } catch (SQLException ex) {
56.2212 + ex.printStackTrace();
56.2213 + }
56.2214 + }
56.2215 + }
56.2216 + }
56.2217
56.2218 - @Override
56.2219 - public void setConfigValue(String key, String value)
56.2220 - throws StorageBackendException
56.2221 - {
56.2222 - try
56.2223 - {
56.2224 - conn.setAutoCommit(false);
56.2225 - this.pstmtSetConfigValue0.setString(1, key);
56.2226 - this.pstmtSetConfigValue0.execute();
56.2227 - this.pstmtSetConfigValue1.setString(1, key);
56.2228 - this.pstmtSetConfigValue1.setString(2, value);
56.2229 - this.pstmtSetConfigValue1.execute();
56.2230 - conn.commit();
56.2231 - conn.setAutoCommit(true);
56.2232 - }
56.2233 - catch(SQLException ex)
56.2234 - {
56.2235 - restartConnection(ex);
56.2236 - setConfigValue(key, value);
56.2237 - }
56.2238 - }
56.2239 -
56.2240 - /**
56.2241 - * Closes the JDBCDatabase connection.
56.2242 - */
56.2243 - public void shutdown()
56.2244 - throws StorageBackendException
56.2245 - {
56.2246 - try
56.2247 - {
56.2248 - if(this.conn != null)
56.2249 - {
56.2250 - this.conn.close();
56.2251 - }
56.2252 - }
56.2253 - catch(SQLException ex)
56.2254 - {
56.2255 - throw new StorageBackendException(ex);
56.2256 - }
56.2257 - }
56.2258 + @Override
56.2259 + public String getConfigValue(String key)
56.2260 + throws StorageBackendException
56.2261 + {
56.2262 + ResultSet rs = null;
56.2263 + try {
56.2264 + this.pstmtGetConfigValue.setString(1, key);
56.2265
56.2266 - @Override
56.2267 - public void purgeGroup(Group group)
56.2268 - throws StorageBackendException
56.2269 - {
56.2270 - try
56.2271 - {
56.2272 - this.pstmtPurgeGroup0.setLong(1, group.getInternalID());
56.2273 - this.pstmtPurgeGroup0.executeUpdate();
56.2274 + rs = this.pstmtGetConfigValue.executeQuery();
56.2275 + if (rs.next()) {
56.2276 + return rs.getString(1); // First data on index 1 not 0
56.2277 + } else {
56.2278 + return null;
56.2279 + }
56.2280 + } catch (SQLException ex) {
56.2281 + restartConnection(ex);
56.2282 + return getConfigValue(key);
56.2283 + } finally {
56.2284 + if (rs != null) {
56.2285 + try {
56.2286 + rs.close();
56.2287 + } catch (SQLException ex) {
56.2288 + ex.printStackTrace();
56.2289 + }
56.2290 + restarts = 0; // Clear the restart count after successful request
56.2291 + }
56.2292 + }
56.2293 + }
56.2294
56.2295 - this.pstmtPurgeGroup1.setLong(1, group.getInternalID());
56.2296 - this.pstmtPurgeGroup1.executeUpdate();
56.2297 - }
56.2298 - catch(SQLException ex)
56.2299 - {
56.2300 - restartConnection(ex);
56.2301 - purgeGroup(group);
56.2302 - }
56.2303 - }
56.2304 -
56.2305 - private void restartConnection(SQLException cause)
56.2306 - throws StorageBackendException
56.2307 - {
56.2308 - restarts++;
56.2309 - Log.get().severe(Thread.currentThread()
56.2310 - + ": Database connection was closed (restart " + restarts + ").");
56.2311 -
56.2312 - if(restarts >= MAX_RESTARTS)
56.2313 - {
56.2314 - // Delete the current, probably broken JDBCDatabase instance.
56.2315 - // So no one can use the instance any more.
56.2316 - JDBCDatabaseProvider.instances.remove(Thread.currentThread());
56.2317 -
56.2318 - // Throw the exception upwards
56.2319 - throw new StorageBackendException(cause);
56.2320 - }
56.2321 -
56.2322 - try
56.2323 - {
56.2324 - Thread.sleep(1500L * restarts);
56.2325 - }
56.2326 - catch(InterruptedException ex)
56.2327 - {
56.2328 - Log.get().warning("Interrupted: " + ex.getMessage());
56.2329 - }
56.2330 -
56.2331 - // Try to properly close the old database connection
56.2332 - try
56.2333 - {
56.2334 - if(this.conn != null)
56.2335 - {
56.2336 - this.conn.close();
56.2337 - }
56.2338 - }
56.2339 - catch(SQLException ex)
56.2340 - {
56.2341 - Log.get().warning(ex.getMessage());
56.2342 - }
56.2343 -
56.2344 - try
56.2345 - {
56.2346 - // Try to reinitialize database connection
56.2347 - arise();
56.2348 - }
56.2349 - catch(SQLException ex)
56.2350 - {
56.2351 - Log.get().warning(ex.getMessage());
56.2352 - restartConnection(ex);
56.2353 - }
56.2354 - }
56.2355 + @Override
56.2356 + public int getEventsCount(int type, long start, long end, Channel channel)
56.2357 + throws StorageBackendException
56.2358 + {
56.2359 + ResultSet rs = null;
56.2360
56.2361 - @Override
56.2362 - public boolean update(Article article)
56.2363 - throws StorageBackendException
56.2364 - {
56.2365 - // DELETE FROM headers WHERE article_id = ?
56.2366 + try {
56.2367 + if (channel == null) {
56.2368 + this.pstmtGetEventsCount0.setInt(1, type);
56.2369 + this.pstmtGetEventsCount0.setLong(2, start);
56.2370 + this.pstmtGetEventsCount0.setLong(3, end);
56.2371 + rs = this.pstmtGetEventsCount0.executeQuery();
56.2372 + } else {
56.2373 + this.pstmtGetEventsCount1.setInt(1, type);
56.2374 + this.pstmtGetEventsCount1.setLong(2, start);
56.2375 + this.pstmtGetEventsCount1.setLong(3, end);
56.2376 + this.pstmtGetEventsCount1.setLong(4, channel.getInternalID());
56.2377 + rs = this.pstmtGetEventsCount1.executeQuery();
56.2378 + }
56.2379
56.2380 - // INSERT INTO headers ...
56.2381 + if (rs.next()) {
56.2382 + return rs.getInt(1);
56.2383 + } else {
56.2384 + return -1;
56.2385 + }
56.2386 + } catch (SQLException ex) {
56.2387 + restartConnection(ex);
56.2388 + return getEventsCount(type, start, end, channel);
56.2389 + } finally {
56.2390 + if (rs != null) {
56.2391 + try {
56.2392 + rs.close();
56.2393 + } catch (SQLException ex) {
56.2394 + ex.printStackTrace();
56.2395 + }
56.2396 + }
56.2397 + }
56.2398 + }
56.2399
56.2400 - // SELECT * FROM postings WHERE article_id = ? AND group_id = ?
56.2401 - return false;
56.2402 - }
56.2403 + /**
56.2404 + * Reads all Groups from the JDBCDatabase.
56.2405 + * @return
56.2406 + * @throws StorageBackendException
56.2407 + */
56.2408 + @Override
56.2409 + public List<Channel> getGroups()
56.2410 + throws StorageBackendException
56.2411 + {
56.2412 + ResultSet rs;
56.2413 + List<Channel> buffer = new ArrayList<Channel>();
56.2414 + Statement stmt = null;
56.2415
56.2416 - /**
56.2417 - * Writes the flags and the name of the given group to the database.
56.2418 - * @param group
56.2419 - * @throws StorageBackendException
56.2420 - */
56.2421 - @Override
56.2422 - public boolean update(Group group)
56.2423 - throws StorageBackendException
56.2424 - {
56.2425 - try
56.2426 - {
56.2427 - this.pstmtUpdateGroup.setInt(1, group.getFlags());
56.2428 - this.pstmtUpdateGroup.setString(2, group.getName());
56.2429 - this.pstmtUpdateGroup.setLong(3, group.getInternalID());
56.2430 - int rs = this.pstmtUpdateGroup.executeUpdate();
56.2431 - return rs == 1;
56.2432 - }
56.2433 - catch(SQLException ex)
56.2434 - {
56.2435 - restartConnection(ex);
56.2436 - return update(group);
56.2437 - }
56.2438 - }
56.2439 + try {
56.2440 + stmt = conn.createStatement();
56.2441 + rs = stmt.executeQuery("SELECT * FROM groups ORDER BY name");
56.2442
56.2443 + while (rs.next()) {
56.2444 + String name = rs.getString("name");
56.2445 + long id = rs.getLong("group_id");
56.2446 + int flags = rs.getInt("flags");
56.2447 +
56.2448 + Group group = new Group(name, id, flags);
56.2449 + buffer.add(group);
56.2450 + }
56.2451 +
56.2452 + return buffer;
56.2453 + } catch (SQLException ex) {
56.2454 + restartConnection(ex);
56.2455 + return getGroups();
56.2456 + } finally {
56.2457 + if (stmt != null) {
56.2458 + try {
56.2459 + stmt.close(); // Implicitely closes ResultSets
56.2460 + } catch (SQLException ex) {
56.2461 + ex.printStackTrace();
56.2462 + }
56.2463 + }
56.2464 + }
56.2465 + }
56.2466 +
56.2467 + @Override
56.2468 + public List<String> getGroupsForList(String listAddress)
56.2469 + throws StorageBackendException
56.2470 + {
56.2471 + ResultSet rs = null;
56.2472 +
56.2473 + try {
56.2474 + this.pstmtGetGroupForList.setString(1, listAddress);
56.2475 +
56.2476 + rs = this.pstmtGetGroupForList.executeQuery();
56.2477 + List<String> groups = new ArrayList<String>();
56.2478 + while (rs.next()) {
56.2479 + String group = rs.getString(1);
56.2480 + groups.add(group);
56.2481 + }
56.2482 + return groups;
56.2483 + } catch (SQLException ex) {
56.2484 + restartConnection(ex);
56.2485 + return getGroupsForList(listAddress);
56.2486 + } finally {
56.2487 + if (rs != null) {
56.2488 + try {
56.2489 + rs.close();
56.2490 + } catch (SQLException ex) {
56.2491 + ex.printStackTrace();
56.2492 + }
56.2493 + }
56.2494 + }
56.2495 + }
56.2496 +
56.2497 + /**
56.2498 + * Returns the Group that is identified by the name.
56.2499 + * @param name
56.2500 + * @return
56.2501 + * @throws StorageBackendException
56.2502 + */
56.2503 + @Override
56.2504 + public Group getGroup(String name)
56.2505 + throws StorageBackendException
56.2506 + {
56.2507 + ResultSet rs = null;
56.2508 +
56.2509 + try {
56.2510 + this.pstmtGetGroup0.setString(1, name);
56.2511 + rs = this.pstmtGetGroup0.executeQuery();
56.2512 +
56.2513 + if (!rs.next()) {
56.2514 + return null;
56.2515 + } else {
56.2516 + long id = rs.getLong("group_id");
56.2517 + int flags = rs.getInt("flags");
56.2518 + return new Group(name, id, flags);
56.2519 + }
56.2520 + } catch (SQLException ex) {
56.2521 + restartConnection(ex);
56.2522 + return getGroup(name);
56.2523 + } finally {
56.2524 + if (rs != null) {
56.2525 + try {
56.2526 + rs.close();
56.2527 + } catch (SQLException ex) {
56.2528 + ex.printStackTrace();
56.2529 + }
56.2530 + }
56.2531 + }
56.2532 + }
56.2533 +
56.2534 + @Override
56.2535 + public List<String> getListsForGroup(String group)
56.2536 + throws StorageBackendException
56.2537 + {
56.2538 + ResultSet rs = null;
56.2539 + List<String> lists = new ArrayList<String>();
56.2540 +
56.2541 + try {
56.2542 + this.pstmtGetListForGroup.setString(1, group);
56.2543 + rs = this.pstmtGetListForGroup.executeQuery();
56.2544 +
56.2545 + while (rs.next()) {
56.2546 + lists.add(rs.getString(1));
56.2547 + }
56.2548 + return lists;
56.2549 + } catch (SQLException ex) {
56.2550 + restartConnection(ex);
56.2551 + return getListsForGroup(group);
56.2552 + } finally {
56.2553 + if (rs != null) {
56.2554 + try {
56.2555 + rs.close();
56.2556 + } catch (SQLException ex) {
56.2557 + ex.printStackTrace();
56.2558 + }
56.2559 + }
56.2560 + }
56.2561 + }
56.2562 +
56.2563 + private int getMaxArticleIndex(long groupID)
56.2564 + throws StorageBackendException
56.2565 + {
56.2566 + ResultSet rs = null;
56.2567 +
56.2568 + try {
56.2569 + this.pstmtGetMaxArticleIndex.setLong(1, groupID);
56.2570 + rs = this.pstmtGetMaxArticleIndex.executeQuery();
56.2571 +
56.2572 + int maxIndex = 0;
56.2573 + if (rs.next()) {
56.2574 + maxIndex = rs.getInt(1);
56.2575 + }
56.2576 +
56.2577 + return maxIndex;
56.2578 + } catch (SQLException ex) {
56.2579 + restartConnection(ex);
56.2580 + return getMaxArticleIndex(groupID);
56.2581 + } finally {
56.2582 + if (rs != null) {
56.2583 + try {
56.2584 + rs.close();
56.2585 + } catch (SQLException ex) {
56.2586 + ex.printStackTrace();
56.2587 + }
56.2588 + }
56.2589 + }
56.2590 + }
56.2591 +
56.2592 + private int getMaxArticleID()
56.2593 + throws StorageBackendException
56.2594 + {
56.2595 + ResultSet rs = null;
56.2596 +
56.2597 + try {
56.2598 + rs = this.pstmtGetMaxArticleID.executeQuery();
56.2599 +
56.2600 + int maxIndex = 0;
56.2601 + if (rs.next()) {
56.2602 + maxIndex = rs.getInt(1);
56.2603 + }
56.2604 +
56.2605 + return maxIndex;
56.2606 + } catch (SQLException ex) {
56.2607 + restartConnection(ex);
56.2608 + return getMaxArticleID();
56.2609 + } finally {
56.2610 + if (rs != null) {
56.2611 + try {
56.2612 + rs.close();
56.2613 + } catch (SQLException ex) {
56.2614 + ex.printStackTrace();
56.2615 + }
56.2616 + }
56.2617 + }
56.2618 + }
56.2619 +
56.2620 + @Override
56.2621 + public int getLastArticleNumber(Group group)
56.2622 + throws StorageBackendException
56.2623 + {
56.2624 + ResultSet rs = null;
56.2625 +
56.2626 + try {
56.2627 + this.pstmtGetLastArticleNumber.setLong(1, group.getInternalID());
56.2628 + rs = this.pstmtGetLastArticleNumber.executeQuery();
56.2629 + if (rs.next()) {
56.2630 + return rs.getInt(1);
56.2631 + } else {
56.2632 + return 0;
56.2633 + }
56.2634 + } catch (SQLException ex) {
56.2635 + restartConnection(ex);
56.2636 + return getLastArticleNumber(group);
56.2637 + } finally {
56.2638 + if (rs != null) {
56.2639 + try {
56.2640 + rs.close();
56.2641 + } catch (SQLException ex) {
56.2642 + ex.printStackTrace();
56.2643 + }
56.2644 + }
56.2645 + }
56.2646 + }
56.2647 +
56.2648 + @Override
56.2649 + public int getFirstArticleNumber(Group group)
56.2650 + throws StorageBackendException
56.2651 + {
56.2652 + ResultSet rs = null;
56.2653 + try {
56.2654 + this.pstmtGetFirstArticleNumber.setLong(1, group.getInternalID());
56.2655 + rs = this.pstmtGetFirstArticleNumber.executeQuery();
56.2656 + if (rs.next()) {
56.2657 + return rs.getInt(1);
56.2658 + } else {
56.2659 + return 0;
56.2660 + }
56.2661 + } catch (SQLException ex) {
56.2662 + restartConnection(ex);
56.2663 + return getFirstArticleNumber(group);
56.2664 + } finally {
56.2665 + if (rs != null) {
56.2666 + try {
56.2667 + rs.close();
56.2668 + } catch (SQLException ex) {
56.2669 + ex.printStackTrace();
56.2670 + }
56.2671 + }
56.2672 + }
56.2673 + }
56.2674 +
56.2675 + /**
56.2676 + * Returns a group name identified by the given id.
56.2677 + * @param id
56.2678 + * @return
56.2679 + * @throws StorageBackendException
56.2680 + */
56.2681 + public String getGroup(int id)
56.2682 + throws StorageBackendException
56.2683 + {
56.2684 + ResultSet rs = null;
56.2685 +
56.2686 + try {
56.2687 + this.pstmtGetGroup1.setInt(1, id);
56.2688 + rs = this.pstmtGetGroup1.executeQuery();
56.2689 +
56.2690 + if (rs.next()) {
56.2691 + return rs.getString(1);
56.2692 + } else {
56.2693 + return null;
56.2694 + }
56.2695 + } catch (SQLException ex) {
56.2696 + restartConnection(ex);
56.2697 + return getGroup(id);
56.2698 + } finally {
56.2699 + if (rs != null) {
56.2700 + try {
56.2701 + rs.close();
56.2702 + } catch (SQLException ex) {
56.2703 + ex.printStackTrace();
56.2704 + }
56.2705 + }
56.2706 + }
56.2707 + }
56.2708 +
56.2709 + @Override
56.2710 + public double getEventsPerHour(int key, long gid)
56.2711 + throws StorageBackendException
56.2712 + {
56.2713 + String gidquery = "";
56.2714 + if (gid >= 0) {
56.2715 + gidquery = " AND group_id = " + gid;
56.2716 + }
56.2717 +
56.2718 + Statement stmt = null;
56.2719 + ResultSet rs = null;
56.2720 +
56.2721 + try {
56.2722 + stmt = this.conn.createStatement();
56.2723 + rs = stmt.executeQuery("SELECT Count(*) / (Max(event_time) - Min(event_time))"
56.2724 + + " * 1000 * 60 * 60 FROM events WHERE event_key = " + key + gidquery);
56.2725 +
56.2726 + if (rs.next()) {
56.2727 + restarts = 0; // reset error count
56.2728 + return rs.getDouble(1);
56.2729 + } else {
56.2730 + return Double.NaN;
56.2731 + }
56.2732 + } catch (SQLException ex) {
56.2733 + restartConnection(ex);
56.2734 + return getEventsPerHour(key, gid);
56.2735 + } finally {
56.2736 + try {
56.2737 + if (stmt != null) {
56.2738 + stmt.close(); // Implicitely closes the result sets
56.2739 + }
56.2740 + } catch (SQLException ex) {
56.2741 + ex.printStackTrace();
56.2742 + }
56.2743 + }
56.2744 + }
56.2745 +
56.2746 + @Override
56.2747 + public String getOldestArticle()
56.2748 + throws StorageBackendException
56.2749 + {
56.2750 + ResultSet rs = null;
56.2751 +
56.2752 + try {
56.2753 + rs = this.pstmtGetOldestArticle.executeQuery();
56.2754 + if (rs.next()) {
56.2755 + return rs.getString(1);
56.2756 + } else {
56.2757 + return null;
56.2758 + }
56.2759 + } catch (SQLException ex) {
56.2760 + restartConnection(ex);
56.2761 + return getOldestArticle();
56.2762 + } finally {
56.2763 + if (rs != null) {
56.2764 + try {
56.2765 + rs.close();
56.2766 + } catch (SQLException ex) {
56.2767 + ex.printStackTrace();
56.2768 + }
56.2769 + }
56.2770 + }
56.2771 + }
56.2772 +
56.2773 + @Override
56.2774 + public int getPostingsCount(String groupname)
56.2775 + throws StorageBackendException
56.2776 + {
56.2777 + ResultSet rs = null;
56.2778 +
56.2779 + try {
56.2780 + this.pstmtGetPostingsCount.setString(1, groupname);
56.2781 + rs = this.pstmtGetPostingsCount.executeQuery();
56.2782 + if (rs.next()) {
56.2783 + return rs.getInt(1);
56.2784 + } else {
56.2785 + Log.get().warning("Count on postings return nothing!");
56.2786 + return 0;
56.2787 + }
56.2788 + } catch (SQLException ex) {
56.2789 + restartConnection(ex);
56.2790 + return getPostingsCount(groupname);
56.2791 + } finally {
56.2792 + if (rs != null) {
56.2793 + try {
56.2794 + rs.close();
56.2795 + } catch (SQLException ex) {
56.2796 + ex.printStackTrace();
56.2797 + }
56.2798 + }
56.2799 + }
56.2800 + }
56.2801 +
56.2802 + @Override
56.2803 + public List<Subscription> getSubscriptions(int feedtype)
56.2804 + throws StorageBackendException
56.2805 + {
56.2806 + ResultSet rs = null;
56.2807 +
56.2808 + try {
56.2809 + List<Subscription> subs = new ArrayList<Subscription>();
56.2810 + this.pstmtGetSubscriptions.setInt(1, feedtype);
56.2811 + rs = this.pstmtGetSubscriptions.executeQuery();
56.2812 +
56.2813 + while (rs.next()) {
56.2814 + String host = rs.getString("host");
56.2815 + String group = rs.getString("name");
56.2816 + int port = rs.getInt("port");
56.2817 + subs.add(new Subscription(host, port, feedtype, group));
56.2818 + }
56.2819 +
56.2820 + return subs;
56.2821 + } catch (SQLException ex) {
56.2822 + restartConnection(ex);
56.2823 + return getSubscriptions(feedtype);
56.2824 + } finally {
56.2825 + if (rs != null) {
56.2826 + try {
56.2827 + rs.close();
56.2828 + } catch (SQLException ex) {
56.2829 + ex.printStackTrace();
56.2830 + }
56.2831 + }
56.2832 + }
56.2833 + }
56.2834 +
56.2835 + /**
56.2836 + * Checks if there is an article with the given messageid in the JDBCDatabase.
56.2837 + * @param name
56.2838 + * @return
56.2839 + * @throws StorageBackendException
56.2840 + */
56.2841 + @Override
56.2842 + public boolean isArticleExisting(String messageID)
56.2843 + throws StorageBackendException
56.2844 + {
56.2845 + ResultSet rs = null;
56.2846 +
56.2847 + try {
56.2848 + this.pstmtIsArticleExisting.setString(1, messageID);
56.2849 + rs = this.pstmtIsArticleExisting.executeQuery();
56.2850 + return rs.next() && rs.getInt(1) == 1;
56.2851 + } catch (SQLException ex) {
56.2852 + restartConnection(ex);
56.2853 + return isArticleExisting(messageID);
56.2854 + } finally {
56.2855 + if (rs != null) {
56.2856 + try {
56.2857 + rs.close();
56.2858 + } catch (SQLException ex) {
56.2859 + ex.printStackTrace();
56.2860 + }
56.2861 + }
56.2862 + }
56.2863 + }
56.2864 +
56.2865 + /**
56.2866 + * Checks if there is a group with the given name in the JDBCDatabase.
56.2867 + * @param name
56.2868 + * @return
56.2869 + * @throws StorageBackendException
56.2870 + */
56.2871 + @Override
56.2872 + public boolean isGroupExisting(String name)
56.2873 + throws StorageBackendException
56.2874 + {
56.2875 + ResultSet rs = null;
56.2876 +
56.2877 + try {
56.2878 + this.pstmtIsGroupExisting.setString(1, name);
56.2879 + rs = this.pstmtIsGroupExisting.executeQuery();
56.2880 + return rs.next();
56.2881 + } catch (SQLException ex) {
56.2882 + restartConnection(ex);
56.2883 + return isGroupExisting(name);
56.2884 + } finally {
56.2885 + if (rs != null) {
56.2886 + try {
56.2887 + rs.close();
56.2888 + } catch (SQLException ex) {
56.2889 + ex.printStackTrace();
56.2890 + }
56.2891 + }
56.2892 + }
56.2893 + }
56.2894 +
56.2895 + @Override
56.2896 + public void setConfigValue(String key, String value)
56.2897 + throws StorageBackendException
56.2898 + {
56.2899 + try {
56.2900 + conn.setAutoCommit(false);
56.2901 + this.pstmtSetConfigValue0.setString(1, key);
56.2902 + this.pstmtSetConfigValue0.execute();
56.2903 + this.pstmtSetConfigValue1.setString(1, key);
56.2904 + this.pstmtSetConfigValue1.setString(2, value);
56.2905 + this.pstmtSetConfigValue1.execute();
56.2906 + conn.commit();
56.2907 + conn.setAutoCommit(true);
56.2908 + } catch (SQLException ex) {
56.2909 + restartConnection(ex);
56.2910 + setConfigValue(key, value);
56.2911 + }
56.2912 + }
56.2913 +
56.2914 + /**
56.2915 + * Closes the JDBCDatabase connection.
56.2916 + */
56.2917 + public void shutdown()
56.2918 + throws StorageBackendException
56.2919 + {
56.2920 + try {
56.2921 + if (this.conn != null) {
56.2922 + this.conn.close();
56.2923 + }
56.2924 + } catch (SQLException ex) {
56.2925 + throw new StorageBackendException(ex);
56.2926 + }
56.2927 + }
56.2928 +
56.2929 + @Override
56.2930 + public void purgeGroup(Group group)
56.2931 + throws StorageBackendException
56.2932 + {
56.2933 + try {
56.2934 + this.pstmtPurgeGroup0.setLong(1, group.getInternalID());
56.2935 + this.pstmtPurgeGroup0.executeUpdate();
56.2936 +
56.2937 + this.pstmtPurgeGroup1.setLong(1, group.getInternalID());
56.2938 + this.pstmtPurgeGroup1.executeUpdate();
56.2939 + } catch (SQLException ex) {
56.2940 + restartConnection(ex);
56.2941 + purgeGroup(group);
56.2942 + }
56.2943 + }
56.2944 +
56.2945 + private void restartConnection(SQLException cause)
56.2946 + throws StorageBackendException
56.2947 + {
56.2948 + restarts++;
56.2949 + Log.get().severe(Thread.currentThread()
56.2950 + + ": Database connection was closed (restart " + restarts + ").");
56.2951 +
56.2952 + if (restarts >= MAX_RESTARTS) {
56.2953 + // Delete the current, probably broken JDBCDatabase instance.
56.2954 + // So no one can use the instance any more.
56.2955 + JDBCDatabaseProvider.instances.remove(Thread.currentThread());
56.2956 +
56.2957 + // Throw the exception upwards
56.2958 + throw new StorageBackendException(cause);
56.2959 + }
56.2960 +
56.2961 + try {
56.2962 + Thread.sleep(1500L * restarts);
56.2963 + } catch (InterruptedException ex) {
56.2964 + Log.get().warning("Interrupted: " + ex.getMessage());
56.2965 + }
56.2966 +
56.2967 + // Try to properly close the old database connection
56.2968 + try {
56.2969 + if (this.conn != null) {
56.2970 + this.conn.close();
56.2971 + }
56.2972 + } catch (SQLException ex) {
56.2973 + Log.get().warning(ex.getMessage());
56.2974 + }
56.2975 +
56.2976 + try {
56.2977 + // Try to reinitialize database connection
56.2978 + arise();
56.2979 + } catch (SQLException ex) {
56.2980 + Log.get().warning(ex.getMessage());
56.2981 + restartConnection(ex);
56.2982 + }
56.2983 + }
56.2984 +
56.2985 + @Override
56.2986 + public boolean update(Article article)
56.2987 + throws StorageBackendException
56.2988 + {
56.2989 + // DELETE FROM headers WHERE article_id = ?
56.2990 +
56.2991 + // INSERT INTO headers ...
56.2992 +
56.2993 + // SELECT * FROM postings WHERE article_id = ? AND group_id = ?
56.2994 + return false;
56.2995 + }
56.2996 +
56.2997 + /**
56.2998 + * Writes the flags and the name of the given group to the database.
56.2999 + * @param group
56.3000 + * @throws StorageBackendException
56.3001 + */
56.3002 + @Override
56.3003 + public boolean update(Group group)
56.3004 + throws StorageBackendException
56.3005 + {
56.3006 + try {
56.3007 + this.pstmtUpdateGroup.setInt(1, group.getFlags());
56.3008 + this.pstmtUpdateGroup.setString(2, group.getName());
56.3009 + this.pstmtUpdateGroup.setLong(3, group.getInternalID());
56.3010 + int rs = this.pstmtUpdateGroup.executeUpdate();
56.3011 + return rs == 1;
56.3012 + } catch (SQLException ex) {
56.3013 + restartConnection(ex);
56.3014 + return update(group);
56.3015 + }
56.3016 + }
56.3017 }
57.1 --- a/src/org/sonews/storage/impl/JDBCDatabaseProvider.java Sun Aug 29 17:43:58 2010 +0200
57.2 +++ b/src/org/sonews/storage/impl/JDBCDatabaseProvider.java Sun Aug 29 18:17:37 2010 +0200
57.3 @@ -33,37 +33,29 @@
57.4 public class JDBCDatabaseProvider implements StorageProvider
57.5 {
57.6
57.7 - protected static final Map<Thread, JDBCDatabase> instances
57.8 - = new ConcurrentHashMap<Thread, JDBCDatabase>();
57.9 + protected static final Map<Thread, JDBCDatabase> instances = new ConcurrentHashMap<Thread, JDBCDatabase>();
57.10
57.11 - @Override
57.12 - public boolean isSupported(String uri)
57.13 - {
57.14 - throw new UnsupportedOperationException("Not supported yet.");
57.15 - }
57.16 + @Override
57.17 + public boolean isSupported(String uri)
57.18 + {
57.19 + throw new UnsupportedOperationException("Not supported yet.");
57.20 + }
57.21
57.22 - @Override
57.23 - public Storage storage(Thread thread)
57.24 - throws StorageBackendException
57.25 - {
57.26 - try
57.27 - {
57.28 - if(!instances.containsKey(Thread.currentThread()))
57.29 - {
57.30 - JDBCDatabase db = new JDBCDatabase();
57.31 - db.arise();
57.32 - instances.put(Thread.currentThread(), db);
57.33 - return db;
57.34 - }
57.35 - else
57.36 - {
57.37 - return instances.get(Thread.currentThread());
57.38 - }
57.39 - }
57.40 - catch(SQLException ex)
57.41 - {
57.42 - throw new StorageBackendException(ex);
57.43 - }
57.44 - }
57.45 -
57.46 + @Override
57.47 + public Storage storage(Thread thread)
57.48 + throws StorageBackendException
57.49 + {
57.50 + try {
57.51 + if (!instances.containsKey(Thread.currentThread())) {
57.52 + JDBCDatabase db = new JDBCDatabase();
57.53 + db.arise();
57.54 + instances.put(Thread.currentThread(), db);
57.55 + return db;
57.56 + } else {
57.57 + return instances.get(Thread.currentThread());
57.58 + }
57.59 + } catch (SQLException ex) {
57.60 + throw new StorageBackendException(ex);
57.61 + }
57.62 + }
57.63 }
58.1 --- a/src/org/sonews/util/DatabaseSetup.java Sun Aug 29 17:43:58 2010 +0200
58.2 +++ b/src/org/sonews/util/DatabaseSetup.java Sun Aug 29 18:17:37 2010 +0200
58.3 @@ -25,7 +25,6 @@
58.4 import java.sql.Statement;
58.5 import java.util.HashMap;
58.6 import java.util.Map;
58.7 -import org.sonews.config.Config;
58.8 import org.sonews.util.io.Resource;
58.9
58.10 /**
58.11 @@ -33,95 +32,85 @@
58.12 * @author Christian Lins
58.13 * @since sonews/0.5.0
58.14 */
58.15 -public final class DatabaseSetup
58.16 +public final class DatabaseSetup
58.17 {
58.18
58.19 - private static final Map<String, String> templateMap
58.20 - = new HashMap<String, String>();
58.21 - private static final Map<String, StringTemplate> urlMap
58.22 - = new HashMap<String, StringTemplate>();
58.23 - private static final Map<String, String> driverMap
58.24 - = new HashMap<String, String>();
58.25 -
58.26 - static
58.27 - {
58.28 - templateMap.put("1", "helpers/database_mysql5_tmpl.sql");
58.29 - templateMap.put("2", "helpers/database_postgresql8_tmpl.sql");
58.30 -
58.31 - urlMap.put("1", new StringTemplate("jdbc:mysql://%HOSTNAME/%DB"));
58.32 - urlMap.put("2", new StringTemplate("jdbc:postgresql://%HOSTNAME/%DB"));
58.33 -
58.34 - driverMap.put("1", "com.mysql.jdbc.Driver");
58.35 - driverMap.put("2", "org.postgresql.Driver");
58.36 - }
58.37 -
58.38 - public static void main(String[] args)
58.39 - throws Exception
58.40 - {
58.41 - System.out.println("sonews Database setup helper");
58.42 - System.out.println("This program will create a initial database table structure");
58.43 - System.out.println("for the sonews Newsserver.");
58.44 - System.out.println("You need to create a database and a db user manually before!");
58.45 -
58.46 - System.out.println("Select DBMS type:");
58.47 - System.out.println("[1] MySQL 5.x or higher");
58.48 - System.out.println("[2] PostgreSQL 8.x or higher");
58.49 - System.out.print("Your choice: ");
58.50 -
58.51 - BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
58.52 - String dbmsType = in.readLine();
58.53 - String tmplName = templateMap.get(dbmsType);
58.54 - if(tmplName == null)
58.55 - {
58.56 - System.err.println("Invalid choice. Try again you fool!");
58.57 - main(args);
58.58 - return;
58.59 - }
58.60 -
58.61 - // Load JDBC Driver class
58.62 - Class.forName(driverMap.get(dbmsType));
58.63 -
58.64 - String tmpl = Resource.getAsString(tmplName, true);
58.65 -
58.66 - System.out.print("Database server hostname (e.g. localhost): ");
58.67 - String dbHostname = in.readLine();
58.68 -
58.69 - System.out.print("Database name: ");
58.70 - String dbName = in.readLine();
58.71 + private static final Map<String, String> templateMap = new HashMap<String, String>();
58.72 + private static final Map<String, StringTemplate> urlMap = new HashMap<String, StringTemplate>();
58.73 + private static final Map<String, String> driverMap = new HashMap<String, String>();
58.74
58.75 - System.out.print("Give name of DB user that can create tables: ");
58.76 - String dbUser = in.readLine();
58.77 + static {
58.78 + templateMap.put("1", "helpers/database_mysql5_tmpl.sql");
58.79 + templateMap.put("2", "helpers/database_postgresql8_tmpl.sql");
58.80
58.81 - System.out.print("Password: ");
58.82 - String dbPassword = in.readLine();
58.83 -
58.84 - String url = urlMap.get(dbmsType)
58.85 - .set("HOSTNAME", dbHostname)
58.86 - .set("DB", dbName).toString();
58.87 -
58.88 - Connection conn =
58.89 - DriverManager.getConnection(url, dbUser, dbPassword);
58.90 - conn.setAutoCommit(false);
58.91 -
58.92 - String[] tmplChunks = tmpl.split(";");
58.93 -
58.94 - for(String chunk : tmplChunks)
58.95 - {
58.96 - if(chunk.trim().equals(""))
58.97 - {
58.98 - continue;
58.99 - }
58.100 -
58.101 - Statement stmt = conn.createStatement();
58.102 - stmt.execute(chunk);
58.103 - }
58.104 -
58.105 - conn.commit();
58.106 - conn.setAutoCommit(true);
58.107 -
58.108 - // Create config file
58.109 -
58.110 - System.out.println("Ok");
58.111 - }
58.112 -
58.113 + urlMap.put("1", new StringTemplate("jdbc:mysql://%HOSTNAME/%DB"));
58.114 + urlMap.put("2", new StringTemplate("jdbc:postgresql://%HOSTNAME/%DB"));
58.115 +
58.116 + driverMap.put("1", "com.mysql.jdbc.Driver");
58.117 + driverMap.put("2", "org.postgresql.Driver");
58.118 + }
58.119 +
58.120 + public static void main(String[] args)
58.121 + throws Exception
58.122 + {
58.123 + System.out.println("sonews Database setup helper");
58.124 + System.out.println("This program will create a initial database table structure");
58.125 + System.out.println("for the sonews Newsserver.");
58.126 + System.out.println("You need to create a database and a db user manually before!");
58.127 +
58.128 + System.out.println("Select DBMS type:");
58.129 + System.out.println("[1] MySQL 5.x or higher");
58.130 + System.out.println("[2] PostgreSQL 8.x or higher");
58.131 + System.out.print("Your choice: ");
58.132 +
58.133 + BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
58.134 + String dbmsType = in.readLine();
58.135 + String tmplName = templateMap.get(dbmsType);
58.136 + if (tmplName == null) {
58.137 + System.err.println("Invalid choice. Try again you fool!");
58.138 + main(args);
58.139 + return;
58.140 + }
58.141 +
58.142 + // Load JDBC Driver class
58.143 + Class.forName(driverMap.get(dbmsType));
58.144 +
58.145 + String tmpl = Resource.getAsString(tmplName, true);
58.146 +
58.147 + System.out.print("Database server hostname (e.g. localhost): ");
58.148 + String dbHostname = in.readLine();
58.149 +
58.150 + System.out.print("Database name: ");
58.151 + String dbName = in.readLine();
58.152 +
58.153 + System.out.print("Give name of DB user that can create tables: ");
58.154 + String dbUser = in.readLine();
58.155 +
58.156 + System.out.print("Password: ");
58.157 + String dbPassword = in.readLine();
58.158 +
58.159 + String url = urlMap.get(dbmsType).set("HOSTNAME", dbHostname).set("DB", dbName).toString();
58.160 +
58.161 + Connection conn =
58.162 + DriverManager.getConnection(url, dbUser, dbPassword);
58.163 + conn.setAutoCommit(false);
58.164 +
58.165 + String[] tmplChunks = tmpl.split(";");
58.166 +
58.167 + for (String chunk : tmplChunks) {
58.168 + if (chunk.trim().equals("")) {
58.169 + continue;
58.170 + }
58.171 +
58.172 + Statement stmt = conn.createStatement();
58.173 + stmt.execute(chunk);
58.174 + }
58.175 +
58.176 + conn.commit();
58.177 + conn.setAutoCommit(true);
58.178 +
58.179 + // Create config file
58.180 +
58.181 + System.out.println("Ok");
58.182 + }
58.183 }
59.1 --- a/src/org/sonews/util/Log.java Sun Aug 29 17:43:58 2010 +0200
59.2 +++ b/src/org/sonews/util/Log.java Sun Aug 29 18:17:37 2010 +0200
59.3 @@ -33,25 +33,24 @@
59.4 public class Log extends Logger
59.5 {
59.6
59.7 - private static Log instance = new Log();
59.8 + private static Log instance = new Log();
59.9
59.10 - private Log()
59.11 - {
59.12 - super("org.sonews", null);
59.13 + private Log()
59.14 + {
59.15 + super("org.sonews", null);
59.16
59.17 - StreamHandler handler = new StreamHandler(System.out, new SimpleFormatter());
59.18 - Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
59.19 - handler.setLevel(level);
59.20 - addHandler(handler);
59.21 - setLevel(level);
59.22 - LogManager.getLogManager().addLogger(this);
59.23 - }
59.24 + StreamHandler handler = new StreamHandler(System.out, new SimpleFormatter());
59.25 + Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
59.26 + handler.setLevel(level);
59.27 + addHandler(handler);
59.28 + setLevel(level);
59.29 + LogManager.getLogManager().addLogger(this);
59.30 + }
59.31
59.32 - public static Logger get()
59.33 - {
59.34 - Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
59.35 - instance.setLevel(level);
59.36 - return instance;
59.37 - }
59.38 -
59.39 + public static Logger get()
59.40 + {
59.41 + Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
59.42 + instance.setLevel(level);
59.43 + return instance;
59.44 + }
59.45 }
60.1 --- a/src/org/sonews/util/Pair.java Sun Aug 29 17:43:58 2010 +0200
60.2 +++ b/src/org/sonews/util/Pair.java Sun Aug 29 18:17:37 2010 +0200
60.3 @@ -23,26 +23,25 @@
60.4 * @author Christian Lins
60.5 * @since sonews/0.5.0
60.6 */
60.7 -public class Pair<T1, T2>
60.8 +public class Pair<T1, T2>
60.9 {
60.10 -
60.11 - private T1 a;
60.12 - private T2 b;
60.13 -
60.14 - public Pair(T1 a, T2 b)
60.15 - {
60.16 - this.a = a;
60.17 - this.b = b;
60.18 - }
60.19
60.20 - public T1 getA()
60.21 - {
60.22 - return a;
60.23 - }
60.24 + private T1 a;
60.25 + private T2 b;
60.26
60.27 - public T2 getB()
60.28 - {
60.29 - return b;
60.30 - }
60.31 -
60.32 + public Pair(T1 a, T2 b)
60.33 + {
60.34 + this.a = a;
60.35 + this.b = b;
60.36 + }
60.37 +
60.38 + public T1 getA()
60.39 + {
60.40 + return a;
60.41 + }
60.42 +
60.43 + public T2 getB()
60.44 + {
60.45 + return b;
60.46 + }
60.47 }
61.1 --- a/src/org/sonews/util/Purger.java Sun Aug 29 17:43:58 2010 +0200
61.2 +++ b/src/org/sonews/util/Purger.java Sun Aug 29 18:17:37 2010 +0200
61.3 @@ -40,110 +40,91 @@
61.4 public class Purger extends AbstractDaemon
61.5 {
61.6
61.7 - /**
61.8 - * Loops through all messages and deletes them if their time
61.9 - * has come.
61.10 - */
61.11 - @Override
61.12 - public void run()
61.13 - {
61.14 - try
61.15 - {
61.16 - while(isRunning())
61.17 - {
61.18 - purgeDeleted();
61.19 - purgeOutdated();
61.20 + /**
61.21 + * Loops through all messages and deletes them if their time
61.22 + * has come.
61.23 + */
61.24 + @Override
61.25 + public void run()
61.26 + {
61.27 + try {
61.28 + while (isRunning()) {
61.29 + purgeDeleted();
61.30 + purgeOutdated();
61.31
61.32 - Thread.sleep(120000); // Sleep for two minutes
61.33 - }
61.34 - }
61.35 - catch(StorageBackendException ex)
61.36 - {
61.37 - ex.printStackTrace();
61.38 - }
61.39 - catch(InterruptedException ex)
61.40 - {
61.41 - Log.get().warning("Purger interrupted: " + ex);
61.42 - }
61.43 - }
61.44 + Thread.sleep(120000); // Sleep for two minutes
61.45 + }
61.46 + } catch (StorageBackendException ex) {
61.47 + ex.printStackTrace();
61.48 + } catch (InterruptedException ex) {
61.49 + Log.get().warning("Purger interrupted: " + ex);
61.50 + }
61.51 + }
61.52
61.53 - private void purgeDeleted()
61.54 - throws StorageBackendException
61.55 - {
61.56 - List<Channel> groups = StorageManager.current().getGroups();
61.57 - for(Channel channel : groups)
61.58 - {
61.59 - if(!(channel instanceof Group))
61.60 - continue;
61.61 -
61.62 - Group group = (Group)channel;
61.63 - // Look for groups that are marked as deleted
61.64 - if(group.isDeleted())
61.65 - {
61.66 - List<Long> ids = StorageManager.current().getArticleNumbers(group.getInternalID());
61.67 - if(ids.size() == 0)
61.68 - {
61.69 - StorageManager.current().purgeGroup(group);
61.70 - Log.get().info("Group " + group.getName() + " purged.");
61.71 - }
61.72 + private void purgeDeleted()
61.73 + throws StorageBackendException
61.74 + {
61.75 + List<Channel> groups = StorageManager.current().getGroups();
61.76 + for (Channel channel : groups) {
61.77 + if (!(channel instanceof Group)) {
61.78 + continue;
61.79 + }
61.80
61.81 - for(int n = 0; n < ids.size() && n < 10; n++)
61.82 - {
61.83 - Article art = StorageManager.current().getArticle(ids.get(n), group.getInternalID());
61.84 - StorageManager.current().delete(art.getMessageID());
61.85 - Log.get().info("Article " + art.getMessageID() + " purged.");
61.86 - }
61.87 - }
61.88 - }
61.89 - }
61.90 + Group group = (Group) channel;
61.91 + // Look for groups that are marked as deleted
61.92 + if (group.isDeleted()) {
61.93 + List<Long> ids = StorageManager.current().getArticleNumbers(group.getInternalID());
61.94 + if (ids.size() == 0) {
61.95 + StorageManager.current().purgeGroup(group);
61.96 + Log.get().info("Group " + group.getName() + " purged.");
61.97 + }
61.98
61.99 - private void purgeOutdated()
61.100 - throws InterruptedException, StorageBackendException
61.101 - {
61.102 - long articleMaximum =
61.103 - Config.inst().get("sonews.article.maxnum", Long.MAX_VALUE);
61.104 - long lifetime =
61.105 - Config.inst().get("sonews.article.lifetime", -1);
61.106 + for (int n = 0; n < ids.size() && n < 10; n++) {
61.107 + Article art = StorageManager.current().getArticle(ids.get(n), group.getInternalID());
61.108 + StorageManager.current().delete(art.getMessageID());
61.109 + Log.get().info("Article " + art.getMessageID() + " purged.");
61.110 + }
61.111 + }
61.112 + }
61.113 + }
61.114
61.115 - if(lifetime > 0 || articleMaximum < Stats.getInstance().getNumberOfNews())
61.116 - {
61.117 - Log.get().info("Purging old messages...");
61.118 - String mid = StorageManager.current().getOldestArticle();
61.119 - if (mid == null) // No articles in the database
61.120 - {
61.121 - return;
61.122 - }
61.123 + private void purgeOutdated()
61.124 + throws InterruptedException, StorageBackendException
61.125 + {
61.126 + long articleMaximum =
61.127 + Config.inst().get("sonews.article.maxnum", Long.MAX_VALUE);
61.128 + long lifetime =
61.129 + Config.inst().get("sonews.article.lifetime", -1);
61.130
61.131 - Article art = StorageManager.current().getArticle(mid);
61.132 - long artDate = 0;
61.133 - String dateStr = art.getHeader(Headers.DATE)[0];
61.134 - try
61.135 - {
61.136 - artDate = Date.parse(dateStr) / 1000 / 60 / 60 / 24;
61.137 - }
61.138 - catch (IllegalArgumentException ex)
61.139 - {
61.140 - Log.get().warning("Could not parse date string: " + dateStr + " " + ex);
61.141 - }
61.142 + if (lifetime > 0 || articleMaximum < Stats.getInstance().getNumberOfNews()) {
61.143 + Log.get().info("Purging old messages...");
61.144 + String mid = StorageManager.current().getOldestArticle();
61.145 + if (mid == null) // No articles in the database
61.146 + {
61.147 + return;
61.148 + }
61.149
61.150 - // Should we delete the message because of its age or because the
61.151 - // article maximum was reached?
61.152 - if (lifetime < 0 || artDate < (new Date().getTime() + lifetime))
61.153 - {
61.154 - StorageManager.current().delete(mid);
61.155 - System.out.println("Deleted: " + mid);
61.156 - }
61.157 - else
61.158 - {
61.159 - Thread.sleep(1000 * 60); // Wait 60 seconds
61.160 - return;
61.161 - }
61.162 - }
61.163 - else
61.164 - {
61.165 - Log.get().info("Lifetime purger is disabled");
61.166 - Thread.sleep(1000 * 60 * 30); // Wait 30 minutes
61.167 - }
61.168 - }
61.169 + Article art = StorageManager.current().getArticle(mid);
61.170 + long artDate = 0;
61.171 + String dateStr = art.getHeader(Headers.DATE)[0];
61.172 + try {
61.173 + artDate = Date.parse(dateStr) / 1000 / 60 / 60 / 24;
61.174 + } catch (IllegalArgumentException ex) {
61.175 + Log.get().warning("Could not parse date string: " + dateStr + " " + ex);
61.176 + }
61.177
61.178 + // Should we delete the message because of its age or because the
61.179 + // article maximum was reached?
61.180 + if (lifetime < 0 || artDate < (new Date().getTime() + lifetime)) {
61.181 + StorageManager.current().delete(mid);
61.182 + System.out.println("Deleted: " + mid);
61.183 + } else {
61.184 + Thread.sleep(1000 * 60); // Wait 60 seconds
61.185 + return;
61.186 + }
61.187 + } else {
61.188 + Log.get().info("Lifetime purger is disabled");
61.189 + Thread.sleep(1000 * 60 * 30); // Wait 30 minutes
61.190 + }
61.191 + }
61.192 }
62.1 --- a/src/org/sonews/util/Stats.java Sun Aug 29 17:43:58 2010 +0200
62.2 +++ b/src/org/sonews/util/Stats.java Sun Aug 29 18:17:37 2010 +0200
62.3 @@ -29,178 +29,157 @@
62.4 * @author Christian Lins
62.5 * @since sonews/0.5.0
62.6 */
62.7 -public final class Stats
62.8 +public final class Stats
62.9 {
62.10 -
62.11 - public static final byte CONNECTIONS = 1;
62.12 - public static final byte POSTED_NEWS = 2;
62.13 - public static final byte GATEWAYED_NEWS = 3;
62.14 - public static final byte FEEDED_NEWS = 4;
62.15 - public static final byte MLGW_RUNSTART = 5;
62.16 - public static final byte MLGW_RUNEND = 6;
62.17
62.18 - private static Stats instance = new Stats();
62.19 -
62.20 - public static Stats getInstance()
62.21 - {
62.22 - return Stats.instance;
62.23 - }
62.24 -
62.25 - private Stats() {}
62.26 -
62.27 - private volatile int connectedClients = 0;
62.28 + public static final byte CONNECTIONS = 1;
62.29 + public static final byte POSTED_NEWS = 2;
62.30 + public static final byte GATEWAYED_NEWS = 3;
62.31 + public static final byte FEEDED_NEWS = 4;
62.32 + public static final byte MLGW_RUNSTART = 5;
62.33 + public static final byte MLGW_RUNEND = 6;
62.34 + private static Stats instance = new Stats();
62.35
62.36 - /**
62.37 - * A generic method that writes event data to the storage backend.
62.38 - * If event logging is disabled with sonews.eventlog=false this method
62.39 - * simply does nothing.
62.40 - * @param type
62.41 - * @param groupname
62.42 - */
62.43 - private void addEvent(byte type, String groupname)
62.44 - {
62.45 - try
62.46 - {
62.47 - if (Config.inst().get(Config.EVENTLOG, true))
62.48 - {
62.49 + public static Stats getInstance()
62.50 + {
62.51 + return Stats.instance;
62.52 + }
62.53
62.54 - Channel group = Channel.getByName(groupname);
62.55 - if (group != null)
62.56 - {
62.57 - StorageManager.current().addEvent(
62.58 - System.currentTimeMillis(), type, group.getInternalID());
62.59 - }
62.60 - }
62.61 - else
62.62 - {
62.63 - Log.get().info("Group " + groupname + " does not exist.");
62.64 - }
62.65 - }
62.66 - catch (StorageBackendException ex)
62.67 - {
62.68 - ex.printStackTrace();
62.69 - }
62.70 - }
62.71 -
62.72 - public void clientConnect()
62.73 - {
62.74 - this.connectedClients++;
62.75 - }
62.76 -
62.77 - public void clientDisconnect()
62.78 - {
62.79 - this.connectedClients--;
62.80 - }
62.81 -
62.82 - public int connectedClients()
62.83 - {
62.84 - return this.connectedClients;
62.85 - }
62.86 -
62.87 - public int getNumberOfGroups()
62.88 - {
62.89 - try
62.90 - {
62.91 - return StorageManager.current().countGroups();
62.92 - }
62.93 - catch(StorageBackendException ex)
62.94 - {
62.95 - ex.printStackTrace();
62.96 - return -1;
62.97 - }
62.98 - }
62.99 -
62.100 - public int getNumberOfNews()
62.101 - {
62.102 - try
62.103 - {
62.104 - return StorageManager.current().countArticles();
62.105 - }
62.106 - catch(StorageBackendException ex)
62.107 - {
62.108 - ex.printStackTrace();
62.109 - return -1;
62.110 - }
62.111 - }
62.112 -
62.113 - public int getYesterdaysEvents(final byte eventType, final int hour,
62.114 - final Channel group)
62.115 - {
62.116 - // Determine the timestamp values for yesterday and the given hour
62.117 - Calendar cal = Calendar.getInstance();
62.118 - int year = cal.get(Calendar.YEAR);
62.119 - int month = cal.get(Calendar.MONTH);
62.120 - int dayom = cal.get(Calendar.DAY_OF_MONTH) - 1; // Yesterday
62.121 -
62.122 - cal.set(year, month, dayom, hour, 0, 0);
62.123 - long startTimestamp = cal.getTimeInMillis();
62.124 -
62.125 - cal.set(year, month, dayom, hour + 1, 0, 0);
62.126 - long endTimestamp = cal.getTimeInMillis();
62.127 -
62.128 - try
62.129 - {
62.130 - return StorageManager.current()
62.131 - .getEventsCount(eventType, startTimestamp, endTimestamp, group);
62.132 - }
62.133 - catch(StorageBackendException ex)
62.134 - {
62.135 - ex.printStackTrace();
62.136 - return -1;
62.137 - }
62.138 - }
62.139 -
62.140 - public void mailPosted(String groupname)
62.141 - {
62.142 - addEvent(POSTED_NEWS, groupname);
62.143 - }
62.144 -
62.145 - public void mailGatewayed(String groupname)
62.146 - {
62.147 - addEvent(GATEWAYED_NEWS, groupname);
62.148 - }
62.149 -
62.150 - public void mailFeeded(String groupname)
62.151 - {
62.152 - addEvent(FEEDED_NEWS, groupname);
62.153 - }
62.154 -
62.155 - public void mlgwRunStart()
62.156 - {
62.157 - addEvent(MLGW_RUNSTART, "control");
62.158 - }
62.159 -
62.160 - public void mlgwRunEnd()
62.161 - {
62.162 - addEvent(MLGW_RUNEND, "control");
62.163 - }
62.164 -
62.165 - private double perHour(int key, long gid)
62.166 - {
62.167 - try
62.168 - {
62.169 - return StorageManager.current().getEventsPerHour(key, gid);
62.170 - }
62.171 - catch(StorageBackendException ex)
62.172 - {
62.173 - ex.printStackTrace();
62.174 - return -1;
62.175 - }
62.176 - }
62.177 -
62.178 - public double postedPerHour(long gid)
62.179 - {
62.180 - return perHour(POSTED_NEWS, gid);
62.181 - }
62.182 -
62.183 - public double gatewayedPerHour(long gid)
62.184 - {
62.185 - return perHour(GATEWAYED_NEWS, gid);
62.186 - }
62.187 -
62.188 - public double feededPerHour(long gid)
62.189 - {
62.190 - return perHour(FEEDED_NEWS, gid);
62.191 - }
62.192 -
62.193 + private Stats()
62.194 + {
62.195 + }
62.196 + private volatile int connectedClients = 0;
62.197 +
62.198 + /**
62.199 + * A generic method that writes event data to the storage backend.
62.200 + * If event logging is disabled with sonews.eventlog=false this method
62.201 + * simply does nothing.
62.202 + * @param type
62.203 + * @param groupname
62.204 + */
62.205 + private void addEvent(byte type, String groupname)
62.206 + {
62.207 + try {
62.208 + if (Config.inst().get(Config.EVENTLOG, true)) {
62.209 +
62.210 + Channel group = Channel.getByName(groupname);
62.211 + if (group != null) {
62.212 + StorageManager.current().addEvent(
62.213 + System.currentTimeMillis(), type, group.getInternalID());
62.214 + }
62.215 + } else {
62.216 + Log.get().info("Group " + groupname + " does not exist.");
62.217 + }
62.218 + } catch (StorageBackendException ex) {
62.219 + ex.printStackTrace();
62.220 + }
62.221 + }
62.222 +
62.223 + public void clientConnect()
62.224 + {
62.225 + this.connectedClients++;
62.226 + }
62.227 +
62.228 + public void clientDisconnect()
62.229 + {
62.230 + this.connectedClients--;
62.231 + }
62.232 +
62.233 + public int connectedClients()
62.234 + {
62.235 + return this.connectedClients;
62.236 + }
62.237 +
62.238 + public int getNumberOfGroups()
62.239 + {
62.240 + try {
62.241 + return StorageManager.current().countGroups();
62.242 + } catch (StorageBackendException ex) {
62.243 + ex.printStackTrace();
62.244 + return -1;
62.245 + }
62.246 + }
62.247 +
62.248 + public int getNumberOfNews()
62.249 + {
62.250 + try {
62.251 + return StorageManager.current().countArticles();
62.252 + } catch (StorageBackendException ex) {
62.253 + ex.printStackTrace();
62.254 + return -1;
62.255 + }
62.256 + }
62.257 +
62.258 + public int getYesterdaysEvents(final byte eventType, final int hour,
62.259 + final Channel group)
62.260 + {
62.261 + // Determine the timestamp values for yesterday and the given hour
62.262 + Calendar cal = Calendar.getInstance();
62.263 + int year = cal.get(Calendar.YEAR);
62.264 + int month = cal.get(Calendar.MONTH);
62.265 + int dayom = cal.get(Calendar.DAY_OF_MONTH) - 1; // Yesterday
62.266 +
62.267 + cal.set(year, month, dayom, hour, 0, 0);
62.268 + long startTimestamp = cal.getTimeInMillis();
62.269 +
62.270 + cal.set(year, month, dayom, hour + 1, 0, 0);
62.271 + long endTimestamp = cal.getTimeInMillis();
62.272 +
62.273 + try {
62.274 + return StorageManager.current().getEventsCount(eventType, startTimestamp, endTimestamp, group);
62.275 + } catch (StorageBackendException ex) {
62.276 + ex.printStackTrace();
62.277 + return -1;
62.278 + }
62.279 + }
62.280 +
62.281 + public void mailPosted(String groupname)
62.282 + {
62.283 + addEvent(POSTED_NEWS, groupname);
62.284 + }
62.285 +
62.286 + public void mailGatewayed(String groupname)
62.287 + {
62.288 + addEvent(GATEWAYED_NEWS, groupname);
62.289 + }
62.290 +
62.291 + public void mailFeeded(String groupname)
62.292 + {
62.293 + addEvent(FEEDED_NEWS, groupname);
62.294 + }
62.295 +
62.296 + public void mlgwRunStart()
62.297 + {
62.298 + addEvent(MLGW_RUNSTART, "control");
62.299 + }
62.300 +
62.301 + public void mlgwRunEnd()
62.302 + {
62.303 + addEvent(MLGW_RUNEND, "control");
62.304 + }
62.305 +
62.306 + private double perHour(int key, long gid)
62.307 + {
62.308 + try {
62.309 + return StorageManager.current().getEventsPerHour(key, gid);
62.310 + } catch (StorageBackendException ex) {
62.311 + ex.printStackTrace();
62.312 + return -1;
62.313 + }
62.314 + }
62.315 +
62.316 + public double postedPerHour(long gid)
62.317 + {
62.318 + return perHour(POSTED_NEWS, gid);
62.319 + }
62.320 +
62.321 + public double gatewayedPerHour(long gid)
62.322 + {
62.323 + return perHour(GATEWAYED_NEWS, gid);
62.324 + }
62.325 +
62.326 + public double feededPerHour(long gid)
62.327 + {
62.328 + return perHour(FEEDED_NEWS, gid);
62.329 + }
62.330 }
63.1 --- a/src/org/sonews/util/StringTemplate.java Sun Aug 29 17:43:58 2010 +0200
63.2 +++ b/src/org/sonews/util/StringTemplate.java Sun Aug 29 18:17:37 2010 +0200
63.3 @@ -26,72 +26,67 @@
63.4 * @author Christian Lins
63.5 * @since sonews/0.5.0
63.6 */
63.7 -public class StringTemplate
63.8 +public class StringTemplate
63.9 {
63.10
63.11 - private String str = null;
63.12 - private String templateDelimiter = "%";
63.13 - private Map<String, String> templateValues = new HashMap<String, String>();
63.14 -
63.15 - public StringTemplate(String str, final String templateDelimiter)
63.16 - {
63.17 - if(str == null || templateDelimiter == null)
63.18 - {
63.19 - throw new IllegalArgumentException("null arguments not allowed");
63.20 - }
63.21 + private String str = null;
63.22 + private String templateDelimiter = "%";
63.23 + private Map<String, String> templateValues = new HashMap<String, String>();
63.24
63.25 - this.str = str;
63.26 - this.templateDelimiter = templateDelimiter;
63.27 - }
63.28 -
63.29 - public StringTemplate(String str)
63.30 - {
63.31 - this(str, "%");
63.32 - }
63.33 -
63.34 - public StringTemplate set(String template, String value)
63.35 - {
63.36 - if(template == null || value == null)
63.37 - {
63.38 - throw new IllegalArgumentException("null arguments not allowed");
63.39 - }
63.40 -
63.41 - this.templateValues.put(template, value);
63.42 - return this;
63.43 - }
63.44 -
63.45 - public StringTemplate set(String template, long value)
63.46 - {
63.47 - return set(template, Long.toString(value));
63.48 - }
63.49 -
63.50 - public StringTemplate set(String template, double value)
63.51 - {
63.52 - return set(template, Double.toString(value));
63.53 - }
63.54 -
63.55 - public StringTemplate set(String template, Object obj)
63.56 - {
63.57 - if(template == null || obj == null)
63.58 - {
63.59 - throw new IllegalArgumentException("null arguments not allowed");
63.60 - }
63.61 + public StringTemplate(String str, final String templateDelimiter)
63.62 + {
63.63 + if (str == null || templateDelimiter == null) {
63.64 + throw new IllegalArgumentException("null arguments not allowed");
63.65 + }
63.66
63.67 - return set(template, obj.toString());
63.68 - }
63.69 -
63.70 - @Override
63.71 - public String toString()
63.72 - {
63.73 - String ret = str;
63.74 + this.str = str;
63.75 + this.templateDelimiter = templateDelimiter;
63.76 + }
63.77
63.78 - for(String key : this.templateValues.keySet())
63.79 - {
63.80 - String value = this.templateValues.get(key);
63.81 - ret = ret.replace(templateDelimiter + key, value);
63.82 - }
63.83 -
63.84 - return ret;
63.85 - }
63.86 + public StringTemplate(String str)
63.87 + {
63.88 + this(str, "%");
63.89 + }
63.90
63.91 + public StringTemplate set(String template, String value)
63.92 + {
63.93 + if (template == null || value == null) {
63.94 + throw new IllegalArgumentException("null arguments not allowed");
63.95 + }
63.96 +
63.97 + this.templateValues.put(template, value);
63.98 + return this;
63.99 + }
63.100 +
63.101 + public StringTemplate set(String template, long value)
63.102 + {
63.103 + return set(template, Long.toString(value));
63.104 + }
63.105 +
63.106 + public StringTemplate set(String template, double value)
63.107 + {
63.108 + return set(template, Double.toString(value));
63.109 + }
63.110 +
63.111 + public StringTemplate set(String template, Object obj)
63.112 + {
63.113 + if (template == null || obj == null) {
63.114 + throw new IllegalArgumentException("null arguments not allowed");
63.115 + }
63.116 +
63.117 + return set(template, obj.toString());
63.118 + }
63.119 +
63.120 + @Override
63.121 + public String toString()
63.122 + {
63.123 + String ret = str;
63.124 +
63.125 + for (String key : this.templateValues.keySet()) {
63.126 + String value = this.templateValues.get(key);
63.127 + ret = ret.replace(templateDelimiter + key, value);
63.128 + }
63.129 +
63.130 + return ret;
63.131 + }
63.132 }
64.1 --- a/src/org/sonews/util/TimeoutMap.java Sun Aug 29 17:43:58 2010 +0200
64.2 +++ b/src/org/sonews/util/TimeoutMap.java Sun Aug 29 18:17:37 2010 +0200
64.3 @@ -31,115 +31,99 @@
64.4 * @author Christian Lins
64.5 * @since sonews/0.5.0
64.6 */
64.7 -public class TimeoutMap<K,V> extends ConcurrentHashMap<K, V>
64.8 +public class TimeoutMap<K, V> extends ConcurrentHashMap<K, V>
64.9 {
64.10 -
64.11 - private static final long serialVersionUID = 453453467700345L;
64.12
64.13 - private int timeout = 60000; // 60 sec
64.14 - private transient Map<K, Long> timeoutMap = new HashMap<K, Long>();
64.15 -
64.16 - /**
64.17 - * Constructor.
64.18 - * @param timeout Timeout in milliseconds
64.19 - */
64.20 - public TimeoutMap(final int timeout)
64.21 - {
64.22 - this.timeout = timeout;
64.23 - }
64.24 -
64.25 - /**
64.26 - * Uses default timeout (60 sec).
64.27 - */
64.28 - public TimeoutMap()
64.29 - {
64.30 - }
64.31 -
64.32 - /**
64.33 - *
64.34 - * @param key
64.35 - * @return true if key is still valid.
64.36 - */
64.37 - protected boolean checkTimeOut(Object key)
64.38 - {
64.39 - synchronized(this.timeoutMap)
64.40 - {
64.41 - if(this.timeoutMap.containsKey(key))
64.42 - {
64.43 - long keytime = this.timeoutMap.get(key);
64.44 - if((System.currentTimeMillis() - keytime) < this.timeout)
64.45 - {
64.46 - return true;
64.47 - }
64.48 - else
64.49 - {
64.50 - remove(key);
64.51 - return false;
64.52 - }
64.53 - }
64.54 - else
64.55 - {
64.56 - return false;
64.57 - }
64.58 - }
64.59 - }
64.60 -
64.61 - @Override
64.62 - public boolean containsKey(Object key)
64.63 - {
64.64 - return checkTimeOut(key);
64.65 - }
64.66 + private static final long serialVersionUID = 453453467700345L;
64.67 + private int timeout = 60000; // 60 sec
64.68 + private transient Map<K, Long> timeoutMap = new HashMap<K, Long>();
64.69
64.70 - @Override
64.71 - public synchronized V get(Object key)
64.72 - {
64.73 - if(checkTimeOut(key))
64.74 - {
64.75 - return super.get(key);
64.76 - }
64.77 - else
64.78 - {
64.79 - return null;
64.80 - }
64.81 - }
64.82 + /**
64.83 + * Constructor.
64.84 + * @param timeout Timeout in milliseconds
64.85 + */
64.86 + public TimeoutMap(final int timeout)
64.87 + {
64.88 + this.timeout = timeout;
64.89 + }
64.90
64.91 - @Override
64.92 - public V put(K key, V value)
64.93 - {
64.94 - synchronized(this.timeoutMap)
64.95 - {
64.96 - removeStaleKeys();
64.97 - this.timeoutMap.put(key, System.currentTimeMillis());
64.98 - return super.put(key, value);
64.99 - }
64.100 - }
64.101 + /**
64.102 + * Uses default timeout (60 sec).
64.103 + */
64.104 + public TimeoutMap()
64.105 + {
64.106 + }
64.107
64.108 - /**
64.109 - * @param arg0
64.110 - * @return
64.111 - */
64.112 - @Override
64.113 - public V remove(Object arg0)
64.114 - {
64.115 - synchronized(this.timeoutMap)
64.116 - {
64.117 - this.timeoutMap.remove(arg0);
64.118 - V val = super.remove(arg0);
64.119 - return val;
64.120 - }
64.121 - }
64.122 + /**
64.123 + *
64.124 + * @param key
64.125 + * @return true if key is still valid.
64.126 + */
64.127 + protected boolean checkTimeOut(Object key)
64.128 + {
64.129 + synchronized (this.timeoutMap) {
64.130 + if (this.timeoutMap.containsKey(key)) {
64.131 + long keytime = this.timeoutMap.get(key);
64.132 + if ((System.currentTimeMillis() - keytime) < this.timeout) {
64.133 + return true;
64.134 + } else {
64.135 + remove(key);
64.136 + return false;
64.137 + }
64.138 + } else {
64.139 + return false;
64.140 + }
64.141 + }
64.142 + }
64.143
64.144 - protected void removeStaleKeys()
64.145 - {
64.146 - synchronized(this.timeoutMap)
64.147 - {
64.148 - Set<Object> keySet = new HashSet<Object>(this.timeoutMap.keySet());
64.149 - for(Object key : keySet)
64.150 - {
64.151 - // The key/value is removed by the checkTimeOut() method if true
64.152 - checkTimeOut(key);
64.153 - }
64.154 - }
64.155 - }
64.156 -
64.157 + @Override
64.158 + public boolean containsKey(Object key)
64.159 + {
64.160 + return checkTimeOut(key);
64.161 + }
64.162 +
64.163 + @Override
64.164 + public synchronized V get(Object key)
64.165 + {
64.166 + if (checkTimeOut(key)) {
64.167 + return super.get(key);
64.168 + } else {
64.169 + return null;
64.170 + }
64.171 + }
64.172 +
64.173 + @Override
64.174 + public V put(K key, V value)
64.175 + {
64.176 + synchronized (this.timeoutMap) {
64.177 + removeStaleKeys();
64.178 + this.timeoutMap.put(key, System.currentTimeMillis());
64.179 + return super.put(key, value);
64.180 + }
64.181 + }
64.182 +
64.183 + /**
64.184 + * @param arg0
64.185 + * @return
64.186 + */
64.187 + @Override
64.188 + public V remove(Object arg0)
64.189 + {
64.190 + synchronized (this.timeoutMap) {
64.191 + this.timeoutMap.remove(arg0);
64.192 + V val = super.remove(arg0);
64.193 + return val;
64.194 + }
64.195 + }
64.196 +
64.197 + protected void removeStaleKeys()
64.198 + {
64.199 + synchronized (this.timeoutMap) {
64.200 + Set<Object> keySet = new HashSet<Object>(this.timeoutMap.keySet());
64.201 + for (Object key : keySet) {
64.202 + // The key/value is removed by the checkTimeOut() method if true
64.203 + checkTimeOut(key);
64.204 + }
64.205 + }
64.206 + }
64.207 }
65.1 --- a/src/org/sonews/util/io/ArticleInputStream.java Sun Aug 29 17:43:58 2010 +0200
65.2 +++ b/src/org/sonews/util/io/ArticleInputStream.java Sun Aug 29 18:17:37 2010 +0200
65.3 @@ -32,40 +32,36 @@
65.4 public class ArticleInputStream extends InputStream
65.5 {
65.6
65.7 - private byte[] buf;
65.8 - private int pos = 0;
65.9 -
65.10 - public ArticleInputStream(final Article art)
65.11 - throws IOException, UnsupportedEncodingException
65.12 - {
65.13 - final ByteArrayOutputStream out = new ByteArrayOutputStream();
65.14 - out.write(art.getHeaderSource().getBytes("UTF-8"));
65.15 - out.write("\r\n\r\n".getBytes());
65.16 - out.write(art.getBody()); // Without CRLF
65.17 - out.flush();
65.18 - this.buf = out.toByteArray();
65.19 - }
65.20 + private byte[] buf;
65.21 + private int pos = 0;
65.22
65.23 - /**
65.24 - * This method reads one byte from the stream. The <code>pos</code>
65.25 - * counter is advanced to the next byte to be read. The byte read is
65.26 - * returned as an int in the range of 0-255. If the stream position
65.27 - * is already at the end of the buffer, no byte is read and a -1 is
65.28 - * returned in order to indicate the end of the stream.
65.29 - *
65.30 - * @return The byte read, or -1 if end of stream
65.31 - */
65.32 - @Override
65.33 - public synchronized int read()
65.34 - {
65.35 - if(pos < buf.length)
65.36 - {
65.37 - return ((int)buf[pos++]) & 0xFF;
65.38 - }
65.39 - else
65.40 - {
65.41 - return -1;
65.42 - }
65.43 - }
65.44 -
65.45 + public ArticleInputStream(final Article art)
65.46 + throws IOException, UnsupportedEncodingException
65.47 + {
65.48 + final ByteArrayOutputStream out = new ByteArrayOutputStream();
65.49 + out.write(art.getHeaderSource().getBytes("UTF-8"));
65.50 + out.write("\r\n\r\n".getBytes());
65.51 + out.write(art.getBody()); // Without CRLF
65.52 + out.flush();
65.53 + this.buf = out.toByteArray();
65.54 + }
65.55 +
65.56 + /**
65.57 + * This method reads one byte from the stream. The <code>pos</code>
65.58 + * counter is advanced to the next byte to be read. The byte read is
65.59 + * returned as an int in the range of 0-255. If the stream position
65.60 + * is already at the end of the buffer, no byte is read and a -1 is
65.61 + * returned in order to indicate the end of the stream.
65.62 + *
65.63 + * @return The byte read, or -1 if end of stream
65.64 + */
65.65 + @Override
65.66 + public synchronized int read()
65.67 + {
65.68 + if (pos < buf.length) {
65.69 + return ((int) buf[pos++]) & 0xFF;
65.70 + } else {
65.71 + return -1;
65.72 + }
65.73 + }
65.74 }
66.1 --- a/src/org/sonews/util/io/ArticleReader.java Sun Aug 29 17:43:58 2010 +0200
66.2 +++ b/src/org/sonews/util/io/ArticleReader.java Sun Aug 29 18:17:37 2010 +0200
66.3 @@ -34,102 +34,87 @@
66.4 * @author Christian Lins
66.5 * @since sonews/0.5.0
66.6 */
66.7 -public class ArticleReader
66.8 +public class ArticleReader
66.9 {
66.10
66.11 - private BufferedOutputStream out;
66.12 - private BufferedInputStream in;
66.13 - private String messageID;
66.14 -
66.15 - public ArticleReader(String host, int port, String messageID)
66.16 - throws IOException, UnknownHostException
66.17 - {
66.18 - this.messageID = messageID;
66.19 + private BufferedOutputStream out;
66.20 + private BufferedInputStream in;
66.21 + private String messageID;
66.22
66.23 - // Connect to NNTP server
66.24 - Socket socket = new Socket(host, port);
66.25 - this.out = new BufferedOutputStream(socket.getOutputStream());
66.26 - this.in = new BufferedInputStream(socket.getInputStream());
66.27 - String line = readln(this.in);
66.28 - if(!line.startsWith("200 "))
66.29 - {
66.30 - throw new IOException("Invalid hello from server: " + line);
66.31 - }
66.32 - }
66.33 -
66.34 - private boolean eofArticle(byte[] buf)
66.35 - {
66.36 - if(buf.length < 4)
66.37 - {
66.38 - return false;
66.39 - }
66.40 -
66.41 - int l = buf.length - 1;
66.42 - return buf[l-3] == 10 // '*\n'
66.43 - && buf[l-2] == '.' // '.'
66.44 - && buf[l-1] == 13 && buf[l] == 10; // '\r\n'
66.45 - }
66.46 -
66.47 - public byte[] getArticleData()
66.48 - throws IOException, UnsupportedEncodingException
66.49 - {
66.50 - long maxSize = Config.inst().get(Config.ARTICLE_MAXSIZE, 1024) * 1024L;
66.51 + public ArticleReader(String host, int port, String messageID)
66.52 + throws IOException, UnknownHostException
66.53 + {
66.54 + this.messageID = messageID;
66.55
66.56 - try
66.57 - {
66.58 - this.out.write(("ARTICLE " + this.messageID + "\r\n").getBytes("UTF-8"));
66.59 - this.out.flush();
66.60 + // Connect to NNTP server
66.61 + Socket socket = new Socket(host, port);
66.62 + this.out = new BufferedOutputStream(socket.getOutputStream());
66.63 + this.in = new BufferedInputStream(socket.getInputStream());
66.64 + String line = readln(this.in);
66.65 + if (!line.startsWith("200 ")) {
66.66 + throw new IOException("Invalid hello from server: " + line);
66.67 + }
66.68 + }
66.69
66.70 - String line = readln(this.in);
66.71 - if(line.startsWith("220 "))
66.72 - {
66.73 - ByteArrayOutputStream buf = new ByteArrayOutputStream();
66.74 -
66.75 - while(!eofArticle(buf.toByteArray()))
66.76 - {
66.77 - for(int b = in.read(); b != 10; b = in.read())
66.78 - {
66.79 - buf.write(b);
66.80 - }
66.81 + private boolean eofArticle(byte[] buf)
66.82 + {
66.83 + if (buf.length < 4) {
66.84 + return false;
66.85 + }
66.86
66.87 - buf.write(10);
66.88 - if(buf.size() > maxSize)
66.89 - {
66.90 - Log.get().warning("Skipping message that is too large: " + buf.size());
66.91 - return null;
66.92 - }
66.93 - }
66.94 -
66.95 - return buf.toByteArray();
66.96 - }
66.97 - else
66.98 - {
66.99 - Log.get().warning("ArticleReader: " + line);
66.100 - return null;
66.101 - }
66.102 - }
66.103 - catch(IOException ex)
66.104 - {
66.105 - throw ex;
66.106 - }
66.107 - finally
66.108 - {
66.109 - this.out.write("QUIT\r\n".getBytes("UTF-8"));
66.110 - this.out.flush();
66.111 - this.out.close();
66.112 - }
66.113 - }
66.114 -
66.115 - private String readln(InputStream in)
66.116 - throws IOException
66.117 - {
66.118 - ByteArrayOutputStream buf = new ByteArrayOutputStream();
66.119 - for(int b = in.read(); b != 10 /* \n */; b = in.read())
66.120 - {
66.121 - buf.write(b);
66.122 - }
66.123 -
66.124 - return new String(buf.toByteArray());
66.125 - }
66.126 + int l = buf.length - 1;
66.127 + return buf[l - 3] == 10 // '*\n'
66.128 + && buf[l - 2] == '.' // '.'
66.129 + && buf[l - 1] == 13 && buf[l] == 10; // '\r\n'
66.130 + }
66.131
66.132 + public byte[] getArticleData()
66.133 + throws IOException, UnsupportedEncodingException
66.134 + {
66.135 + long maxSize = Config.inst().get(Config.ARTICLE_MAXSIZE, 1024) * 1024L;
66.136 +
66.137 + try {
66.138 + this.out.write(("ARTICLE " + this.messageID + "\r\n").getBytes("UTF-8"));
66.139 + this.out.flush();
66.140 +
66.141 + String line = readln(this.in);
66.142 + if (line.startsWith("220 ")) {
66.143 + ByteArrayOutputStream buf = new ByteArrayOutputStream();
66.144 +
66.145 + while (!eofArticle(buf.toByteArray())) {
66.146 + for (int b = in.read(); b != 10; b = in.read()) {
66.147 + buf.write(b);
66.148 + }
66.149 +
66.150 + buf.write(10);
66.151 + if (buf.size() > maxSize) {
66.152 + Log.get().warning("Skipping message that is too large: " + buf.size());
66.153 + return null;
66.154 + }
66.155 + }
66.156 +
66.157 + return buf.toByteArray();
66.158 + } else {
66.159 + Log.get().warning("ArticleReader: " + line);
66.160 + return null;
66.161 + }
66.162 + } catch (IOException ex) {
66.163 + throw ex;
66.164 + } finally {
66.165 + this.out.write("QUIT\r\n".getBytes("UTF-8"));
66.166 + this.out.flush();
66.167 + this.out.close();
66.168 + }
66.169 + }
66.170 +
66.171 + private String readln(InputStream in)
66.172 + throws IOException
66.173 + {
66.174 + ByteArrayOutputStream buf = new ByteArrayOutputStream();
66.175 + for (int b = in.read(); b != 10 /* \n */; b = in.read()) {
66.176 + buf.write(b);
66.177 + }
66.178 +
66.179 + return new String(buf.toByteArray());
66.180 + }
66.181 }
67.1 --- a/src/org/sonews/util/io/ArticleWriter.java Sun Aug 29 17:43:58 2010 +0200
67.2 +++ b/src/org/sonews/util/io/ArticleWriter.java Sun Aug 29 18:17:37 2010 +0200
67.3 @@ -32,102 +32,97 @@
67.4 * @author Christian Lins
67.5 * @since sonews/0.5.0
67.6 */
67.7 -public class ArticleWriter
67.8 +public class ArticleWriter
67.9 {
67.10 -
67.11 - private BufferedOutputStream out;
67.12 - private BufferedReader inr;
67.13
67.14 - public ArticleWriter(String host, int port)
67.15 - throws IOException, UnknownHostException
67.16 - {
67.17 - // Connect to NNTP server
67.18 - Socket socket = new Socket(host, port);
67.19 - this.out = new BufferedOutputStream(socket.getOutputStream());
67.20 - this.inr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
67.21 - String line = inr.readLine();
67.22 - if(line == null || !line.startsWith("200 "))
67.23 - {
67.24 - throw new IOException("Invalid hello from server: " + line);
67.25 - }
67.26 - }
67.27 -
67.28 - public void close()
67.29 - throws IOException, UnsupportedEncodingException
67.30 - {
67.31 - this.out.write("QUIT\r\n".getBytes("UTF-8"));
67.32 - this.out.flush();
67.33 - }
67.34 + private BufferedOutputStream out;
67.35 + private BufferedReader inr;
67.36
67.37 - protected void finishPOST()
67.38 - throws IOException
67.39 - {
67.40 - this.out.write("\r\n.\r\n".getBytes());
67.41 - this.out.flush();
67.42 - String line = inr.readLine();
67.43 - if(line == null || !line.startsWith("240 ") || !line.startsWith("441 "))
67.44 - {
67.45 - throw new IOException(line);
67.46 - }
67.47 - }
67.48 + public ArticleWriter(String host, int port)
67.49 + throws IOException, UnknownHostException
67.50 + {
67.51 + // Connect to NNTP server
67.52 + Socket socket = new Socket(host, port);
67.53 + this.out = new BufferedOutputStream(socket.getOutputStream());
67.54 + this.inr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
67.55 + String line = inr.readLine();
67.56 + if (line == null || !line.startsWith("200 ")) {
67.57 + throw new IOException("Invalid hello from server: " + line);
67.58 + }
67.59 + }
67.60
67.61 - protected void preparePOST()
67.62 - throws IOException
67.63 - {
67.64 - this.out.write("POST\r\n".getBytes("UTF-8"));
67.65 - this.out.flush();
67.66 + public void close()
67.67 + throws IOException, UnsupportedEncodingException
67.68 + {
67.69 + this.out.write("QUIT\r\n".getBytes("UTF-8"));
67.70 + this.out.flush();
67.71 + }
67.72
67.73 - String line = this.inr.readLine();
67.74 - if(line == null || !line.startsWith("340 "))
67.75 - {
67.76 - throw new IOException(line);
67.77 - }
67.78 - }
67.79 + protected void finishPOST()
67.80 + throws IOException
67.81 + {
67.82 + this.out.write("\r\n.\r\n".getBytes());
67.83 + this.out.flush();
67.84 + String line = inr.readLine();
67.85 + if (line == null || !line.startsWith("240 ") || !line.startsWith("441 ")) {
67.86 + throw new IOException(line);
67.87 + }
67.88 + }
67.89
67.90 - public void writeArticle(Article article)
67.91 - throws IOException, UnsupportedEncodingException
67.92 - {
67.93 - byte[] buf = new byte[512];
67.94 - ArticleInputStream in = new ArticleInputStream(article);
67.95 + protected void preparePOST()
67.96 + throws IOException
67.97 + {
67.98 + this.out.write("POST\r\n".getBytes("UTF-8"));
67.99 + this.out.flush();
67.100
67.101 - preparePOST();
67.102 -
67.103 - int len = in.read(buf);
67.104 - while(len != -1)
67.105 - {
67.106 - writeLine(buf, len);
67.107 - len = in.read(buf);
67.108 - }
67.109 + String line = this.inr.readLine();
67.110 + if (line == null || !line.startsWith("340 ")) {
67.111 + throw new IOException(line);
67.112 + }
67.113 + }
67.114
67.115 - finishPOST();
67.116 - }
67.117 + public void writeArticle(Article article)
67.118 + throws IOException, UnsupportedEncodingException
67.119 + {
67.120 + byte[] buf = new byte[512];
67.121 + ArticleInputStream in = new ArticleInputStream(article);
67.122
67.123 - /**
67.124 - * Writes the raw content of an article to the remote server. This method
67.125 - * does no charset conversion/handling of any kind so its the preferred
67.126 - * method for sending an article to remote peers.
67.127 - * @param rawArticle
67.128 - * @throws IOException
67.129 - */
67.130 - public void writeArticle(byte[] rawArticle)
67.131 - throws IOException
67.132 - {
67.133 - preparePOST();
67.134 - writeLine(rawArticle, rawArticle.length);
67.135 - finishPOST();
67.136 - }
67.137 + preparePOST();
67.138
67.139 - /**
67.140 - * Writes the given buffer to the connect remote server.
67.141 - * @param buffer
67.142 - * @param len
67.143 - * @throws IOException
67.144 - */
67.145 - protected void writeLine(byte[] buffer, int len)
67.146 - throws IOException
67.147 - {
67.148 - this.out.write(buffer, 0, len);
67.149 - this.out.flush();
67.150 - }
67.151 + int len = in.read(buf);
67.152 + while (len != -1) {
67.153 + writeLine(buf, len);
67.154 + len = in.read(buf);
67.155 + }
67.156
67.157 + finishPOST();
67.158 + }
67.159 +
67.160 + /**
67.161 + * Writes the raw content of an article to the remote server. This method
67.162 + * does no charset conversion/handling of any kind so its the preferred
67.163 + * method for sending an article to remote peers.
67.164 + * @param rawArticle
67.165 + * @throws IOException
67.166 + */
67.167 + public void writeArticle(byte[] rawArticle)
67.168 + throws IOException
67.169 + {
67.170 + preparePOST();
67.171 + writeLine(rawArticle, rawArticle.length);
67.172 + finishPOST();
67.173 + }
67.174 +
67.175 + /**
67.176 + * Writes the given buffer to the connect remote server.
67.177 + * @param buffer
67.178 + * @param len
67.179 + * @throws IOException
67.180 + */
67.181 + protected void writeLine(byte[] buffer, int len)
67.182 + throws IOException
67.183 + {
67.184 + this.out.write(buffer, 0, len);
67.185 + this.out.flush();
67.186 + }
67.187 }
68.1 --- a/src/org/sonews/util/io/Resource.java Sun Aug 29 17:43:58 2010 +0200
68.2 +++ b/src/org/sonews/util/io/Resource.java Sun Aug 29 18:17:37 2010 +0200
68.3 @@ -32,101 +32,89 @@
68.4 */
68.5 public final class Resource
68.6 {
68.7 -
68.8 - /**
68.9 - * Loads a resource and returns it as URL reference.
68.10 - * The Resource's classloader is used to load the resource, not
68.11 - * the System's ClassLoader so it may be safe to use this method
68.12 - * in a sandboxed environment.
68.13 - * @return
68.14 - */
68.15 - public static URL getAsURL(final String name)
68.16 - {
68.17 - if(name == null)
68.18 - {
68.19 - return null;
68.20 - }
68.21
68.22 - return Resource.class.getClassLoader().getResource(name);
68.23 - }
68.24 -
68.25 - /**
68.26 - * Loads a resource and returns an InputStream to it.
68.27 - * @param name
68.28 - * @return
68.29 - */
68.30 - public static InputStream getAsStream(String name)
68.31 - {
68.32 - try
68.33 - {
68.34 - URL url = getAsURL(name);
68.35 - if(url == null)
68.36 - {
68.37 - return null;
68.38 - }
68.39 - else
68.40 - {
68.41 - return url.openStream();
68.42 - }
68.43 - }
68.44 - catch(IOException e)
68.45 - {
68.46 - e.printStackTrace();
68.47 - return null;
68.48 - }
68.49 - }
68.50 + /**
68.51 + * Loads a resource and returns it as URL reference.
68.52 + * The Resource's classloader is used to load the resource, not
68.53 + * the System's ClassLoader so it may be safe to use this method
68.54 + * in a sandboxed environment.
68.55 + * @return
68.56 + */
68.57 + public static URL getAsURL(final String name)
68.58 + {
68.59 + if (name == null) {
68.60 + return null;
68.61 + }
68.62
68.63 - /**
68.64 - * Loads a plain text resource.
68.65 - * @param withNewline If false all newlines are removed from the
68.66 - * return String
68.67 - */
68.68 - public static String getAsString(String name, boolean withNewline)
68.69 - {
68.70 - if(name == null)
68.71 - return null;
68.72 + return Resource.class.getClassLoader().getResource(name);
68.73 + }
68.74
68.75 - BufferedReader in = null;
68.76 - try
68.77 - {
68.78 - InputStream ins = getAsStream(name);
68.79 - if(ins == null)
68.80 - return null;
68.81 + /**
68.82 + * Loads a resource and returns an InputStream to it.
68.83 + * @param name
68.84 + * @return
68.85 + */
68.86 + public static InputStream getAsStream(String name)
68.87 + {
68.88 + try {
68.89 + URL url = getAsURL(name);
68.90 + if (url == null) {
68.91 + return null;
68.92 + } else {
68.93 + return url.openStream();
68.94 + }
68.95 + } catch (IOException e) {
68.96 + e.printStackTrace();
68.97 + return null;
68.98 + }
68.99 + }
68.100
68.101 - in = new BufferedReader(
68.102 - new InputStreamReader(ins, Charset.forName("UTF-8")));
68.103 - StringBuffer buf = new StringBuffer();
68.104 + /**
68.105 + * Loads a plain text resource.
68.106 + * @param withNewline If false all newlines are removed from the
68.107 + * return String
68.108 + */
68.109 + public static String getAsString(String name, boolean withNewline)
68.110 + {
68.111 + if (name == null) {
68.112 + return null;
68.113 + }
68.114
68.115 - for(;;)
68.116 - {
68.117 - String line = in.readLine();
68.118 - if(line == null)
68.119 - break;
68.120 + BufferedReader in = null;
68.121 + try {
68.122 + InputStream ins = getAsStream(name);
68.123 + if (ins == null) {
68.124 + return null;
68.125 + }
68.126
68.127 - buf.append(line);
68.128 - if(withNewline)
68.129 - buf.append('\n');
68.130 - }
68.131 + in = new BufferedReader(
68.132 + new InputStreamReader(ins, Charset.forName("UTF-8")));
68.133 + StringBuffer buf = new StringBuffer();
68.134
68.135 - return buf.toString();
68.136 - }
68.137 - catch(Exception e)
68.138 - {
68.139 - e.printStackTrace();
68.140 - return null;
68.141 - }
68.142 - finally
68.143 - {
68.144 - try
68.145 - {
68.146 - if(in != null)
68.147 - in.close();
68.148 - }
68.149 - catch(IOException ex)
68.150 - {
68.151 - ex.printStackTrace();
68.152 - }
68.153 - }
68.154 - }
68.155 + for (;;) {
68.156 + String line = in.readLine();
68.157 + if (line == null) {
68.158 + break;
68.159 + }
68.160
68.161 + buf.append(line);
68.162 + if (withNewline) {
68.163 + buf.append('\n');
68.164 + }
68.165 + }
68.166 +
68.167 + return buf.toString();
68.168 + } catch (Exception e) {
68.169 + e.printStackTrace();
68.170 + return null;
68.171 + } finally {
68.172 + try {
68.173 + if (in != null) {
68.174 + in.close();
68.175 + }
68.176 + } catch (IOException ex) {
68.177 + ex.printStackTrace();
68.178 + }
68.179 + }
68.180 + }
68.181 }