Moving source files into src/-subdir.
1.1 --- a/org/sonews/Main.java Sun Aug 29 17:04:25 2010 +0200
1.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1.3 @@ -1,198 +0,0 @@
1.4 -/*
1.5 - * SONEWS News Server
1.6 - * see AUTHORS for the list of contributors
1.7 - *
1.8 - * This program is free software: you can redistribute it and/or modify
1.9 - * it under the terms of the GNU General Public License as published by
1.10 - * the Free Software Foundation, either version 3 of the License, or
1.11 - * (at your option) any later version.
1.12 - *
1.13 - * This program is distributed in the hope that it will be useful,
1.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.16 - * GNU General Public License for more details.
1.17 - *
1.18 - * You should have received a copy of the GNU General Public License
1.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
1.20 - */
1.21 -
1.22 -package org.sonews;
1.23 -
1.24 -import java.sql.Driver;
1.25 -import java.sql.DriverManager;
1.26 -import java.util.Enumeration;
1.27 -import java.util.Date;
1.28 -import java.util.logging.Level;
1.29 -import org.sonews.config.Config;
1.30 -import org.sonews.daemon.ChannelLineBuffers;
1.31 -import org.sonews.daemon.CommandSelector;
1.32 -import org.sonews.daemon.Connections;
1.33 -import org.sonews.daemon.NNTPDaemon;
1.34 -import org.sonews.feed.FeedManager;
1.35 -import org.sonews.mlgw.MailPoller;
1.36 -import org.sonews.storage.StorageBackendException;
1.37 -import org.sonews.storage.StorageManager;
1.38 -import org.sonews.storage.StorageProvider;
1.39 -import org.sonews.util.Log;
1.40 -import org.sonews.util.Purger;
1.41 -import org.sonews.util.io.Resource;
1.42 -
1.43 -/**
1.44 - * Startup class of the daemon.
1.45 - * @author Christian Lins
1.46 - * @since sonews/0.5.0
1.47 - */
1.48 -public final class Main
1.49 -{
1.50 -
1.51 - private Main()
1.52 - {
1.53 - }
1.54 -
1.55 - /** Version information of the sonews daemon */
1.56 - public static final String VERSION = "sonews/1.1.0";
1.57 - public static final Date STARTDATE = new Date();
1.58 -
1.59 - /**
1.60 - * The main entrypoint.
1.61 - * @param args
1.62 - * @throws Exception
1.63 - */
1.64 - public static void main(String[] args) throws Exception
1.65 - {
1.66 - System.out.println(VERSION);
1.67 - Thread.currentThread().setName("Mainthread");
1.68 -
1.69 - // Command line arguments
1.70 - boolean feed = false; // Enable feeding?
1.71 - boolean mlgw = false; // Enable Mailinglist gateway?
1.72 - int port = -1;
1.73 -
1.74 - for(int n = 0; n < args.length; n++)
1.75 - {
1.76 - if(args[n].equals("-c") || args[n].equals("-config"))
1.77 - {
1.78 - Config.inst().set(Config.LEVEL_CLI, Config.CONFIGFILE, args[++n]);
1.79 - System.out.println("Using config file " + args[n]);
1.80 - }
1.81 - else if(args[n].equals("-dumpjdbcdriver"))
1.82 - {
1.83 - System.out.println("Available JDBC drivers:");
1.84 - Enumeration<Driver> drvs = DriverManager.getDrivers();
1.85 - while(drvs.hasMoreElements())
1.86 - {
1.87 - System.out.println(drvs.nextElement());
1.88 - }
1.89 - return;
1.90 - }
1.91 - else if(args[n].equals("-feed"))
1.92 - {
1.93 - feed = true;
1.94 - }
1.95 - else if(args[n].equals("-h") || args[n].equals("-help"))
1.96 - {
1.97 - printArguments();
1.98 - return;
1.99 - }
1.100 - else if(args[n].equals("-mlgw"))
1.101 - {
1.102 - mlgw = true;
1.103 - }
1.104 - else if(args[n].equals("-p"))
1.105 - {
1.106 - port = Integer.parseInt(args[++n]);
1.107 - }
1.108 - else if(args[n].equals("-plugin"))
1.109 - {
1.110 - System.out.println("Warning: -plugin-storage is not implemented!");
1.111 - }
1.112 - else if(args[n].equals("-plugin-command"))
1.113 - {
1.114 - try
1.115 - {
1.116 - CommandSelector.addCommandHandler(args[++n]);
1.117 - }
1.118 - catch(Exception ex)
1.119 - {
1.120 - Log.get().warning("Could not load command plugin: " + args[n]);
1.121 - Log.get().log(Level.INFO, "Main.java", ex);
1.122 - }
1.123 - }
1.124 - else if(args[n].equals("-plugin-storage"))
1.125 - {
1.126 - System.out.println("Warning: -plugin-storage is not implemented!");
1.127 - }
1.128 - else if(args[n].equals("-v") || args[n].equals("-version"))
1.129 - {
1.130 - // Simply return as the version info is already printed above
1.131 - return;
1.132 - }
1.133 - }
1.134 -
1.135 - // Try to load the JDBCDatabase;
1.136 - // Do NOT USE BackendConfig or Log classes before this point because they require
1.137 - // a working JDBCDatabase connection.
1.138 - try
1.139 - {
1.140 - StorageProvider sprov =
1.141 - StorageManager.loadProvider("org.sonews.storage.impl.JDBCDatabaseProvider");
1.142 - StorageManager.enableProvider(sprov);
1.143 -
1.144 - // Make sure some elementary groups are existing
1.145 - if(!StorageManager.current().isGroupExisting("control"))
1.146 - {
1.147 - StorageManager.current().addGroup("control", 0);
1.148 - Log.get().info("Group 'control' created.");
1.149 - }
1.150 - }
1.151 - catch(StorageBackendException ex)
1.152 - {
1.153 - ex.printStackTrace();
1.154 - System.err.println("Database initialization failed with " + ex.toString());
1.155 - System.err.println("Make sure you have specified the correct database" +
1.156 - " settings in sonews.conf!");
1.157 - return;
1.158 - }
1.159 -
1.160 - ChannelLineBuffers.allocateDirect();
1.161 -
1.162 - // Add shutdown hook
1.163 - Runtime.getRuntime().addShutdownHook(new ShutdownHook());
1.164 -
1.165 - // Start the listening daemon
1.166 - if(port <= 0)
1.167 - {
1.168 - port = Config.inst().get(Config.PORT, 119);
1.169 - }
1.170 - final NNTPDaemon daemon = NNTPDaemon.createInstance(port);
1.171 - daemon.start();
1.172 -
1.173 - // Start Connections purger thread...
1.174 - Connections.getInstance().start();
1.175 -
1.176 - // Start mailinglist gateway...
1.177 - if(mlgw)
1.178 - {
1.179 - new MailPoller().start();
1.180 - }
1.181 -
1.182 - // Start feeds
1.183 - if(feed)
1.184 - {
1.185 - FeedManager.startFeeding();
1.186 - }
1.187 -
1.188 - Purger purger = new Purger();
1.189 - purger.start();
1.190 -
1.191 - // Wait for main thread to exit (setDaemon(false))
1.192 - daemon.join();
1.193 - }
1.194 -
1.195 - private static void printArguments()
1.196 - {
1.197 - String usage = Resource.getAsString("helpers/usage", true);
1.198 - System.out.println(usage);
1.199 - }
1.200 -
1.201 -}
2.1 --- a/org/sonews/ShutdownHook.java Sun Aug 29 17:04:25 2010 +0200
2.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2.3 @@ -1,84 +0,0 @@
2.4 -/*
2.5 - * SONEWS News Server
2.6 - * see AUTHORS for the list of contributors
2.7 - *
2.8 - * This program is free software: you can redistribute it and/or modify
2.9 - * it under the terms of the GNU General Public License as published by
2.10 - * the Free Software Foundation, either version 3 of the License, or
2.11 - * (at your option) any later version.
2.12 - *
2.13 - * This program is distributed in the hope that it will be useful,
2.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
2.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2.16 - * GNU General Public License for more details.
2.17 - *
2.18 - * You should have received a copy of the GNU General Public License
2.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
2.20 - */
2.21 -
2.22 -package org.sonews;
2.23 -
2.24 -import java.sql.SQLException;
2.25 -import java.util.Map;
2.26 -import org.sonews.daemon.AbstractDaemon;
2.27 -
2.28 -/**
2.29 - * Will force all other threads to shutdown cleanly.
2.30 - * @author Christian Lins
2.31 - * @since sonews/0.5.0
2.32 - */
2.33 -class ShutdownHook extends Thread
2.34 -{
2.35 -
2.36 - /**
2.37 - * Called when the JVM exits.
2.38 - */
2.39 - @Override
2.40 - public void run()
2.41 - {
2.42 - System.out.println("sonews: Trying to shutdown all threads...");
2.43 -
2.44 - Map<Thread, StackTraceElement[]> threadsMap = Thread.getAllStackTraces();
2.45 - for(Thread thread : threadsMap.keySet())
2.46 - {
2.47 - // Interrupt the thread if it's a AbstractDaemon
2.48 - AbstractDaemon daemon;
2.49 - if(thread instanceof AbstractDaemon && thread.isAlive())
2.50 - {
2.51 - try
2.52 - {
2.53 - daemon = (AbstractDaemon)thread;
2.54 - daemon.shutdownNow();
2.55 - }
2.56 - catch(SQLException ex)
2.57 - {
2.58 - System.out.println("sonews: " + ex);
2.59 - }
2.60 - }
2.61 - }
2.62 -
2.63 - for(Thread thread : threadsMap.keySet())
2.64 - {
2.65 - AbstractDaemon daemon;
2.66 - if(thread instanceof AbstractDaemon && thread.isAlive())
2.67 - {
2.68 - daemon = (AbstractDaemon)thread;
2.69 - System.out.println("sonews: Waiting for " + daemon + " to exit...");
2.70 - try
2.71 - {
2.72 - daemon.join(500);
2.73 - }
2.74 - catch(InterruptedException ex)
2.75 - {
2.76 - System.out.println(ex.getLocalizedMessage());
2.77 - }
2.78 - }
2.79 - }
2.80 -
2.81 - // We have notified all not-sleeping AbstractDaemons of the shutdown;
2.82 - // all other threads can be simply purged on VM shutdown
2.83 -
2.84 - System.out.println("sonews: Clean shutdown.");
2.85 - }
2.86 -
2.87 -}
3.1 --- a/org/sonews/acl/AccessControl.java Sun Aug 29 17:04:25 2010 +0200
3.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
3.3 @@ -1,31 +0,0 @@
3.4 -/*
3.5 - * SONEWS News Server
3.6 - * see AUTHORS for the list of contributors
3.7 - *
3.8 - * This program is free software: you can redistribute it and/or modify
3.9 - * it under the terms of the GNU General Public License as published by
3.10 - * the Free Software Foundation, either version 3 of the License, or
3.11 - * (at your option) any later version.
3.12 - *
3.13 - * This program is distributed in the hope that it will be useful,
3.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
3.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3.16 - * GNU General Public License for more details.
3.17 - *
3.18 - * You should have received a copy of the GNU General Public License
3.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
3.20 - */
3.21 -
3.22 -package org.sonews.acl;
3.23 -
3.24 -/**
3.25 - *
3.26 - * @author Christian Lins
3.27 - * @since sonews/1.1
3.28 - */
3.29 -public interface AccessControl
3.30 -{
3.31 -
3.32 - boolean hasPermission(String user, char[] secret, String permission);
3.33 -
3.34 -}
4.1 --- a/org/sonews/acl/AuthInfoCommand.java Sun Aug 29 17:04:25 2010 +0200
4.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
4.3 @@ -1,64 +0,0 @@
4.4 -/*
4.5 - * SONEWS News Server
4.6 - * see AUTHORS for the list of contributors
4.7 - *
4.8 - * This program is free software: you can redistribute it and/or modify
4.9 - * it under the terms of the GNU General Public License as published by
4.10 - * the Free Software Foundation, either version 3 of the License, or
4.11 - * (at your option) any later version.
4.12 - *
4.13 - * This program is distributed in the hope that it will be useful,
4.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
4.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4.16 - * GNU General Public License for more details.
4.17 - *
4.18 - * You should have received a copy of the GNU General Public License
4.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
4.20 - */
4.21 -
4.22 -package org.sonews.acl;
4.23 -
4.24 -import java.io.IOException;
4.25 -import org.sonews.daemon.NNTPConnection;
4.26 -import org.sonews.daemon.command.Command;
4.27 -import org.sonews.storage.StorageBackendException;
4.28 -
4.29 -/**
4.30 - *
4.31 - * @author Christian Lins
4.32 - * @since sonews/1.1
4.33 - */
4.34 -public class AuthInfoCommand implements Command
4.35 -{
4.36 -
4.37 - @Override
4.38 - public String[] getSupportedCommandStrings()
4.39 - {
4.40 - throw new UnsupportedOperationException("Not supported yet.");
4.41 - }
4.42 -
4.43 - @Override
4.44 - public boolean hasFinished()
4.45 - {
4.46 - throw new UnsupportedOperationException("Not supported yet.");
4.47 - }
4.48 -
4.49 - @Override
4.50 - public String impliedCapability()
4.51 - {
4.52 - throw new UnsupportedOperationException("Not supported yet.");
4.53 - }
4.54 -
4.55 - @Override
4.56 - public boolean isStateful()
4.57 - {
4.58 - throw new UnsupportedOperationException("Not supported yet.");
4.59 - }
4.60 -
4.61 - @Override
4.62 - public void processLine(NNTPConnection conn, String line, byte[] rawLine) throws IOException, StorageBackendException
4.63 - {
4.64 - throw new UnsupportedOperationException("Not supported yet.");
4.65 - }
4.66 -
4.67 -}
5.1 --- a/org/sonews/config/AbstractConfig.java Sun Aug 29 17:04:25 2010 +0200
5.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
5.3 @@ -1,57 +0,0 @@
5.4 -/*
5.5 - * SONEWS News Server
5.6 - * see AUTHORS for the list of contributors
5.7 - *
5.8 - * This program is free software: you can redistribute it and/or modify
5.9 - * it under the terms of the GNU General Public License as published by
5.10 - * the Free Software Foundation, either version 3 of the License, or
5.11 - * (at your option) any later version.
5.12 - *
5.13 - * This program is distributed in the hope that it will be useful,
5.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
5.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
5.16 - * GNU General Public License for more details.
5.17 - *
5.18 - * You should have received a copy of the GNU General Public License
5.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
5.20 - */
5.21 -
5.22 -package org.sonews.config;
5.23 -
5.24 -/**
5.25 - * Base class for Config and BootstrapConfig.
5.26 - * @author Christian Lins
5.27 - * @since sonews/0.5.0
5.28 - */
5.29 -public abstract class AbstractConfig
5.30 -{
5.31 -
5.32 - public abstract String get(String key, String defVal);
5.33 -
5.34 - public int get(final String key, final int defVal)
5.35 - {
5.36 - return Integer.parseInt(
5.37 - get(key, Integer.toString(defVal)));
5.38 - }
5.39 -
5.40 - public boolean get(String key, boolean defVal)
5.41 - {
5.42 - String val = get(key, Boolean.toString(defVal));
5.43 - return Boolean.parseBoolean(val);
5.44 - }
5.45 -
5.46 - /**
5.47 - * Returns a long config value specified via the given key.
5.48 - * @param key
5.49 - * @param defVal
5.50 - * @return
5.51 - */
5.52 - public long get(String key, long defVal)
5.53 - {
5.54 - String val = get(key, Long.toString(defVal));
5.55 - return Long.parseLong(val);
5.56 - }
5.57 -
5.58 - protected abstract void set(String key, String val);
5.59 -
5.60 -}
6.1 --- a/org/sonews/config/BackendConfig.java Sun Aug 29 17:04:25 2010 +0200
6.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
6.3 @@ -1,115 +0,0 @@
6.4 -/*
6.5 - * SONEWS News Server
6.6 - * see AUTHORS for the list of contributors
6.7 - *
6.8 - * This program is free software: you can redistribute it and/or modify
6.9 - * it under the terms of the GNU General Public License as published by
6.10 - * the Free Software Foundation, either version 3 of the License, or
6.11 - * (at your option) any later version.
6.12 - *
6.13 - * This program is distributed in the hope that it will be useful,
6.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
6.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
6.16 - * GNU General Public License for more details.
6.17 - *
6.18 - * You should have received a copy of the GNU General Public License
6.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
6.20 - */
6.21 -
6.22 -package org.sonews.config;
6.23 -
6.24 -import java.util.logging.Level;
6.25 -import org.sonews.util.Log;
6.26 -import org.sonews.storage.StorageBackendException;
6.27 -import org.sonews.storage.StorageManager;
6.28 -import org.sonews.util.TimeoutMap;
6.29 -
6.30 -/**
6.31 - * Provides access to the program wide configuration that is stored within
6.32 - * the server's database.
6.33 - * @author Christian Lins
6.34 - * @since sonews/0.5.0
6.35 - */
6.36 -class BackendConfig extends AbstractConfig
6.37 -{
6.38 -
6.39 - private static BackendConfig instance = new BackendConfig();
6.40 -
6.41 - public static BackendConfig getInstance()
6.42 - {
6.43 - return instance;
6.44 - }
6.45 -
6.46 - private final TimeoutMap<String, String> values
6.47 - = new TimeoutMap<String, String>();
6.48 -
6.49 - private BackendConfig()
6.50 - {
6.51 - super();
6.52 - }
6.53 -
6.54 - /**
6.55 - * Returns the config value for the given key or the defaultValue if the
6.56 - * key is not found in config.
6.57 - * @param key
6.58 - * @param defaultValue
6.59 - * @return
6.60 - */
6.61 - @Override
6.62 - public String get(String key, String defaultValue)
6.63 - {
6.64 - try
6.65 - {
6.66 - String configValue = values.get(key);
6.67 - if(configValue == null)
6.68 - {
6.69 - if(StorageManager.current() == null)
6.70 - {
6.71 - Log.get().warning("BackendConfig not available, using default.");
6.72 - return defaultValue;
6.73 - }
6.74 -
6.75 - configValue = StorageManager.current().getConfigValue(key);
6.76 - if(configValue == null)
6.77 - {
6.78 - return defaultValue;
6.79 - }
6.80 - else
6.81 - {
6.82 - values.put(key, configValue);
6.83 - return configValue;
6.84 - }
6.85 - }
6.86 - else
6.87 - {
6.88 - return configValue;
6.89 - }
6.90 - }
6.91 - catch(StorageBackendException ex)
6.92 - {
6.93 - Log.get().log(Level.SEVERE, "Storage backend problem", ex);
6.94 - return defaultValue;
6.95 - }
6.96 - }
6.97 -
6.98 - /**
6.99 - * Sets the config value which is identified by the given key.
6.100 - * @param key
6.101 - * @param value
6.102 - */
6.103 - public void set(String key, String value)
6.104 - {
6.105 - values.put(key, value);
6.106 -
6.107 - try
6.108 - {
6.109 - // Write values to database
6.110 - StorageManager.current().setConfigValue(key, value);
6.111 - }
6.112 - catch(StorageBackendException ex)
6.113 - {
6.114 - ex.printStackTrace();
6.115 - }
6.116 - }
6.117 -
6.118 -}
7.1 --- a/org/sonews/config/CommandLineConfig.java Sun Aug 29 17:04:25 2010 +0200
7.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
7.3 @@ -1,64 +0,0 @@
7.4 -/*
7.5 - * SONEWS News Server
7.6 - * see AUTHORS for the list of contributors
7.7 - *
7.8 - * This program is free software: you can redistribute it and/or modify
7.9 - * it under the terms of the GNU General Public License as published by
7.10 - * the Free Software Foundation, either version 3 of the License, or
7.11 - * (at your option) any later version.
7.12 - *
7.13 - * This program is distributed in the hope that it will be useful,
7.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
7.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
7.16 - * GNU General Public License for more details.
7.17 - *
7.18 - * You should have received a copy of the GNU General Public License
7.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
7.20 - */
7.21 -
7.22 -package org.sonews.config;
7.23 -
7.24 -import java.util.Map;
7.25 -import java.util.HashMap;
7.26 -
7.27 -/**
7.28 - *
7.29 - * @author Christian Lins
7.30 - */
7.31 -class CommandLineConfig extends AbstractConfig
7.32 -{
7.33 -
7.34 - private static final CommandLineConfig instance = new CommandLineConfig();
7.35 -
7.36 - public static CommandLineConfig getInstance()
7.37 - {
7.38 - return instance;
7.39 - }
7.40 -
7.41 - private final Map<String, String> values = new HashMap<String, String>();
7.42 -
7.43 - private CommandLineConfig() {}
7.44 -
7.45 - @Override
7.46 - public String get(String key, String def)
7.47 - {
7.48 - synchronized(this.values)
7.49 - {
7.50 - if(this.values.containsKey(key))
7.51 - {
7.52 - def = this.values.get(key);
7.53 - }
7.54 - }
7.55 - return def;
7.56 - }
7.57 -
7.58 - @Override
7.59 - public void set(String key, String val)
7.60 - {
7.61 - synchronized(this.values)
7.62 - {
7.63 - this.values.put(key, val);
7.64 - }
7.65 - }
7.66 -
7.67 -}
8.1 --- a/org/sonews/config/Config.java Sun Aug 29 17:04:25 2010 +0200
8.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
8.3 @@ -1,175 +0,0 @@
8.4 -/*
8.5 - * SONEWS News Server
8.6 - * see AUTHORS for the list of contributors
8.7 - *
8.8 - * This program is free software: you can redistribute it and/or modify
8.9 - * it under the terms of the GNU General Public License as published by
8.10 - * the Free Software Foundation, either version 3 of the License, or
8.11 - * (at your option) any later version.
8.12 - *
8.13 - * This program is distributed in the hope that it will be useful,
8.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
8.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8.16 - * GNU General Public License for more details.
8.17 - *
8.18 - * You should have received a copy of the GNU General Public License
8.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
8.20 - */
8.21 -
8.22 -package org.sonews.config;
8.23 -
8.24 -/**
8.25 - * Configuration facade class.
8.26 - * @author Christian Lins
8.27 - * @since sonews/1.0
8.28 - */
8.29 -public class Config extends AbstractConfig
8.30 -{
8.31 -
8.32 - public static final int LEVEL_CLI = 1;
8.33 - public static final int LEVEL_FILE = 2;
8.34 - public static final int LEVEL_BACKEND = 3;
8.35 -
8.36 - public static final String CONFIGFILE = "sonews.configfile";
8.37 -
8.38 - /** BackendConfig key constant. Value is the maximum article size in kilobytes. */
8.39 - public static final String ARTICLE_MAXSIZE = "sonews.article.maxsize";
8.40 -
8.41 - /** BackendConfig key constant. Value: Amount of news that are feeded per run. */
8.42 - public static final String EVENTLOG = "sonews.eventlog";
8.43 - public static final String FEED_NEWSPERRUN = "sonews.feed.newsperrun";
8.44 - public static final String FEED_PULLINTERVAL = "sonews.feed.pullinterval";
8.45 - public static final String HOSTNAME = "sonews.hostname";
8.46 - public static final String PORT = "sonews.port";
8.47 - public static final String TIMEOUT = "sonews.timeout";
8.48 - public static final String LOGLEVEL = "sonews.loglevel";
8.49 - public static final String MLPOLL_DELETEUNKNOWN = "sonews.mlpoll.deleteunknown";
8.50 - public static final String MLPOLL_HOST = "sonews.mlpoll.host";
8.51 - public static final String MLPOLL_PASSWORD = "sonews.mlpoll.password";
8.52 - public static final String MLPOLL_USER = "sonews.mlpoll.user";
8.53 - public static final String MLSEND_ADDRESS = "sonews.mlsend.address";
8.54 - public static final String MLSEND_RW_FROM = "sonews.mlsend.rewrite.from";
8.55 - public static final String MLSEND_RW_SENDER = "sonews.mlsend.rewrite.sender";
8.56 - public static final String MLSEND_HOST = "sonews.mlsend.host";
8.57 - public static final String MLSEND_PASSWORD = "sonews.mlsend.password";
8.58 - public static final String MLSEND_PORT = "sonews.mlsend.port";
8.59 - public static final String MLSEND_USER = "sonews.mlsend.user";
8.60 -
8.61 - /** Key constant. If value is "true" every I/O is written to logfile
8.62 - * (which is a lot!)
8.63 - */
8.64 - public static final String DEBUG = "sonews.debug";
8.65 -
8.66 - /** Key constant. Value is classname of the JDBC driver */
8.67 - public static final String STORAGE_DBMSDRIVER = "sonews.storage.dbmsdriver";
8.68 -
8.69 - /** Key constant. Value is JDBC connect String to the database. */
8.70 - public static final String STORAGE_DATABASE = "sonews.storage.database";
8.71 -
8.72 - /** Key constant. Value is the username for the DBMS. */
8.73 - public static final String STORAGE_USER = "sonews.storage.user";
8.74 -
8.75 - /** Key constant. Value is the password for the DBMS. */
8.76 - public static final String STORAGE_PASSWORD = "sonews.storage.password";
8.77 -
8.78 - /** Key constant. Value is the name of the host which is allowed to use the
8.79 - * XDAEMON command; default: "localhost" */
8.80 - public static final String XDAEMON_HOST = "sonews.xdaemon.host";
8.81 -
8.82 - /** The config key for the filename of the logfile */
8.83 - public static final String LOGFILE = "sonews.log";
8.84 -
8.85 - public static final String[] AVAILABLE_KEYS = {
8.86 - ARTICLE_MAXSIZE,
8.87 - EVENTLOG,
8.88 - FEED_NEWSPERRUN,
8.89 - FEED_PULLINTERVAL,
8.90 - HOSTNAME,
8.91 - MLPOLL_DELETEUNKNOWN,
8.92 - MLPOLL_HOST,
8.93 - MLPOLL_PASSWORD,
8.94 - MLPOLL_USER,
8.95 - MLSEND_ADDRESS,
8.96 - MLSEND_HOST,
8.97 - MLSEND_PASSWORD,
8.98 - MLSEND_PORT,
8.99 - MLSEND_RW_FROM,
8.100 - MLSEND_RW_SENDER,
8.101 - MLSEND_USER,
8.102 - PORT,
8.103 - TIMEOUT,
8.104 - XDAEMON_HOST
8.105 - };
8.106 -
8.107 - private static Config instance = new Config();
8.108 -
8.109 - public static Config inst()
8.110 - {
8.111 - return instance;
8.112 - }
8.113 -
8.114 - private Config(){}
8.115 -
8.116 - @Override
8.117 - public String get(String key, String def)
8.118 - {
8.119 - String val = CommandLineConfig.getInstance().get(key, null);
8.120 -
8.121 - if(val == null)
8.122 - {
8.123 - val = FileConfig.getInstance().get(key, null);
8.124 - }
8.125 -
8.126 - if(val == null)
8.127 - {
8.128 - val = BackendConfig.getInstance().get(key, def);
8.129 - }
8.130 -
8.131 - return val;
8.132 - }
8.133 -
8.134 - public String get(int maxLevel, String key, String def)
8.135 - {
8.136 - String val = CommandLineConfig.getInstance().get(key, null);
8.137 -
8.138 - if(val == null && maxLevel >= LEVEL_FILE)
8.139 - {
8.140 - val = FileConfig.getInstance().get(key, null);
8.141 - if(val == null && maxLevel >= LEVEL_BACKEND)
8.142 - {
8.143 - val = BackendConfig.getInstance().get(key, def);
8.144 - }
8.145 - }
8.146 -
8.147 - return val != null ? val : def;
8.148 - }
8.149 -
8.150 - @Override
8.151 - public void set(String key, String val)
8.152 - {
8.153 - set(LEVEL_BACKEND, key, val);
8.154 - }
8.155 -
8.156 - public void set(int level, String key, String val)
8.157 - {
8.158 - switch(level)
8.159 - {
8.160 - case LEVEL_CLI:
8.161 - {
8.162 - CommandLineConfig.getInstance().set(key, val);
8.163 - break;
8.164 - }
8.165 - case LEVEL_FILE:
8.166 - {
8.167 - FileConfig.getInstance().set(key, val);
8.168 - break;
8.169 - }
8.170 - case LEVEL_BACKEND:
8.171 - {
8.172 - BackendConfig.getInstance().set(key, val);
8.173 - break;
8.174 - }
8.175 - }
8.176 - }
8.177 -
8.178 -}
9.1 --- a/org/sonews/config/FileConfig.java Sun Aug 29 17:04:25 2010 +0200
9.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
9.3 @@ -1,170 +0,0 @@
9.4 -/*
9.5 - * SONEWS News Server
9.6 - * see AUTHORS for the list of contributors
9.7 - *
9.8 - * This program is free software: you can redistribute it and/or modify
9.9 - * it under the terms of the GNU General Public License as published by
9.10 - * the Free Software Foundation, either version 3 of the License, or
9.11 - * (at your option) any later version.
9.12 - *
9.13 - * This program is distributed in the hope that it will be useful,
9.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
9.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9.16 - * GNU General Public License for more details.
9.17 - *
9.18 - * You should have received a copy of the GNU General Public License
9.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
9.20 - */
9.21 -
9.22 -package org.sonews.config;
9.23 -
9.24 -import java.io.FileInputStream;
9.25 -import java.io.FileNotFoundException;
9.26 -import java.io.FileOutputStream;
9.27 -import java.io.IOException;
9.28 -import java.util.Properties;
9.29 -
9.30 -/**
9.31 - * Manages the bootstrap configuration. It MUST contain all config values
9.32 - * that are needed to establish a database connection.
9.33 - * For further configuration values use the Config class instead as that class
9.34 - * stores its values within the database.
9.35 - * @author Christian Lins
9.36 - * @since sonews/0.5.0
9.37 - */
9.38 -class FileConfig extends AbstractConfig
9.39 -{
9.40 -
9.41 - private static final Properties defaultConfig = new Properties();
9.42 -
9.43 - private static FileConfig instance = null;
9.44 -
9.45 - static
9.46 - {
9.47 - // Set some default values
9.48 - defaultConfig.setProperty(Config.STORAGE_DATABASE, "jdbc:mysql://localhost/sonews");
9.49 - defaultConfig.setProperty(Config.STORAGE_DBMSDRIVER, "com.mysql.jdbc.Driver");
9.50 - defaultConfig.setProperty(Config.STORAGE_USER, "sonews_user");
9.51 - defaultConfig.setProperty(Config.STORAGE_PASSWORD, "mysecret");
9.52 - defaultConfig.setProperty(Config.DEBUG, "false");
9.53 - }
9.54 -
9.55 - /**
9.56 - * Note: this method is not thread-safe
9.57 - * @return A Config instance
9.58 - */
9.59 - public static synchronized FileConfig getInstance()
9.60 - {
9.61 - if(instance == null)
9.62 - {
9.63 - instance = new FileConfig();
9.64 - }
9.65 - return instance;
9.66 - }
9.67 -
9.68 - // Every config instance is initialized with the default values.
9.69 - private final Properties settings = (Properties)defaultConfig.clone();
9.70 -
9.71 - /**
9.72 - * Config is a singelton class with only one instance at time.
9.73 - * So the constructor is private to prevent the creation of more
9.74 - * then one Config instance.
9.75 - * @see Config.getInstance() to retrieve an instance of Config
9.76 - */
9.77 - private FileConfig()
9.78 - {
9.79 - try
9.80 - {
9.81 - // Load settings from file
9.82 - load();
9.83 - }
9.84 - catch(IOException ex)
9.85 - {
9.86 - ex.printStackTrace();
9.87 - }
9.88 - }
9.89 -
9.90 - /**
9.91 - * Loads the configuration from the config file. By default this is done
9.92 - * by the (private) constructor but it can be useful to reload the config
9.93 - * by invoking this method.
9.94 - * @throws IOException
9.95 - */
9.96 - public void load()
9.97 - throws IOException
9.98 - {
9.99 - FileInputStream in = null;
9.100 -
9.101 - try
9.102 - {
9.103 - in = new FileInputStream(
9.104 - Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
9.105 - settings.load(in);
9.106 - }
9.107 - catch (FileNotFoundException e)
9.108 - {
9.109 - // MUST NOT use Log otherwise endless loop
9.110 - System.err.println(e.getMessage());
9.111 - save();
9.112 - }
9.113 - finally
9.114 - {
9.115 - if(in != null)
9.116 - in.close();
9.117 - }
9.118 - }
9.119 -
9.120 - /**
9.121 - * Saves this Config to the config file. By default this is done
9.122 - * at program end.
9.123 - * @throws FileNotFoundException
9.124 - * @throws IOException
9.125 - */
9.126 - public void save() throws FileNotFoundException, IOException
9.127 - {
9.128 - FileOutputStream out = null;
9.129 - try
9.130 - {
9.131 - out = new FileOutputStream(
9.132 - Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
9.133 - settings.store(out, "SONEWS Config File");
9.134 - out.flush();
9.135 - }
9.136 - catch(IOException ex)
9.137 - {
9.138 - throw ex;
9.139 - }
9.140 - finally
9.141 - {
9.142 - if(out != null)
9.143 - out.close();
9.144 - }
9.145 - }
9.146 -
9.147 - /**
9.148 - * Returns the value that is stored within this config
9.149 - * identified by the given key. If the key cannot be found
9.150 - * the default value is returned.
9.151 - * @param key Key to identify the value.
9.152 - * @param def The default value that is returned if the key
9.153 - * is not found in this Config.
9.154 - * @return
9.155 - */
9.156 - @Override
9.157 - public String get(String key, String def)
9.158 - {
9.159 - return settings.getProperty(key, def);
9.160 - }
9.161 -
9.162 - /**
9.163 - * Sets the value for a given key.
9.164 - * @param key
9.165 - * @param value
9.166 - */
9.167 - @Override
9.168 - public void set(final String key, final String value)
9.169 - {
9.170 - settings.setProperty(key, value);
9.171 - }
9.172 -
9.173 -}
10.1 --- a/org/sonews/daemon/AbstractDaemon.java Sun Aug 29 17:04:25 2010 +0200
10.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
10.3 @@ -1,101 +0,0 @@
10.4 -/*
10.5 - * SONEWS News Server
10.6 - * see AUTHORS for the list of contributors
10.7 - *
10.8 - * This program is free software: you can redistribute it and/or modify
10.9 - * it under the terms of the GNU General Public License as published by
10.10 - * the Free Software Foundation, either version 3 of the License, or
10.11 - * (at your option) any later version.
10.12 - *
10.13 - * This program is distributed in the hope that it will be useful,
10.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
10.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10.16 - * GNU General Public License for more details.
10.17 - *
10.18 - * You should have received a copy of the GNU General Public License
10.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
10.20 - */
10.21 -
10.22 -package org.sonews.daemon;
10.23 -
10.24 -import java.sql.SQLException;
10.25 -import org.sonews.storage.StorageManager;
10.26 -import org.sonews.util.Log;
10.27 -
10.28 -/**
10.29 - * Base class of all sonews threads.
10.30 - * Instances of this class will be automatically registered at the ShutdownHook
10.31 - * to be cleanly exited when the server is forced to exit.
10.32 - * @author Christian Lins
10.33 - * @since sonews/0.5.0
10.34 - */
10.35 -public abstract class AbstractDaemon extends Thread
10.36 -{
10.37 -
10.38 - /** This variable is write synchronized through setRunning */
10.39 - private boolean isRunning = false;
10.40 -
10.41 - /**
10.42 - * Protected constructor. Will be called by derived classes.
10.43 - */
10.44 - protected AbstractDaemon()
10.45 - {
10.46 - setDaemon(true); // VM will exit when all threads are daemons
10.47 - setName(getClass().getSimpleName());
10.48 - }
10.49 -
10.50 - /**
10.51 - * @return true if shutdown() was not yet called.
10.52 - */
10.53 - public boolean isRunning()
10.54 - {
10.55 - synchronized(this)
10.56 - {
10.57 - return this.isRunning;
10.58 - }
10.59 - }
10.60 -
10.61 - /**
10.62 - * Marks this thread to exit soon. Closes the associated JDBCDatabase connection
10.63 - * if available.
10.64 - * @throws java.sql.SQLException
10.65 - */
10.66 - public void shutdownNow()
10.67 - throws SQLException
10.68 - {
10.69 - synchronized(this)
10.70 - {
10.71 - this.isRunning = false;
10.72 - StorageManager.disableProvider();
10.73 - }
10.74 - }
10.75 -
10.76 - /**
10.77 - * Calls shutdownNow() but catches SQLExceptions if occurring.
10.78 - */
10.79 - public void shutdown()
10.80 - {
10.81 - try
10.82 - {
10.83 - shutdownNow();
10.84 - }
10.85 - catch(SQLException ex)
10.86 - {
10.87 - Log.get().warning(ex.toString());
10.88 - }
10.89 - }
10.90 -
10.91 - /**
10.92 - * Starts this daemon.
10.93 - */
10.94 - @Override
10.95 - public void start()
10.96 - {
10.97 - synchronized(this)
10.98 - {
10.99 - this.isRunning = true;
10.100 - }
10.101 - super.start();
10.102 - }
10.103 -
10.104 -}
11.1 --- a/org/sonews/daemon/ChannelLineBuffers.java Sun Aug 29 17:04:25 2010 +0200
11.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
11.3 @@ -1,283 +0,0 @@
11.4 -/*
11.5 - * SONEWS News Server
11.6 - * see AUTHORS for the list of contributors
11.7 - *
11.8 - * This program is free software: you can redistribute it and/or modify
11.9 - * it under the terms of the GNU General Public License as published by
11.10 - * the Free Software Foundation, either version 3 of the License, or
11.11 - * (at your option) any later version.
11.12 - *
11.13 - * This program is distributed in the hope that it will be useful,
11.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
11.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11.16 - * GNU General Public License for more details.
11.17 - *
11.18 - * You should have received a copy of the GNU General Public License
11.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
11.20 - */
11.21 -
11.22 -package org.sonews.daemon;
11.23 -
11.24 -import java.nio.ByteBuffer;
11.25 -import java.nio.channels.ClosedChannelException;
11.26 -import java.util.ArrayList;
11.27 -import java.util.List;
11.28 -
11.29 -/**
11.30 - * Class holding ByteBuffers for SocketChannels/NNTPConnection.
11.31 - * Due to the complex nature of AIO/NIO we must properly handle the line
11.32 - * buffers for the input and output of the SocketChannels.
11.33 - * @author Christian Lins
11.34 - * @since sonews/0.5.0
11.35 - */
11.36 -public class ChannelLineBuffers
11.37 -{
11.38 -
11.39 - /**
11.40 - * Size of one small buffer;
11.41 - * per default this is 512 bytes to fit one standard line.
11.42 - */
11.43 - public static final int BUFFER_SIZE = 512;
11.44 -
11.45 - private static int maxCachedBuffers = 2048; // Cached buffers maximum
11.46 -
11.47 - private static final List<ByteBuffer> freeSmallBuffers
11.48 - = new ArrayList<ByteBuffer>(maxCachedBuffers);
11.49 -
11.50 - /**
11.51 - * Allocates a predefined number of direct ByteBuffers (allocated via
11.52 - * ByteBuffer.allocateDirect()). This method is Thread-safe, but should only
11.53 - * called at startup.
11.54 - */
11.55 - public static void allocateDirect()
11.56 - {
11.57 - synchronized(freeSmallBuffers)
11.58 - {
11.59 - for(int n = 0; n < maxCachedBuffers; n++)
11.60 - {
11.61 - ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
11.62 - freeSmallBuffers.add(buffer);
11.63 - }
11.64 - }
11.65 - }
11.66 -
11.67 - private ByteBuffer inputBuffer = newLineBuffer();
11.68 - private List<ByteBuffer> outputBuffers = new ArrayList<ByteBuffer>();
11.69 -
11.70 - /**
11.71 - * Add the given ByteBuffer to the list of buffers to be send to the client.
11.72 - * This method is Thread-safe.
11.73 - * @param buffer
11.74 - * @throws java.nio.channels.ClosedChannelException If the client channel was
11.75 - * already closed.
11.76 - */
11.77 - public void addOutputBuffer(ByteBuffer buffer)
11.78 - throws ClosedChannelException
11.79 - {
11.80 - if(outputBuffers == null)
11.81 - {
11.82 - throw new ClosedChannelException();
11.83 - }
11.84 -
11.85 - synchronized(outputBuffers)
11.86 - {
11.87 - outputBuffers.add(buffer);
11.88 - }
11.89 - }
11.90 -
11.91 - /**
11.92 - * Currently a channel has only one input buffer. This *may* be a bottleneck
11.93 - * and should investigated in the future.
11.94 - * @param channel
11.95 - * @return The input buffer associated with given channel.
11.96 - */
11.97 - public ByteBuffer getInputBuffer()
11.98 - {
11.99 - return inputBuffer;
11.100 - }
11.101 -
11.102 - /**
11.103 - * Returns the current output buffer for writing(!) to SocketChannel.
11.104 - * @param channel
11.105 - * @return The next input buffer that contains unprocessed data or null
11.106 - * if the connection was closed or there are no more unprocessed buffers.
11.107 - */
11.108 - public ByteBuffer getOutputBuffer()
11.109 - {
11.110 - synchronized(outputBuffers)
11.111 - {
11.112 - if(outputBuffers == null || outputBuffers.isEmpty())
11.113 - {
11.114 - return null;
11.115 - }
11.116 - else
11.117 - {
11.118 - ByteBuffer buffer = outputBuffers.get(0);
11.119 - if(buffer.remaining() == 0)
11.120 - {
11.121 - outputBuffers.remove(0);
11.122 - // Add old buffers to the list of free buffers
11.123 - recycleBuffer(buffer);
11.124 - buffer = getOutputBuffer();
11.125 - }
11.126 - return buffer;
11.127 - }
11.128 - }
11.129 - }
11.130 -
11.131 - /**
11.132 - * @return false if there are output buffers pending to be written to the
11.133 - * client.
11.134 - */
11.135 - boolean isOutputBufferEmpty()
11.136 - {
11.137 - synchronized(outputBuffers)
11.138 - {
11.139 - return outputBuffers.isEmpty();
11.140 - }
11.141 - }
11.142 -
11.143 - /**
11.144 - * Goes through the input buffer of the given channel and searches
11.145 - * for next line terminator. If a '\n' is found, the bytes up to the
11.146 - * line terminator are returned as array of bytes (the line terminator
11.147 - * is omitted). If none is found the method returns null.
11.148 - * @param channel
11.149 - * @return A ByteBuffer wrapping the line.
11.150 - */
11.151 - ByteBuffer nextInputLine()
11.152 - {
11.153 - if(inputBuffer == null)
11.154 - {
11.155 - return null;
11.156 - }
11.157 -
11.158 - synchronized(inputBuffer)
11.159 - {
11.160 - ByteBuffer buffer = inputBuffer;
11.161 -
11.162 - // Mark the current write position
11.163 - int mark = buffer.position();
11.164 -
11.165 - // Set position to 0 and limit to current position
11.166 - buffer.flip();
11.167 -
11.168 - ByteBuffer lineBuffer = newLineBuffer();
11.169 -
11.170 - while (buffer.position() < buffer.limit())
11.171 - {
11.172 - byte b = buffer.get();
11.173 - if (b == 10) // '\n'
11.174 - {
11.175 - // The bytes between the buffer's current position and its limit,
11.176 - // if any, are copied to the beginning of the buffer. That is, the
11.177 - // byte at index p = position() is copied to index zero, the byte at
11.178 - // index p + 1 is copied to index one, and so forth until the byte
11.179 - // at index limit() - 1 is copied to index n = limit() - 1 - p.
11.180 - // The buffer's position is then set to n+1 and its limit is set to
11.181 - // its capacity.
11.182 - buffer.compact();
11.183 -
11.184 - lineBuffer.flip(); // limit to position, position to 0
11.185 - return lineBuffer;
11.186 - }
11.187 - else
11.188 - {
11.189 - lineBuffer.put(b);
11.190 - }
11.191 - }
11.192 -
11.193 - buffer.limit(BUFFER_SIZE);
11.194 - buffer.position(mark);
11.195 -
11.196 - if(buffer.hasRemaining())
11.197 - {
11.198 - return null;
11.199 - }
11.200 - else
11.201 - {
11.202 - // In the first 512 was no newline found, so the input is not standard
11.203 - // compliant. We return the current buffer as new line and add a space
11.204 - // to the beginning of the next line which corrects some overlong header
11.205 - // lines.
11.206 - inputBuffer = newLineBuffer();
11.207 - inputBuffer.put((byte)' ');
11.208 - buffer.flip();
11.209 - return buffer;
11.210 - }
11.211 - }
11.212 - }
11.213 -
11.214 - /**
11.215 - * Returns a at least 512 bytes long ByteBuffer ready for usage.
11.216 - * The method first try to reuse an already allocated (cached) buffer but
11.217 - * if that fails returns a newly allocated direct buffer.
11.218 - * Use recycleBuffer() method when you do not longer use the allocated buffer.
11.219 - */
11.220 - static ByteBuffer newLineBuffer()
11.221 - {
11.222 - ByteBuffer buf = null;
11.223 - synchronized(freeSmallBuffers)
11.224 - {
11.225 - if(!freeSmallBuffers.isEmpty())
11.226 - {
11.227 - buf = freeSmallBuffers.remove(0);
11.228 - }
11.229 - }
11.230 -
11.231 - if(buf == null)
11.232 - {
11.233 - // Allocate a non-direct buffer
11.234 - buf = ByteBuffer.allocate(BUFFER_SIZE);
11.235 - }
11.236 -
11.237 - assert buf.position() == 0;
11.238 - assert buf.limit() >= BUFFER_SIZE;
11.239 -
11.240 - return buf;
11.241 - }
11.242 -
11.243 - /**
11.244 - * Adds the given buffer to the list of free buffers if it is a valuable
11.245 - * direct allocated buffer.
11.246 - * @param buffer
11.247 - */
11.248 - public static void recycleBuffer(ByteBuffer buffer)
11.249 - {
11.250 - assert buffer != null;
11.251 -
11.252 - if(buffer.isDirect())
11.253 - {
11.254 - assert buffer.capacity() >= BUFFER_SIZE;
11.255 -
11.256 - // Add old buffers to the list of free buffers
11.257 - synchronized(freeSmallBuffers)
11.258 - {
11.259 - buffer.clear(); // Set position to 0 and limit to capacity
11.260 - freeSmallBuffers.add(buffer);
11.261 - }
11.262 - } // if(buffer.isDirect())
11.263 - }
11.264 -
11.265 - /**
11.266 - * Recycles all buffers of this ChannelLineBuffers object.
11.267 - */
11.268 - public void recycleBuffers()
11.269 - {
11.270 - synchronized(inputBuffer)
11.271 - {
11.272 - recycleBuffer(inputBuffer);
11.273 - this.inputBuffer = null;
11.274 - }
11.275 -
11.276 - synchronized(outputBuffers)
11.277 - {
11.278 - for(ByteBuffer buf : outputBuffers)
11.279 - {
11.280 - recycleBuffer(buf);
11.281 - }
11.282 - outputBuffers = null;
11.283 - }
11.284 - }
11.285 -
11.286 -}
12.1 --- a/org/sonews/daemon/ChannelReader.java Sun Aug 29 17:04:25 2010 +0200
12.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
12.3 @@ -1,202 +0,0 @@
12.4 -/*
12.5 - * SONEWS News Server
12.6 - * see AUTHORS for the list of contributors
12.7 - *
12.8 - * This program is free software: you can redistribute it and/or modify
12.9 - * it under the terms of the GNU General Public License as published by
12.10 - * the Free Software Foundation, either version 3 of the License, or
12.11 - * (at your option) any later version.
12.12 - *
12.13 - * This program is distributed in the hope that it will be useful,
12.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
12.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12.16 - * GNU General Public License for more details.
12.17 - *
12.18 - * You should have received a copy of the GNU General Public License
12.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
12.20 - */
12.21 -
12.22 -package org.sonews.daemon;
12.23 -
12.24 -import java.io.IOException;
12.25 -import java.nio.ByteBuffer;
12.26 -import java.nio.channels.CancelledKeyException;
12.27 -import java.nio.channels.SelectionKey;
12.28 -import java.nio.channels.Selector;
12.29 -import java.nio.channels.SocketChannel;
12.30 -import java.util.Iterator;
12.31 -import java.util.Set;
12.32 -import java.util.logging.Level;
12.33 -import org.sonews.util.Log;
12.34 -
12.35 -/**
12.36 - * A Thread task listening for OP_READ events from SocketChannels.
12.37 - * @author Christian Lins
12.38 - * @since sonews/0.5.0
12.39 - */
12.40 -class ChannelReader extends AbstractDaemon
12.41 -{
12.42 -
12.43 - private static ChannelReader instance = new ChannelReader();
12.44 -
12.45 - /**
12.46 - * @return Active ChannelReader instance.
12.47 - */
12.48 - public static ChannelReader getInstance()
12.49 - {
12.50 - return instance;
12.51 - }
12.52 -
12.53 - private Selector selector = null;
12.54 -
12.55 - protected ChannelReader()
12.56 - {
12.57 - }
12.58 -
12.59 - /**
12.60 - * Sets the selector which is used by this reader to determine the channel
12.61 - * to read from.
12.62 - * @param selector
12.63 - */
12.64 - public void setSelector(final Selector selector)
12.65 - {
12.66 - this.selector = selector;
12.67 - }
12.68 -
12.69 - /**
12.70 - * Run loop. Blocks until some data is available in a channel.
12.71 - */
12.72 - @Override
12.73 - public void run()
12.74 - {
12.75 - assert selector != null;
12.76 -
12.77 - while(isRunning())
12.78 - {
12.79 - try
12.80 - {
12.81 - // select() blocks until some SelectableChannels are ready for
12.82 - // processing. There is no need to lock the selector as we have only
12.83 - // one thread per selector.
12.84 - selector.select();
12.85 -
12.86 - // Get list of selection keys with pending events.
12.87 - // Note: the selected key set is not thread-safe
12.88 - SocketChannel channel = null;
12.89 - NNTPConnection conn = null;
12.90 - final Set<SelectionKey> selKeys = selector.selectedKeys();
12.91 - SelectionKey selKey = null;
12.92 -
12.93 - synchronized (selKeys)
12.94 - {
12.95 - Iterator it = selKeys.iterator();
12.96 -
12.97 - // Process the first pending event
12.98 - while (it.hasNext())
12.99 - {
12.100 - selKey = (SelectionKey) it.next();
12.101 - channel = (SocketChannel) selKey.channel();
12.102 - conn = Connections.getInstance().get(channel);
12.103 -
12.104 - // Because we cannot lock the selKey as that would cause a deadlock
12.105 - // we lock the connection. To preserve the order of the received
12.106 - // byte blocks a selection key for a connection that has pending
12.107 - // read events is skipped.
12.108 - if (conn == null || conn.tryReadLock())
12.109 - {
12.110 - // Remove from set to indicate that it's being processed
12.111 - it.remove();
12.112 - if (conn != null)
12.113 - {
12.114 - break; // End while loop
12.115 - }
12.116 - }
12.117 - else
12.118 - {
12.119 - selKey = null;
12.120 - channel = null;
12.121 - conn = null;
12.122 - }
12.123 - }
12.124 - }
12.125 -
12.126 - // Do not lock the selKeys while processing because this causes
12.127 - // a deadlock in sun.nio.ch.SelectorImpl.lockAndDoSelect()
12.128 - if (selKey != null && channel != null && conn != null)
12.129 - {
12.130 - processSelectionKey(conn, channel, selKey);
12.131 - conn.unlockReadLock();
12.132 - }
12.133 -
12.134 - }
12.135 - catch(CancelledKeyException ex)
12.136 - {
12.137 - Log.get().warning("ChannelReader.run(): " + ex);
12.138 - Log.get().log(Level.INFO, "", ex);
12.139 - }
12.140 - catch(Exception ex)
12.141 - {
12.142 - ex.printStackTrace();
12.143 - }
12.144 -
12.145 - // Eventually wait for a register operation
12.146 - synchronized (NNTPDaemon.RegisterGate)
12.147 - {
12.148 - // Do nothing; FindBugs may warn about an empty synchronized
12.149 - // statement, but we cannot use a wait()/notify() mechanism here.
12.150 - // If we used something like RegisterGate.wait() we block here
12.151 - // until the NNTPDaemon calls notify(). But the daemon only
12.152 - // calls notify() if itself is NOT blocked in the listening socket.
12.153 - }
12.154 - } // while(isRunning())
12.155 - }
12.156 -
12.157 - private void processSelectionKey(final NNTPConnection connection,
12.158 - final SocketChannel socketChannel, final SelectionKey selKey)
12.159 - throws InterruptedException, IOException
12.160 - {
12.161 - assert selKey != null;
12.162 - assert selKey.isReadable();
12.163 -
12.164 - // Some bytes are available for reading
12.165 - if(selKey.isValid())
12.166 - {
12.167 - // Lock the channel
12.168 - //synchronized(socketChannel)
12.169 - {
12.170 - // Read the data into the appropriate buffer
12.171 - ByteBuffer buf = connection.getInputBuffer();
12.172 - int read = -1;
12.173 - try
12.174 - {
12.175 - read = socketChannel.read(buf);
12.176 - }
12.177 - catch(IOException ex)
12.178 - {
12.179 - // The connection was probably closed by the remote host
12.180 - // in a non-clean fashion
12.181 - Log.get().info("ChannelReader.processSelectionKey(): " + ex);
12.182 - }
12.183 - catch(Exception ex)
12.184 - {
12.185 - Log.get().warning("ChannelReader.processSelectionKey(): " + ex);
12.186 - }
12.187 -
12.188 - if(read == -1) // End of stream
12.189 - {
12.190 - selKey.cancel();
12.191 - }
12.192 - else if(read > 0) // If some data was read
12.193 - {
12.194 - ConnectionWorker.addChannel(socketChannel);
12.195 - }
12.196 - }
12.197 - }
12.198 - else
12.199 - {
12.200 - // Should not happen
12.201 - Log.get().severe("Should not happen: " + selKey.toString());
12.202 - }
12.203 - }
12.204 -
12.205 -}
13.1 --- a/org/sonews/daemon/ChannelWriter.java Sun Aug 29 17:04:25 2010 +0200
13.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
13.3 @@ -1,210 +0,0 @@
13.4 -/*
13.5 - * SONEWS News Server
13.6 - * see AUTHORS for the list of contributors
13.7 - *
13.8 - * This program is free software: you can redistribute it and/or modify
13.9 - * it under the terms of the GNU General Public License as published by
13.10 - * the Free Software Foundation, either version 3 of the License, or
13.11 - * (at your option) any later version.
13.12 - *
13.13 - * This program is distributed in the hope that it will be useful,
13.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
13.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13.16 - * GNU General Public License for more details.
13.17 - *
13.18 - * You should have received a copy of the GNU General Public License
13.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
13.20 - */
13.21 -
13.22 -package org.sonews.daemon;
13.23 -
13.24 -import org.sonews.util.Log;
13.25 -import java.io.IOException;
13.26 -import java.nio.ByteBuffer;
13.27 -import java.nio.channels.CancelledKeyException;
13.28 -import java.nio.channels.SelectionKey;
13.29 -import java.nio.channels.Selector;
13.30 -import java.nio.channels.SocketChannel;
13.31 -import java.util.Iterator;
13.32 -
13.33 -/**
13.34 - * A Thread task that processes OP_WRITE events for SocketChannels.
13.35 - * @author Christian Lins
13.36 - * @since sonews/0.5.0
13.37 - */
13.38 -class ChannelWriter extends AbstractDaemon
13.39 -{
13.40 -
13.41 - private static ChannelWriter instance = new ChannelWriter();
13.42 -
13.43 - /**
13.44 - * @return Returns the active ChannelWriter instance.
13.45 - */
13.46 - public static ChannelWriter getInstance()
13.47 - {
13.48 - return instance;
13.49 - }
13.50 -
13.51 - private Selector selector = null;
13.52 -
13.53 - protected ChannelWriter()
13.54 - {
13.55 - }
13.56 -
13.57 - /**
13.58 - * @return Selector associated with this instance.
13.59 - */
13.60 - public Selector getSelector()
13.61 - {
13.62 - return this.selector;
13.63 - }
13.64 -
13.65 - /**
13.66 - * Sets the selector that is used by this ChannelWriter.
13.67 - * @param selector
13.68 - */
13.69 - public void setSelector(final Selector selector)
13.70 - {
13.71 - this.selector = selector;
13.72 - }
13.73 -
13.74 - /**
13.75 - * Run loop.
13.76 - */
13.77 - @Override
13.78 - public void run()
13.79 - {
13.80 - assert selector != null;
13.81 -
13.82 - while(isRunning())
13.83 - {
13.84 - try
13.85 - {
13.86 - SelectionKey selKey = null;
13.87 - SocketChannel socketChannel = null;
13.88 - NNTPConnection connection = null;
13.89 -
13.90 - // select() blocks until some SelectableChannels are ready for
13.91 - // processing. There is no need to synchronize the selector as we
13.92 - // have only one thread per selector.
13.93 - selector.select(); // The return value of select can be ignored
13.94 -
13.95 - // Get list of selection keys with pending OP_WRITE events.
13.96 - // The keySET is not thread-safe whereas the keys itself are.
13.97 - Iterator it = selector.selectedKeys().iterator();
13.98 -
13.99 - while (it.hasNext())
13.100 - {
13.101 - // We remove the first event from the set and store it for
13.102 - // later processing.
13.103 - selKey = (SelectionKey) it.next();
13.104 - socketChannel = (SocketChannel) selKey.channel();
13.105 - connection = Connections.getInstance().get(socketChannel);
13.106 -
13.107 - it.remove();
13.108 - if (connection != null)
13.109 - {
13.110 - break;
13.111 - }
13.112 - else
13.113 - {
13.114 - selKey = null;
13.115 - }
13.116 - }
13.117 -
13.118 - if (selKey != null)
13.119 - {
13.120 - try
13.121 - {
13.122 - // Process the selected key.
13.123 - // As there is only one OP_WRITE key for a given channel, we need
13.124 - // not to synchronize this processing to retain the order.
13.125 - processSelectionKey(connection, socketChannel, selKey);
13.126 - }
13.127 - catch (IOException ex)
13.128 - {
13.129 - Log.get().warning("Error writing to channel: " + ex);
13.130 -
13.131 - // Cancel write events for this channel
13.132 - selKey.cancel();
13.133 - connection.shutdownInput();
13.134 - connection.shutdownOutput();
13.135 - }
13.136 - }
13.137 -
13.138 - // Eventually wait for a register operation
13.139 - synchronized(NNTPDaemon.RegisterGate) { /* do nothing */ }
13.140 - }
13.141 - catch(CancelledKeyException ex)
13.142 - {
13.143 - Log.get().info("ChannelWriter.run(): " + ex);
13.144 - }
13.145 - catch(Exception ex)
13.146 - {
13.147 - ex.printStackTrace();
13.148 - }
13.149 - } // while(isRunning())
13.150 - }
13.151 -
13.152 - private void processSelectionKey(final NNTPConnection connection,
13.153 - final SocketChannel socketChannel, final SelectionKey selKey)
13.154 - throws InterruptedException, IOException
13.155 - {
13.156 - assert connection != null;
13.157 - assert socketChannel != null;
13.158 - assert selKey != null;
13.159 - assert selKey.isWritable();
13.160 -
13.161 - // SocketChannel is ready for writing
13.162 - if(selKey.isValid())
13.163 - {
13.164 - // Lock the socket channel
13.165 - synchronized(socketChannel)
13.166 - {
13.167 - // Get next output buffer
13.168 - ByteBuffer buf = connection.getOutputBuffer();
13.169 - if(buf == null)
13.170 - {
13.171 - // Currently we have nothing to write, so we stop the writeable
13.172 - // events until we have something to write to the socket channel
13.173 - //selKey.cancel();
13.174 - selKey.interestOps(0);
13.175 - // Update activity timestamp to prevent too early disconnects
13.176 - // on slow client connections
13.177 - connection.setLastActivity(System.currentTimeMillis());
13.178 - return;
13.179 - }
13.180 -
13.181 - while(buf != null) // There is data to be send
13.182 - {
13.183 - // Write buffer to socket channel; this method does not block
13.184 - if(socketChannel.write(buf) <= 0)
13.185 - {
13.186 - // Perhaps there is data to be written, but the SocketChannel's
13.187 - // buffer is full, so we stop writing to until the next event.
13.188 - break;
13.189 - }
13.190 - else
13.191 - {
13.192 - // Retrieve next buffer if available; method may return the same
13.193 - // buffer instance if it still have some bytes remaining
13.194 - buf = connection.getOutputBuffer();
13.195 - }
13.196 - }
13.197 - }
13.198 - }
13.199 - else
13.200 - {
13.201 - Log.get().warning("Invalid OP_WRITE key: " + selKey);
13.202 -
13.203 - if(socketChannel.socket().isClosed())
13.204 - {
13.205 - connection.shutdownInput();
13.206 - connection.shutdownOutput();
13.207 - socketChannel.close();
13.208 - Log.get().info("Connection closed.");
13.209 - }
13.210 - }
13.211 - }
13.212 -
13.213 -}
14.1 --- a/org/sonews/daemon/CommandSelector.java Sun Aug 29 17:04:25 2010 +0200
14.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
14.3 @@ -1,141 +0,0 @@
14.4 -/*
14.5 - * SONEWS News Server
14.6 - * see AUTHORS for the list of contributors
14.7 - *
14.8 - * This program is free software: you can redistribute it and/or modify
14.9 - * it under the terms of the GNU General Public License as published by
14.10 - * the Free Software Foundation, either version 3 of the License, or
14.11 - * (at your option) any later version.
14.12 - *
14.13 - * This program is distributed in the hope that it will be useful,
14.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
14.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14.16 - * GNU General Public License for more details.
14.17 - *
14.18 - * You should have received a copy of the GNU General Public License
14.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
14.20 - */
14.21 -
14.22 -package org.sonews.daemon;
14.23 -
14.24 -import java.util.HashMap;
14.25 -import java.util.Map;
14.26 -import java.util.Set;
14.27 -import java.util.concurrent.ConcurrentHashMap;
14.28 -import org.sonews.daemon.command.Command;
14.29 -import org.sonews.daemon.command.UnsupportedCommand;
14.30 -import org.sonews.util.Log;
14.31 -import org.sonews.util.io.Resource;
14.32 -
14.33 -/**
14.34 - * Selects the correct command processing class.
14.35 - * @author Christian Lins
14.36 - * @since sonews/1.0
14.37 - */
14.38 -public class CommandSelector
14.39 -{
14.40 -
14.41 - private static Map<Thread, CommandSelector> instances
14.42 - = new ConcurrentHashMap<Thread, CommandSelector>();
14.43 - private static Map<String, Class<?>> commandClassesMapping
14.44 - = new ConcurrentHashMap<String, Class<?>>();
14.45 -
14.46 - static
14.47 - {
14.48 - String[] classes = Resource.getAsString("helpers/commands.list", true).split("\n");
14.49 - for(String className : classes)
14.50 - {
14.51 - if(className.charAt(0) == '#')
14.52 - {
14.53 - // Skip comments
14.54 - continue;
14.55 - }
14.56 -
14.57 - try
14.58 - {
14.59 - addCommandHandler(className);
14.60 - }
14.61 - catch(ClassNotFoundException ex)
14.62 - {
14.63 - Log.get().warning("Could not load command class: " + ex);
14.64 - }
14.65 - catch(InstantiationException ex)
14.66 - {
14.67 - Log.get().severe("Could not instantiate command class: " + ex);
14.68 - }
14.69 - catch(IllegalAccessException ex)
14.70 - {
14.71 - Log.get().severe("Could not access command class: " + ex);
14.72 - }
14.73 - }
14.74 - }
14.75 -
14.76 - public static void addCommandHandler(String className)
14.77 - throws ClassNotFoundException, InstantiationException, IllegalAccessException
14.78 - {
14.79 - Class<?> clazz = Class.forName(className);
14.80 - Command cmd = (Command)clazz.newInstance();
14.81 - String[] cmdStrs = cmd.getSupportedCommandStrings();
14.82 - for (String cmdStr : cmdStrs)
14.83 - {
14.84 - commandClassesMapping.put(cmdStr, clazz);
14.85 - }
14.86 - }
14.87 -
14.88 - public static Set<String> getCommandNames()
14.89 - {
14.90 - return commandClassesMapping.keySet();
14.91 - }
14.92 -
14.93 - public static CommandSelector getInstance()
14.94 - {
14.95 - CommandSelector csel = instances.get(Thread.currentThread());
14.96 - if(csel == null)
14.97 - {
14.98 - csel = new CommandSelector();
14.99 - instances.put(Thread.currentThread(), csel);
14.100 - }
14.101 - return csel;
14.102 - }
14.103 -
14.104 - private Map<String, Command> commandMapping = new HashMap<String, Command>();
14.105 - private Command unsupportedCmd = new UnsupportedCommand();
14.106 -
14.107 - private CommandSelector()
14.108 - {}
14.109 -
14.110 - public Command get(String commandName)
14.111 - {
14.112 - try
14.113 - {
14.114 - commandName = commandName.toUpperCase();
14.115 - Command cmd = this.commandMapping.get(commandName);
14.116 -
14.117 - if(cmd == null)
14.118 - {
14.119 - Class<?> clazz = commandClassesMapping.get(commandName);
14.120 - if(clazz == null)
14.121 - {
14.122 - cmd = this.unsupportedCmd;
14.123 - }
14.124 - else
14.125 - {
14.126 - cmd = (Command)clazz.newInstance();
14.127 - this.commandMapping.put(commandName, cmd);
14.128 - }
14.129 - }
14.130 - else if(cmd.isStateful())
14.131 - {
14.132 - cmd = cmd.getClass().newInstance();
14.133 - }
14.134 -
14.135 - return cmd;
14.136 - }
14.137 - catch(Exception ex)
14.138 - {
14.139 - ex.printStackTrace();
14.140 - return this.unsupportedCmd;
14.141 - }
14.142 - }
14.143 -
14.144 -}
15.1 --- a/org/sonews/daemon/ConnectionWorker.java Sun Aug 29 17:04:25 2010 +0200
15.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
15.3 @@ -1,102 +0,0 @@
15.4 -/*
15.5 - * SONEWS News Server
15.6 - * see AUTHORS for the list of contributors
15.7 - *
15.8 - * This program is free software: you can redistribute it and/or modify
15.9 - * it under the terms of the GNU General Public License as published by
15.10 - * the Free Software Foundation, either version 3 of the License, or
15.11 - * (at your option) any later version.
15.12 - *
15.13 - * This program is distributed in the hope that it will be useful,
15.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
15.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15.16 - * GNU General Public License for more details.
15.17 - *
15.18 - * You should have received a copy of the GNU General Public License
15.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
15.20 - */
15.21 -
15.22 -package org.sonews.daemon;
15.23 -
15.24 -import org.sonews.util.Log;
15.25 -import java.nio.ByteBuffer;
15.26 -import java.nio.channels.SocketChannel;
15.27 -import java.util.concurrent.ArrayBlockingQueue;
15.28 -
15.29 -/**
15.30 - * Does most of the work: parsing input, talking to client and Database.
15.31 - * @author Christian Lins
15.32 - * @since sonews/0.5.0
15.33 - */
15.34 -class ConnectionWorker extends AbstractDaemon
15.35 -{
15.36 -
15.37 - // 256 pending events should be enough
15.38 - private static ArrayBlockingQueue<SocketChannel> pendingChannels
15.39 - = new ArrayBlockingQueue<SocketChannel>(256, true);
15.40 -
15.41 - /**
15.42 - * Registers the given channel for further event processing.
15.43 - * @param channel
15.44 - */
15.45 - public static void addChannel(SocketChannel channel)
15.46 - throws InterruptedException
15.47 - {
15.48 - pendingChannels.put(channel);
15.49 - }
15.50 -
15.51 - /**
15.52 - * Processing loop.
15.53 - */
15.54 - @Override
15.55 - public void run()
15.56 - {
15.57 - while(isRunning())
15.58 - {
15.59 - try
15.60 - {
15.61 - // Retrieve and remove if available, otherwise wait.
15.62 - SocketChannel channel = pendingChannels.take();
15.63 -
15.64 - if(channel != null)
15.65 - {
15.66 - // Connections.getInstance().get() MAY return null
15.67 - NNTPConnection conn = Connections.getInstance().get(channel);
15.68 -
15.69 - // Try to lock the connection object
15.70 - if(conn != null && conn.tryReadLock())
15.71 - {
15.72 - ByteBuffer buf = conn.getBuffers().nextInputLine();
15.73 - while(buf != null) // Complete line was received
15.74 - {
15.75 - final byte[] line = new byte[buf.limit()];
15.76 - buf.get(line);
15.77 - ChannelLineBuffers.recycleBuffer(buf);
15.78 -
15.79 - // Here is the actual work done
15.80 - conn.lineReceived(line);
15.81 -
15.82 - // Read next line as we could have already received the next line
15.83 - buf = conn.getBuffers().nextInputLine();
15.84 - }
15.85 - conn.unlockReadLock();
15.86 - }
15.87 - else
15.88 - {
15.89 - addChannel(channel);
15.90 - }
15.91 - }
15.92 - }
15.93 - catch(InterruptedException ex)
15.94 - {
15.95 - Log.get().info("ConnectionWorker interrupted: " + ex);
15.96 - }
15.97 - catch(Exception ex)
15.98 - {
15.99 - Log.get().severe("Exception in ConnectionWorker: " + ex);
15.100 - ex.printStackTrace();
15.101 - }
15.102 - } // end while(isRunning())
15.103 - }
15.104 -
15.105 -}
16.1 --- a/org/sonews/daemon/Connections.java Sun Aug 29 17:04:25 2010 +0200
16.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
16.3 @@ -1,181 +0,0 @@
16.4 -/*
16.5 - * SONEWS News Server
16.6 - * see AUTHORS for the list of contributors
16.7 - *
16.8 - * This program is free software: you can redistribute it and/or modify
16.9 - * it under the terms of the GNU General Public License as published by
16.10 - * the Free Software Foundation, either version 3 of the License, or
16.11 - * (at your option) any later version.
16.12 - *
16.13 - * This program is distributed in the hope that it will be useful,
16.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
16.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16.16 - * GNU General Public License for more details.
16.17 - *
16.18 - * You should have received a copy of the GNU General Public License
16.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
16.20 - */
16.21 -
16.22 -package org.sonews.daemon;
16.23 -
16.24 -import org.sonews.config.Config;
16.25 -import org.sonews.util.Log;
16.26 -import org.sonews.util.Stats;
16.27 -import java.io.IOException;
16.28 -import java.net.InetSocketAddress;
16.29 -import java.net.Socket;
16.30 -import java.nio.channels.SocketChannel;
16.31 -import java.util.ArrayList;
16.32 -import java.util.HashMap;
16.33 -import java.util.List;
16.34 -import java.util.ListIterator;
16.35 -import java.util.Map;
16.36 -
16.37 -/**
16.38 - * Daemon thread collecting all NNTPConnection instances. The thread
16.39 - * checks periodically if there are stale/timed out connections and
16.40 - * removes and purges them properly.
16.41 - * @author Christian Lins
16.42 - * @since sonews/0.5.0
16.43 - */
16.44 -public final class Connections extends AbstractDaemon
16.45 -{
16.46 -
16.47 - private static final Connections instance = new Connections();
16.48 -
16.49 - /**
16.50 - * @return Active Connections instance.
16.51 - */
16.52 - public static Connections getInstance()
16.53 - {
16.54 - return Connections.instance;
16.55 - }
16.56 -
16.57 - private final List<NNTPConnection> connections
16.58 - = new ArrayList<NNTPConnection>();
16.59 - private final Map<SocketChannel, NNTPConnection> connByChannel
16.60 - = new HashMap<SocketChannel, NNTPConnection>();
16.61 -
16.62 - private Connections()
16.63 - {
16.64 - setName("Connections");
16.65 - }
16.66 -
16.67 - /**
16.68 - * Adds the given NNTPConnection to the Connections management.
16.69 - * @param conn
16.70 - * @see org.sonews.daemon.NNTPConnection
16.71 - */
16.72 - public void add(final NNTPConnection conn)
16.73 - {
16.74 - synchronized(this.connections)
16.75 - {
16.76 - this.connections.add(conn);
16.77 - this.connByChannel.put(conn.getSocketChannel(), conn);
16.78 - }
16.79 - }
16.80 -
16.81 - /**
16.82 - * @param channel
16.83 - * @return NNTPConnection instance that is associated with the given
16.84 - * SocketChannel.
16.85 - */
16.86 - public NNTPConnection get(final SocketChannel channel)
16.87 - {
16.88 - synchronized(this.connections)
16.89 - {
16.90 - return this.connByChannel.get(channel);
16.91 - }
16.92 - }
16.93 -
16.94 - int getConnectionCount(String remote)
16.95 - {
16.96 - int cnt = 0;
16.97 - synchronized(this.connections)
16.98 - {
16.99 - for(NNTPConnection conn : this.connections)
16.100 - {
16.101 - assert conn != null;
16.102 - assert conn.getSocketChannel() != null;
16.103 -
16.104 - Socket socket = conn.getSocketChannel().socket();
16.105 - if(socket != null)
16.106 - {
16.107 - InetSocketAddress sockAddr = (InetSocketAddress)socket.getRemoteSocketAddress();
16.108 - if(sockAddr != null)
16.109 - {
16.110 - if(sockAddr.getHostName().equals(remote))
16.111 - {
16.112 - cnt++;
16.113 - }
16.114 - }
16.115 - } // if(socket != null)
16.116 - }
16.117 - }
16.118 - return cnt;
16.119 - }
16.120 -
16.121 - /**
16.122 - * Run loops. Checks periodically for timed out connections and purged them
16.123 - * from the lists.
16.124 - */
16.125 - @Override
16.126 - public void run()
16.127 - {
16.128 - while(isRunning())
16.129 - {
16.130 - int timeoutMillis = 1000 * Config.inst().get(Config.TIMEOUT, 180);
16.131 -
16.132 - synchronized (this.connections)
16.133 - {
16.134 - final ListIterator<NNTPConnection> iter = this.connections.listIterator();
16.135 - NNTPConnection conn;
16.136 -
16.137 - while (iter.hasNext())
16.138 - {
16.139 - conn = iter.next();
16.140 - if((System.currentTimeMillis() - conn.getLastActivity()) > timeoutMillis
16.141 - && conn.getBuffers().isOutputBufferEmpty())
16.142 - {
16.143 - // A connection timeout has occurred so purge the connection
16.144 - iter.remove();
16.145 -
16.146 - // Close and remove the channel
16.147 - SocketChannel channel = conn.getSocketChannel();
16.148 - connByChannel.remove(channel);
16.149 -
16.150 - try
16.151 - {
16.152 - assert channel != null;
16.153 - assert channel.socket() != null;
16.154 -
16.155 - // Close the channel; implicitely cancels all selectionkeys
16.156 - channel.close();
16.157 - Log.get().info("Disconnected: " + channel.socket().getRemoteSocketAddress() +
16.158 - " (timeout)");
16.159 - }
16.160 - catch(IOException ex)
16.161 - {
16.162 - Log.get().warning("Connections.run(): " + ex);
16.163 - }
16.164 -
16.165 - // Recycle the used buffers
16.166 - conn.getBuffers().recycleBuffers();
16.167 -
16.168 - Stats.getInstance().clientDisconnect();
16.169 - }
16.170 - }
16.171 - }
16.172 -
16.173 - try
16.174 - {
16.175 - Thread.sleep(10000); // Sleep ten seconds
16.176 - }
16.177 - catch(InterruptedException ex)
16.178 - {
16.179 - Log.get().warning("Connections Thread was interrupted: " + ex.getMessage());
16.180 - }
16.181 - }
16.182 - }
16.183 -
16.184 -}
17.1 --- a/org/sonews/daemon/LineEncoder.java Sun Aug 29 17:04:25 2010 +0200
17.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
17.3 @@ -1,80 +0,0 @@
17.4 -/*
17.5 - * SONEWS News Server
17.6 - * see AUTHORS for the list of contributors
17.7 - *
17.8 - * This program is free software: you can redistribute it and/or modify
17.9 - * it under the terms of the GNU General Public License as published by
17.10 - * the Free Software Foundation, either version 3 of the License, or
17.11 - * (at your option) any later version.
17.12 - *
17.13 - * This program is distributed in the hope that it will be useful,
17.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
17.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17.16 - * GNU General Public License for more details.
17.17 - *
17.18 - * You should have received a copy of the GNU General Public License
17.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
17.20 - */
17.21 -
17.22 -package org.sonews.daemon;
17.23 -
17.24 -import java.nio.ByteBuffer;
17.25 -import java.nio.CharBuffer;
17.26 -import java.nio.channels.ClosedChannelException;
17.27 -import java.nio.charset.Charset;
17.28 -import java.nio.charset.CharsetEncoder;
17.29 -import java.nio.charset.CoderResult;
17.30 -
17.31 -/**
17.32 - * Encodes a line to buffers using the correct charset.
17.33 - * @author Christian Lins
17.34 - * @since sonews/0.5.0
17.35 - */
17.36 -class LineEncoder
17.37 -{
17.38 -
17.39 - private CharBuffer characters;
17.40 - private Charset charset;
17.41 -
17.42 - /**
17.43 - * Constructs new LineEncoder.
17.44 - * @param characters
17.45 - * @param charset
17.46 - */
17.47 - public LineEncoder(CharBuffer characters, Charset charset)
17.48 - {
17.49 - this.characters = characters;
17.50 - this.charset = charset;
17.51 - }
17.52 -
17.53 - /**
17.54 - * Encodes the characters of this instance to the given ChannelLineBuffers
17.55 - * using the Charset of this instance.
17.56 - * @param buffer
17.57 - * @throws java.nio.channels.ClosedChannelException
17.58 - */
17.59 - public void encode(ChannelLineBuffers buffer)
17.60 - throws ClosedChannelException
17.61 - {
17.62 - CharsetEncoder encoder = charset.newEncoder();
17.63 - while (characters.hasRemaining())
17.64 - {
17.65 - ByteBuffer buf = ChannelLineBuffers.newLineBuffer();
17.66 - assert buf.position() == 0;
17.67 - assert buf.capacity() >= 512;
17.68 -
17.69 - CoderResult res = encoder.encode(characters, buf, true);
17.70 -
17.71 - // Set limit to current position and current position to 0;
17.72 - // means make ready for read from buffer
17.73 - buf.flip();
17.74 - buffer.addOutputBuffer(buf);
17.75 -
17.76 - if (res.isUnderflow()) // All input processed
17.77 - {
17.78 - break;
17.79 - }
17.80 - }
17.81 - }
17.82 -
17.83 -}
18.1 --- a/org/sonews/daemon/NNTPConnection.java Sun Aug 29 17:04:25 2010 +0200
18.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
18.3 @@ -1,428 +0,0 @@
18.4 -/*
18.5 - * SONEWS News Server
18.6 - * see AUTHORS for the list of contributors
18.7 - *
18.8 - * This program is free software: you can redistribute it and/or modify
18.9 - * it under the terms of the GNU General Public License as published by
18.10 - * the Free Software Foundation, either version 3 of the License, or
18.11 - * (at your option) any later version.
18.12 - *
18.13 - * This program is distributed in the hope that it will be useful,
18.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
18.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18.16 - * GNU General Public License for more details.
18.17 - *
18.18 - * You should have received a copy of the GNU General Public License
18.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
18.20 - */
18.21 -
18.22 -package org.sonews.daemon;
18.23 -
18.24 -import java.io.IOException;
18.25 -import java.net.InetSocketAddress;
18.26 -import java.net.SocketException;
18.27 -import java.nio.ByteBuffer;
18.28 -import java.nio.CharBuffer;
18.29 -import java.nio.channels.ClosedChannelException;
18.30 -import java.nio.channels.SelectionKey;
18.31 -import java.nio.channels.SocketChannel;
18.32 -import java.nio.charset.Charset;
18.33 -import java.util.Arrays;
18.34 -import java.util.Timer;
18.35 -import java.util.TimerTask;
18.36 -import org.sonews.daemon.command.Command;
18.37 -import org.sonews.storage.Article;
18.38 -import org.sonews.storage.Channel;
18.39 -import org.sonews.storage.StorageBackendException;
18.40 -import org.sonews.util.Log;
18.41 -import org.sonews.util.Stats;
18.42 -
18.43 -/**
18.44 - * For every SocketChannel (so TCP/IP connection) there is an instance of
18.45 - * this class.
18.46 - * @author Christian Lins
18.47 - * @since sonews/0.5.0
18.48 - */
18.49 -public final class NNTPConnection
18.50 -{
18.51 -
18.52 - public static final String NEWLINE = "\r\n"; // RFC defines this as newline
18.53 - public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
18.54 -
18.55 - private static final Timer cancelTimer = new Timer(true); // Thread-safe? True for run as daemon
18.56 -
18.57 - /** SocketChannel is generally thread-safe */
18.58 - private SocketChannel channel = null;
18.59 - private Charset charset = Charset.forName("UTF-8");
18.60 - private Command command = null;
18.61 - private Article currentArticle = null;
18.62 - private Channel currentGroup = null;
18.63 - private volatile long lastActivity = System.currentTimeMillis();
18.64 - private ChannelLineBuffers lineBuffers = new ChannelLineBuffers();
18.65 - private int readLock = 0;
18.66 - private final Object readLockGate = new Object();
18.67 - private SelectionKey writeSelKey = null;
18.68 -
18.69 - public NNTPConnection(final SocketChannel channel)
18.70 - throws IOException
18.71 - {
18.72 - if(channel == null)
18.73 - {
18.74 - throw new IllegalArgumentException("channel is null");
18.75 - }
18.76 -
18.77 - this.channel = channel;
18.78 - Stats.getInstance().clientConnect();
18.79 - }
18.80 -
18.81 - /**
18.82 - * Tries to get the read lock for this NNTPConnection. This method is Thread-
18.83 - * safe and returns true of the read lock was successfully set. If the lock
18.84 - * is still hold by another Thread the method returns false.
18.85 - */
18.86 - boolean tryReadLock()
18.87 - {
18.88 - // As synchronizing simple types may cause deadlocks,
18.89 - // we use a gate object.
18.90 - synchronized(readLockGate)
18.91 - {
18.92 - if(readLock != 0)
18.93 - {
18.94 - return false;
18.95 - }
18.96 - else
18.97 - {
18.98 - readLock = Thread.currentThread().hashCode();
18.99 - return true;
18.100 - }
18.101 - }
18.102 - }
18.103 -
18.104 - /**
18.105 - * Releases the read lock in a Thread-safe way.
18.106 - * @throws IllegalMonitorStateException if a Thread not holding the lock
18.107 - * tries to release it.
18.108 - */
18.109 - void unlockReadLock()
18.110 - {
18.111 - synchronized(readLockGate)
18.112 - {
18.113 - if(readLock == Thread.currentThread().hashCode())
18.114 - {
18.115 - readLock = 0;
18.116 - }
18.117 - else
18.118 - {
18.119 - throw new IllegalMonitorStateException();
18.120 - }
18.121 - }
18.122 - }
18.123 -
18.124 - /**
18.125 - * @return Current input buffer of this NNTPConnection instance.
18.126 - */
18.127 - public ByteBuffer getInputBuffer()
18.128 - {
18.129 - return this.lineBuffers.getInputBuffer();
18.130 - }
18.131 -
18.132 - /**
18.133 - * @return Output buffer of this NNTPConnection which has at least one byte
18.134 - * free storage.
18.135 - */
18.136 - public ByteBuffer getOutputBuffer()
18.137 - {
18.138 - return this.lineBuffers.getOutputBuffer();
18.139 - }
18.140 -
18.141 - /**
18.142 - * @return ChannelLineBuffers instance associated with this NNTPConnection.
18.143 - */
18.144 - public ChannelLineBuffers getBuffers()
18.145 - {
18.146 - return this.lineBuffers;
18.147 - }
18.148 -
18.149 - /**
18.150 - * @return true if this connection comes from a local remote address.
18.151 - */
18.152 - public boolean isLocalConnection()
18.153 - {
18.154 - return ((InetSocketAddress)this.channel.socket().getRemoteSocketAddress())
18.155 - .getHostName().equalsIgnoreCase("localhost");
18.156 - }
18.157 -
18.158 - void setWriteSelectionKey(SelectionKey selKey)
18.159 - {
18.160 - this.writeSelKey = selKey;
18.161 - }
18.162 -
18.163 - public void shutdownInput()
18.164 - {
18.165 - try
18.166 - {
18.167 - // Closes the input line of the channel's socket, so no new data
18.168 - // will be received and a timeout can be triggered.
18.169 - this.channel.socket().shutdownInput();
18.170 - }
18.171 - catch(IOException ex)
18.172 - {
18.173 - Log.get().warning("Exception in NNTPConnection.shutdownInput(): " + ex);
18.174 - }
18.175 - }
18.176 -
18.177 - public void shutdownOutput()
18.178 - {
18.179 - cancelTimer.schedule(new TimerTask()
18.180 - {
18.181 - @Override
18.182 - public void run()
18.183 - {
18.184 - try
18.185 - {
18.186 - // Closes the output line of the channel's socket.
18.187 - channel.socket().shutdownOutput();
18.188 - channel.close();
18.189 - }
18.190 - catch(SocketException ex)
18.191 - {
18.192 - // Socket was already disconnected
18.193 - Log.get().info("NNTPConnection.shutdownOutput(): " + ex);
18.194 - }
18.195 - catch(Exception ex)
18.196 - {
18.197 - Log.get().warning("NNTPConnection.shutdownOutput(): " + ex);
18.198 - }
18.199 - }
18.200 - }, 3000);
18.201 - }
18.202 -
18.203 - public SocketChannel getSocketChannel()
18.204 - {
18.205 - return this.channel;
18.206 - }
18.207 -
18.208 - public Article getCurrentArticle()
18.209 - {
18.210 - return this.currentArticle;
18.211 - }
18.212 -
18.213 - public Charset getCurrentCharset()
18.214 - {
18.215 - return this.charset;
18.216 - }
18.217 -
18.218 - /**
18.219 - * @return The currently selected communication channel (not SocketChannel)
18.220 - */
18.221 - public Channel getCurrentChannel()
18.222 - {
18.223 - return this.currentGroup;
18.224 - }
18.225 -
18.226 - public void setCurrentArticle(final Article article)
18.227 - {
18.228 - this.currentArticle = article;
18.229 - }
18.230 -
18.231 - public void setCurrentGroup(final Channel group)
18.232 - {
18.233 - this.currentGroup = group;
18.234 - }
18.235 -
18.236 - public long getLastActivity()
18.237 - {
18.238 - return this.lastActivity;
18.239 - }
18.240 -
18.241 - /**
18.242 - * Due to the readLockGate there is no need to synchronize this method.
18.243 - * @param raw
18.244 - * @throws IllegalArgumentException if raw is null.
18.245 - * @throws IllegalStateException if calling thread does not own the readLock.
18.246 - */
18.247 - void lineReceived(byte[] raw)
18.248 - {
18.249 - if(raw == null)
18.250 - {
18.251 - throw new IllegalArgumentException("raw is null");
18.252 - }
18.253 -
18.254 - if(readLock == 0 || readLock != Thread.currentThread().hashCode())
18.255 - {
18.256 - throw new IllegalStateException("readLock not properly set");
18.257 - }
18.258 -
18.259 - this.lastActivity = System.currentTimeMillis();
18.260 -
18.261 - String line = new String(raw, this.charset);
18.262 -
18.263 - // There might be a trailing \r, but trim() is a bad idea
18.264 - // as it removes also leading spaces from long header lines.
18.265 - if(line.endsWith("\r"))
18.266 - {
18.267 - line = line.substring(0, line.length() - 1);
18.268 - raw = Arrays.copyOf(raw, raw.length - 1);
18.269 - }
18.270 -
18.271 - Log.get().fine("<< " + line);
18.272 -
18.273 - if(command == null)
18.274 - {
18.275 - command = parseCommandLine(line);
18.276 - assert command != null;
18.277 - }
18.278 -
18.279 - try
18.280 - {
18.281 - // The command object will process the line we just received
18.282 - try
18.283 - {
18.284 - command.processLine(this, line, raw);
18.285 - }
18.286 - catch(StorageBackendException ex)
18.287 - {
18.288 - Log.get().info("Retry command processing after StorageBackendException");
18.289 -
18.290 - // Try it a second time, so that the backend has time to recover
18.291 - command.processLine(this, line, raw);
18.292 - }
18.293 - }
18.294 - catch(ClosedChannelException ex0)
18.295 - {
18.296 - try
18.297 - {
18.298 - Log.get().info("Connection to " + channel.socket().getRemoteSocketAddress()
18.299 - + " closed: " + ex0);
18.300 - }
18.301 - catch(Exception ex0a)
18.302 - {
18.303 - ex0a.printStackTrace();
18.304 - }
18.305 - }
18.306 - catch(Exception ex1) // This will catch a second StorageBackendException
18.307 - {
18.308 - try
18.309 - {
18.310 - command = null;
18.311 - ex1.printStackTrace();
18.312 - println("500 Internal server error");
18.313 - }
18.314 - catch(Exception ex2)
18.315 - {
18.316 - ex2.printStackTrace();
18.317 - }
18.318 - }
18.319 -
18.320 - if(command == null || command.hasFinished())
18.321 - {
18.322 - command = null;
18.323 - charset = Charset.forName("UTF-8"); // Reset to default
18.324 - }
18.325 - }
18.326 -
18.327 - /**
18.328 - * This method determines the fitting command processing class.
18.329 - * @param line
18.330 - * @return
18.331 - */
18.332 - private Command parseCommandLine(String line)
18.333 - {
18.334 - String cmdStr = line.split(" ")[0];
18.335 - return CommandSelector.getInstance().get(cmdStr);
18.336 - }
18.337 -
18.338 - /**
18.339 - * Puts the given line into the output buffer, adds a newline character
18.340 - * and returns. The method returns immediately and does not block until
18.341 - * the line was sent. If line is longer than 510 octets it is split up in
18.342 - * several lines. Each line is terminated by \r\n (NNTPConnection.NEWLINE).
18.343 - * @param line
18.344 - */
18.345 - public void println(final CharSequence line, final Charset charset)
18.346 - throws IOException
18.347 - {
18.348 - writeToChannel(CharBuffer.wrap(line), charset, line);
18.349 - writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
18.350 - }
18.351 -
18.352 - /**
18.353 - * Writes the given raw lines to the output buffers and finishes with
18.354 - * a newline character (\r\n).
18.355 - * @param rawLines
18.356 - */
18.357 - public void println(final byte[] rawLines)
18.358 - throws IOException
18.359 - {
18.360 - this.lineBuffers.addOutputBuffer(ByteBuffer.wrap(rawLines));
18.361 - writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
18.362 - }
18.363 -
18.364 - /**
18.365 - * Encodes the given CharBuffer using the given Charset to a bunch of
18.366 - * ByteBuffers (each 512 bytes large) and enqueues them for writing at the
18.367 - * connected SocketChannel.
18.368 - * @throws java.io.IOException
18.369 - */
18.370 - private void writeToChannel(CharBuffer characters, final Charset charset,
18.371 - CharSequence debugLine)
18.372 - throws IOException
18.373 - {
18.374 - if(!charset.canEncode())
18.375 - {
18.376 - Log.get().severe("FATAL: Charset " + charset + " cannot encode!");
18.377 - return;
18.378 - }
18.379 -
18.380 - // Write characters to output buffers
18.381 - LineEncoder lenc = new LineEncoder(characters, charset);
18.382 - lenc.encode(lineBuffers);
18.383 -
18.384 - enableWriteEvents(debugLine);
18.385 - }
18.386 -
18.387 - private void enableWriteEvents(CharSequence debugLine)
18.388 - {
18.389 - // Enable OP_WRITE events so that the buffers are processed
18.390 - try
18.391 - {
18.392 - this.writeSelKey.interestOps(SelectionKey.OP_WRITE);
18.393 - ChannelWriter.getInstance().getSelector().wakeup();
18.394 - }
18.395 - catch(Exception ex) // CancelledKeyException and ChannelCloseException
18.396 - {
18.397 - Log.get().warning("NNTPConnection.writeToChannel(): " + ex);
18.398 - return;
18.399 - }
18.400 -
18.401 - // Update last activity timestamp
18.402 - this.lastActivity = System.currentTimeMillis();
18.403 - if(debugLine != null)
18.404 - {
18.405 - Log.get().fine(">> " + debugLine);
18.406 - }
18.407 - }
18.408 -
18.409 - public void println(final CharSequence line)
18.410 - throws IOException
18.411 - {
18.412 - println(line, charset);
18.413 - }
18.414 -
18.415 - public void print(final String line)
18.416 - throws IOException
18.417 - {
18.418 - writeToChannel(CharBuffer.wrap(line), charset, line);
18.419 - }
18.420 -
18.421 - public void setCurrentCharset(final Charset charset)
18.422 - {
18.423 - this.charset = charset;
18.424 - }
18.425 -
18.426 - void setLastActivity(long timestamp)
18.427 - {
18.428 - this.lastActivity = timestamp;
18.429 - }
18.430 -
18.431 -}
19.1 --- a/org/sonews/daemon/NNTPDaemon.java Sun Aug 29 17:04:25 2010 +0200
19.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
19.3 @@ -1,197 +0,0 @@
19.4 -/*
19.5 - * SONEWS News Server
19.6 - * see AUTHORS for the list of contributors
19.7 - *
19.8 - * This program is free software: you can redistribute it and/or modify
19.9 - * it under the terms of the GNU General Public License as published by
19.10 - * the Free Software Foundation, either version 3 of the License, or
19.11 - * (at your option) any later version.
19.12 - *
19.13 - * This program is distributed in the hope that it will be useful,
19.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
19.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19.16 - * GNU General Public License for more details.
19.17 - *
19.18 - * You should have received a copy of the GNU General Public License
19.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
19.20 - */
19.21 -
19.22 -package org.sonews.daemon;
19.23 -
19.24 -import org.sonews.config.Config;
19.25 -import org.sonews.Main;
19.26 -import org.sonews.util.Log;
19.27 -import java.io.IOException;
19.28 -import java.net.BindException;
19.29 -import java.net.InetSocketAddress;
19.30 -import java.net.ServerSocket;
19.31 -import java.nio.channels.CancelledKeyException;
19.32 -import java.nio.channels.ClosedChannelException;
19.33 -import java.nio.channels.SelectionKey;
19.34 -import java.nio.channels.Selector;
19.35 -import java.nio.channels.ServerSocketChannel;
19.36 -import java.nio.channels.SocketChannel;
19.37 -
19.38 -/**
19.39 - * NNTP daemon using SelectableChannels.
19.40 - * @author Christian Lins
19.41 - * @since sonews/0.5.0
19.42 - */
19.43 -public final class NNTPDaemon extends AbstractDaemon
19.44 -{
19.45 -
19.46 - public static final Object RegisterGate = new Object();
19.47 -
19.48 - private static NNTPDaemon instance = null;
19.49 -
19.50 - public static synchronized NNTPDaemon createInstance(int port)
19.51 - {
19.52 - if(instance == null)
19.53 - {
19.54 - instance = new NNTPDaemon(port);
19.55 - return instance;
19.56 - }
19.57 - else
19.58 - {
19.59 - throw new RuntimeException("NNTPDaemon.createInstance() called twice");
19.60 - }
19.61 - }
19.62 -
19.63 - private int port;
19.64 -
19.65 - private NNTPDaemon(final int port)
19.66 - {
19.67 - Log.get().info("Server listening on port " + port);
19.68 - this.port = port;
19.69 - }
19.70 -
19.71 - @Override
19.72 - public void run()
19.73 - {
19.74 - try
19.75 - {
19.76 - // Create a Selector that handles the SocketChannel multiplexing
19.77 - final Selector readSelector = Selector.open();
19.78 - final Selector writeSelector = Selector.open();
19.79 -
19.80 - // Start working threads
19.81 - final int workerThreads = Runtime.getRuntime().availableProcessors() * 4;
19.82 - ConnectionWorker[] cworkers = new ConnectionWorker[workerThreads];
19.83 - for(int n = 0; n < workerThreads; n++)
19.84 - {
19.85 - cworkers[n] = new ConnectionWorker();
19.86 - cworkers[n].start();
19.87 - }
19.88 -
19.89 - ChannelWriter.getInstance().setSelector(writeSelector);
19.90 - ChannelReader.getInstance().setSelector(readSelector);
19.91 - ChannelWriter.getInstance().start();
19.92 - ChannelReader.getInstance().start();
19.93 -
19.94 - final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
19.95 - serverSocketChannel.configureBlocking(true); // Set to blocking mode
19.96 -
19.97 - // Configure ServerSocket; bind to socket...
19.98 - final ServerSocket serverSocket = serverSocketChannel.socket();
19.99 - serverSocket.bind(new InetSocketAddress(this.port));
19.100 -
19.101 - while(isRunning())
19.102 - {
19.103 - SocketChannel socketChannel;
19.104 -
19.105 - try
19.106 - {
19.107 - // As we set the server socket channel to blocking mode the accept()
19.108 - // method will block.
19.109 - socketChannel = serverSocketChannel.accept();
19.110 - socketChannel.configureBlocking(false);
19.111 - assert socketChannel.isConnected();
19.112 - assert socketChannel.finishConnect();
19.113 - }
19.114 - catch(IOException ex)
19.115 - {
19.116 - // Under heavy load an IOException "Too many open files may
19.117 - // be thrown. It most cases we should slow down the connection
19.118 - // accepting, to give the worker threads some time to process work.
19.119 - Log.get().severe("IOException while accepting connection: " + ex.getMessage());
19.120 - Log.get().info("Connection accepting sleeping for seconds...");
19.121 - Thread.sleep(5000); // 5 seconds
19.122 - continue;
19.123 - }
19.124 -
19.125 - final NNTPConnection conn;
19.126 - try
19.127 - {
19.128 - conn = new NNTPConnection(socketChannel);
19.129 - Connections.getInstance().add(conn);
19.130 - }
19.131 - catch(IOException ex)
19.132 - {
19.133 - Log.get().warning(ex.toString());
19.134 - socketChannel.close();
19.135 - continue;
19.136 - }
19.137 -
19.138 - try
19.139 - {
19.140 - SelectionKey selKeyWrite =
19.141 - registerSelector(writeSelector, socketChannel, SelectionKey.OP_WRITE);
19.142 - registerSelector(readSelector, socketChannel, SelectionKey.OP_READ);
19.143 -
19.144 - Log.get().info("Connected: " + socketChannel.socket().getRemoteSocketAddress());
19.145 -
19.146 - // Set write selection key and send hello to client
19.147 - conn.setWriteSelectionKey(selKeyWrite);
19.148 - conn.println("200 " + Config.inst().get(Config.HOSTNAME, "localhost")
19.149 - + " " + Main.VERSION + " news server ready - (posting ok).");
19.150 - }
19.151 - catch(CancelledKeyException cke)
19.152 - {
19.153 - Log.get().warning("CancelledKeyException " + cke.getMessage() + " was thrown: "
19.154 - + socketChannel.socket());
19.155 - }
19.156 - catch(ClosedChannelException cce)
19.157 - {
19.158 - Log.get().warning("ClosedChannelException " + cce.getMessage() + " was thrown: "
19.159 - + socketChannel.socket());
19.160 - }
19.161 - }
19.162 - }
19.163 - catch(BindException ex)
19.164 - {
19.165 - // Could not bind to socket; this is a fatal problem; so perform shutdown
19.166 - ex.printStackTrace();
19.167 - System.exit(1);
19.168 - }
19.169 - catch(IOException ex)
19.170 - {
19.171 - ex.printStackTrace();
19.172 - }
19.173 - catch(Exception ex)
19.174 - {
19.175 - ex.printStackTrace();
19.176 - }
19.177 - }
19.178 -
19.179 - public static SelectionKey registerSelector(final Selector selector,
19.180 - final SocketChannel channel, final int op)
19.181 - throws CancelledKeyException, ClosedChannelException
19.182 - {
19.183 - // Register the selector at the channel, so that it will be notified
19.184 - // on the socket's events
19.185 - synchronized(RegisterGate)
19.186 - {
19.187 - // Wakeup the currently blocking reader/writer thread; we have locked
19.188 - // the RegisterGate to prevent the awakened thread to block again
19.189 - selector.wakeup();
19.190 -
19.191 - // Lock the selector to prevent the waiting worker threads going into
19.192 - // selector.select() which would block the selector.
19.193 - synchronized (selector)
19.194 - {
19.195 - return channel.register(selector, op, null);
19.196 - }
19.197 - }
19.198 - }
19.199 -
19.200 -}
20.1 --- a/org/sonews/daemon/command/ArticleCommand.java Sun Aug 29 17:04:25 2010 +0200
20.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
20.3 @@ -1,174 +0,0 @@
20.4 -/*
20.5 - * SONEWS News Server
20.6 - * see AUTHORS for the list of contributors
20.7 - *
20.8 - * This program is free software: you can redistribute it and/or modify
20.9 - * it under the terms of the GNU General Public License as published by
20.10 - * the Free Software Foundation, either version 3 of the License, or
20.11 - * (at your option) any later version.
20.12 - *
20.13 - * This program is distributed in the hope that it will be useful,
20.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
20.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20.16 - * GNU General Public License for more details.
20.17 - *
20.18 - * You should have received a copy of the GNU General Public License
20.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
20.20 - */
20.21 -
20.22 -package org.sonews.daemon.command;
20.23 -
20.24 -import java.io.IOException;
20.25 -import org.sonews.storage.Article;
20.26 -import org.sonews.daemon.NNTPConnection;
20.27 -import org.sonews.storage.Channel;
20.28 -import org.sonews.storage.StorageBackendException;
20.29 -
20.30 -/**
20.31 - * Class handling the ARTICLE, BODY and HEAD commands.
20.32 - * @author Christian Lins
20.33 - * @author Dennis Schwerdel
20.34 - * @since n3tpd/0.1
20.35 - */
20.36 -public class ArticleCommand implements Command
20.37 -{
20.38 -
20.39 - @Override
20.40 - public String[] getSupportedCommandStrings()
20.41 - {
20.42 - return new String[] {"ARTICLE", "BODY", "HEAD"};
20.43 - }
20.44 -
20.45 - @Override
20.46 - public boolean hasFinished()
20.47 - {
20.48 - return true;
20.49 - }
20.50 -
20.51 - @Override
20.52 - public String impliedCapability()
20.53 - {
20.54 - return null;
20.55 - }
20.56 -
20.57 - @Override
20.58 - public boolean isStateful()
20.59 - {
20.60 - return false;
20.61 - }
20.62 -
20.63 - // TODO: Refactor this method to reduce its complexity!
20.64 - @Override
20.65 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
20.66 - throws IOException
20.67 - {
20.68 - final String[] command = line.split(" ");
20.69 -
20.70 - Article article = null;
20.71 - long artIndex = -1;
20.72 - if (command.length == 1)
20.73 - {
20.74 - article = conn.getCurrentArticle();
20.75 - if (article == null)
20.76 - {
20.77 - conn.println("420 no current article has been selected");
20.78 - return;
20.79 - }
20.80 - }
20.81 - else if (command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN))
20.82 - {
20.83 - // Message-ID
20.84 - article = Article.getByMessageID(command[1]);
20.85 - if (article == null)
20.86 - {
20.87 - conn.println("430 no such article found");
20.88 - return;
20.89 - }
20.90 - }
20.91 - else
20.92 - {
20.93 - // Message Number
20.94 - try
20.95 - {
20.96 - Channel currentGroup = conn.getCurrentChannel();
20.97 - if(currentGroup == null)
20.98 - {
20.99 - conn.println("400 no group selected");
20.100 - return;
20.101 - }
20.102 -
20.103 - artIndex = Long.parseLong(command[1]);
20.104 - article = currentGroup.getArticle(artIndex);
20.105 - }
20.106 - catch(NumberFormatException ex)
20.107 - {
20.108 - ex.printStackTrace();
20.109 - }
20.110 - catch(StorageBackendException ex)
20.111 - {
20.112 - ex.printStackTrace();
20.113 - }
20.114 -
20.115 - if (article == null)
20.116 - {
20.117 - conn.println("423 no such article number in this group");
20.118 - return;
20.119 - }
20.120 - conn.setCurrentArticle(article);
20.121 - }
20.122 -
20.123 - if(command[0].equalsIgnoreCase("ARTICLE"))
20.124 - {
20.125 - conn.println("220 " + artIndex + " " + article.getMessageID()
20.126 - + " article retrieved - head and body follow");
20.127 - conn.println(article.getHeaderSource());
20.128 - conn.println("");
20.129 - conn.println(article.getBody());
20.130 - conn.println(".");
20.131 - }
20.132 - else if(command[0].equalsIgnoreCase("BODY"))
20.133 - {
20.134 - conn.println("222 " + artIndex + " " + article.getMessageID() + " body");
20.135 - conn.println(article.getBody());
20.136 - conn.println(".");
20.137 - }
20.138 -
20.139 - /*
20.140 - * HEAD: This command is mandatory.
20.141 - *
20.142 - * Syntax
20.143 - * HEAD message-id
20.144 - * HEAD number
20.145 - * HEAD
20.146 - *
20.147 - * Responses
20.148 - *
20.149 - * First form (message-id specified)
20.150 - * 221 0|n message-id Headers follow (multi-line)
20.151 - * 430 No article with that message-id
20.152 - *
20.153 - * Second form (article number specified)
20.154 - * 221 n message-id Headers follow (multi-line)
20.155 - * 412 No newsgroup selected
20.156 - * 423 No article with that number
20.157 - *
20.158 - * Third form (current article number used)
20.159 - * 221 n message-id Headers follow (multi-line)
20.160 - * 412 No newsgroup selected
20.161 - * 420 Current article number is invalid
20.162 - *
20.163 - * Parameters
20.164 - * number Requested article number
20.165 - * n Returned article number
20.166 - * message-id Article message-id
20.167 - */
20.168 - else if(command[0].equalsIgnoreCase("HEAD"))
20.169 - {
20.170 - conn.println("221 " + artIndex + " " + article.getMessageID()
20.171 - + " Headers follow (multi-line)");
20.172 - conn.println(article.getHeaderSource());
20.173 - conn.println(".");
20.174 - }
20.175 - }
20.176 -
20.177 -}
21.1 --- a/org/sonews/daemon/command/CapabilitiesCommand.java Sun Aug 29 17:04:25 2010 +0200
21.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
21.3 @@ -1,93 +0,0 @@
21.4 -/*
21.5 - * SONEWS News Server
21.6 - * see AUTHORS for the list of contributors
21.7 - *
21.8 - * This program is free software: you can redistribute it and/or modify
21.9 - * it under the terms of the GNU General Public License as published by
21.10 - * the Free Software Foundation, either version 3 of the License, or
21.11 - * (at your option) any later version.
21.12 - *
21.13 - * This program is distributed in the hope that it will be useful,
21.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
21.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21.16 - * GNU General Public License for more details.
21.17 - *
21.18 - * You should have received a copy of the GNU General Public License
21.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
21.20 - */
21.21 -
21.22 -package org.sonews.daemon.command;
21.23 -
21.24 -import java.io.IOException;
21.25 -import org.sonews.daemon.NNTPConnection;
21.26 -
21.27 -/**
21.28 - * <pre>
21.29 - * The CAPABILITIES command allows a client to determine the
21.30 - * capabilities of the server at any given time.
21.31 - *
21.32 - * This command MAY be issued at any time; the server MUST NOT require
21.33 - * it to be issued in order to make use of any capability. The response
21.34 - * generated by this command MAY change during a session because of
21.35 - * other state information (which, in turn, may be changed by the
21.36 - * effects of other commands or by external events). An NNTP client is
21.37 - * only able to get the current and correct information concerning
21.38 - * available capabilities at any point during a session by issuing a
21.39 - * CAPABILITIES command at that point of that session and processing the
21.40 - * response.
21.41 - * </pre>
21.42 - * @author Christian Lins
21.43 - * @since sonews/0.5.0
21.44 - */
21.45 -public class CapabilitiesCommand implements Command
21.46 -{
21.47 -
21.48 - static final String[] CAPABILITIES = new String[]
21.49 - {
21.50 - "VERSION 2", // MUST be the first one; VERSION 2 refers to RFC3977
21.51 - "READER", // Server implements commands for reading
21.52 - "POST", // Server implements POST command
21.53 - "OVER" // Server implements OVER command
21.54 - };
21.55 -
21.56 - @Override
21.57 - public String[] getSupportedCommandStrings()
21.58 - {
21.59 - return new String[] {"CAPABILITIES"};
21.60 - }
21.61 -
21.62 - /**
21.63 - * First called after one call to processLine().
21.64 - * @return
21.65 - */
21.66 - @Override
21.67 - public boolean hasFinished()
21.68 - {
21.69 - return true;
21.70 - }
21.71 -
21.72 - @Override
21.73 - public String impliedCapability()
21.74 - {
21.75 - return null;
21.76 - }
21.77 -
21.78 - @Override
21.79 - public boolean isStateful()
21.80 - {
21.81 - return false;
21.82 - }
21.83 -
21.84 - @Override
21.85 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
21.86 - throws IOException
21.87 - {
21.88 - conn.println("101 Capabilities list:");
21.89 - for(String cap : CAPABILITIES)
21.90 - {
21.91 - conn.println(cap);
21.92 - }
21.93 - conn.println(".");
21.94 - }
21.95 -
21.96 -}
22.1 --- a/org/sonews/daemon/command/Command.java Sun Aug 29 17:04:25 2010 +0200
22.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
22.3 @@ -1,51 +0,0 @@
22.4 -/*
22.5 - * SONEWS News Server
22.6 - * see AUTHORS for the list of contributors
22.7 - *
22.8 - * This program is free software: you can redistribute it and/or modify
22.9 - * it under the terms of the GNU General Public License as published by
22.10 - * the Free Software Foundation, either version 3 of the License, or
22.11 - * (at your option) any later version.
22.12 - *
22.13 - * This program is distributed in the hope that it will be useful,
22.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
22.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22.16 - * GNU General Public License for more details.
22.17 - *
22.18 - * You should have received a copy of the GNU General Public License
22.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
22.20 - */
22.21 -
22.22 -package org.sonews.daemon.command;
22.23 -
22.24 -import java.io.IOException;
22.25 -import org.sonews.daemon.NNTPConnection;
22.26 -import org.sonews.storage.StorageBackendException;
22.27 -
22.28 -/**
22.29 - * Interface for pluggable NNTP commands handling classes.
22.30 - * @author Christian Lins
22.31 - * @since sonews/0.6.0
22.32 - */
22.33 -public interface Command
22.34 -{
22.35 -
22.36 - /**
22.37 - * @return true if this instance can be reused.
22.38 - */
22.39 - boolean hasFinished();
22.40 -
22.41 - /**
22.42 - * Returns capability string that is implied by this command class.
22.43 - * MAY return null if the command is required by the NNTP standard.
22.44 - */
22.45 - String impliedCapability();
22.46 -
22.47 - boolean isStateful();
22.48 -
22.49 - String[] getSupportedCommandStrings();
22.50 -
22.51 - void processLine(NNTPConnection conn, String line, byte[] rawLine)
22.52 - throws IOException, StorageBackendException;
22.53 -
22.54 -}
23.1 --- a/org/sonews/daemon/command/GroupCommand.java Sun Aug 29 17:04:25 2010 +0200
23.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
23.3 @@ -1,102 +0,0 @@
23.4 -/*
23.5 - * SONEWS News Server
23.6 - * see AUTHORS for the list of contributors
23.7 - *
23.8 - * This program is free software: you can redistribute it and/or modify
23.9 - * it under the terms of the GNU General Public License as published by
23.10 - * the Free Software Foundation, either version 3 of the License, or
23.11 - * (at your option) any later version.
23.12 - *
23.13 - * This program is distributed in the hope that it will be useful,
23.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
23.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23.16 - * GNU General Public License for more details.
23.17 - *
23.18 - * You should have received a copy of the GNU General Public License
23.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
23.20 - */
23.21 -
23.22 -package org.sonews.daemon.command;
23.23 -
23.24 -import java.io.IOException;
23.25 -import org.sonews.daemon.NNTPConnection;
23.26 -import org.sonews.storage.Channel;
23.27 -import org.sonews.storage.StorageBackendException;
23.28 -
23.29 -/**
23.30 - * Class handling the GROUP command.
23.31 - * <pre>
23.32 - * Syntax
23.33 - * GROUP group
23.34 - *
23.35 - * Responses
23.36 - * 211 number low high group Group successfully selected
23.37 - * 411 No such newsgroup
23.38 - *
23.39 - * Parameters
23.40 - * group Name of newsgroup
23.41 - * number Estimated number of articles in the group
23.42 - * low Reported low water mark
23.43 - * high Reported high water mark
23.44 - * </pre>
23.45 - * (from RFC 3977)
23.46 - *
23.47 - * @author Christian Lins
23.48 - * @author Dennis Schwerdel
23.49 - * @since n3tpd/0.1
23.50 - */
23.51 -public class GroupCommand implements Command
23.52 -{
23.53 -
23.54 - @Override
23.55 - public String[] getSupportedCommandStrings()
23.56 - {
23.57 - return new String[]{"GROUP"};
23.58 - }
23.59 -
23.60 - @Override
23.61 - public boolean hasFinished()
23.62 - {
23.63 - return true;
23.64 - }
23.65 -
23.66 - @Override
23.67 - public String impliedCapability()
23.68 - {
23.69 - return null;
23.70 - }
23.71 -
23.72 - @Override
23.73 - public boolean isStateful()
23.74 - {
23.75 - return true;
23.76 - }
23.77 -
23.78 - @Override
23.79 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
23.80 - throws IOException, StorageBackendException
23.81 - {
23.82 - final String[] command = line.split(" ");
23.83 -
23.84 - Channel group;
23.85 - if(command.length >= 2)
23.86 - {
23.87 - group = Channel.getByName(command[1]);
23.88 - if(group == null || group.isDeleted())
23.89 - {
23.90 - conn.println("411 no such news group");
23.91 - }
23.92 - else
23.93 - {
23.94 - conn.setCurrentGroup(group);
23.95 - conn.println("211 " + group.getPostingsCount() + " " + group.getFirstArticleNumber()
23.96 - + " " + group.getLastArticleNumber() + " " + group.getName() + " group selected");
23.97 - }
23.98 - }
23.99 - else
23.100 - {
23.101 - conn.println("500 no group name given");
23.102 - }
23.103 - }
23.104 -
23.105 -}
24.1 --- a/org/sonews/daemon/command/HelpCommand.java Sun Aug 29 17:04:25 2010 +0200
24.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
24.3 @@ -1,100 +0,0 @@
24.4 -/*
24.5 - * SONEWS News Server
24.6 - * see AUTHORS for the list of contributors
24.7 - *
24.8 - * This program is free software: you can redistribute it and/or modify
24.9 - * it under the terms of the GNU General Public License as published by
24.10 - * the Free Software Foundation, either version 3 of the License, or
24.11 - * (at your option) any later version.
24.12 - *
24.13 - * This program is distributed in the hope that it will be useful,
24.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
24.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24.16 - * GNU General Public License for more details.
24.17 - *
24.18 - * You should have received a copy of the GNU General Public License
24.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
24.20 - */
24.21 -
24.22 -package org.sonews.daemon.command;
24.23 -
24.24 -import java.io.IOException;
24.25 -import java.util.Set;
24.26 -import org.sonews.daemon.CommandSelector;
24.27 -import org.sonews.daemon.NNTPConnection;
24.28 -import org.sonews.util.io.Resource;
24.29 -
24.30 -/**
24.31 - * This command provides a short summary of the commands that are
24.32 - * understood by this implementation of the server. The help text will
24.33 - * be presented as a multi-line data block following the 100 response
24.34 - * code (taken from RFC).
24.35 - * @author Christian Lins
24.36 - * @since sonews/0.5.0
24.37 - */
24.38 -public class HelpCommand implements Command
24.39 -{
24.40 -
24.41 - @Override
24.42 - public boolean hasFinished()
24.43 - {
24.44 - return true;
24.45 - }
24.46 -
24.47 - @Override
24.48 - public String impliedCapability()
24.49 - {
24.50 - return null;
24.51 - }
24.52 -
24.53 - @Override
24.54 - public boolean isStateful()
24.55 - {
24.56 - return true;
24.57 - }
24.58 -
24.59 - @Override
24.60 - public String[] getSupportedCommandStrings()
24.61 - {
24.62 - return new String[]{"HELP"};
24.63 - }
24.64 -
24.65 - @Override
24.66 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
24.67 - throws IOException
24.68 - {
24.69 - final String[] command = line.split(" ");
24.70 - conn.println("100 help text follows");
24.71 -
24.72 - if(line.length() <= 1)
24.73 - {
24.74 - final String[] help = Resource
24.75 - .getAsString("helpers/helptext", true).split("\n");
24.76 - for(String hstr : help)
24.77 - {
24.78 - conn.println(hstr);
24.79 - }
24.80 -
24.81 - Set<String> commandNames = CommandSelector.getCommandNames();
24.82 - for(String cmdName : commandNames)
24.83 - {
24.84 - conn.println(cmdName);
24.85 - }
24.86 - }
24.87 - else
24.88 - {
24.89 - Command cmd = CommandSelector.getInstance().get(command[1]);
24.90 - if(cmd instanceof HelpfulCommand)
24.91 - {
24.92 - conn.println(((HelpfulCommand)cmd).getHelpString());
24.93 - }
24.94 - else
24.95 - {
24.96 - conn.println("No further help information available.");
24.97 - }
24.98 - }
24.99 -
24.100 - conn.println(".");
24.101 - }
24.102 -
24.103 -}
25.1 --- a/org/sonews/daemon/command/HelpfulCommand.java Sun Aug 29 17:04:25 2010 +0200
25.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
25.3 @@ -1,35 +0,0 @@
25.4 -/*
25.5 - * SONEWS News Server
25.6 - * see AUTHORS for the list of contributors
25.7 - *
25.8 - * This program is free software: you can redistribute it and/or modify
25.9 - * it under the terms of the GNU General Public License as published by
25.10 - * the Free Software Foundation, either version 3 of the License, or
25.11 - * (at your option) any later version.
25.12 - *
25.13 - * This program is distributed in the hope that it will be useful,
25.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
25.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25.16 - * GNU General Public License for more details.
25.17 - *
25.18 - * You should have received a copy of the GNU General Public License
25.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
25.20 - */
25.21 -
25.22 -package org.sonews.daemon.command;
25.23 -
25.24 -/**
25.25 - *
25.26 - * @since sonews/1.1
25.27 - * @author Christian Lins
25.28 - */
25.29 -public interface HelpfulCommand extends Command
25.30 -{
25.31 -
25.32 - /**
25.33 - * @return A short description of this command, that is
25.34 - * used within the output of the HELP command.
25.35 - */
25.36 - String getHelpString();
25.37 -
25.38 -}
26.1 --- a/org/sonews/daemon/command/ListCommand.java Sun Aug 29 17:04:25 2010 +0200
26.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
26.3 @@ -1,153 +0,0 @@
26.4 -/*
26.5 - * SONEWS News Server
26.6 - * see AUTHORS for the list of contributors
26.7 - *
26.8 - * This program is free software: you can redistribute it and/or modify
26.9 - * it under the terms of the GNU General Public License as published by
26.10 - * the Free Software Foundation, either version 3 of the License, or
26.11 - * (at your option) any later version.
26.12 - *
26.13 - * This program is distributed in the hope that it will be useful,
26.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
26.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26.16 - * GNU General Public License for more details.
26.17 - *
26.18 - * You should have received a copy of the GNU General Public License
26.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
26.20 - */
26.21 -
26.22 -package org.sonews.daemon.command;
26.23 -
26.24 -import java.io.IOException;
26.25 -import java.util.List;
26.26 -import java.util.regex.Matcher;
26.27 -import java.util.regex.Pattern;
26.28 -import java.util.regex.PatternSyntaxException;
26.29 -import org.sonews.daemon.NNTPConnection;
26.30 -import org.sonews.storage.Channel;
26.31 -import org.sonews.storage.StorageBackendException;
26.32 -import org.sonews.util.Log;
26.33 -
26.34 -/**
26.35 - * Class handling the LIST command.
26.36 - * @author Christian Lins
26.37 - * @author Dennis Schwerdel
26.38 - * @since n3tpd/0.1
26.39 - */
26.40 -public class ListCommand implements Command
26.41 -{
26.42 -
26.43 - @Override
26.44 - public String[] getSupportedCommandStrings()
26.45 - {
26.46 - return new String[]{"LIST"};
26.47 - }
26.48 -
26.49 - @Override
26.50 - public boolean hasFinished()
26.51 - {
26.52 - return true;
26.53 - }
26.54 -
26.55 - @Override
26.56 - public String impliedCapability()
26.57 - {
26.58 - return null;
26.59 - }
26.60 -
26.61 - @Override
26.62 - public boolean isStateful()
26.63 - {
26.64 - return false;
26.65 - }
26.66 -
26.67 - @Override
26.68 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
26.69 - throws IOException, StorageBackendException
26.70 - {
26.71 - final String[] command = line.split(" ");
26.72 -
26.73 - if(command.length >= 2)
26.74 - {
26.75 - if(command[1].equalsIgnoreCase("OVERVIEW.FMT"))
26.76 - {
26.77 - conn.println("215 information follows");
26.78 - conn.println("Subject:\nFrom:\nDate:\nMessage-ID:\nReferences:\nBytes:\nLines:\nXref");
26.79 - conn.println(".");
26.80 - }
26.81 - else if(command[1].equalsIgnoreCase("NEWSGROUPS"))
26.82 - {
26.83 - conn.println("215 information follows");
26.84 - final List<Channel> list = Channel.getAll();
26.85 - for (Channel g : list)
26.86 - {
26.87 - conn.println(g.getName() + "\t" + "-");
26.88 - }
26.89 - conn.println(".");
26.90 - }
26.91 - else if(command[1].equalsIgnoreCase("SUBSCRIPTIONS"))
26.92 - {
26.93 - conn.println("215 information follows");
26.94 - conn.println(".");
26.95 - }
26.96 - else if(command[1].equalsIgnoreCase("EXTENSIONS"))
26.97 - {
26.98 - conn.println("202 Supported NNTP extensions.");
26.99 - conn.println("LISTGROUP");
26.100 - conn.println("XDAEMON");
26.101 - conn.println("XPAT");
26.102 - conn.println(".");
26.103 - }
26.104 - else if(command[1].equalsIgnoreCase("ACTIVE"))
26.105 - {
26.106 - String pattern = command.length == 2
26.107 - ? null : command[2].replace("*", "\\w*");
26.108 - printGroupInfo(conn, pattern);
26.109 - }
26.110 - else
26.111 - {
26.112 - conn.println("500 unknown argument to LIST command");
26.113 - }
26.114 - }
26.115 - else
26.116 - {
26.117 - printGroupInfo(conn, null);
26.118 - }
26.119 - }
26.120 -
26.121 - private void printGroupInfo(NNTPConnection conn, String pattern)
26.122 - throws IOException, StorageBackendException
26.123 - {
26.124 - final List<Channel> groups = Channel.getAll();
26.125 - if(groups != null)
26.126 - {
26.127 - conn.println("215 list of newsgroups follows");
26.128 - for(Channel g : groups)
26.129 - {
26.130 - try
26.131 - {
26.132 - Matcher matcher = pattern == null ?
26.133 - null : Pattern.compile(pattern).matcher(g.getName());
26.134 - if(!g.isDeleted() &&
26.135 - (matcher == null || matcher.find()))
26.136 - {
26.137 - String writeable = g.isWriteable() ? " y" : " n";
26.138 - // Indeed first the higher article number then the lower
26.139 - conn.println(g.getName() + " " + g.getLastArticleNumber() + " "
26.140 - + g.getFirstArticleNumber() + writeable);
26.141 - }
26.142 - }
26.143 - catch(PatternSyntaxException ex)
26.144 - {
26.145 - Log.get().info(ex.toString());
26.146 - }
26.147 - }
26.148 - conn.println(".");
26.149 - }
26.150 - else
26.151 - {
26.152 - conn.println("500 server backend malfunction");
26.153 - }
26.154 - }
26.155 -
26.156 -}
27.1 --- a/org/sonews/daemon/command/ListGroupCommand.java Sun Aug 29 17:04:25 2010 +0200
27.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
27.3 @@ -1,94 +0,0 @@
27.4 -/*
27.5 - * SONEWS News Server
27.6 - * see AUTHORS for the list of contributors
27.7 - *
27.8 - * This program is free software: you can redistribute it and/or modify
27.9 - * it under the terms of the GNU General Public License as published by
27.10 - * the Free Software Foundation, either version 3 of the License, or
27.11 - * (at your option) any later version.
27.12 - *
27.13 - * This program is distributed in the hope that it will be useful,
27.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
27.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27.16 - * GNU General Public License for more details.
27.17 - *
27.18 - * You should have received a copy of the GNU General Public License
27.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
27.20 - */
27.21 -
27.22 -package org.sonews.daemon.command;
27.23 -
27.24 -import java.io.IOException;
27.25 -import java.util.List;
27.26 -import org.sonews.daemon.NNTPConnection;
27.27 -import org.sonews.storage.Channel;
27.28 -import org.sonews.storage.StorageBackendException;
27.29 -
27.30 -/**
27.31 - * Class handling the LISTGROUP command.
27.32 - * @author Christian Lins
27.33 - * @author Dennis Schwerdel
27.34 - * @since n3tpd/0.1
27.35 - */
27.36 -public class ListGroupCommand implements Command
27.37 -{
27.38 -
27.39 - @Override
27.40 - public String[] getSupportedCommandStrings()
27.41 - {
27.42 - return new String[]{"LISTGROUP"};
27.43 - }
27.44 -
27.45 - @Override
27.46 - public boolean hasFinished()
27.47 - {
27.48 - return true;
27.49 - }
27.50 -
27.51 - @Override
27.52 - public String impliedCapability()
27.53 - {
27.54 - return null;
27.55 - }
27.56 -
27.57 - @Override
27.58 - public boolean isStateful()
27.59 - {
27.60 - return false;
27.61 - }
27.62 -
27.63 - @Override
27.64 - public void processLine(NNTPConnection conn, final String commandName, byte[] raw)
27.65 - throws IOException, StorageBackendException
27.66 - {
27.67 - final String[] command = commandName.split(" ");
27.68 -
27.69 - Channel group;
27.70 - if(command.length >= 2)
27.71 - {
27.72 - group = Channel.getByName(command[1]);
27.73 - }
27.74 - else
27.75 - {
27.76 - group = conn.getCurrentChannel();
27.77 - }
27.78 -
27.79 - if (group == null)
27.80 - {
27.81 - conn.println("412 no group selected; use GROUP <group> command");
27.82 - return;
27.83 - }
27.84 -
27.85 - List<Long> ids = group.getArticleNumbers();
27.86 - conn.println("211 " + ids.size() + " " +
27.87 - group.getFirstArticleNumber() + " " +
27.88 - group.getLastArticleNumber() + " list of article numbers follow");
27.89 - for(long id : ids)
27.90 - {
27.91 - // One index number per line
27.92 - conn.println(Long.toString(id));
27.93 - }
27.94 - conn.println(".");
27.95 - }
27.96 -
27.97 -}
28.1 --- a/org/sonews/daemon/command/ModeReaderCommand.java Sun Aug 29 17:04:25 2010 +0200
28.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
28.3 @@ -1,72 +0,0 @@
28.4 -/*
28.5 - * SONEWS News Server
28.6 - * see AUTHORS for the list of contributors
28.7 - *
28.8 - * This program is free software: you can redistribute it and/or modify
28.9 - * it under the terms of the GNU General Public License as published by
28.10 - * the Free Software Foundation, either version 3 of the License, or
28.11 - * (at your option) any later version.
28.12 - *
28.13 - * This program is distributed in the hope that it will be useful,
28.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
28.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28.16 - * GNU General Public License for more details.
28.17 - *
28.18 - * You should have received a copy of the GNU General Public License
28.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
28.20 - */
28.21 -
28.22 -package org.sonews.daemon.command;
28.23 -
28.24 -import java.io.IOException;
28.25 -import org.sonews.daemon.NNTPConnection;
28.26 -import org.sonews.storage.StorageBackendException;
28.27 -
28.28 -/**
28.29 - * Class handling the MODE READER command. This command actually does nothing
28.30 - * but returning a success status code.
28.31 - * @author Christian Lins
28.32 - * @since sonews/0.5.0
28.33 - */
28.34 -public class ModeReaderCommand implements Command
28.35 -{
28.36 -
28.37 - @Override
28.38 - public String[] getSupportedCommandStrings()
28.39 - {
28.40 - return new String[]{"MODE"};
28.41 - }
28.42 -
28.43 - @Override
28.44 - public boolean hasFinished()
28.45 - {
28.46 - return true;
28.47 - }
28.48 -
28.49 - @Override
28.50 - public String impliedCapability()
28.51 - {
28.52 - return null;
28.53 - }
28.54 -
28.55 - @Override
28.56 - public boolean isStateful()
28.57 - {
28.58 - return false;
28.59 - }
28.60 -
28.61 - @Override
28.62 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
28.63 - throws IOException, StorageBackendException
28.64 - {
28.65 - if(line.equalsIgnoreCase("MODE READER"))
28.66 - {
28.67 - conn.println("200 hello you can post");
28.68 - }
28.69 - else
28.70 - {
28.71 - conn.println("500 I do not know this mode command");
28.72 - }
28.73 - }
28.74 -
28.75 -}
29.1 --- a/org/sonews/daemon/command/NewGroupsCommand.java Sun Aug 29 17:04:25 2010 +0200
29.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
29.3 @@ -1,78 +0,0 @@
29.4 -/*
29.5 - * SONEWS News Server
29.6 - * see AUTHORS for the list of contributors
29.7 - *
29.8 - * This program is free software: you can redistribute it and/or modify
29.9 - * it under the terms of the GNU General Public License as published by
29.10 - * the Free Software Foundation, either version 3 of the License, or
29.11 - * (at your option) any later version.
29.12 - *
29.13 - * This program is distributed in the hope that it will be useful,
29.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
29.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29.16 - * GNU General Public License for more details.
29.17 - *
29.18 - * You should have received a copy of the GNU General Public License
29.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
29.20 - */
29.21 -
29.22 -package org.sonews.daemon.command;
29.23 -
29.24 -import java.io.IOException;
29.25 -import org.sonews.daemon.NNTPConnection;
29.26 -import org.sonews.storage.StorageBackendException;
29.27 -
29.28 -/**
29.29 - * Class handling the NEWGROUPS command.
29.30 - * @author Christian Lins
29.31 - * @author Dennis Schwerdel
29.32 - * @since n3tpd/0.1
29.33 - */
29.34 -public class NewGroupsCommand implements Command
29.35 -{
29.36 -
29.37 - @Override
29.38 - public String[] getSupportedCommandStrings()
29.39 - {
29.40 - return new String[]{"NEWGROUPS"};
29.41 - }
29.42 -
29.43 - @Override
29.44 - public boolean hasFinished()
29.45 - {
29.46 - return true;
29.47 - }
29.48 -
29.49 - @Override
29.50 - public String impliedCapability()
29.51 - {
29.52 - return null;
29.53 - }
29.54 -
29.55 - @Override
29.56 - public boolean isStateful()
29.57 - {
29.58 - return false;
29.59 - }
29.60 -
29.61 - @Override
29.62 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
29.63 - throws IOException, StorageBackendException
29.64 - {
29.65 - final String[] command = line.split(" ");
29.66 -
29.67 - if(command.length == 3)
29.68 - {
29.69 - conn.println("231 list of new newsgroups follows");
29.70 -
29.71 - // Currently we do not store a group's creation date;
29.72 - // so we return an empty list which is a valid response
29.73 - conn.println(".");
29.74 - }
29.75 - else
29.76 - {
29.77 - conn.println("500 invalid command usage");
29.78 - }
29.79 - }
29.80 -
29.81 -}
30.1 --- a/org/sonews/daemon/command/NextPrevCommand.java Sun Aug 29 17:04:25 2010 +0200
30.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
30.3 @@ -1,116 +0,0 @@
30.4 -/*
30.5 - * SONEWS News Server
30.6 - * see AUTHORS for the list of contributors
30.7 - *
30.8 - * This program is free software: you can redistribute it and/or modify
30.9 - * it under the terms of the GNU General Public License as published by
30.10 - * the Free Software Foundation, either version 3 of the License, or
30.11 - * (at your option) any later version.
30.12 - *
30.13 - * This program is distributed in the hope that it will be useful,
30.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
30.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30.16 - * GNU General Public License for more details.
30.17 - *
30.18 - * You should have received a copy of the GNU General Public License
30.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
30.20 - */
30.21 -
30.22 -package org.sonews.daemon.command;
30.23 -
30.24 -import java.io.IOException;
30.25 -import org.sonews.daemon.NNTPConnection;
30.26 -import org.sonews.storage.Article;
30.27 -import org.sonews.storage.Channel;
30.28 -import org.sonews.storage.StorageBackendException;
30.29 -
30.30 -/**
30.31 - * Class handling the NEXT and LAST command.
30.32 - * @author Christian Lins
30.33 - * @author Dennis Schwerdel
30.34 - * @since n3tpd/0.1
30.35 - */
30.36 -public class NextPrevCommand implements Command
30.37 -{
30.38 -
30.39 - @Override
30.40 - public String[] getSupportedCommandStrings()
30.41 - {
30.42 - return new String[]{"NEXT", "PREV"};
30.43 - }
30.44 -
30.45 - @Override
30.46 - public boolean hasFinished()
30.47 - {
30.48 - return true;
30.49 - }
30.50 -
30.51 - @Override
30.52 - public String impliedCapability()
30.53 - {
30.54 - return null;
30.55 - }
30.56 -
30.57 - @Override
30.58 - public boolean isStateful()
30.59 - {
30.60 - return false;
30.61 - }
30.62 -
30.63 - @Override
30.64 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
30.65 - throws IOException, StorageBackendException
30.66 - {
30.67 - final Article currA = conn.getCurrentArticle();
30.68 - final Channel currG = conn.getCurrentChannel();
30.69 -
30.70 - if (currA == null)
30.71 - {
30.72 - conn.println("420 no current article has been selected");
30.73 - return;
30.74 - }
30.75 -
30.76 - if (currG == null)
30.77 - {
30.78 - conn.println("412 no newsgroup selected");
30.79 - return;
30.80 - }
30.81 -
30.82 - final String[] command = line.split(" ");
30.83 -
30.84 - if(command[0].equalsIgnoreCase("NEXT"))
30.85 - {
30.86 - selectNewArticle(conn, currA, currG, 1);
30.87 - }
30.88 - else if(command[0].equalsIgnoreCase("PREV"))
30.89 - {
30.90 - selectNewArticle(conn, currA, currG, -1);
30.91 - }
30.92 - else
30.93 - {
30.94 - conn.println("500 internal server error");
30.95 - }
30.96 - }
30.97 -
30.98 - private void selectNewArticle(NNTPConnection conn, Article article, Channel grp,
30.99 - final int delta)
30.100 - throws IOException, StorageBackendException
30.101 - {
30.102 - assert article != null;
30.103 -
30.104 - article = grp.getArticle(grp.getIndexOf(article) + delta);
30.105 -
30.106 - if(article == null)
30.107 - {
30.108 - conn.println("421 no next article in this group");
30.109 - }
30.110 - else
30.111 - {
30.112 - conn.setCurrentArticle(article);
30.113 - conn.println("223 " + conn.getCurrentChannel().getIndexOf(article)
30.114 - + " " + article.getMessageID()
30.115 - + " article retrieved - request text separately");
30.116 - }
30.117 - }
30.118 -
30.119 -}
31.1 --- a/org/sonews/daemon/command/OverCommand.java Sun Aug 29 17:04:25 2010 +0200
31.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
31.3 @@ -1,294 +0,0 @@
31.4 -/*
31.5 - * SONEWS News Server
31.6 - * see AUTHORS for the list of contributors
31.7 - *
31.8 - * This program is free software: you can redistribute it and/or modify
31.9 - * it under the terms of the GNU General Public License as published by
31.10 - * the Free Software Foundation, either version 3 of the License, or
31.11 - * (at your option) any later version.
31.12 - *
31.13 - * This program is distributed in the hope that it will be useful,
31.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
31.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31.16 - * GNU General Public License for more details.
31.17 - *
31.18 - * You should have received a copy of the GNU General Public License
31.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
31.20 - */
31.21 -
31.22 -package org.sonews.daemon.command;
31.23 -
31.24 -import java.io.IOException;
31.25 -import java.util.List;
31.26 -import org.sonews.util.Log;
31.27 -import org.sonews.daemon.NNTPConnection;
31.28 -import org.sonews.storage.Article;
31.29 -import org.sonews.storage.ArticleHead;
31.30 -import org.sonews.storage.Headers;
31.31 -import org.sonews.storage.StorageBackendException;
31.32 -import org.sonews.util.Pair;
31.33 -
31.34 -/**
31.35 - * Class handling the OVER/XOVER command.
31.36 - *
31.37 - * Description of the XOVER command:
31.38 - * <pre>
31.39 - * XOVER [range]
31.40 - *
31.41 - * The XOVER command returns information from the overview
31.42 - * database for the article(s) specified.
31.43 - *
31.44 - * The optional range argument may be any of the following:
31.45 - * an article number
31.46 - * an article number followed by a dash to indicate
31.47 - * all following
31.48 - * an article number followed by a dash followed by
31.49 - * another article number
31.50 - *
31.51 - * If no argument is specified, then information from the
31.52 - * current article is displayed. Successful responses start
31.53 - * with a 224 response followed by the overview information
31.54 - * for all matched messages. Once the output is complete, a
31.55 - * period is sent on a line by itself. If no argument is
31.56 - * specified, the information for the current article is
31.57 - * returned. A news group must have been selected earlier,
31.58 - * else a 412 error response is returned. If no articles are
31.59 - * in the range specified, a 420 error response is returned
31.60 - * by the server. A 502 response will be returned if the
31.61 - * client only has permission to transfer articles.
31.62 - *
31.63 - * Each line of output will be formatted with the article number,
31.64 - * followed by each of the headers in the overview database or the
31.65 - * article itself (when the data is not available in the overview
31.66 - * database) for that article separated by a tab character. The
31.67 - * sequence of fields must be in this order: subject, author,
31.68 - * date, message-id, references, byte count, and line count. Other
31.69 - * optional fields may follow line count. Other optional fields may
31.70 - * follow line count. These fields are specified by examining the
31.71 - * response to the LIST OVERVIEW.FMT command. Where no data exists,
31.72 - * a null field must be provided (i.e. the output will have two tab
31.73 - * characters adjacent to each other). Servers should not output
31.74 - * fields for articles that have been removed since the XOVER database
31.75 - * was created.
31.76 - *
31.77 - * The LIST OVERVIEW.FMT command should be implemented if XOVER
31.78 - * is implemented. A client can use LIST OVERVIEW.FMT to determine
31.79 - * what optional fields and in which order all fields will be
31.80 - * supplied by the XOVER command.
31.81 - *
31.82 - * Note that any tab and end-of-line characters in any header
31.83 - * data that is returned will be converted to a space character.
31.84 - *
31.85 - * Responses:
31.86 - *
31.87 - * 224 Overview information follows
31.88 - * 412 No news group current selected
31.89 - * 420 No article(s) selected
31.90 - * 502 no permission
31.91 - *
31.92 - * OVER defines additional responses:
31.93 - *
31.94 - * First form (message-id specified)
31.95 - * 224 Overview information follows (multi-line)
31.96 - * 430 No article with that message-id
31.97 - *
31.98 - * Second form (range specified)
31.99 - * 224 Overview information follows (multi-line)
31.100 - * 412 No newsgroup selected
31.101 - * 423 No articles in that range
31.102 - *
31.103 - * Third form (current article number used)
31.104 - * 224 Overview information follows (multi-line)
31.105 - * 412 No newsgroup selected
31.106 - * 420 Current article number is invalid
31.107 - *
31.108 - * </pre>
31.109 - * @author Christian Lins
31.110 - * @since sonews/0.5.0
31.111 - */
31.112 -public class OverCommand implements Command
31.113 -{
31.114 -
31.115 - public static final int MAX_LINES_PER_DBREQUEST = 200;
31.116 -
31.117 - @Override
31.118 - public String[] getSupportedCommandStrings()
31.119 - {
31.120 - return new String[]{"OVER", "XOVER"};
31.121 - }
31.122 -
31.123 - @Override
31.124 - public boolean hasFinished()
31.125 - {
31.126 - return true;
31.127 - }
31.128 -
31.129 - @Override
31.130 - public String impliedCapability()
31.131 - {
31.132 - return null;
31.133 - }
31.134 -
31.135 - @Override
31.136 - public boolean isStateful()
31.137 - {
31.138 - return false;
31.139 - }
31.140 -
31.141 - @Override
31.142 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
31.143 - throws IOException, StorageBackendException
31.144 - {
31.145 - if(conn.getCurrentChannel() == null)
31.146 - {
31.147 - conn.println("412 no newsgroup selected");
31.148 - }
31.149 - else
31.150 - {
31.151 - String[] command = line.split(" ");
31.152 -
31.153 - // If no parameter was specified, show information about
31.154 - // the currently selected article(s)
31.155 - if(command.length == 1)
31.156 - {
31.157 - final Article art = conn.getCurrentArticle();
31.158 - if(art == null)
31.159 - {
31.160 - conn.println("420 no article(s) selected");
31.161 - return;
31.162 - }
31.163 -
31.164 - conn.println(buildOverview(art, -1));
31.165 - }
31.166 - // otherwise print information about the specified range
31.167 - else
31.168 - {
31.169 - long artStart;
31.170 - long artEnd = conn.getCurrentChannel().getLastArticleNumber();
31.171 - String[] nums = command[1].split("-");
31.172 - if(nums.length >= 1)
31.173 - {
31.174 - try
31.175 - {
31.176 - artStart = Integer.parseInt(nums[0]);
31.177 - }
31.178 - catch(NumberFormatException e)
31.179 - {
31.180 - Log.get().info(e.getMessage());
31.181 - artStart = Integer.parseInt(command[1]);
31.182 - }
31.183 - }
31.184 - else
31.185 - {
31.186 - artStart = conn.getCurrentChannel().getFirstArticleNumber();
31.187 - }
31.188 -
31.189 - if(nums.length >=2)
31.190 - {
31.191 - try
31.192 - {
31.193 - artEnd = Integer.parseInt(nums[1]);
31.194 - }
31.195 - catch(NumberFormatException e)
31.196 - {
31.197 - e.printStackTrace();
31.198 - }
31.199 - }
31.200 -
31.201 - if(artStart > artEnd)
31.202 - {
31.203 - if(command[0].equalsIgnoreCase("OVER"))
31.204 - {
31.205 - conn.println("423 no articles in that range");
31.206 - }
31.207 - else
31.208 - {
31.209 - conn.println("224 (empty) overview information follows:");
31.210 - conn.println(".");
31.211 - }
31.212 - }
31.213 - else
31.214 - {
31.215 - for(long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST)
31.216 - {
31.217 - long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
31.218 - List<Pair<Long, ArticleHead>> articleHeads = conn.getCurrentChannel()
31.219 - .getArticleHeads(n, nEnd);
31.220 - if(articleHeads.isEmpty() && n == artStart
31.221 - && command[0].equalsIgnoreCase("OVER"))
31.222 - {
31.223 - // This reply is only valid for OVER, not for XOVER command
31.224 - conn.println("423 no articles in that range");
31.225 - return;
31.226 - }
31.227 - else if(n == artStart)
31.228 - {
31.229 - // XOVER replies this although there is no data available
31.230 - conn.println("224 overview information follows");
31.231 - }
31.232 -
31.233 - for(Pair<Long, ArticleHead> article : articleHeads)
31.234 - {
31.235 - String overview = buildOverview(article.getB(), article.getA());
31.236 - conn.println(overview);
31.237 - }
31.238 - } // for
31.239 - conn.println(".");
31.240 - }
31.241 - }
31.242 - }
31.243 - }
31.244 -
31.245 - private String buildOverview(ArticleHead art, long nr)
31.246 - {
31.247 - StringBuilder overview = new StringBuilder();
31.248 - overview.append(nr);
31.249 - overview.append('\t');
31.250 -
31.251 - String subject = art.getHeader(Headers.SUBJECT)[0];
31.252 - if("".equals(subject))
31.253 - {
31.254 - subject = "<empty>";
31.255 - }
31.256 - overview.append(escapeString(subject));
31.257 - overview.append('\t');
31.258 -
31.259 - overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
31.260 - overview.append('\t');
31.261 - overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
31.262 - overview.append('\t');
31.263 - overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
31.264 - overview.append('\t');
31.265 - overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
31.266 - overview.append('\t');
31.267 -
31.268 - String bytes = art.getHeader(Headers.BYTES)[0];
31.269 - if("".equals(bytes))
31.270 - {
31.271 - bytes = "0";
31.272 - }
31.273 - overview.append(escapeString(bytes));
31.274 - overview.append('\t');
31.275 -
31.276 - String lines = art.getHeader(Headers.LINES)[0];
31.277 - if("".equals(lines))
31.278 - {
31.279 - lines = "0";
31.280 - }
31.281 - overview.append(escapeString(lines));
31.282 - overview.append('\t');
31.283 - overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
31.284 -
31.285 - // Remove trailing tabs if some data is empty
31.286 - return overview.toString().trim();
31.287 - }
31.288 -
31.289 - private String escapeString(String str)
31.290 - {
31.291 - String nstr = str.replace("\r", "");
31.292 - nstr = nstr.replace('\n', ' ');
31.293 - nstr = nstr.replace('\t', ' ');
31.294 - return nstr.trim();
31.295 - }
31.296 -
31.297 -}
32.1 --- a/org/sonews/daemon/command/PostCommand.java Sun Aug 29 17:04:25 2010 +0200
32.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
32.3 @@ -1,332 +0,0 @@
32.4 -/*
32.5 - * SONEWS News Server
32.6 - * see AUTHORS for the list of contributors
32.7 - *
32.8 - * This program is free software: you can redistribute it and/or modify
32.9 - * it under the terms of the GNU General Public License as published by
32.10 - * the Free Software Foundation, either version 3 of the License, or
32.11 - * (at your option) any later version.
32.12 - *
32.13 - * This program is distributed in the hope that it will be useful,
32.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
32.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32.16 - * GNU General Public License for more details.
32.17 - *
32.18 - * You should have received a copy of the GNU General Public License
32.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
32.20 - */
32.21 -
32.22 -package org.sonews.daemon.command;
32.23 -
32.24 -import java.io.IOException;
32.25 -import java.io.ByteArrayInputStream;
32.26 -import java.io.ByteArrayOutputStream;
32.27 -import java.sql.SQLException;
32.28 -import java.util.Arrays;
32.29 -import javax.mail.MessagingException;
32.30 -import javax.mail.internet.AddressException;
32.31 -import javax.mail.internet.InternetHeaders;
32.32 -import org.sonews.config.Config;
32.33 -import org.sonews.util.Log;
32.34 -import org.sonews.mlgw.Dispatcher;
32.35 -import org.sonews.storage.Article;
32.36 -import org.sonews.storage.Group;
32.37 -import org.sonews.daemon.NNTPConnection;
32.38 -import org.sonews.storage.Headers;
32.39 -import org.sonews.storage.StorageBackendException;
32.40 -import org.sonews.storage.StorageManager;
32.41 -import org.sonews.feed.FeedManager;
32.42 -import org.sonews.util.Stats;
32.43 -
32.44 -/**
32.45 - * Implementation of the POST command. This command requires multiple lines
32.46 - * from the client, so the handling of asynchronous reading is a little tricky
32.47 - * to handle.
32.48 - * @author Christian Lins
32.49 - * @since sonews/0.5.0
32.50 - */
32.51 -public class PostCommand implements Command
32.52 -{
32.53 -
32.54 - private final Article article = new Article();
32.55 - private int lineCount = 0;
32.56 - private long bodySize = 0;
32.57 - private InternetHeaders headers = null;
32.58 - private long maxBodySize =
32.59 - Config.inst().get(Config.ARTICLE_MAXSIZE, 128) * 1024L; // Size in bytes
32.60 - private PostState state = PostState.WaitForLineOne;
32.61 - private final ByteArrayOutputStream bufBody = new ByteArrayOutputStream();
32.62 - private final StringBuilder strHead = new StringBuilder();
32.63 -
32.64 - @Override
32.65 - public String[] getSupportedCommandStrings()
32.66 - {
32.67 - return new String[]{"POST"};
32.68 - }
32.69 -
32.70 - @Override
32.71 - public boolean hasFinished()
32.72 - {
32.73 - return this.state == PostState.Finished;
32.74 - }
32.75 -
32.76 - @Override
32.77 - public String impliedCapability()
32.78 - {
32.79 - return null;
32.80 - }
32.81 -
32.82 - @Override
32.83 - public boolean isStateful()
32.84 - {
32.85 - return true;
32.86 - }
32.87 -
32.88 - /**
32.89 - * Process the given line String. line.trim() was called by NNTPConnection.
32.90 - * @param line
32.91 - * @throws java.io.IOException
32.92 - * @throws java.sql.SQLException
32.93 - */
32.94 - @Override // TODO: Refactor this method to reduce complexity!
32.95 - public void processLine(NNTPConnection conn, String line, byte[] raw)
32.96 - throws IOException, StorageBackendException
32.97 - {
32.98 - switch(state)
32.99 - {
32.100 - case WaitForLineOne:
32.101 - {
32.102 - if(line.equalsIgnoreCase("POST"))
32.103 - {
32.104 - conn.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
32.105 - state = PostState.ReadingHeaders;
32.106 - }
32.107 - else
32.108 - {
32.109 - conn.println("500 invalid command usage");
32.110 - }
32.111 - break;
32.112 - }
32.113 - case ReadingHeaders:
32.114 - {
32.115 - strHead.append(line);
32.116 - strHead.append(NNTPConnection.NEWLINE);
32.117 -
32.118 - if("".equals(line) || ".".equals(line))
32.119 - {
32.120 - // we finally met the blank line
32.121 - // separating headers from body
32.122 -
32.123 - try
32.124 - {
32.125 - // Parse the header using the InternetHeader class from JavaMail API
32.126 - headers = new InternetHeaders(
32.127 - new ByteArrayInputStream(strHead.toString().trim()
32.128 - .getBytes(conn.getCurrentCharset())));
32.129 -
32.130 - // add the header entries for the article
32.131 - article.setHeaders(headers);
32.132 - }
32.133 - catch (MessagingException e)
32.134 - {
32.135 - e.printStackTrace();
32.136 - conn.println("500 posting failed - invalid header");
32.137 - state = PostState.Finished;
32.138 - break;
32.139 - }
32.140 -
32.141 - // Change charset for reading body;
32.142 - // for multipart messages UTF-8 is returned
32.143 - //conn.setCurrentCharset(article.getBodyCharset());
32.144 -
32.145 - state = PostState.ReadingBody;
32.146 -
32.147 - if(".".equals(line))
32.148 - {
32.149 - // Post an article without body
32.150 - postArticle(conn, article);
32.151 - state = PostState.Finished;
32.152 - }
32.153 - }
32.154 - break;
32.155 - }
32.156 - case ReadingBody:
32.157 - {
32.158 - if(".".equals(line))
32.159 - {
32.160 - // Set some headers needed for Over command
32.161 - headers.setHeader(Headers.LINES, Integer.toString(lineCount));
32.162 - headers.setHeader(Headers.BYTES, Long.toString(bodySize));
32.163 -
32.164 - byte[] body = bufBody.toByteArray();
32.165 - if(body.length >= 2)
32.166 - {
32.167 - // Remove trailing CRLF
32.168 - body = Arrays.copyOf(body, body.length - 2);
32.169 - }
32.170 - article.setBody(body); // set the article body
32.171 -
32.172 - postArticle(conn, article);
32.173 - state = PostState.Finished;
32.174 - }
32.175 - else
32.176 - {
32.177 - bodySize += line.length() + 1;
32.178 - lineCount++;
32.179 -
32.180 - // Add line to body buffer
32.181 - bufBody.write(raw, 0, raw.length);
32.182 - bufBody.write(NNTPConnection.NEWLINE.getBytes());
32.183 -
32.184 - if(bodySize > maxBodySize)
32.185 - {
32.186 - conn.println("500 article is too long");
32.187 - state = PostState.Finished;
32.188 - break;
32.189 - }
32.190 - }
32.191 - break;
32.192 - }
32.193 - default:
32.194 - {
32.195 - // Should never happen
32.196 - Log.get().severe("PostCommand::processLine(): already finished...");
32.197 - }
32.198 - }
32.199 - }
32.200 -
32.201 - /**
32.202 - * Article is a control message and needs special handling.
32.203 - * @param article
32.204 - */
32.205 - private void controlMessage(NNTPConnection conn, Article article)
32.206 - throws IOException
32.207 - {
32.208 - String[] ctrl = article.getHeader(Headers.CONTROL)[0].split(" ");
32.209 - if(ctrl.length == 2) // "cancel <mid>"
32.210 - {
32.211 - try
32.212 - {
32.213 - StorageManager.current().delete(ctrl[1]);
32.214 -
32.215 - // Move cancel message to "control" group
32.216 - article.setHeader(Headers.NEWSGROUPS, "control");
32.217 - StorageManager.current().addArticle(article);
32.218 - conn.println("240 article cancelled");
32.219 - }
32.220 - catch(StorageBackendException ex)
32.221 - {
32.222 - Log.get().severe(ex.toString());
32.223 - conn.println("500 internal server error");
32.224 - }
32.225 - }
32.226 - else
32.227 - {
32.228 - conn.println("441 unknown control header");
32.229 - }
32.230 - }
32.231 -
32.232 - private void supersedeMessage(NNTPConnection conn, Article article)
32.233 - throws IOException
32.234 - {
32.235 - try
32.236 - {
32.237 - String oldMsg = article.getHeader(Headers.SUPERSEDES)[0];
32.238 - StorageManager.current().delete(oldMsg);
32.239 - StorageManager.current().addArticle(article);
32.240 - conn.println("240 article replaced");
32.241 - }
32.242 - catch(StorageBackendException ex)
32.243 - {
32.244 - Log.get().severe(ex.toString());
32.245 - conn.println("500 internal server error");
32.246 - }
32.247 - }
32.248 -
32.249 - private void postArticle(NNTPConnection conn, Article article)
32.250 - throws IOException
32.251 - {
32.252 - if(article.getHeader(Headers.CONTROL)[0].length() > 0)
32.253 - {
32.254 - controlMessage(conn, article);
32.255 - }
32.256 - else if(article.getHeader(Headers.SUPERSEDES)[0].length() > 0)
32.257 - {
32.258 - supersedeMessage(conn, article);
32.259 - }
32.260 - else // Post the article regularily
32.261 - {
32.262 - // Circle check; note that Path can already contain the hostname here
32.263 - String host = Config.inst().get(Config.HOSTNAME, "localhost");
32.264 - if(article.getHeader(Headers.PATH)[0].indexOf(host + "!", 1) > 0)
32.265 - {
32.266 - Log.get().info(article.getMessageID() + " skipped for host " + host);
32.267 - conn.println("441 I know this article already");
32.268 - return;
32.269 - }
32.270 -
32.271 - // Try to create the article in the database or post it to
32.272 - // appropriate mailing list
32.273 - try
32.274 - {
32.275 - boolean success = false;
32.276 - String[] groupnames = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
32.277 - for(String groupname : groupnames)
32.278 - {
32.279 - Group group = StorageManager.current().getGroup(groupname);
32.280 - if(group != null && !group.isDeleted())
32.281 - {
32.282 - if(group.isMailingList() && !conn.isLocalConnection())
32.283 - {
32.284 - // Send to mailing list; the Dispatcher writes
32.285 - // statistics to database
32.286 - Dispatcher.toList(article, group.getName());
32.287 - success = true;
32.288 - }
32.289 - else
32.290 - {
32.291 - // Store in database
32.292 - if(!StorageManager.current().isArticleExisting(article.getMessageID()))
32.293 - {
32.294 - StorageManager.current().addArticle(article);
32.295 -
32.296 - // Log this posting to statistics
32.297 - Stats.getInstance().mailPosted(
32.298 - article.getHeader(Headers.NEWSGROUPS)[0]);
32.299 - }
32.300 - success = true;
32.301 - }
32.302 - }
32.303 - } // end for
32.304 -
32.305 - if(success)
32.306 - {
32.307 - conn.println("240 article posted ok");
32.308 - FeedManager.queueForPush(article);
32.309 - }
32.310 - else
32.311 - {
32.312 - conn.println("441 newsgroup not found");
32.313 - }
32.314 - }
32.315 - catch(AddressException ex)
32.316 - {
32.317 - Log.get().warning(ex.getMessage());
32.318 - conn.println("441 invalid sender address");
32.319 - }
32.320 - catch(MessagingException ex)
32.321 - {
32.322 - // A MessageException is thrown when the sender email address is
32.323 - // invalid or something is wrong with the SMTP server.
32.324 - System.err.println(ex.getLocalizedMessage());
32.325 - conn.println("441 " + ex.getClass().getCanonicalName() + ": " + ex.getLocalizedMessage());
32.326 - }
32.327 - catch(StorageBackendException ex)
32.328 - {
32.329 - ex.printStackTrace();
32.330 - conn.println("500 internal server error");
32.331 - }
32.332 - }
32.333 - }
32.334 -
32.335 -}
33.1 --- a/org/sonews/daemon/command/PostState.java Sun Aug 29 17:04:25 2010 +0200
33.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
33.3 @@ -1,29 +0,0 @@
33.4 -/*
33.5 - * SONEWS News Server
33.6 - * see AUTHORS for the list of contributors
33.7 - *
33.8 - * This program is free software: you can redistribute it and/or modify
33.9 - * it under the terms of the GNU General Public License as published by
33.10 - * the Free Software Foundation, either version 3 of the License, or
33.11 - * (at your option) any later version.
33.12 - *
33.13 - * This program is distributed in the hope that it will be useful,
33.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
33.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33.16 - * GNU General Public License for more details.
33.17 - *
33.18 - * You should have received a copy of the GNU General Public License
33.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
33.20 - */
33.21 -
33.22 -package org.sonews.daemon.command;
33.23 -
33.24 -/**
33.25 - * States of the POST command's finite state machine.
33.26 - * @author Christian Lins
33.27 - * @since sonews/0.5.0
33.28 - */
33.29 -enum PostState
33.30 -{
33.31 - WaitForLineOne, ReadingHeaders, ReadingBody, Finished
33.32 -}
34.1 --- a/org/sonews/daemon/command/QuitCommand.java Sun Aug 29 17:04:25 2010 +0200
34.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
34.3 @@ -1,67 +0,0 @@
34.4 -/*
34.5 - * SONEWS News Server
34.6 - * see AUTHORS for the list of contributors
34.7 - *
34.8 - * This program is free software: you can redistribute it and/or modify
34.9 - * it under the terms of the GNU General Public License as published by
34.10 - * the Free Software Foundation, either version 3 of the License, or
34.11 - * (at your option) any later version.
34.12 - *
34.13 - * This program is distributed in the hope that it will be useful,
34.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
34.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34.16 - * GNU General Public License for more details.
34.17 - *
34.18 - * You should have received a copy of the GNU General Public License
34.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
34.20 - */
34.21 -
34.22 -package org.sonews.daemon.command;
34.23 -
34.24 -import java.io.IOException;
34.25 -import org.sonews.daemon.NNTPConnection;
34.26 -import org.sonews.storage.StorageBackendException;
34.27 -
34.28 -/**
34.29 - * Implementation of the QUIT command; client wants to shutdown the connection.
34.30 - * @author Christian Lins
34.31 - * @since sonews/0.5.0
34.32 - */
34.33 -public class QuitCommand implements Command
34.34 -{
34.35 -
34.36 - @Override
34.37 - public String[] getSupportedCommandStrings()
34.38 - {
34.39 - return new String[]{"QUIT"};
34.40 - }
34.41 -
34.42 - @Override
34.43 - public boolean hasFinished()
34.44 - {
34.45 - return true;
34.46 - }
34.47 -
34.48 - @Override
34.49 - public String impliedCapability()
34.50 - {
34.51 - return null;
34.52 - }
34.53 -
34.54 - @Override
34.55 - public boolean isStateful()
34.56 - {
34.57 - return false;
34.58 - }
34.59 -
34.60 - @Override
34.61 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
34.62 - throws IOException, StorageBackendException
34.63 - {
34.64 - conn.println("205 cya");
34.65 -
34.66 - conn.shutdownInput();
34.67 - conn.shutdownOutput();
34.68 - }
34.69 -
34.70 -}
35.1 --- a/org/sonews/daemon/command/StatCommand.java Sun Aug 29 17:04:25 2010 +0200
35.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
35.3 @@ -1,114 +0,0 @@
35.4 -/*
35.5 - * SONEWS News Server
35.6 - * see AUTHORS for the list of contributors
35.7 - *
35.8 - * This program is free software: you can redistribute it and/or modify
35.9 - * it under the terms of the GNU General Public License as published by
35.10 - * the Free Software Foundation, either version 3 of the License, or
35.11 - * (at your option) any later version.
35.12 - *
35.13 - * This program is distributed in the hope that it will be useful,
35.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
35.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35.16 - * GNU General Public License for more details.
35.17 - *
35.18 - * You should have received a copy of the GNU General Public License
35.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
35.20 - */
35.21 -
35.22 -package org.sonews.daemon.command;
35.23 -
35.24 -import java.io.IOException;
35.25 -import org.sonews.storage.Article;
35.26 -import org.sonews.daemon.NNTPConnection;
35.27 -import org.sonews.storage.StorageBackendException;
35.28 -
35.29 -/**
35.30 - * Implementation of the STAT command.
35.31 - * @author Christian Lins
35.32 - * @since sonews/0.5.0
35.33 - */
35.34 -public class StatCommand implements Command
35.35 -{
35.36 -
35.37 - @Override
35.38 - public String[] getSupportedCommandStrings()
35.39 - {
35.40 - return new String[]{"STAT"};
35.41 - }
35.42 -
35.43 - @Override
35.44 - public boolean hasFinished()
35.45 - {
35.46 - return true;
35.47 - }
35.48 -
35.49 - @Override
35.50 - public String impliedCapability()
35.51 - {
35.52 - return null;
35.53 - }
35.54 -
35.55 - @Override
35.56 - public boolean isStateful()
35.57 - {
35.58 - return false;
35.59 - }
35.60 -
35.61 - // TODO: Method has various exit points => Refactor!
35.62 - @Override
35.63 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
35.64 - throws IOException, StorageBackendException
35.65 - {
35.66 - final String[] command = line.split(" ");
35.67 -
35.68 - Article article = null;
35.69 - if(command.length == 1)
35.70 - {
35.71 - article = conn.getCurrentArticle();
35.72 - if(article == null)
35.73 - {
35.74 - conn.println("420 no current article has been selected");
35.75 - return;
35.76 - }
35.77 - }
35.78 - else if(command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN))
35.79 - {
35.80 - // Message-ID
35.81 - article = Article.getByMessageID(command[1]);
35.82 - if (article == null)
35.83 - {
35.84 - conn.println("430 no such article found");
35.85 - return;
35.86 - }
35.87 - }
35.88 - else
35.89 - {
35.90 - // Message Number
35.91 - try
35.92 - {
35.93 - long aid = Long.parseLong(command[1]);
35.94 - article = conn.getCurrentChannel().getArticle(aid);
35.95 - }
35.96 - catch(NumberFormatException ex)
35.97 - {
35.98 - ex.printStackTrace();
35.99 - }
35.100 - catch(StorageBackendException ex)
35.101 - {
35.102 - ex.printStackTrace();
35.103 - }
35.104 - if (article == null)
35.105 - {
35.106 - conn.println("423 no such article number in this group");
35.107 - return;
35.108 - }
35.109 - conn.setCurrentArticle(article);
35.110 - }
35.111 -
35.112 - conn.println("223 " + conn.getCurrentChannel().getIndexOf(article) + " "
35.113 - + article.getMessageID()
35.114 - + " article retrieved - request text separately");
35.115 - }
35.116 -
35.117 -}
36.1 --- a/org/sonews/daemon/command/UnsupportedCommand.java Sun Aug 29 17:04:25 2010 +0200
36.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
36.3 @@ -1,67 +0,0 @@
36.4 -/*
36.5 - * SONEWS News Server
36.6 - * see AUTHORS for the list of contributors
36.7 - *
36.8 - * This program is free software: you can redistribute it and/or modify
36.9 - * it under the terms of the GNU General Public License as published by
36.10 - * the Free Software Foundation, either version 3 of the License, or
36.11 - * (at your option) any later version.
36.12 - *
36.13 - * This program is distributed in the hope that it will be useful,
36.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
36.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36.16 - * GNU General Public License for more details.
36.17 - *
36.18 - * You should have received a copy of the GNU General Public License
36.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
36.20 - */
36.21 -
36.22 -package org.sonews.daemon.command;
36.23 -
36.24 -import java.io.IOException;
36.25 -import org.sonews.daemon.NNTPConnection;
36.26 -
36.27 -/**
36.28 - * A default "Unsupported Command". Simply returns error code 500 and a
36.29 - * "command not supported" message.
36.30 - * @author Christian Lins
36.31 - * @since sonews/0.5.0
36.32 - */
36.33 -public class UnsupportedCommand implements Command
36.34 -{
36.35 -
36.36 - /**
36.37 - * @return Always returns null.
36.38 - */
36.39 - @Override
36.40 - public String[] getSupportedCommandStrings()
36.41 - {
36.42 - return null;
36.43 - }
36.44 -
36.45 - @Override
36.46 - public boolean hasFinished()
36.47 - {
36.48 - return true;
36.49 - }
36.50 -
36.51 - @Override
36.52 - public String impliedCapability()
36.53 - {
36.54 - return null;
36.55 - }
36.56 -
36.57 - @Override
36.58 - public boolean isStateful()
36.59 - {
36.60 - return false;
36.61 - }
36.62 -
36.63 - @Override
36.64 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
36.65 - throws IOException
36.66 - {
36.67 - conn.println("500 command not supported");
36.68 - }
36.69 -
36.70 -}
37.1 --- a/org/sonews/daemon/command/XDaemonCommand.java Sun Aug 29 17:04:25 2010 +0200
37.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
37.3 @@ -1,270 +0,0 @@
37.4 -/*
37.5 - * SONEWS News Server
37.6 - * see AUTHORS for the list of contributors
37.7 - *
37.8 - * This program is free software: you can redistribute it and/or modify
37.9 - * it under the terms of the GNU General Public License as published by
37.10 - * the Free Software Foundation, either version 3 of the License, or
37.11 - * (at your option) any later version.
37.12 - *
37.13 - * This program is distributed in the hope that it will be useful,
37.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
37.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37.16 - * GNU General Public License for more details.
37.17 - *
37.18 - * You should have received a copy of the GNU General Public License
37.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
37.20 - */
37.21 -
37.22 -package org.sonews.daemon.command;
37.23 -
37.24 -import java.io.IOException;
37.25 -import java.net.InetSocketAddress;
37.26 -import java.util.List;
37.27 -import org.sonews.config.Config;
37.28 -import org.sonews.daemon.NNTPConnection;
37.29 -import org.sonews.storage.StorageBackendException;
37.30 -import org.sonews.storage.StorageManager;
37.31 -import org.sonews.feed.FeedManager;
37.32 -import org.sonews.feed.Subscription;
37.33 -import org.sonews.storage.Channel;
37.34 -import org.sonews.storage.Group;
37.35 -import org.sonews.util.Stats;
37.36 -
37.37 -/**
37.38 - * The XDAEMON command allows a client to get/set properties of the
37.39 - * running server daemon. Only locally connected clients are allowed to
37.40 - * use this command.
37.41 - * The restriction to localhost connection can be suppressed by overriding
37.42 - * the sonews.xdaemon.host bootstrap config property.
37.43 - * @author Christian Lins
37.44 - * @since sonews/0.5.0
37.45 - */
37.46 -public class XDaemonCommand implements Command
37.47 -{
37.48 -
37.49 - @Override
37.50 - public String[] getSupportedCommandStrings()
37.51 - {
37.52 - return new String[]{"XDAEMON"};
37.53 - }
37.54 -
37.55 - @Override
37.56 - public boolean hasFinished()
37.57 - {
37.58 - return true;
37.59 - }
37.60 -
37.61 - @Override
37.62 - public String impliedCapability()
37.63 - {
37.64 - return null;
37.65 - }
37.66 -
37.67 - @Override
37.68 - public boolean isStateful()
37.69 - {
37.70 - return false;
37.71 - }
37.72 -
37.73 - private void channelAdd(String[] commands, NNTPConnection conn)
37.74 - throws IOException, StorageBackendException
37.75 - {
37.76 - String groupName = commands[2];
37.77 - if(StorageManager.current().isGroupExisting(groupName))
37.78 - {
37.79 - conn.println("400 group " + groupName + " already existing!");
37.80 - }
37.81 - else
37.82 - {
37.83 - StorageManager.current().addGroup(groupName, Integer.parseInt(commands[3]));
37.84 - conn.println("200 group " + groupName + " created");
37.85 - }
37.86 - }
37.87 -
37.88 - // TODO: Refactor this method to reduce complexity!
37.89 - @Override
37.90 - public void processLine(NNTPConnection conn, String line, byte[] raw)
37.91 - throws IOException, StorageBackendException
37.92 - {
37.93 - InetSocketAddress addr = (InetSocketAddress)conn.getSocketChannel().socket()
37.94 - .getRemoteSocketAddress();
37.95 - if(addr.getHostName().equals(
37.96 - Config.inst().get(Config.XDAEMON_HOST, "localhost")))
37.97 - {
37.98 - String[] commands = line.split(" ", 4);
37.99 - if(commands.length == 3 && commands[1].equalsIgnoreCase("LIST"))
37.100 - {
37.101 - if(commands[2].equalsIgnoreCase("CONFIGKEYS"))
37.102 - {
37.103 - conn.println("100 list of available config keys follows");
37.104 - for(String key : Config.AVAILABLE_KEYS)
37.105 - {
37.106 - conn.println(key);
37.107 - }
37.108 - conn.println(".");
37.109 - }
37.110 - else if(commands[2].equalsIgnoreCase("PEERINGRULES"))
37.111 - {
37.112 - List<Subscription> pull =
37.113 - StorageManager.current().getSubscriptions(FeedManager.TYPE_PULL);
37.114 - List<Subscription> push =
37.115 - StorageManager.current().getSubscriptions(FeedManager.TYPE_PUSH);
37.116 - conn.println("100 list of peering rules follows");
37.117 - for(Subscription sub : pull)
37.118 - {
37.119 - conn.println("PULL " + sub.getHost() + ":" + sub.getPort()
37.120 - + " " + sub.getGroup());
37.121 - }
37.122 - for(Subscription sub : push)
37.123 - {
37.124 - conn.println("PUSH " + sub.getHost() + ":" + sub.getPort()
37.125 - + " " + sub.getGroup());
37.126 - }
37.127 - conn.println(".");
37.128 - }
37.129 - else
37.130 - {
37.131 - conn.println("401 unknown sub command");
37.132 - }
37.133 - }
37.134 - else if(commands.length == 3 && commands[1].equalsIgnoreCase("DELETE"))
37.135 - {
37.136 - StorageManager.current().delete(commands[2]);
37.137 - conn.println("200 article " + commands[2] + " deleted");
37.138 - }
37.139 - else if(commands.length == 4 && commands[1].equalsIgnoreCase("GROUPADD"))
37.140 - {
37.141 - channelAdd(commands, conn);
37.142 - }
37.143 - else if(commands.length == 3 && commands[1].equalsIgnoreCase("GROUPDEL"))
37.144 - {
37.145 - Group group = StorageManager.current().getGroup(commands[2]);
37.146 - if(group == null)
37.147 - {
37.148 - conn.println("400 group not found");
37.149 - }
37.150 - else
37.151 - {
37.152 - group.setFlag(Group.DELETED);
37.153 - group.update();
37.154 - conn.println("200 group " + commands[2] + " marked as deleted");
37.155 - }
37.156 - }
37.157 - else if(commands.length == 4 && commands[1].equalsIgnoreCase("SET"))
37.158 - {
37.159 - String key = commands[2];
37.160 - String val = commands[3];
37.161 - Config.inst().set(key, val);
37.162 - conn.println("200 new config value set");
37.163 - }
37.164 - else if(commands.length == 3 && commands[1].equalsIgnoreCase("GET"))
37.165 - {
37.166 - String key = commands[2];
37.167 - String val = Config.inst().get(key, null);
37.168 - if(val != null)
37.169 - {
37.170 - conn.println("100 config value for " + key + " follows");
37.171 - conn.println(val);
37.172 - conn.println(".");
37.173 - }
37.174 - else
37.175 - {
37.176 - conn.println("400 config value not set");
37.177 - }
37.178 - }
37.179 - else if(commands.length >= 3 && commands[1].equalsIgnoreCase("LOG"))
37.180 - {
37.181 - Group group = null;
37.182 - if(commands.length > 3)
37.183 - {
37.184 - group = (Group)Channel.getByName(commands[3]);
37.185 - }
37.186 -
37.187 - if(commands[2].equalsIgnoreCase("CONNECTED_CLIENTS"))
37.188 - {
37.189 - conn.println("100 number of connections follow");
37.190 - conn.println(Integer.toString(Stats.getInstance().connectedClients()));
37.191 - conn.println(".");
37.192 - }
37.193 - else if(commands[2].equalsIgnoreCase("POSTED_NEWS"))
37.194 - {
37.195 - conn.println("100 hourly numbers of posted news yesterday");
37.196 - for(int n = 0; n < 24; n++)
37.197 - {
37.198 - conn.println(n + " " + Stats.getInstance()
37.199 - .getYesterdaysEvents(Stats.POSTED_NEWS, n, group));
37.200 - }
37.201 - conn.println(".");
37.202 - }
37.203 - else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS"))
37.204 - {
37.205 - conn.println("100 hourly numbers of gatewayed news yesterday");
37.206 - for(int n = 0; n < 24; n++)
37.207 - {
37.208 - conn.println(n + " " + Stats.getInstance()
37.209 - .getYesterdaysEvents(Stats.GATEWAYED_NEWS, n, group));
37.210 - }
37.211 - conn.println(".");
37.212 - }
37.213 - else if(commands[2].equalsIgnoreCase("TRANSMITTED_NEWS"))
37.214 - {
37.215 - conn.println("100 hourly numbers of news transmitted to peers yesterday");
37.216 - for(int n = 0; n < 24; n++)
37.217 - {
37.218 - conn.println(n + " " + Stats.getInstance()
37.219 - .getYesterdaysEvents(Stats.FEEDED_NEWS, n, group));
37.220 - }
37.221 - conn.println(".");
37.222 - }
37.223 - else if(commands[2].equalsIgnoreCase("HOSTED_NEWS"))
37.224 - {
37.225 - conn.println("100 number of overall hosted news");
37.226 - conn.println(Integer.toString(Stats.getInstance().getNumberOfNews()));
37.227 - conn.println(".");
37.228 - }
37.229 - else if(commands[2].equalsIgnoreCase("HOSTED_GROUPS"))
37.230 - {
37.231 - conn.println("100 number of hosted groups");
37.232 - conn.println(Integer.toString(Stats.getInstance().getNumberOfGroups()));
37.233 - conn.println(".");
37.234 - }
37.235 - else if(commands[2].equalsIgnoreCase("POSTED_NEWS_PER_HOUR"))
37.236 - {
37.237 - conn.println("100 posted news per hour");
37.238 - conn.println(Double.toString(Stats.getInstance().postedPerHour(-1)));
37.239 - conn.println(".");
37.240 - }
37.241 - else if(commands[2].equalsIgnoreCase("FEEDED_NEWS_PER_HOUR"))
37.242 - {
37.243 - conn.println("100 feeded news per hour");
37.244 - conn.println(Double.toString(Stats.getInstance().feededPerHour(-1)));
37.245 - conn.println(".");
37.246 - }
37.247 - else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS_PER_HOUR"))
37.248 - {
37.249 - conn.println("100 gatewayed news per hour");
37.250 - conn.println(Double.toString(Stats.getInstance().gatewayedPerHour(-1)));
37.251 - conn.println(".");
37.252 - }
37.253 - else
37.254 - {
37.255 - conn.println("401 unknown sub command");
37.256 - }
37.257 - }
37.258 - else if(commands.length >= 3 && commands[1].equalsIgnoreCase("PLUGIN"))
37.259 - {
37.260 -
37.261 - }
37.262 - else
37.263 - {
37.264 - conn.println("400 invalid command usage");
37.265 - }
37.266 - }
37.267 - else
37.268 - {
37.269 - conn.println("501 not allowed");
37.270 - }
37.271 - }
37.272 -
37.273 -}
38.1 --- a/org/sonews/daemon/command/XPatCommand.java Sun Aug 29 17:04:25 2010 +0200
38.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
38.3 @@ -1,170 +0,0 @@
38.4 -/*
38.5 - * SONEWS News Server
38.6 - * see AUTHORS for the list of contributors
38.7 - *
38.8 - * This program is free software: you can redistribute it and/or modify
38.9 - * it under the terms of the GNU General Public License as published by
38.10 - * the Free Software Foundation, either version 3 of the License, or
38.11 - * (at your option) any later version.
38.12 - *
38.13 - * This program is distributed in the hope that it will be useful,
38.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
38.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38.16 - * GNU General Public License for more details.
38.17 - *
38.18 - * You should have received a copy of the GNU General Public License
38.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
38.20 - */
38.21 -
38.22 -package org.sonews.daemon.command;
38.23 -
38.24 -import java.io.IOException;
38.25 -import java.util.List;
38.26 -import java.util.Locale;
38.27 -import java.util.regex.PatternSyntaxException;
38.28 -import org.sonews.daemon.NNTPConnection;
38.29 -import org.sonews.storage.StorageBackendException;
38.30 -import org.sonews.storage.StorageManager;
38.31 -import org.sonews.util.Pair;
38.32 -
38.33 -/**
38.34 - * <pre>
38.35 - * XPAT header range|<message-id> pat [pat...]
38.36 - *
38.37 - * The XPAT command is used to retrieve specific headers from
38.38 - * specific articles, based on pattern matching on the contents of
38.39 - * the header. This command was first available in INN.
38.40 - *
38.41 - * The required header parameter is the name of a header line (e.g.
38.42 - * "subject") in a news group article. See RFC-1036 for a list
38.43 - * of valid header lines. The required range argument may be
38.44 - * any of the following:
38.45 - * an article number
38.46 - * an article number followed by a dash to indicate
38.47 - * all following
38.48 - * an article number followed by a dash followed by
38.49 - * another article number
38.50 - *
38.51 - * The required message-id argument indicates a specific
38.52 - * article. The range and message-id arguments are mutually
38.53 - * exclusive. At least one pattern in wildmat must be specified
38.54 - * as well. If there are additional arguments the are joined
38.55 - * together separated by a single space to form one complete
38.56 - * pattern. Successful responses start with a 221 response
38.57 - * followed by a the headers from all messages in which the
38.58 - * pattern matched the contents of the specified header line. This
38.59 - * includes an empty list. Once the output is complete, a period
38.60 - * is sent on a line by itself. If the optional argument is a
38.61 - * message-id and no such article exists, the 430 error response
38.62 - * is returned. A 502 response will be returned if the client only
38.63 - * has permission to transfer articles.
38.64 - *
38.65 - * Responses
38.66 - *
38.67 - * 221 Header follows
38.68 - * 430 no such article
38.69 - * 502 no permission
38.70 - *
38.71 - * Response Data:
38.72 - *
38.73 - * art_nr fitting_header_value
38.74 - *
38.75 - * </pre>
38.76 - * [Source:"draft-ietf-nntp-imp-02.txt"] [Copyright: 1998 S. Barber]
38.77 - *
38.78 - * @author Christian Lins
38.79 - * @since sonews/0.5.0
38.80 - */
38.81 -public class XPatCommand implements Command
38.82 -{
38.83 -
38.84 - @Override
38.85 - public String[] getSupportedCommandStrings()
38.86 - {
38.87 - return new String[]{"XPAT"};
38.88 - }
38.89 -
38.90 - @Override
38.91 - public boolean hasFinished()
38.92 - {
38.93 - return true;
38.94 - }
38.95 -
38.96 - @Override
38.97 - public String impliedCapability()
38.98 - {
38.99 - return null;
38.100 - }
38.101 -
38.102 - @Override
38.103 - public boolean isStateful()
38.104 - {
38.105 - return false;
38.106 - }
38.107 -
38.108 - @Override
38.109 - public void processLine(NNTPConnection conn, final String line, byte[] raw)
38.110 - throws IOException, StorageBackendException
38.111 - {
38.112 - if(conn.getCurrentChannel() == null)
38.113 - {
38.114 - conn.println("430 no group selected");
38.115 - return;
38.116 - }
38.117 -
38.118 - String[] command = line.split("\\p{Space}+");
38.119 -
38.120 - // There may be multiple patterns and Thunderbird produces
38.121 - // additional spaces between range and pattern
38.122 - if(command.length >= 4)
38.123 - {
38.124 - String header = command[1].toLowerCase(Locale.US);
38.125 - String range = command[2];
38.126 - String pattern = command[3];
38.127 -
38.128 - long start = -1;
38.129 - long end = -1;
38.130 - if(range.contains("-"))
38.131 - {
38.132 - String[] rsplit = range.split("-", 2);
38.133 - start = Long.parseLong(rsplit[0]);
38.134 - if(rsplit[1].length() > 0)
38.135 - {
38.136 - end = Long.parseLong(rsplit[1]);
38.137 - }
38.138 - }
38.139 - else // TODO: Handle Message-IDs
38.140 - {
38.141 - start = Long.parseLong(range);
38.142 - }
38.143 -
38.144 - try
38.145 - {
38.146 - List<Pair<Long, String>> heads = StorageManager.current().
38.147 - getArticleHeaders(conn.getCurrentChannel(), start, end, header, pattern);
38.148 -
38.149 - conn.println("221 header follows");
38.150 - for(Pair<Long, String> head : heads)
38.151 - {
38.152 - conn.println(head.getA() + " " + head.getB());
38.153 - }
38.154 - conn.println(".");
38.155 - }
38.156 - catch(PatternSyntaxException ex)
38.157 - {
38.158 - ex.printStackTrace();
38.159 - conn.println("500 invalid pattern syntax");
38.160 - }
38.161 - catch(StorageBackendException ex)
38.162 - {
38.163 - ex.printStackTrace();
38.164 - conn.println("500 internal server error");
38.165 - }
38.166 - }
38.167 - else
38.168 - {
38.169 - conn.println("430 invalid command usage");
38.170 - }
38.171 - }
38.172 -
38.173 -}
39.1 --- a/org/sonews/daemon/command/package.html Sun Aug 29 17:04:25 2010 +0200
39.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
39.3 @@ -1,1 +0,0 @@
39.4 -Contains a class for every NNTP command.
39.5 \ No newline at end of file
40.1 --- a/org/sonews/daemon/package.html Sun Aug 29 17:04:25 2010 +0200
40.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
40.3 @@ -1,1 +0,0 @@
40.4 -Contains basic classes of the daemon.
40.5 \ No newline at end of file
41.1 --- a/org/sonews/feed/FeedManager.java Sun Aug 29 17:04:25 2010 +0200
41.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
41.3 @@ -1,54 +0,0 @@
41.4 -/*
41.5 - * SONEWS News Server
41.6 - * see AUTHORS for the list of contributors
41.7 - *
41.8 - * This program is free software: you can redistribute it and/or modify
41.9 - * it under the terms of the GNU General Public License as published by
41.10 - * the Free Software Foundation, either version 3 of the License, or
41.11 - * (at your option) any later version.
41.12 - *
41.13 - * This program is distributed in the hope that it will be useful,
41.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
41.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41.16 - * GNU General Public License for more details.
41.17 - *
41.18 - * You should have received a copy of the GNU General Public License
41.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
41.20 - */
41.21 -
41.22 -package org.sonews.feed;
41.23 -
41.24 -import org.sonews.storage.Article;
41.25 -
41.26 -/**
41.27 - * Controlls push and pull feeder.
41.28 - * @author Christian Lins
41.29 - * @since sonews/0.5.0
41.30 - */
41.31 -public final class FeedManager
41.32 -{
41.33 -
41.34 - public static final int TYPE_PULL = 0;
41.35 - public static final int TYPE_PUSH = 1;
41.36 -
41.37 - private static PullFeeder pullFeeder = new PullFeeder();
41.38 - private static PushFeeder pushFeeder = new PushFeeder();
41.39 -
41.40 - /**
41.41 - * Reads the peer subscriptions from database and starts the appropriate
41.42 - * PullFeeder or PushFeeder.
41.43 - */
41.44 - public static synchronized void startFeeding()
41.45 - {
41.46 - pullFeeder.start();
41.47 - pushFeeder.start();
41.48 - }
41.49 -
41.50 - public static void queueForPush(Article article)
41.51 - {
41.52 - pushFeeder.queueForPush(article);
41.53 - }
41.54 -
41.55 - private FeedManager() {}
41.56 -
41.57 -}
42.1 --- a/org/sonews/feed/PullFeeder.java Sun Aug 29 17:04:25 2010 +0200
42.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
42.3 @@ -1,276 +0,0 @@
42.4 -/*
42.5 - * SONEWS News Server
42.6 - * see AUTHORS for the list of contributors
42.7 - *
42.8 - * This program is free software: you can redistribute it and/or modify
42.9 - * it under the terms of the GNU General Public License as published by
42.10 - * the Free Software Foundation, either version 3 of the License, or
42.11 - * (at your option) any later version.
42.12 - *
42.13 - * This program is distributed in the hope that it will be useful,
42.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
42.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
42.16 - * GNU General Public License for more details.
42.17 - *
42.18 - * You should have received a copy of the GNU General Public License
42.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
42.20 - */
42.21 -
42.22 -package org.sonews.feed;
42.23 -
42.24 -import java.io.BufferedReader;
42.25 -import java.io.IOException;
42.26 -import java.io.InputStreamReader;
42.27 -import java.io.PrintWriter;
42.28 -import java.net.Socket;
42.29 -import java.net.SocketException;
42.30 -import java.net.UnknownHostException;
42.31 -import java.util.ArrayList;
42.32 -import java.util.HashMap;
42.33 -import java.util.HashSet;
42.34 -import java.util.List;
42.35 -import java.util.Map;
42.36 -import java.util.Set;
42.37 -import java.util.logging.Level;
42.38 -import org.sonews.config.Config;
42.39 -import org.sonews.daemon.AbstractDaemon;
42.40 -import org.sonews.util.Log;
42.41 -import org.sonews.storage.StorageBackendException;
42.42 -import org.sonews.storage.StorageManager;
42.43 -import org.sonews.util.Stats;
42.44 -import org.sonews.util.io.ArticleReader;
42.45 -import org.sonews.util.io.ArticleWriter;
42.46 -
42.47 -/**
42.48 - * The PullFeeder class regularily checks another Newsserver for new
42.49 - * messages.
42.50 - * @author Christian Lins
42.51 - * @since sonews/0.5.0
42.52 - */
42.53 -class PullFeeder extends AbstractDaemon
42.54 -{
42.55 -
42.56 - private Map<Subscription, Integer> highMarks = new HashMap<Subscription, Integer>();
42.57 - private BufferedReader in;
42.58 - private PrintWriter out;
42.59 - private Set<Subscription> subscriptions = new HashSet<Subscription>();
42.60 -
42.61 - private void addSubscription(final Subscription sub)
42.62 - {
42.63 - subscriptions.add(sub);
42.64 -
42.65 - if(!highMarks.containsKey(sub))
42.66 - {
42.67 - // Set a initial highMark
42.68 - this.highMarks.put(sub, 0);
42.69 - }
42.70 - }
42.71 -
42.72 - /**
42.73 - * Changes to the given group and returns its high mark.
42.74 - * @param groupName
42.75 - * @return
42.76 - */
42.77 - private int changeGroup(String groupName)
42.78 - throws IOException
42.79 - {
42.80 - this.out.print("GROUP " + groupName + "\r\n");
42.81 - this.out.flush();
42.82 -
42.83 - String line = this.in.readLine();
42.84 - if(line.startsWith("211 "))
42.85 - {
42.86 - int highmark = Integer.parseInt(line.split(" ")[3]);
42.87 - return highmark;
42.88 - }
42.89 - else
42.90 - {
42.91 - throw new IOException("GROUP " + groupName + " returned: " + line);
42.92 - }
42.93 - }
42.94 -
42.95 - private void connectTo(final String host, final int port)
42.96 - throws IOException, UnknownHostException
42.97 - {
42.98 - Socket socket = new Socket(host, port);
42.99 - this.out = new PrintWriter(socket.getOutputStream());
42.100 - this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
42.101 -
42.102 - String line = in.readLine();
42.103 - if(!(line.charAt(0) == '2')) // Could be 200 or 2xx if posting is not allowed
42.104 - {
42.105 - throw new IOException(line);
42.106 - }
42.107 -
42.108 - // Send MODE READER to peer, some newsservers are friendlier then
42.109 - this.out.println("MODE READER\r\n");
42.110 - this.out.flush();
42.111 - line = this.in.readLine();
42.112 - }
42.113 -
42.114 - private void disconnect()
42.115 - throws IOException
42.116 - {
42.117 - this.out.print("QUIT\r\n");
42.118 - this.out.flush();
42.119 - this.out.close();
42.120 - this.in.close();
42.121 -
42.122 - this.out = null;
42.123 - this.in = null;
42.124 - }
42.125 -
42.126 - /**
42.127 - * Uses the OVER or XOVER command to get a list of message overviews that
42.128 - * may be unknown to this feeder and are about to be peered.
42.129 - * @param start
42.130 - * @param end
42.131 - * @return A list of message ids with potentially interesting messages.
42.132 - */
42.133 - private List<String> over(int start, int end)
42.134 - throws IOException
42.135 - {
42.136 - this.out.print("OVER " + start + "-" + end + "\r\n");
42.137 - this.out.flush();
42.138 -
42.139 - String line = this.in.readLine();
42.140 - if(line.startsWith("500 ")) // OVER not supported
42.141 - {
42.142 - this.out.print("XOVER " + start + "-" + end + "\r\n");
42.143 - this.out.flush();
42.144 -
42.145 - line = this.in.readLine();
42.146 - }
42.147 -
42.148 - if(line.startsWith("224 "))
42.149 - {
42.150 - List<String> messages = new ArrayList<String>();
42.151 - line = this.in.readLine();
42.152 - while(!".".equals(line))
42.153 - {
42.154 - String mid = line.split("\t")[4]; // 5th should be the Message-ID
42.155 - messages.add(mid);
42.156 - line = this.in.readLine();
42.157 - }
42.158 - return messages;
42.159 - }
42.160 - else
42.161 - {
42.162 - throw new IOException("Server return for OVER/XOVER: " + line);
42.163 - }
42.164 - }
42.165 -
42.166 - @Override
42.167 - public void run()
42.168 - {
42.169 - while(isRunning())
42.170 - {
42.171 - int pullInterval = 1000 *
42.172 - Config.inst().get(Config.FEED_PULLINTERVAL, 3600);
42.173 - String host = "localhost";
42.174 - int port = 119;
42.175 -
42.176 - Log.get().info("Start PullFeeder run...");
42.177 -
42.178 - try
42.179 - {
42.180 - this.subscriptions.clear();
42.181 - List<Subscription> subsPull = StorageManager.current()
42.182 - .getSubscriptions(FeedManager.TYPE_PULL);
42.183 - for(Subscription sub : subsPull)
42.184 - {
42.185 - addSubscription(sub);
42.186 - }
42.187 - }
42.188 - catch(StorageBackendException ex)
42.189 - {
42.190 - Log.get().log(Level.SEVERE, host, ex);
42.191 - }
42.192 -
42.193 - try
42.194 - {
42.195 - for(Subscription sub : this.subscriptions)
42.196 - {
42.197 - host = sub.getHost();
42.198 - port = sub.getPort();
42.199 -
42.200 - try
42.201 - {
42.202 - Log.get().info("Feeding " + sub.getGroup() + " from " + sub.getHost());
42.203 - try
42.204 - {
42.205 - connectTo(host, port);
42.206 - }
42.207 - catch(SocketException ex)
42.208 - {
42.209 - Log.get().info("Skipping " + sub.getHost() + ": " + ex);
42.210 - continue;
42.211 - }
42.212 -
42.213 - int oldMark = this.highMarks.get(sub);
42.214 - int newMark = changeGroup(sub.getGroup());
42.215 -
42.216 - if(oldMark != newMark)
42.217 - {
42.218 - List<String> messageIDs = over(oldMark, newMark);
42.219 -
42.220 - for(String messageID : messageIDs)
42.221 - {
42.222 - if(!StorageManager.current().isArticleExisting(messageID))
42.223 - {
42.224 - try
42.225 - {
42.226 - // Post the message via common socket connection
42.227 - ArticleReader aread =
42.228 - new ArticleReader(sub.getHost(), sub.getPort(), messageID);
42.229 - byte[] abuf = aread.getArticleData();
42.230 - if(abuf == null)
42.231 - {
42.232 - Log.get().warning("Could not feed " + messageID
42.233 - + " from " + sub.getHost());
42.234 - }
42.235 - else
42.236 - {
42.237 - Log.get().info("Feeding " + messageID);
42.238 - ArticleWriter awrite = new ArticleWriter(
42.239 - "localhost", Config.inst().get(Config.PORT, 119));
42.240 - awrite.writeArticle(abuf);
42.241 - awrite.close();
42.242 - }
42.243 - Stats.getInstance().mailFeeded(sub.getGroup());
42.244 - }
42.245 - catch(IOException ex)
42.246 - {
42.247 - // There may be a temporary network failure
42.248 - ex.printStackTrace();
42.249 - Log.get().warning("Skipping mail " + messageID + " due to exception.");
42.250 - }
42.251 - }
42.252 - } // for(;;)
42.253 - this.highMarks.put(sub, newMark);
42.254 - }
42.255 -
42.256 - disconnect();
42.257 - }
42.258 - catch(StorageBackendException ex)
42.259 - {
42.260 - ex.printStackTrace();
42.261 - }
42.262 - catch(IOException ex)
42.263 - {
42.264 - ex.printStackTrace();
42.265 - Log.get().severe("PullFeeder run stopped due to exception.");
42.266 - }
42.267 - } // for(Subscription sub : subscriptions)
42.268 -
42.269 - Log.get().info("PullFeeder run ended. Waiting " + pullInterval / 1000 + "s");
42.270 - Thread.sleep(pullInterval);
42.271 - }
42.272 - catch(InterruptedException ex)
42.273 - {
42.274 - Log.get().warning(ex.getMessage());
42.275 - }
42.276 - }
42.277 - }
42.278 -
42.279 -}
43.1 --- a/org/sonews/feed/PushFeeder.java Sun Aug 29 17:04:25 2010 +0200
43.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
43.3 @@ -1,118 +0,0 @@
43.4 -/*
43.5 - * SONEWS News Server
43.6 - * see AUTHORS for the list of contributors
43.7 - *
43.8 - * This program is free software: you can redistribute it and/or modify
43.9 - * it under the terms of the GNU General Public License as published by
43.10 - * the Free Software Foundation, either version 3 of the License, or
43.11 - * (at your option) any later version.
43.12 - *
43.13 - * This program is distributed in the hope that it will be useful,
43.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
43.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
43.16 - * GNU General Public License for more details.
43.17 - *
43.18 - * You should have received a copy of the GNU General Public License
43.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
43.20 - */
43.21 -
43.22 -package org.sonews.feed;
43.23 -
43.24 -import java.io.IOException;
43.25 -import java.util.List;
43.26 -import java.util.concurrent.ConcurrentLinkedQueue;
43.27 -import org.sonews.daemon.AbstractDaemon;
43.28 -import org.sonews.storage.Article;
43.29 -import org.sonews.storage.Headers;
43.30 -import org.sonews.storage.StorageBackendException;
43.31 -import org.sonews.storage.StorageManager;
43.32 -import org.sonews.util.Log;
43.33 -import org.sonews.util.io.ArticleWriter;
43.34 -
43.35 -/**
43.36 - * Pushes new articles to remote newsservers. This feeder sleeps until a new
43.37 - * message is posted to the sonews instance.
43.38 - * @author Christian Lins
43.39 - * @since sonews/0.5.0
43.40 - */
43.41 -class PushFeeder extends AbstractDaemon
43.42 -{
43.43 -
43.44 - private ConcurrentLinkedQueue<Article> articleQueue =
43.45 - new ConcurrentLinkedQueue<Article>();
43.46 -
43.47 - @Override
43.48 - public void run()
43.49 - {
43.50 - while(isRunning())
43.51 - {
43.52 - try
43.53 - {
43.54 - synchronized(this)
43.55 - {
43.56 - this.wait();
43.57 - }
43.58 -
43.59 - List<Subscription> subscriptions = StorageManager.current()
43.60 - .getSubscriptions(FeedManager.TYPE_PUSH);
43.61 -
43.62 - Article article = this.articleQueue.poll();
43.63 - String[] groups = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
43.64 - Log.get().info("PushFeed: " + article.getMessageID());
43.65 - for(Subscription sub : subscriptions)
43.66 - {
43.67 - // Circle check
43.68 - if(article.getHeader(Headers.PATH)[0].contains(sub.getHost()))
43.69 - {
43.70 - Log.get().info(article.getMessageID() + " skipped for host "
43.71 - + sub.getHost());
43.72 - continue;
43.73 - }
43.74 -
43.75 - try
43.76 - {
43.77 - for(String group : groups)
43.78 - {
43.79 - if(sub.getGroup().equals(group))
43.80 - {
43.81 - // Delete headers that may cause problems
43.82 - article.removeHeader(Headers.NNTP_POSTING_DATE);
43.83 - article.removeHeader(Headers.NNTP_POSTING_HOST);
43.84 - article.removeHeader(Headers.X_COMPLAINTS_TO);
43.85 - article.removeHeader(Headers.X_TRACE);
43.86 - article.removeHeader(Headers.XREF);
43.87 -
43.88 - // POST the message to remote server
43.89 - ArticleWriter awriter = new ArticleWriter(sub.getHost(), sub.getPort());
43.90 - awriter.writeArticle(article);
43.91 - break;
43.92 - }
43.93 - }
43.94 - }
43.95 - catch(IOException ex)
43.96 - {
43.97 - Log.get().warning(ex.toString());
43.98 - }
43.99 - }
43.100 - }
43.101 - catch(StorageBackendException ex)
43.102 - {
43.103 - Log.get().severe(ex.toString());
43.104 - }
43.105 - catch(InterruptedException ex)
43.106 - {
43.107 - Log.get().warning("PushFeeder interrupted: " + ex);
43.108 - }
43.109 - }
43.110 - }
43.111 -
43.112 - public void queueForPush(Article article)
43.113 - {
43.114 - this.articleQueue.add(article);
43.115 - synchronized(this)
43.116 - {
43.117 - this.notifyAll();
43.118 - }
43.119 - }
43.120 -
43.121 -}
44.1 --- a/org/sonews/feed/Subscription.java Sun Aug 29 17:04:25 2010 +0200
44.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
44.3 @@ -1,84 +0,0 @@
44.4 -/*
44.5 - * SONEWS News Server
44.6 - * see AUTHORS for the list of contributors
44.7 - *
44.8 - * This program is free software: you can redistribute it and/or modify
44.9 - * it under the terms of the GNU General Public License as published by
44.10 - * the Free Software Foundation, either version 3 of the License, or
44.11 - * (at your option) any later version.
44.12 - *
44.13 - * This program is distributed in the hope that it will be useful,
44.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
44.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
44.16 - * GNU General Public License for more details.
44.17 - *
44.18 - * You should have received a copy of the GNU General Public License
44.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
44.20 - */
44.21 -
44.22 -package org.sonews.feed;
44.23 -
44.24 -/**
44.25 - * For every group that is synchronized with or from a remote newsserver
44.26 - * a Subscription instance exists.
44.27 - * @author Christian Lins
44.28 - * @since sonews/0.5.0
44.29 - */
44.30 -public class Subscription
44.31 -{
44.32 -
44.33 - private String host;
44.34 - private int port;
44.35 - private int feedtype;
44.36 - private String group;
44.37 -
44.38 - public Subscription(String host, int port, int feedtype, String group)
44.39 - {
44.40 - this.host = host;
44.41 - this.port = port;
44.42 - this.feedtype = feedtype;
44.43 - this.group = group;
44.44 - }
44.45 -
44.46 - @Override
44.47 - public boolean equals(Object obj)
44.48 - {
44.49 - if(obj instanceof Subscription)
44.50 - {
44.51 - Subscription sub = (Subscription)obj;
44.52 - return sub.host.equals(host) && sub.group.equals(group)
44.53 - && sub.port == port && sub.feedtype == feedtype;
44.54 - }
44.55 - else
44.56 - {
44.57 - return false;
44.58 - }
44.59 - }
44.60 -
44.61 - @Override
44.62 - public int hashCode()
44.63 - {
44.64 - return host.hashCode() + port + feedtype + group.hashCode();
44.65 - }
44.66 -
44.67 - public int getFeedtype()
44.68 - {
44.69 - return feedtype;
44.70 - }
44.71 -
44.72 - public String getGroup()
44.73 - {
44.74 - return group;
44.75 - }
44.76 -
44.77 - public String getHost()
44.78 - {
44.79 - return host;
44.80 - }
44.81 -
44.82 - public int getPort()
44.83 - {
44.84 - return port;
44.85 - }
44.86 -
44.87 -}
45.1 --- a/org/sonews/feed/package.html Sun Aug 29 17:04:25 2010 +0200
45.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
45.3 @@ -1,2 +0,0 @@
45.4 -Contains classes for the peering functionality, e.g. pulling and pushing
45.5 -mails from and to remote newsservers.
45.6 \ No newline at end of file
46.1 --- a/org/sonews/mlgw/Dispatcher.java Sun Aug 29 17:04:25 2010 +0200
46.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
46.3 @@ -1,301 +0,0 @@
46.4 -/*
46.5 - * SONEWS News Server
46.6 - * see AUTHORS for the list of contributors
46.7 - *
46.8 - * This program is free software: you can redistribute it and/or modify
46.9 - * it under the terms of the GNU General Public License as published by
46.10 - * the Free Software Foundation, either version 3 of the License, or
46.11 - * (at your option) any later version.
46.12 - *
46.13 - * This program is distributed in the hope that it will be useful,
46.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
46.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
46.16 - * GNU General Public License for more details.
46.17 - *
46.18 - * You should have received a copy of the GNU General Public License
46.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
46.20 - */
46.21 -
46.22 -package org.sonews.mlgw;
46.23 -
46.24 -import java.io.IOException;
46.25 -import java.util.ArrayList;
46.26 -import java.util.List;
46.27 -import java.util.regex.Matcher;
46.28 -import java.util.regex.Pattern;
46.29 -import javax.mail.Address;
46.30 -import javax.mail.Authenticator;
46.31 -import javax.mail.Message;
46.32 -import javax.mail.MessagingException;
46.33 -import javax.mail.PasswordAuthentication;
46.34 -import javax.mail.internet.InternetAddress;
46.35 -import org.sonews.config.Config;
46.36 -import org.sonews.storage.Article;
46.37 -import org.sonews.storage.Group;
46.38 -import org.sonews.storage.Headers;
46.39 -import org.sonews.storage.StorageBackendException;
46.40 -import org.sonews.storage.StorageManager;
46.41 -import org.sonews.util.Log;
46.42 -import org.sonews.util.Stats;
46.43 -
46.44 -/**
46.45 - * Dispatches messages from mailing list to newsserver or vice versa.
46.46 - * @author Christian Lins
46.47 - * @since sonews/0.5.0
46.48 - */
46.49 -public class Dispatcher
46.50 -{
46.51 -
46.52 - static class PasswordAuthenticator extends Authenticator
46.53 - {
46.54 -
46.55 - @Override
46.56 - public PasswordAuthentication getPasswordAuthentication()
46.57 - {
46.58 - final String username =
46.59 - Config.inst().get(Config.MLSEND_USER, "user");
46.60 - final String password =
46.61 - Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
46.62 -
46.63 - return new PasswordAuthentication(username, password);
46.64 - }
46.65 -
46.66 - }
46.67 -
46.68 - /**
46.69 - * Chunks out the email address of the full List-Post header field.
46.70 - * @param listPostValue
46.71 - * @return The matching email address or null
46.72 - */
46.73 - private static String chunkListPost(String listPostValue)
46.74 - {
46.75 - // listPostValue is of form "<mailto:dev@openoffice.org>"
46.76 - Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
46.77 - Matcher mailMatcher = mailPattern.matcher(listPostValue);
46.78 - if(mailMatcher.find())
46.79 - {
46.80 - return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
46.81 - }
46.82 - else
46.83 - {
46.84 - return null;
46.85 - }
46.86 - }
46.87 -
46.88 - /**
46.89 - * This method inspects the header of the given message, trying
46.90 - * to find the most appropriate recipient.
46.91 - * @param msg
46.92 - * @param fallback If this is false only List-Post and X-List-Post headers
46.93 - * are examined.
46.94 - * @return null or fitting group name for the given message.
46.95 - */
46.96 - private static List<String> getGroupFor(final Message msg, final boolean fallback)
46.97 - throws MessagingException, StorageBackendException
46.98 - {
46.99 - List<String> groups = null;
46.100 -
46.101 - // Is there a List-Post header?
46.102 - String[] listPost = msg.getHeader(Headers.LIST_POST);
46.103 - InternetAddress listPostAddr;
46.104 -
46.105 - if(listPost == null || listPost.length == 0 || "".equals(listPost[0]))
46.106 - {
46.107 - // Is there a X-List-Post header?
46.108 - listPost = msg.getHeader(Headers.X_LIST_POST);
46.109 - }
46.110 -
46.111 - if(listPost != null && listPost.length > 0
46.112 - && !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null)
46.113 - {
46.114 - // listPost[0] is of form "<mailto:dev@openoffice.org>"
46.115 - listPost[0] = chunkListPost(listPost[0]);
46.116 - listPostAddr = new InternetAddress(listPost[0], false);
46.117 - groups = StorageManager.current().getGroupsForList(listPostAddr.getAddress());
46.118 - }
46.119 - else if(fallback)
46.120 - {
46.121 - Log.get().info("Using fallback recipient discovery for: " + msg.getSubject());
46.122 - groups = new ArrayList<String>();
46.123 - // Fallback to TO/CC/BCC addresses
46.124 - Address[] to = msg.getAllRecipients();
46.125 - for(Address toa : to) // Address can have '<' '>' around
46.126 - {
46.127 - if(toa instanceof InternetAddress)
46.128 - {
46.129 - List<String> g = StorageManager.current()
46.130 - .getGroupsForList(((InternetAddress)toa).getAddress());
46.131 - groups.addAll(g);
46.132 - }
46.133 - }
46.134 - }
46.135 -
46.136 - return groups;
46.137 - }
46.138 -
46.139 - /**
46.140 - * Posts a message that was received from a mailing list to the
46.141 - * appropriate newsgroup.
46.142 - * If the message already exists in the storage, this message checks
46.143 - * if it must be posted in an additional group. This can happen for
46.144 - * crosspostings in different mailing lists.
46.145 - * @param msg
46.146 - */
46.147 - public static boolean toGroup(final Message msg)
46.148 - {
46.149 - try
46.150 - {
46.151 - // Create new Article object
46.152 - Article article = new Article(msg);
46.153 - boolean posted = false;
46.154 -
46.155 - // Check if this mail is already existing the storage
46.156 - boolean updateReq =
46.157 - StorageManager.current().isArticleExisting(article.getMessageID());
46.158 -
46.159 - List<String> newsgroups = getGroupFor(msg, !updateReq);
46.160 - List<String> oldgroups = new ArrayList<String>();
46.161 - if(updateReq)
46.162 - {
46.163 - // Check for duplicate entries of the same group
46.164 - Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
46.165 - List<Group> oldGroups = oldArticle.getGroups();
46.166 - for(Group oldGroup : oldGroups)
46.167 - {
46.168 - if(!newsgroups.contains(oldGroup.getName()))
46.169 - {
46.170 - oldgroups.add(oldGroup.getName());
46.171 - }
46.172 - }
46.173 - }
46.174 -
46.175 - if(newsgroups.size() > 0)
46.176 - {
46.177 - newsgroups.addAll(oldgroups);
46.178 - StringBuilder groups = new StringBuilder();
46.179 - for(int n = 0; n < newsgroups.size(); n++)
46.180 - {
46.181 - groups.append(newsgroups.get(n));
46.182 - if (n + 1 != newsgroups.size())
46.183 - {
46.184 - groups.append(',');
46.185 - }
46.186 - }
46.187 - Log.get().info("Posting to group " + groups.toString());
46.188 -
46.189 - article.setGroup(groups.toString());
46.190 - //article.removeHeader(Headers.REPLY_TO);
46.191 - //article.removeHeader(Headers.TO);
46.192 -
46.193 - // Write article to database
46.194 - if(updateReq)
46.195 - {
46.196 - Log.get().info("Updating " + article.getMessageID()
46.197 - + " with additional groups");
46.198 - StorageManager.current().delete(article.getMessageID());
46.199 - StorageManager.current().addArticle(article);
46.200 - }
46.201 - else
46.202 - {
46.203 - Log.get().info("Gatewaying " + article.getMessageID() + " to "
46.204 - + article.getHeader(Headers.NEWSGROUPS)[0]);
46.205 - StorageManager.current().addArticle(article);
46.206 - Stats.getInstance().mailGatewayed(
46.207 - article.getHeader(Headers.NEWSGROUPS)[0]);
46.208 - }
46.209 - posted = true;
46.210 - }
46.211 - else
46.212 - {
46.213 - StringBuilder buf = new StringBuilder();
46.214 - for (Address toa : msg.getAllRecipients())
46.215 - {
46.216 - buf.append(' ');
46.217 - buf.append(toa.toString());
46.218 - }
46.219 - buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
46.220 - Log.get().warning("No group for" + buf.toString());
46.221 - }
46.222 - return posted;
46.223 - }
46.224 - catch(Exception ex)
46.225 - {
46.226 - ex.printStackTrace();
46.227 - return false;
46.228 - }
46.229 - }
46.230 -
46.231 - /**
46.232 - * Mails a message received through NNTP to the appropriate mailing list.
46.233 - * This method MAY be called several times by PostCommand for the same
46.234 - * article.
46.235 - */
46.236 - public static void toList(Article article, String group)
46.237 - throws IOException, MessagingException, StorageBackendException
46.238 - {
46.239 - // Get mailing lists for the group of this article
46.240 - List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
46.241 -
46.242 - if(rcptAddresses == null || rcptAddresses.size() == 0)
46.243 - {
46.244 - Log.get().warning("No ML-address for " + group + " found.");
46.245 - return;
46.246 - }
46.247 -
46.248 - for(String rcptAddress : rcptAddresses)
46.249 - {
46.250 - // Compose message and send it via given SMTP-Host
46.251 - String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
46.252 - int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
46.253 - String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
46.254 - String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
46.255 - String smtpFrom = Config.inst().get(
46.256 - Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
46.257 -
46.258 - // TODO: Make Article cloneable()
46.259 - article.getMessageID(); // Make sure an ID is existing
46.260 - article.removeHeader(Headers.NEWSGROUPS);
46.261 - article.removeHeader(Headers.PATH);
46.262 - article.removeHeader(Headers.LINES);
46.263 - article.removeHeader(Headers.BYTES);
46.264 -
46.265 - article.setHeader("To", rcptAddress);
46.266 - //article.setHeader("Reply-To", listAddress);
46.267 -
46.268 - if (Config.inst().get(Config.MLSEND_RW_SENDER, false))
46.269 - {
46.270 - rewriteSenderAddress(article); // Set the SENDER address
46.271 - }
46.272 -
46.273 - SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
46.274 - smtpTransport.send(article, smtpFrom, rcptAddress);
46.275 - smtpTransport.close();
46.276 -
46.277 - Stats.getInstance().mailGatewayed(group);
46.278 - Log.get().info("MLGateway: Mail " + article.getHeader("Subject")[0]
46.279 - + " was delivered to " + rcptAddress + ".");
46.280 - }
46.281 - }
46.282 -
46.283 - /**
46.284 - * Sets the SENDER header of the given MimeMessage. This might be necessary
46.285 - * for moderated groups that does not allow the "normal" FROM sender.
46.286 - * @param msg
46.287 - * @throws javax.mail.MessagingException
46.288 - */
46.289 - private static void rewriteSenderAddress(Article msg)
46.290 - throws MessagingException
46.291 - {
46.292 - String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
46.293 -
46.294 - if(mlAddress != null)
46.295 - {
46.296 - msg.setHeader(Headers.SENDER, mlAddress);
46.297 - }
46.298 - else
46.299 - {
46.300 - throw new MessagingException("Cannot rewrite SENDER header!");
46.301 - }
46.302 - }
46.303 -
46.304 -}
47.1 --- a/org/sonews/mlgw/MailPoller.java Sun Aug 29 17:04:25 2010 +0200
47.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
47.3 @@ -1,151 +0,0 @@
47.4 -/*
47.5 - * SONEWS News Server
47.6 - * see AUTHORS for the list of contributors
47.7 - *
47.8 - * This program is free software: you can redistribute it and/or modify
47.9 - * it under the terms of the GNU General Public License as published by
47.10 - * the Free Software Foundation, either version 3 of the License, or
47.11 - * (at your option) any later version.
47.12 - *
47.13 - * This program is distributed in the hope that it will be useful,
47.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
47.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
47.16 - * GNU General Public License for more details.
47.17 - *
47.18 - * You should have received a copy of the GNU General Public License
47.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
47.20 - */
47.21 -
47.22 -package org.sonews.mlgw;
47.23 -
47.24 -import java.util.Properties;
47.25 -import javax.mail.AuthenticationFailedException;
47.26 -import javax.mail.Authenticator;
47.27 -import javax.mail.Flags.Flag;
47.28 -import javax.mail.Folder;
47.29 -import javax.mail.Message;
47.30 -import javax.mail.MessagingException;
47.31 -import javax.mail.NoSuchProviderException;
47.32 -import javax.mail.PasswordAuthentication;
47.33 -import javax.mail.Session;
47.34 -import javax.mail.Store;
47.35 -import org.sonews.config.Config;
47.36 -import org.sonews.daemon.AbstractDaemon;
47.37 -import org.sonews.util.Log;
47.38 -import org.sonews.util.Stats;
47.39 -
47.40 -/**
47.41 - * Daemon polling for new mails in a POP3 account to be delivered to newsgroups.
47.42 - * @author Christian Lins
47.43 - * @since sonews/0.5.0
47.44 - */
47.45 -public class MailPoller extends AbstractDaemon
47.46 -{
47.47 -
47.48 - static class PasswordAuthenticator extends Authenticator
47.49 - {
47.50 -
47.51 - @Override
47.52 - public PasswordAuthentication getPasswordAuthentication()
47.53 - {
47.54 - final String username =
47.55 - Config.inst().get(Config.MLPOLL_USER, "user");
47.56 - final String password =
47.57 - Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
47.58 -
47.59 - return new PasswordAuthentication(username, password);
47.60 - }
47.61 -
47.62 - }
47.63 -
47.64 - @Override
47.65 - public void run()
47.66 - {
47.67 - Log.get().info("Starting Mailinglist Poller...");
47.68 - int errors = 0;
47.69 - while(isRunning())
47.70 - {
47.71 - try
47.72 - {
47.73 - // Wait some time between runs. At the beginning has advantages,
47.74 - // because the wait is not skipped if an exception occurs.
47.75 - Thread.sleep(60000 * (errors + 1)); // one minute * errors
47.76 -
47.77 - final String host =
47.78 - Config.inst().get(Config.MLPOLL_HOST, "samplehost");
47.79 - final String username =
47.80 - Config.inst().get(Config.MLPOLL_USER, "user");
47.81 - final String password =
47.82 - Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
47.83 -
47.84 - Stats.getInstance().mlgwRunStart();
47.85 -
47.86 - // Create empty properties
47.87 - Properties props = System.getProperties();
47.88 - props.put("mail.pop3.host", host);
47.89 - props.put("mail.mime.address.strict", "false");
47.90 -
47.91 - // Get session
47.92 - Session session = Session.getInstance(props);
47.93 -
47.94 - // Get the store
47.95 - Store store = session.getStore("pop3");
47.96 - store.connect(host, 110, username, password);
47.97 -
47.98 - // Get folder
47.99 - Folder folder = store.getFolder("INBOX");
47.100 - folder.open(Folder.READ_WRITE);
47.101 -
47.102 - // Get directory
47.103 - Message[] messages = folder.getMessages();
47.104 -
47.105 - // Dispatch messages and delete it afterwards on the inbox
47.106 - for(Message message : messages)
47.107 - {
47.108 - if(Dispatcher.toGroup(message)
47.109 - || Config.inst().get(Config.MLPOLL_DELETEUNKNOWN, false))
47.110 - {
47.111 - // Delete the message
47.112 - message.setFlag(Flag.DELETED, true);
47.113 - }
47.114 - }
47.115 -
47.116 - // Close connection
47.117 - folder.close(true); // true to expunge deleted messages
47.118 - store.close();
47.119 - errors = 0;
47.120 -
47.121 - Stats.getInstance().mlgwRunEnd();
47.122 - }
47.123 - catch(NoSuchProviderException ex)
47.124 - {
47.125 - Log.get().severe(ex.toString());
47.126 - shutdown();
47.127 - }
47.128 - catch(AuthenticationFailedException ex)
47.129 - {
47.130 - // AuthentificationFailedException may be thrown if credentials are
47.131 - // bad or if the Mailbox is in use (locked).
47.132 - ex.printStackTrace();
47.133 - errors = errors < 5 ? errors + 1 : errors;
47.134 - }
47.135 - catch(InterruptedException ex)
47.136 - {
47.137 - System.out.println("sonews: " + this + " returns: " + ex);
47.138 - return;
47.139 - }
47.140 - catch(MessagingException ex)
47.141 - {
47.142 - ex.printStackTrace();
47.143 - errors = errors < 5 ? errors + 1 : errors;
47.144 - }
47.145 - catch(Exception ex)
47.146 - {
47.147 - ex.printStackTrace();
47.148 - errors = errors < 5 ? errors + 1 : errors;
47.149 - }
47.150 - }
47.151 - Log.get().severe("MailPoller exited.");
47.152 - }
47.153 -
47.154 -}
48.1 --- a/org/sonews/mlgw/SMTPTransport.java Sun Aug 29 17:04:25 2010 +0200
48.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
48.3 @@ -1,133 +0,0 @@
48.4 -/*
48.5 - * SONEWS News Server
48.6 - * see AUTHORS for the list of contributors
48.7 - *
48.8 - * This program is free software: you can redistribute it and/or modify
48.9 - * it under the terms of the GNU General Public License as published by
48.10 - * the Free Software Foundation, either version 3 of the License, or
48.11 - * (at your option) any later version.
48.12 - *
48.13 - * This program is distributed in the hope that it will be useful,
48.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
48.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48.16 - * GNU General Public License for more details.
48.17 - *
48.18 - * You should have received a copy of the GNU General Public License
48.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
48.20 - */
48.21 -
48.22 -package org.sonews.mlgw;
48.23 -
48.24 -import java.io.BufferedOutputStream;
48.25 -import java.io.BufferedReader;
48.26 -import java.io.IOException;
48.27 -import java.io.InputStreamReader;
48.28 -import java.net.Socket;
48.29 -import java.net.UnknownHostException;
48.30 -import org.sonews.config.Config;
48.31 -import org.sonews.storage.Article;
48.32 -import org.sonews.util.io.ArticleInputStream;
48.33 -
48.34 -/**
48.35 - * Connects to a SMTP server and sends a given Article to it.
48.36 - * @author Christian Lins
48.37 - * @since sonews/1.0
48.38 - */
48.39 -class SMTPTransport
48.40 -{
48.41 -
48.42 - protected BufferedReader in;
48.43 - protected BufferedOutputStream out;
48.44 - protected Socket socket;
48.45 -
48.46 - public SMTPTransport(String host, int port)
48.47 - throws IOException, UnknownHostException
48.48 - {
48.49 - socket = new Socket(host, port);
48.50 - this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
48.51 - this.out = new BufferedOutputStream(socket.getOutputStream());
48.52 -
48.53 - // Read helo from server
48.54 - String line = this.in.readLine();
48.55 - if(line == null || !line.startsWith("220 "))
48.56 - {
48.57 - throw new IOException("Invalid helo from server: " + line);
48.58 - }
48.59 -
48.60 - // Send HELO to server
48.61 - this.out.write(
48.62 - ("HELO " + Config.inst().get(Config.HOSTNAME, "localhost") + "\r\n").getBytes("UTF-8"));
48.63 - this.out.flush();
48.64 - line = this.in.readLine();
48.65 - if(line == null || !line.startsWith("250 "))
48.66 - {
48.67 - throw new IOException("Unexpected reply: " + line);
48.68 - }
48.69 - }
48.70 -
48.71 - public SMTPTransport(String host)
48.72 - throws IOException
48.73 - {
48.74 - this(host, 25);
48.75 - }
48.76 -
48.77 - public void close()
48.78 - throws IOException
48.79 - {
48.80 - this.out.write("QUIT".getBytes("UTF-8"));
48.81 - this.out.flush();
48.82 - this.in.readLine();
48.83 -
48.84 - this.socket.close();
48.85 - }
48.86 -
48.87 - public void send(Article article, String mailFrom, String rcptTo)
48.88 - throws IOException
48.89 - {
48.90 - assert(article != null);
48.91 - assert(mailFrom != null);
48.92 - assert(rcptTo != null);
48.93 -
48.94 - this.out.write(("MAIL FROM: " + mailFrom).getBytes("UTF-8"));
48.95 - this.out.flush();
48.96 - String line = this.in.readLine();
48.97 - if(line == null || !line.startsWith("250 "))
48.98 - {
48.99 - throw new IOException("Unexpected reply: " + line);
48.100 - }
48.101 -
48.102 - this.out.write(("RCPT TO: " + rcptTo).getBytes("UTF-8"));
48.103 - this.out.flush();
48.104 - line = this.in.readLine();
48.105 - if(line == null || !line.startsWith("250 "))
48.106 - {
48.107 - throw new IOException("Unexpected reply: " + line);
48.108 - }
48.109 -
48.110 - this.out.write("DATA".getBytes("UTF-8"));
48.111 - this.out.flush();
48.112 - line = this.in.readLine();
48.113 - if(line == null || !line.startsWith("354 "))
48.114 - {
48.115 - throw new IOException("Unexpected reply: " + line);
48.116 - }
48.117 -
48.118 - ArticleInputStream artStream = new ArticleInputStream(article);
48.119 - for(int b = artStream.read(); b >= 0; b = artStream.read())
48.120 - {
48.121 - this.out.write(b);
48.122 - }
48.123 -
48.124 - // Flush the binary stream; important because otherwise the output
48.125 - // will be mixed with the PrintWriter.
48.126 - this.out.flush();
48.127 - this.out.write("\r\n.\r\n".getBytes("UTF-8"));
48.128 - this.out.flush();
48.129 - line = this.in.readLine();
48.130 - if(line == null || !line.startsWith("250 "))
48.131 - {
48.132 - throw new IOException("Unexpected reply: " + line);
48.133 - }
48.134 - }
48.135 -
48.136 -}
49.1 --- a/org/sonews/mlgw/package.html Sun Aug 29 17:04:25 2010 +0200
49.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
49.3 @@ -1,1 +0,0 @@
49.4 -Contains classes of the Mailinglist Gateway.
49.5 \ No newline at end of file
50.1 --- a/org/sonews/plugin/Plugin.java Sun Aug 29 17:04:25 2010 +0200
50.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
50.3 @@ -1,42 +0,0 @@
50.4 -/*
50.5 - * SONEWS News Server
50.6 - * see AUTHORS for the list of contributors
50.7 - *
50.8 - * This program is free software: you can redistribute it and/or modify
50.9 - * it under the terms of the GNU General Public License as published by
50.10 - * the Free Software Foundation, either version 3 of the License, or
50.11 - * (at your option) any later version.
50.12 - *
50.13 - * This program is distributed in the hope that it will be useful,
50.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
50.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
50.16 - * GNU General Public License for more details.
50.17 - *
50.18 - * You should have received a copy of the GNU General Public License
50.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
50.20 - */
50.21 -
50.22 -package org.sonews.plugin;
50.23 -
50.24 -/**
50.25 - * A generic Plugin for sonews. Implementing classes do not really add new
50.26 - * functionality to sonews but can use this interface as convenient procedure
50.27 - * for installing functionality plugins, e.g. Command-Plugins or Storage-Plugins.
50.28 - * @author Christian Lins
50.29 - * @since sonews/1.1
50.30 - */
50.31 -public interface Plugin
50.32 -{
50.33 -
50.34 - /**
50.35 - * Called when the Plugin is loaded by sonews. This method can be used
50.36 - * by implementing classes to install additional or required plugins.
50.37 - */
50.38 - void load();
50.39 -
50.40 - /**
50.41 - * Called when the Plugin is unloaded by sonews.
50.42 - */
50.43 - void unload();
50.44 -
50.45 -}
51.1 --- a/org/sonews/storage/Article.java Sun Aug 29 17:04:25 2010 +0200
51.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
51.3 @@ -1,253 +0,0 @@
51.4 -/*
51.5 - * SONEWS News Server
51.6 - * see AUTHORS for the list of contributors
51.7 - *
51.8 - * This program is free software: you can redistribute it and/or modify
51.9 - * it under the terms of the GNU General Public License as published by
51.10 - * the Free Software Foundation, either version 3 of the License, or
51.11 - * (at your option) any later version.
51.12 - *
51.13 - * This program is distributed in the hope that it will be useful,
51.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
51.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
51.16 - * GNU General Public License for more details.
51.17 - *
51.18 - * You should have received a copy of the GNU General Public License
51.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
51.20 - */
51.21 -
51.22 -package org.sonews.storage;
51.23 -
51.24 -import java.io.ByteArrayInputStream;
51.25 -import java.io.ByteArrayOutputStream;
51.26 -import java.io.IOException;
51.27 -import java.security.MessageDigest;
51.28 -import java.security.NoSuchAlgorithmException;
51.29 -import java.util.UUID;
51.30 -import java.util.ArrayList;
51.31 -import java.util.Enumeration;
51.32 -import java.util.List;
51.33 -import javax.mail.Header;
51.34 -import javax.mail.Message;
51.35 -import javax.mail.MessagingException;
51.36 -import javax.mail.internet.InternetHeaders;
51.37 -import org.sonews.config.Config;
51.38 -
51.39 -/**
51.40 - * Represents a newsgroup article.
51.41 - * @author Christian Lins
51.42 - * @author Denis Schwerdel
51.43 - * @since n3tpd/0.1
51.44 - */
51.45 -public class Article extends ArticleHead
51.46 -{
51.47 -
51.48 - /**
51.49 - * Loads the Article identified by the given ID from the JDBCDatabase.
51.50 - * @param messageID
51.51 - * @return null if Article is not found or if an error occurred.
51.52 - */
51.53 - public static Article getByMessageID(final String messageID)
51.54 - {
51.55 - try
51.56 - {
51.57 - return StorageManager.current().getArticle(messageID);
51.58 - }
51.59 - catch(StorageBackendException ex)
51.60 - {
51.61 - ex.printStackTrace();
51.62 - return null;
51.63 - }
51.64 - }
51.65 -
51.66 - private byte[] body = new byte[0];
51.67 -
51.68 - /**
51.69 - * Default constructor.
51.70 - */
51.71 - public Article()
51.72 - {
51.73 - }
51.74 -
51.75 - /**
51.76 - * Creates a new Article object using the date from the given
51.77 - * raw data.
51.78 - */
51.79 - public Article(String headers, byte[] body)
51.80 - {
51.81 - try
51.82 - {
51.83 - this.body = body;
51.84 -
51.85 - // Parse the header
51.86 - this.headers = new InternetHeaders(
51.87 - new ByteArrayInputStream(headers.getBytes()));
51.88 -
51.89 - this.headerSrc = headers;
51.90 - }
51.91 - catch(MessagingException ex)
51.92 - {
51.93 - ex.printStackTrace();
51.94 - }
51.95 - }
51.96 -
51.97 - /**
51.98 - * Creates an Article instance using the data from the javax.mail.Message
51.99 - * object. This constructor is called by the Mailinglist gateway.
51.100 - * @see javax.mail.Message
51.101 - * @param msg
51.102 - * @throws IOException
51.103 - * @throws MessagingException
51.104 - */
51.105 - public Article(final Message msg)
51.106 - throws IOException, MessagingException
51.107 - {
51.108 - this.headers = new InternetHeaders();
51.109 -
51.110 - for(Enumeration e = msg.getAllHeaders() ; e.hasMoreElements();)
51.111 - {
51.112 - final Header header = (Header)e.nextElement();
51.113 - this.headers.addHeader(header.getName(), header.getValue());
51.114 - }
51.115 -
51.116 - // Reads the raw byte body using Message.writeTo(OutputStream out)
51.117 - this.body = readContent(msg);
51.118 -
51.119 - // Validate headers
51.120 - validateHeaders();
51.121 - }
51.122 -
51.123 - /**
51.124 - * Reads from the given Message into a byte array.
51.125 - * @param in
51.126 - * @return
51.127 - * @throws IOException
51.128 - */
51.129 - private byte[] readContent(Message in)
51.130 - throws IOException, MessagingException
51.131 - {
51.132 - ByteArrayOutputStream out = new ByteArrayOutputStream();
51.133 - in.writeTo(out);
51.134 - return out.toByteArray();
51.135 - }
51.136 -
51.137 - /**
51.138 - * Removes the header identified by the given key.
51.139 - * @param headerKey
51.140 - */
51.141 - public void removeHeader(final String headerKey)
51.142 - {
51.143 - this.headers.removeHeader(headerKey);
51.144 - this.headerSrc = null;
51.145 - }
51.146 -
51.147 - /**
51.148 - * Generates a message id for this article and sets it into
51.149 - * the header object. You have to update the JDBCDatabase manually to make this
51.150 - * change persistent.
51.151 - * Note: a Message-ID should never be changed and only generated once.
51.152 - */
51.153 - private String generateMessageID()
51.154 - {
51.155 - String randomString;
51.156 - MessageDigest md5;
51.157 - try
51.158 - {
51.159 - md5 = MessageDigest.getInstance("MD5");
51.160 - md5.reset();
51.161 - md5.update(getBody());
51.162 - md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
51.163 - md5.update(getHeader(Headers.FROM)[0].getBytes());
51.164 - byte[] result = md5.digest();
51.165 - StringBuffer hexString = new StringBuffer();
51.166 - for (int i = 0; i < result.length; i++)
51.167 - {
51.168 - hexString.append(Integer.toHexString(0xFF & result[i]));
51.169 - }
51.170 - randomString = hexString.toString();
51.171 - }
51.172 - catch (NoSuchAlgorithmException e)
51.173 - {
51.174 - e.printStackTrace();
51.175 - randomString = UUID.randomUUID().toString();
51.176 - }
51.177 - String msgID = "<" + randomString + "@"
51.178 - + Config.inst().get(Config.HOSTNAME, "localhost") + ">";
51.179 -
51.180 - this.headers.setHeader(Headers.MESSAGE_ID, msgID);
51.181 -
51.182 - return msgID;
51.183 - }
51.184 -
51.185 - /**
51.186 - * Returns the body string.
51.187 - */
51.188 - public byte[] getBody()
51.189 - {
51.190 - return body;
51.191 - }
51.192 -
51.193 - /**
51.194 - * @return Numerical IDs of the newsgroups this Article belongs to.
51.195 - */
51.196 - public List<Group> getGroups()
51.197 - {
51.198 - String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
51.199 - ArrayList<Group> groups = new ArrayList<Group>();
51.200 -
51.201 - try
51.202 - {
51.203 - for(String newsgroup : groupnames)
51.204 - {
51.205 - newsgroup = newsgroup.trim();
51.206 - Group group = StorageManager.current().getGroup(newsgroup);
51.207 - if(group != null && // If the server does not provide the group, ignore it
51.208 - !groups.contains(group)) // Yes, there may be duplicates
51.209 - {
51.210 - groups.add(group);
51.211 - }
51.212 - }
51.213 - }
51.214 - catch(StorageBackendException ex)
51.215 - {
51.216 - ex.printStackTrace();
51.217 - return null;
51.218 - }
51.219 - return groups;
51.220 - }
51.221 -
51.222 - public void setBody(byte[] body)
51.223 - {
51.224 - this.body = body;
51.225 - }
51.226 -
51.227 - /**
51.228 - *
51.229 - * @param groupname Name(s) of newsgroups
51.230 - */
51.231 - public void setGroup(String groupname)
51.232 - {
51.233 - this.headers.setHeader(Headers.NEWSGROUPS, groupname);
51.234 - }
51.235 -
51.236 - /**
51.237 - * Returns the Message-ID of this Article. If the appropriate header
51.238 - * is empty, a new Message-ID is created.
51.239 - * @return Message-ID of this Article.
51.240 - */
51.241 - public String getMessageID()
51.242 - {
51.243 - String[] msgID = getHeader(Headers.MESSAGE_ID);
51.244 - return msgID[0].equals("") ? generateMessageID() : msgID[0];
51.245 - }
51.246 -
51.247 - /**
51.248 - * @return String containing the Message-ID.
51.249 - */
51.250 - @Override
51.251 - public String toString()
51.252 - {
51.253 - return getMessageID();
51.254 - }
51.255 -
51.256 -}
52.1 --- a/org/sonews/storage/ArticleHead.java Sun Aug 29 17:04:25 2010 +0200
52.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
52.3 @@ -1,161 +0,0 @@
52.4 -/*
52.5 - * SONEWS News Server
52.6 - * see AUTHORS for the list of contributors
52.7 - *
52.8 - * This program is free software: you can redistribute it and/or modify
52.9 - * it under the terms of the GNU General Public License as published by
52.10 - * the Free Software Foundation, either version 3 of the License, or
52.11 - * (at your option) any later version.
52.12 - *
52.13 - * This program is distributed in the hope that it will be useful,
52.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
52.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
52.16 - * GNU General Public License for more details.
52.17 - *
52.18 - * You should have received a copy of the GNU General Public License
52.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
52.20 - */
52.21 -
52.22 -package org.sonews.storage;
52.23 -
52.24 -import java.io.ByteArrayInputStream;
52.25 -import java.util.Enumeration;
52.26 -import javax.mail.Header;
52.27 -import javax.mail.MessagingException;
52.28 -import javax.mail.internet.InternetHeaders;
52.29 -import javax.mail.internet.MimeUtility;
52.30 -import org.sonews.config.Config;
52.31 -
52.32 -/**
52.33 - * An article with no body only headers.
52.34 - * @author Christian Lins
52.35 - * @since sonews/0.5.0
52.36 - */
52.37 -public class ArticleHead
52.38 -{
52.39 -
52.40 - protected InternetHeaders headers = null;
52.41 - protected String headerSrc = null;
52.42 -
52.43 - protected ArticleHead()
52.44 - {
52.45 - }
52.46 -
52.47 - public ArticleHead(String headers)
52.48 - {
52.49 - try
52.50 - {
52.51 - // Parse the header
52.52 - this.headers = new InternetHeaders(
52.53 - new ByteArrayInputStream(headers.getBytes()));
52.54 - }
52.55 - catch(MessagingException ex)
52.56 - {
52.57 - ex.printStackTrace();
52.58 - }
52.59 - }
52.60 -
52.61 - /**
52.62 - * Returns the header field with given name.
52.63 - * @param name Name of the header field(s).
52.64 - * @param returnNull If set to true, this method will return null instead
52.65 - * of an empty array if there is no header field found.
52.66 - * @return Header values or empty string.
52.67 - */
52.68 - public String[] getHeader(String name, boolean returnNull)
52.69 - {
52.70 - String[] ret = this.headers.getHeader(name);
52.71 - if(ret == null && !returnNull)
52.72 - {
52.73 - ret = new String[]{""};
52.74 - }
52.75 - return ret;
52.76 - }
52.77 -
52.78 - public String[] getHeader(String name)
52.79 - {
52.80 - return getHeader(name, false);
52.81 - }
52.82 -
52.83 - /**
52.84 - * Sets the header value identified through the header name.
52.85 - * @param name
52.86 - * @param value
52.87 - */
52.88 - public void setHeader(String name, String value)
52.89 - {
52.90 - this.headers.setHeader(name, value);
52.91 - this.headerSrc = null;
52.92 - }
52.93 -
52.94 - public Enumeration getAllHeaders()
52.95 - {
52.96 - return this.headers.getAllHeaders();
52.97 - }
52.98 -
52.99 - /**
52.100 - * @return Header source code of this Article.
52.101 - */
52.102 - public String getHeaderSource()
52.103 - {
52.104 - if(this.headerSrc != null)
52.105 - {
52.106 - return this.headerSrc;
52.107 - }
52.108 -
52.109 - StringBuffer buf = new StringBuffer();
52.110 -
52.111 - for(Enumeration en = this.headers.getAllHeaders(); en.hasMoreElements();)
52.112 - {
52.113 - Header entry = (Header)en.nextElement();
52.114 -
52.115 - String value = entry.getValue().replaceAll("[\r\n]", " ");
52.116 - buf.append(entry.getName());
52.117 - buf.append(": ");
52.118 - buf.append(MimeUtility.fold(entry.getName().length() + 2, value));
52.119 -
52.120 - if(en.hasMoreElements())
52.121 - {
52.122 - buf.append("\r\n");
52.123 - }
52.124 - }
52.125 -
52.126 - this.headerSrc = buf.toString();
52.127 - return this.headerSrc;
52.128 - }
52.129 -
52.130 - /**
52.131 - * Sets the headers of this Article. If headers contain no
52.132 - * Message-Id a new one is created.
52.133 - * @param headers
52.134 - */
52.135 - public void setHeaders(InternetHeaders headers)
52.136 - {
52.137 - this.headers = headers;
52.138 - this.headerSrc = null;
52.139 - validateHeaders();
52.140 - }
52.141 -
52.142 - /**
52.143 - * Checks some headers for their validity and generates an
52.144 - * appropriate Path-header for this host if not yet existing.
52.145 - * This method is called by some Article constructors and the
52.146 - * method setHeaders().
52.147 - * @return true if something on the headers was changed.
52.148 - */
52.149 - protected void validateHeaders()
52.150 - {
52.151 - // Check for valid Path-header
52.152 - final String path = getHeader(Headers.PATH)[0];
52.153 - final String host = Config.inst().get(Config.HOSTNAME, "localhost");
52.154 - if(!path.startsWith(host))
52.155 - {
52.156 - StringBuffer pathBuf = new StringBuffer();
52.157 - pathBuf.append(host);
52.158 - pathBuf.append('!');
52.159 - pathBuf.append(path);
52.160 - this.headers.setHeader(Headers.PATH, pathBuf.toString());
52.161 - }
52.162 - }
52.163 -
52.164 -}
53.1 --- a/org/sonews/storage/Channel.java Sun Aug 29 17:04:25 2010 +0200
53.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
53.3 @@ -1,111 +0,0 @@
53.4 -/*
53.5 - * SONEWS News Server
53.6 - * see AUTHORS for the list of contributors
53.7 - *
53.8 - * This program is free software: you can redistribute it and/or modify
53.9 - * it under the terms of the GNU General Public License as published by
53.10 - * the Free Software Foundation, either version 3 of the License, or
53.11 - * (at your option) any later version.
53.12 - *
53.13 - * This program is distributed in the hope that it will be useful,
53.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
53.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
53.16 - * GNU General Public License for more details.
53.17 - *
53.18 - * You should have received a copy of the GNU General Public License
53.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
53.20 - */
53.21 -
53.22 -package org.sonews.storage;
53.23 -
53.24 -import java.util.ArrayList;
53.25 -import java.util.List;
53.26 -import org.sonews.util.Pair;
53.27 -
53.28 -/**
53.29 - * A logical communication Channel is the a generic structural element for sets
53.30 - * of messages; e.g. a Newsgroup for a set of Articles.
53.31 - * A Channel can either be a real set of messages or an aggregated set of
53.32 - * several subsets.
53.33 - * @author Christian Lins
53.34 - * @since sonews/1.0
53.35 - */
53.36 -public abstract class Channel
53.37 -{
53.38 -
53.39 - /**
53.40 - * If this flag is set the Group is no real newsgroup but a mailing list
53.41 - * mirror. In that case every posting and receiving mails must go through
53.42 - * the mailing list gateway.
53.43 - */
53.44 - public static final int MAILINGLIST = 0x1;
53.45 -
53.46 - /**
53.47 - * If this flag is set the Group is marked as readonly and the posting
53.48 - * is prohibited. This can be useful for groups that are synced only in
53.49 - * one direction.
53.50 - */
53.51 - public static final int READONLY = 0x2;
53.52 -
53.53 - /**
53.54 - * If this flag is set the Group is marked as deleted and must not occur
53.55 - * in any output. The deletion is done lazily by a low priority daemon.
53.56 - */
53.57 - public static final int DELETED = 0x80;
53.58 -
53.59 - public static List<Channel> getAll()
53.60 - {
53.61 - List<Channel> all = new ArrayList<Channel>();
53.62 -
53.63 - /*List<Channel> agroups = AggregatedGroup.getAll();
53.64 - if(agroups != null)
53.65 - {
53.66 - all.addAll(agroups);
53.67 - }*/
53.68 -
53.69 - List<Channel> groups = Group.getAll();
53.70 - if(groups != null)
53.71 - {
53.72 - all.addAll(groups);
53.73 - }
53.74 -
53.75 - return all;
53.76 - }
53.77 -
53.78 - public static Channel getByName(String name)
53.79 - throws StorageBackendException
53.80 - {
53.81 - return StorageManager.current().getGroup(name);
53.82 - }
53.83 -
53.84 - public abstract Article getArticle(long idx)
53.85 - throws StorageBackendException;
53.86 -
53.87 - public abstract List<Pair<Long, ArticleHead>> getArticleHeads(
53.88 - final long first, final long last)
53.89 - throws StorageBackendException;
53.90 -
53.91 - public abstract List<Long> getArticleNumbers()
53.92 - throws StorageBackendException;
53.93 -
53.94 - public abstract long getFirstArticleNumber()
53.95 - throws StorageBackendException;
53.96 -
53.97 - public abstract long getIndexOf(Article art)
53.98 - throws StorageBackendException;
53.99 -
53.100 - public abstract long getInternalID();
53.101 -
53.102 - public abstract long getLastArticleNumber()
53.103 - throws StorageBackendException;
53.104 -
53.105 - public abstract String getName();
53.106 -
53.107 - public abstract long getPostingsCount()
53.108 - throws StorageBackendException;
53.109 -
53.110 - public abstract boolean isDeleted();
53.111 -
53.112 - public abstract boolean isWriteable();
53.113 -
53.114 -}
54.1 --- a/org/sonews/storage/Group.java Sun Aug 29 17:04:25 2010 +0200
54.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
54.3 @@ -1,184 +0,0 @@
54.4 -/*
54.5 - * SONEWS News Server
54.6 - * see AUTHORS for the list of contributors
54.7 - *
54.8 - * This program is free software: you can redistribute it and/or modify
54.9 - * it under the terms of the GNU General Public License as published by
54.10 - * the Free Software Foundation, either version 3 of the License, or
54.11 - * (at your option) any later version.
54.12 - *
54.13 - * This program is distributed in the hope that it will be useful,
54.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
54.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
54.16 - * GNU General Public License for more details.
54.17 - *
54.18 - * You should have received a copy of the GNU General Public License
54.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
54.20 - */
54.21 -
54.22 -package org.sonews.storage;
54.23 -
54.24 -import java.sql.SQLException;
54.25 -import java.util.List;
54.26 -import org.sonews.util.Log;
54.27 -import org.sonews.util.Pair;
54.28 -
54.29 -/**
54.30 - * Represents a logical Group within this newsserver.
54.31 - * @author Christian Lins
54.32 - * @since sonews/0.5.0
54.33 - */
54.34 -// TODO: This class should not be public!
54.35 -public class Group extends Channel
54.36 -{
54.37 -
54.38 - private long id = 0;
54.39 - private int flags = -1;
54.40 - private String name = null;
54.41 -
54.42 - /**
54.43 - * @return List of all groups this server handles.
54.44 - */
54.45 - public static List<Channel> getAll()
54.46 - {
54.47 - try
54.48 - {
54.49 - return StorageManager.current().getGroups();
54.50 - }
54.51 - catch(StorageBackendException ex)
54.52 - {
54.53 - Log.get().severe(ex.getMessage());
54.54 - return null;
54.55 - }
54.56 - }
54.57 -
54.58 - /**
54.59 - * @param name
54.60 - * @param id
54.61 - */
54.62 - public Group(final String name, final long id, final int flags)
54.63 - {
54.64 - this.id = id;
54.65 - this.flags = flags;
54.66 - this.name = name;
54.67 - }
54.68 -
54.69 - @Override
54.70 - public boolean equals(Object obj)
54.71 - {
54.72 - if(obj instanceof Group)
54.73 - {
54.74 - return ((Group)obj).id == this.id;
54.75 - }
54.76 - else
54.77 - {
54.78 - return false;
54.79 - }
54.80 - }
54.81 -
54.82 - public Article getArticle(long idx)
54.83 - throws StorageBackendException
54.84 - {
54.85 - return StorageManager.current().getArticle(idx, this.id);
54.86 - }
54.87 -
54.88 - public List<Pair<Long, ArticleHead>> getArticleHeads(final long first, final long last)
54.89 - throws StorageBackendException
54.90 - {
54.91 - return StorageManager.current().getArticleHeads(this, first, last);
54.92 - }
54.93 -
54.94 - public List<Long> getArticleNumbers()
54.95 - throws StorageBackendException
54.96 - {
54.97 - return StorageManager.current().getArticleNumbers(id);
54.98 - }
54.99 -
54.100 - public long getFirstArticleNumber()
54.101 - throws StorageBackendException
54.102 - {
54.103 - return StorageManager.current().getFirstArticleNumber(this);
54.104 - }
54.105 -
54.106 - public int getFlags()
54.107 - {
54.108 - return this.flags;
54.109 - }
54.110 -
54.111 - public long getIndexOf(Article art)
54.112 - throws StorageBackendException
54.113 - {
54.114 - return StorageManager.current().getArticleIndex(art, this);
54.115 - }
54.116 -
54.117 - /**
54.118 - * Returns the group id.
54.119 - */
54.120 - public long getInternalID()
54.121 - {
54.122 - assert id > 0;
54.123 -
54.124 - return id;
54.125 - }
54.126 -
54.127 - public boolean isDeleted()
54.128 - {
54.129 - return (this.flags & DELETED) != 0;
54.130 - }
54.131 -
54.132 - public boolean isMailingList()
54.133 - {
54.134 - return (this.flags & MAILINGLIST) != 0;
54.135 - }
54.136 -
54.137 - public boolean isWriteable()
54.138 - {
54.139 - return true;
54.140 - }
54.141 -
54.142 - public long getLastArticleNumber()
54.143 - throws StorageBackendException
54.144 - {
54.145 - return StorageManager.current().getLastArticleNumber(this);
54.146 - }
54.147 -
54.148 - public String getName()
54.149 - {
54.150 - return name;
54.151 - }
54.152 -
54.153 - /**
54.154 - * Performs this.flags |= flag to set a specified flag and updates the data
54.155 - * in the JDBCDatabase.
54.156 - * @param flag
54.157 - */
54.158 - public void setFlag(final int flag)
54.159 - {
54.160 - this.flags |= flag;
54.161 - }
54.162 -
54.163 - public void setName(final String name)
54.164 - {
54.165 - this.name = name;
54.166 - }
54.167 -
54.168 - /**
54.169 - * @return Number of posted articles in this group.
54.170 - * @throws java.sql.SQLException
54.171 - */
54.172 - public long getPostingsCount()
54.173 - throws StorageBackendException
54.174 - {
54.175 - return StorageManager.current().getPostingsCount(this.name);
54.176 - }
54.177 -
54.178 - /**
54.179 - * Updates flags and name in the backend.
54.180 - */
54.181 - public void update()
54.182 - throws StorageBackendException
54.183 - {
54.184 - StorageManager.current().update(this);
54.185 - }
54.186 -
54.187 -}
55.1 --- a/org/sonews/storage/Headers.java Sun Aug 29 17:04:25 2010 +0200
55.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
55.3 @@ -1,56 +0,0 @@
55.4 -/*
55.5 - * SONEWS News Server
55.6 - * see AUTHORS for the list of contributors
55.7 - *
55.8 - * This program is free software: you can redistribute it and/or modify
55.9 - * it under the terms of the GNU General Public License as published by
55.10 - * the Free Software Foundation, either version 3 of the License, or
55.11 - * (at your option) any later version.
55.12 - *
55.13 - * This program is distributed in the hope that it will be useful,
55.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
55.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
55.16 - * GNU General Public License for more details.
55.17 - *
55.18 - * You should have received a copy of the GNU General Public License
55.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
55.20 - */
55.21 -
55.22 -package org.sonews.storage;
55.23 -
55.24 -/**
55.25 - * Contains header constants. These header keys are no way complete but all
55.26 - * headers that are relevant for sonews.
55.27 - * @author Christian Lins
55.28 - * @since sonews/0.5.0
55.29 - */
55.30 -public final class Headers
55.31 -{
55.32 -
55.33 - public static final String BYTES = "bytes";
55.34 - public static final String CONTENT_TYPE = "content-type";
55.35 - public static final String CONTROL = "control";
55.36 - public static final String DATE = "date";
55.37 - public static final String FROM = "from";
55.38 - public static final String LINES = "lines";
55.39 - public static final String LIST_POST = "list-post";
55.40 - public static final String MESSAGE_ID = "message-id";
55.41 - public static final String NEWSGROUPS = "newsgroups";
55.42 - public static final String NNTP_POSTING_DATE = "nntp-posting-date";
55.43 - public static final String NNTP_POSTING_HOST = "nntp-posting-host";
55.44 - public static final String PATH = "path";
55.45 - public static final String REFERENCES = "references";
55.46 - public static final String REPLY_TO = "reply-to";
55.47 - public static final String SENDER = "sender";
55.48 - public static final String SUBJECT = "subject";
55.49 - public static final String SUPERSEDES = "subersedes";
55.50 - public static final String TO = "to";
55.51 - public static final String X_COMPLAINTS_TO = "x-complaints-to";
55.52 - public static final String X_LIST_POST = "x-list-post";
55.53 - public static final String X_TRACE = "x-trace";
55.54 - public static final String XREF = "xref";
55.55 -
55.56 - private Headers()
55.57 - {}
55.58 -
55.59 -}
56.1 --- a/org/sonews/storage/Storage.java Sun Aug 29 17:04:25 2010 +0200
56.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
56.3 @@ -1,150 +0,0 @@
56.4 -/*
56.5 - * SONEWS News Server
56.6 - * see AUTHORS for the list of contributors
56.7 - *
56.8 - * This program is free software: you can redistribute it and/or modify
56.9 - * it under the terms of the GNU General Public License as published by
56.10 - * the Free Software Foundation, either version 3 of the License, or
56.11 - * (at your option) any later version.
56.12 - *
56.13 - * This program is distributed in the hope that it will be useful,
56.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
56.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
56.16 - * GNU General Public License for more details.
56.17 - *
56.18 - * You should have received a copy of the GNU General Public License
56.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
56.20 - */
56.21 -
56.22 -package org.sonews.storage;
56.23 -
56.24 -import java.util.List;
56.25 -import org.sonews.feed.Subscription;
56.26 -import org.sonews.util.Pair;
56.27 -
56.28 -/**
56.29 - * A generic storage backend interface.
56.30 - * @author Christian Lins
56.31 - * @since sonews/1.0
56.32 - */
56.33 -public interface Storage
56.34 -{
56.35 -
56.36 - /**
56.37 - * Stores the given Article in the storage.
56.38 - * @param art
56.39 - * @throws StorageBackendException
56.40 - */
56.41 - void addArticle(Article art)
56.42 - throws StorageBackendException;
56.43 -
56.44 - void addEvent(long timestamp, int type, long groupID)
56.45 - throws StorageBackendException;
56.46 -
56.47 - void addGroup(String groupname, int flags)
56.48 - throws StorageBackendException;
56.49 -
56.50 - int countArticles()
56.51 - throws StorageBackendException;
56.52 -
56.53 - int countGroups()
56.54 - throws StorageBackendException;
56.55 -
56.56 - void delete(String messageID)
56.57 - throws StorageBackendException;
56.58 -
56.59 - Article getArticle(String messageID)
56.60 - throws StorageBackendException;
56.61 -
56.62 - Article getArticle(long articleIndex, long groupID)
56.63 - throws StorageBackendException;
56.64 -
56.65 - List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first, long last)
56.66 - throws StorageBackendException;
56.67 -
56.68 - List<Pair<Long, String>> getArticleHeaders(Channel channel, long start, long end,
56.69 - String header, String pattern)
56.70 - throws StorageBackendException;
56.71 -
56.72 - long getArticleIndex(Article art, Group group)
56.73 - throws StorageBackendException;
56.74 -
56.75 - List<Long> getArticleNumbers(long groupID)
56.76 - throws StorageBackendException;
56.77 -
56.78 - String getConfigValue(String key)
56.79 - throws StorageBackendException;
56.80 -
56.81 - int getEventsCount(int eventType, long startTimestamp, long endTimestamp,
56.82 - Channel channel)
56.83 - throws StorageBackendException;
56.84 -
56.85 - double getEventsPerHour(int key, long gid)
56.86 - throws StorageBackendException;
56.87 -
56.88 - int getFirstArticleNumber(Group group)
56.89 - throws StorageBackendException;
56.90 -
56.91 - Group getGroup(String name)
56.92 - throws StorageBackendException;
56.93 -
56.94 - List<Channel> getGroups()
56.95 - throws StorageBackendException;
56.96 -
56.97 - /**
56.98 - * Retrieves the collection of groupnames that are associated with the
56.99 - * given list address.
56.100 - * @param inetaddress
56.101 - * @return
56.102 - * @throws StorageBackendException
56.103 - */
56.104 - List<String> getGroupsForList(String listAddress)
56.105 - throws StorageBackendException;
56.106 -
56.107 - int getLastArticleNumber(Group group)
56.108 - throws StorageBackendException;
56.109 -
56.110 - /**
56.111 - * Returns a list of email addresses that are related to the given
56.112 - * groupname. In most cases the list may contain only one entry.
56.113 - * @param groupname
56.114 - * @return
56.115 - * @throws StorageBackendException
56.116 - */
56.117 - List<String> getListsForGroup(String groupname)
56.118 - throws StorageBackendException;
56.119 -
56.120 - String getOldestArticle()
56.121 - throws StorageBackendException;
56.122 -
56.123 - int getPostingsCount(String groupname)
56.124 - throws StorageBackendException;
56.125 -
56.126 - List<Subscription> getSubscriptions(int type)
56.127 - throws StorageBackendException;
56.128 -
56.129 - boolean isArticleExisting(String messageID)
56.130 - throws StorageBackendException;
56.131 -
56.132 - boolean isGroupExisting(String groupname)
56.133 - throws StorageBackendException;
56.134 -
56.135 - void purgeGroup(Group group)
56.136 - throws StorageBackendException;
56.137 -
56.138 - void setConfigValue(String key, String value)
56.139 - throws StorageBackendException;
56.140 -
56.141 - /**
56.142 - * Updates headers and channel references of the given article.
56.143 - * @param article
56.144 - * @return
56.145 - * @throws StorageBackendException
56.146 - */
56.147 - boolean update(Article article)
56.148 - throws StorageBackendException;
56.149 -
56.150 - boolean update(Group group)
56.151 - throws StorageBackendException;
56.152 -
56.153 -}
57.1 --- a/org/sonews/storage/StorageBackendException.java Sun Aug 29 17:04:25 2010 +0200
57.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
57.3 @@ -1,39 +0,0 @@
57.4 -/*
57.5 - * SONEWS News Server
57.6 - * see AUTHORS for the list of contributors
57.7 - *
57.8 - * This program is free software: you can redistribute it and/or modify
57.9 - * it under the terms of the GNU General Public License as published by
57.10 - * the Free Software Foundation, either version 3 of the License, or
57.11 - * (at your option) any later version.
57.12 - *
57.13 - * This program is distributed in the hope that it will be useful,
57.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
57.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
57.16 - * GNU General Public License for more details.
57.17 - *
57.18 - * You should have received a copy of the GNU General Public License
57.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
57.20 - */
57.21 -
57.22 -package org.sonews.storage;
57.23 -
57.24 -/**
57.25 - *
57.26 - * @author Christian Lins
57.27 - * @since sonews/1.0
57.28 - */
57.29 -public class StorageBackendException extends Exception
57.30 -{
57.31 -
57.32 - public StorageBackendException(Throwable cause)
57.33 - {
57.34 - super(cause);
57.35 - }
57.36 -
57.37 - public StorageBackendException(String msg)
57.38 - {
57.39 - super(msg);
57.40 - }
57.41 -
57.42 -}
58.1 --- a/org/sonews/storage/StorageManager.java Sun Aug 29 17:04:25 2010 +0200
58.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
58.3 @@ -1,89 +0,0 @@
58.4 -/*
58.5 - * SONEWS News Server
58.6 - * see AUTHORS for the list of contributors
58.7 - *
58.8 - * This program is free software: you can redistribute it and/or modify
58.9 - * it under the terms of the GNU General Public License as published by
58.10 - * the Free Software Foundation, either version 3 of the License, or
58.11 - * (at your option) any later version.
58.12 - *
58.13 - * This program is distributed in the hope that it will be useful,
58.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
58.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
58.16 - * GNU General Public License for more details.
58.17 - *
58.18 - * You should have received a copy of the GNU General Public License
58.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
58.20 - */
58.21 -
58.22 -package org.sonews.storage;
58.23 -
58.24 -/**
58.25 - *
58.26 - * @author Christian Lins
58.27 - * @since sonews/1.0
58.28 - */
58.29 -public final class StorageManager
58.30 -{
58.31 -
58.32 - private static StorageProvider provider;
58.33 -
58.34 - public static Storage current()
58.35 - throws StorageBackendException
58.36 - {
58.37 - synchronized(StorageManager.class)
58.38 - {
58.39 - if(provider == null)
58.40 - {
58.41 - return null;
58.42 - }
58.43 - else
58.44 - {
58.45 - return provider.storage(Thread.currentThread());
58.46 - }
58.47 - }
58.48 - }
58.49 -
58.50 - public static StorageProvider loadProvider(String pluginClassName)
58.51 - {
58.52 - try
58.53 - {
58.54 - Class<?> clazz = Class.forName(pluginClassName);
58.55 - Object inst = clazz.newInstance();
58.56 - return (StorageProvider)inst;
58.57 - }
58.58 - catch(Exception ex)
58.59 - {
58.60 - System.err.println(ex);
58.61 - return null;
58.62 - }
58.63 - }
58.64 -
58.65 - /**
58.66 - * Sets the current storage provider.
58.67 - * @param provider
58.68 - */
58.69 - public static void enableProvider(StorageProvider provider)
58.70 - {
58.71 - synchronized(StorageManager.class)
58.72 - {
58.73 - if(StorageManager.provider != null)
58.74 - {
58.75 - disableProvider();
58.76 - }
58.77 - StorageManager.provider = provider;
58.78 - }
58.79 - }
58.80 -
58.81 - /**
58.82 - * Disables the current provider.
58.83 - */
58.84 - public static void disableProvider()
58.85 - {
58.86 - synchronized(StorageManager.class)
58.87 - {
58.88 - provider = null;
58.89 - }
58.90 - }
58.91 -
58.92 -}
59.1 --- a/org/sonews/storage/StorageProvider.java Sun Aug 29 17:04:25 2010 +0200
59.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
59.3 @@ -1,40 +0,0 @@
59.4 -/*
59.5 - * SONEWS News Server
59.6 - * see AUTHORS for the list of contributors
59.7 - *
59.8 - * This program is free software: you can redistribute it and/or modify
59.9 - * it under the terms of the GNU General Public License as published by
59.10 - * the Free Software Foundation, either version 3 of the License, or
59.11 - * (at your option) any later version.
59.12 - *
59.13 - * This program is distributed in the hope that it will be useful,
59.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
59.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
59.16 - * GNU General Public License for more details.
59.17 - *
59.18 - * You should have received a copy of the GNU General Public License
59.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
59.20 - */
59.21 -
59.22 -package org.sonews.storage;
59.23 -
59.24 -/**
59.25 - * Provides access to storage backend instances.
59.26 - * @author Christian Lins
59.27 - * @since sonews/1.0
59.28 - */
59.29 -public interface StorageProvider
59.30 -{
59.31 -
59.32 - public boolean isSupported(String uri);
59.33 -
59.34 - /**
59.35 - * This method returns the reference to the associated storage.
59.36 - * The reference MAY be unique for each thread. In any case it MUST be
59.37 - * thread-safe to use this method.
59.38 - * @return The reference to the associated Storage.
59.39 - */
59.40 - public Storage storage(Thread thread)
59.41 - throws StorageBackendException;
59.42 -
59.43 -}
60.1 --- a/org/sonews/storage/impl/JDBCDatabase.java Sun Aug 29 17:04:25 2010 +0200
60.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
60.3 @@ -1,1782 +0,0 @@
60.4 -/*
60.5 - * SONEWS News Server
60.6 - * see AUTHORS for the list of contributors
60.7 - *
60.8 - * This program is free software: you can redistribute it and/or modify
60.9 - * it under the terms of the GNU General Public License as published by
60.10 - * the Free Software Foundation, either version 3 of the License, or
60.11 - * (at your option) any later version.
60.12 - *
60.13 - * This program is distributed in the hope that it will be useful,
60.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
60.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
60.16 - * GNU General Public License for more details.
60.17 - *
60.18 - * You should have received a copy of the GNU General Public License
60.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
60.20 - */
60.21 -
60.22 -package org.sonews.storage.impl;
60.23 -
60.24 -import java.sql.Connection;
60.25 -import java.sql.DriverManager;
60.26 -import java.sql.ResultSet;
60.27 -import java.sql.SQLException;
60.28 -import java.sql.Statement;
60.29 -import java.sql.PreparedStatement;
60.30 -import java.util.ArrayList;
60.31 -import java.util.Enumeration;
60.32 -import java.util.List;
60.33 -import java.util.regex.Matcher;
60.34 -import java.util.regex.Pattern;
60.35 -import java.util.regex.PatternSyntaxException;
60.36 -import javax.mail.Header;
60.37 -import javax.mail.internet.MimeUtility;
60.38 -import org.sonews.config.Config;
60.39 -import org.sonews.util.Log;
60.40 -import org.sonews.feed.Subscription;
60.41 -import org.sonews.storage.Article;
60.42 -import org.sonews.storage.ArticleHead;
60.43 -import org.sonews.storage.Channel;
60.44 -import org.sonews.storage.Group;
60.45 -import org.sonews.storage.Storage;
60.46 -import org.sonews.storage.StorageBackendException;
60.47 -import org.sonews.util.Pair;
60.48 -
60.49 -/**
60.50 - * JDBCDatabase facade class.
60.51 - * @author Christian Lins
60.52 - * @since sonews/0.5.0
60.53 - */
60.54 -// TODO: Refactor this class to reduce size (e.g. ArticleDatabase GroupDatabase)
60.55 -public class JDBCDatabase implements Storage
60.56 -{
60.57 -
60.58 - public static final int MAX_RESTARTS = 2;
60.59 -
60.60 - private Connection conn = null;
60.61 - private PreparedStatement pstmtAddArticle1 = null;
60.62 - private PreparedStatement pstmtAddArticle2 = null;
60.63 - private PreparedStatement pstmtAddArticle3 = null;
60.64 - private PreparedStatement pstmtAddArticle4 = null;
60.65 - private PreparedStatement pstmtAddGroup0 = null;
60.66 - private PreparedStatement pstmtAddEvent = null;
60.67 - private PreparedStatement pstmtCountArticles = null;
60.68 - private PreparedStatement pstmtCountGroups = null;
60.69 - private PreparedStatement pstmtDeleteArticle0 = null;
60.70 - private PreparedStatement pstmtDeleteArticle1 = null;
60.71 - private PreparedStatement pstmtDeleteArticle2 = null;
60.72 - private PreparedStatement pstmtDeleteArticle3 = null;
60.73 - private PreparedStatement pstmtGetArticle0 = null;
60.74 - private PreparedStatement pstmtGetArticle1 = null;
60.75 - private PreparedStatement pstmtGetArticleHeaders0 = null;
60.76 - private PreparedStatement pstmtGetArticleHeaders1 = null;
60.77 - private PreparedStatement pstmtGetArticleHeads = null;
60.78 - private PreparedStatement pstmtGetArticleIDs = null;
60.79 - private PreparedStatement pstmtGetArticleIndex = null;
60.80 - private PreparedStatement pstmtGetConfigValue = null;
60.81 - private PreparedStatement pstmtGetEventsCount0 = null;
60.82 - private PreparedStatement pstmtGetEventsCount1 = null;
60.83 - private PreparedStatement pstmtGetGroupForList = null;
60.84 - private PreparedStatement pstmtGetGroup0 = null;
60.85 - private PreparedStatement pstmtGetGroup1 = null;
60.86 - private PreparedStatement pstmtGetFirstArticleNumber = null;
60.87 - private PreparedStatement pstmtGetListForGroup = null;
60.88 - private PreparedStatement pstmtGetLastArticleNumber = null;
60.89 - private PreparedStatement pstmtGetMaxArticleID = null;
60.90 - private PreparedStatement pstmtGetMaxArticleIndex = null;
60.91 - private PreparedStatement pstmtGetOldestArticle = null;
60.92 - private PreparedStatement pstmtGetPostingsCount = null;
60.93 - private PreparedStatement pstmtGetSubscriptions = null;
60.94 - private PreparedStatement pstmtIsArticleExisting = null;
60.95 - private PreparedStatement pstmtIsGroupExisting = null;
60.96 - private PreparedStatement pstmtPurgeGroup0 = null;
60.97 - private PreparedStatement pstmtPurgeGroup1 = null;
60.98 - private PreparedStatement pstmtSetConfigValue0 = null;
60.99 - private PreparedStatement pstmtSetConfigValue1 = null;
60.100 - private PreparedStatement pstmtUpdateGroup = null;
60.101 -
60.102 - /** How many times the database connection was reinitialized */
60.103 - private int restarts = 0;
60.104 -
60.105 - /**
60.106 - * Rises the database: reconnect and recreate all prepared statements.
60.107 - * @throws java.lang.SQLException
60.108 - */
60.109 - protected void arise()
60.110 - throws SQLException
60.111 - {
60.112 - try
60.113 - {
60.114 - // Load database driver
60.115 - Class.forName(
60.116 - Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DBMSDRIVER, "java.lang.Object"));
60.117 -
60.118 - // Establish database connection
60.119 - this.conn = DriverManager.getConnection(
60.120 - Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DATABASE, "<not specified>"),
60.121 - Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_USER, "root"),
60.122 - Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_PASSWORD, ""));
60.123 -
60.124 - this.conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
60.125 - if(this.conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE)
60.126 - {
60.127 - Log.get().warning("Database is NOT fully serializable!");
60.128 - }
60.129 -
60.130 - // Prepare statements for method addArticle()
60.131 - this.pstmtAddArticle1 = conn.prepareStatement(
60.132 - "INSERT INTO articles (article_id, body) VALUES(?, ?)");
60.133 - this.pstmtAddArticle2 = conn.prepareStatement(
60.134 - "INSERT INTO headers (article_id, header_key, header_value, header_index) " +
60.135 - "VALUES (?, ?, ?, ?)");
60.136 - this.pstmtAddArticle3 = conn.prepareStatement(
60.137 - "INSERT INTO postings (group_id, article_id, article_index)" +
60.138 - "VALUES (?, ?, ?)");
60.139 - this.pstmtAddArticle4 = conn.prepareStatement(
60.140 - "INSERT INTO article_ids (article_id, message_id) VALUES (?, ?)");
60.141 -
60.142 - // Prepare statement for method addStatValue()
60.143 - this.pstmtAddEvent = conn.prepareStatement(
60.144 - "INSERT INTO events VALUES (?, ?, ?)");
60.145 -
60.146 - // Prepare statement for method addGroup()
60.147 - this.pstmtAddGroup0 = conn.prepareStatement(
60.148 - "INSERT INTO groups (name, flags) VALUES (?, ?)");
60.149 -
60.150 - // Prepare statement for method countArticles()
60.151 - this.pstmtCountArticles = conn.prepareStatement(
60.152 - "SELECT Count(article_id) FROM article_ids");
60.153 -
60.154 - // Prepare statement for method countGroups()
60.155 - this.pstmtCountGroups = conn.prepareStatement(
60.156 - "SELECT Count(group_id) FROM groups WHERE " +
60.157 - "flags & " + Channel.DELETED + " = 0");
60.158 -
60.159 - // Prepare statements for method delete(article)
60.160 - this.pstmtDeleteArticle0 = conn.prepareStatement(
60.161 - "DELETE FROM articles WHERE article_id = " +
60.162 - "(SELECT article_id FROM article_ids WHERE message_id = ?)");
60.163 - this.pstmtDeleteArticle1 = conn.prepareStatement(
60.164 - "DELETE FROM headers WHERE article_id = " +
60.165 - "(SELECT article_id FROM article_ids WHERE message_id = ?)");
60.166 - this.pstmtDeleteArticle2 = conn.prepareStatement(
60.167 - "DELETE FROM postings WHERE article_id = " +
60.168 - "(SELECT article_id FROM article_ids WHERE message_id = ?)");
60.169 - this.pstmtDeleteArticle3 = conn.prepareStatement(
60.170 - "DELETE FROM article_ids WHERE message_id = ?");
60.171 -
60.172 - // Prepare statements for methods getArticle()
60.173 - this.pstmtGetArticle0 = conn.prepareStatement(
60.174 - "SELECT * FROM articles WHERE article_id = " +
60.175 - "(SELECT article_id FROM article_ids WHERE message_id = ?)");
60.176 - this.pstmtGetArticle1 = conn.prepareStatement(
60.177 - "SELECT * FROM articles WHERE article_id = " +
60.178 - "(SELECT article_id FROM postings WHERE " +
60.179 - "article_index = ? AND group_id = ?)");
60.180 -
60.181 - // Prepare statement for method getArticleHeaders()
60.182 - this.pstmtGetArticleHeaders0 = conn.prepareStatement(
60.183 - "SELECT header_key, header_value FROM headers WHERE article_id = ? " +
60.184 - "ORDER BY header_index ASC");
60.185 -
60.186 - // Prepare statement for method getArticleHeaders(regular expr pattern)
60.187 - this.pstmtGetArticleHeaders1 = conn.prepareStatement(
60.188 - "SELECT p.article_index, h.header_value FROM headers h " +
60.189 - "INNER JOIN postings p ON h.article_id = p.article_id " +
60.190 - "INNER JOIN groups g ON p.group_id = g.group_id " +
60.191 - "WHERE g.name = ? AND " +
60.192 - "h.header_key = ? AND " +
60.193 - "p.article_index >= ? " +
60.194 - "ORDER BY p.article_index ASC");
60.195 -
60.196 - this.pstmtGetArticleIDs = conn.prepareStatement(
60.197 - "SELECT article_index FROM postings WHERE group_id = ?");
60.198 -
60.199 - // Prepare statement for method getArticleIndex
60.200 - this.pstmtGetArticleIndex = conn.prepareStatement(
60.201 - "SELECT article_index FROM postings WHERE " +
60.202 - "article_id = (SELECT article_id FROM article_ids " +
60.203 - "WHERE message_id = ?) " +
60.204 - " AND group_id = ?");
60.205 -
60.206 - // Prepare statements for method getArticleHeads()
60.207 - this.pstmtGetArticleHeads = conn.prepareStatement(
60.208 - "SELECT article_id, article_index FROM postings WHERE " +
60.209 - "postings.group_id = ? AND article_index >= ? AND " +
60.210 - "article_index <= ?");
60.211 -
60.212 - // Prepare statements for method getConfigValue()
60.213 - this.pstmtGetConfigValue = conn.prepareStatement(
60.214 - "SELECT config_value FROM config WHERE config_key = ?");
60.215 -
60.216 - // Prepare statements for method getEventsCount()
60.217 - this.pstmtGetEventsCount0 = conn.prepareStatement(
60.218 - "SELECT Count(*) FROM events WHERE event_key = ? AND " +
60.219 - "event_time >= ? AND event_time < ?");
60.220 -
60.221 - this.pstmtGetEventsCount1 = conn.prepareStatement(
60.222 - "SELECT Count(*) FROM events WHERE event_key = ? AND " +
60.223 - "event_time >= ? AND event_time < ? AND group_id = ?");
60.224 -
60.225 - // Prepare statement for method getGroupForList()
60.226 - this.pstmtGetGroupForList = conn.prepareStatement(
60.227 - "SELECT name FROM groups INNER JOIN groups2list " +
60.228 - "ON groups.group_id = groups2list.group_id " +
60.229 - "WHERE groups2list.listaddress = ?");
60.230 -
60.231 - // Prepare statement for method getGroup()
60.232 - this.pstmtGetGroup0 = conn.prepareStatement(
60.233 - "SELECT group_id, flags FROM groups WHERE Name = ?");
60.234 - this.pstmtGetGroup1 = conn.prepareStatement(
60.235 - "SELECT name FROM groups WHERE group_id = ?");
60.236 -
60.237 - // Prepare statement for method getLastArticleNumber()
60.238 - this.pstmtGetLastArticleNumber = conn.prepareStatement(
60.239 - "SELECT Max(article_index) FROM postings WHERE group_id = ?");
60.240 -
60.241 - // Prepare statement for method getListForGroup()
60.242 - this.pstmtGetListForGroup = conn.prepareStatement(
60.243 - "SELECT listaddress FROM groups2list INNER JOIN groups " +
60.244 - "ON groups.group_id = groups2list.group_id WHERE name = ?");
60.245 -
60.246 - // Prepare statement for method getMaxArticleID()
60.247 - this.pstmtGetMaxArticleID = conn.prepareStatement(
60.248 - "SELECT Max(article_id) FROM articles");
60.249 -
60.250 - // Prepare statement for method getMaxArticleIndex()
60.251 - this.pstmtGetMaxArticleIndex = conn.prepareStatement(
60.252 - "SELECT Max(article_index) FROM postings WHERE group_id = ?");
60.253 -
60.254 - // Prepare statement for method getOldestArticle()
60.255 - this.pstmtGetOldestArticle = conn.prepareStatement(
60.256 - "SELECT message_id FROM article_ids WHERE article_id = " +
60.257 - "(SELECT Min(article_id) FROM article_ids)");
60.258 -
60.259 - // Prepare statement for method getFirstArticleNumber()
60.260 - this.pstmtGetFirstArticleNumber = conn.prepareStatement(
60.261 - "SELECT Min(article_index) FROM postings WHERE group_id = ?");
60.262 -
60.263 - // Prepare statement for method getPostingsCount()
60.264 - this.pstmtGetPostingsCount = conn.prepareStatement(
60.265 - "SELECT Count(*) FROM postings NATURAL JOIN groups " +
60.266 - "WHERE groups.name = ?");
60.267 -
60.268 - // Prepare statement for method getSubscriptions()
60.269 - this.pstmtGetSubscriptions = conn.prepareStatement(
60.270 - "SELECT host, port, name FROM peers NATURAL JOIN " +
60.271 - "peer_subscriptions NATURAL JOIN groups WHERE feedtype = ?");
60.272 -
60.273 - // Prepare statement for method isArticleExisting()
60.274 - this.pstmtIsArticleExisting = conn.prepareStatement(
60.275 - "SELECT Count(article_id) FROM article_ids WHERE message_id = ?");
60.276 -
60.277 - // Prepare statement for method isGroupExisting()
60.278 - this.pstmtIsGroupExisting = conn.prepareStatement(
60.279 - "SELECT * FROM groups WHERE name = ?");
60.280 -
60.281 - // Prepare statement for method setConfigValue()
60.282 - this.pstmtSetConfigValue0 = conn.prepareStatement(
60.283 - "DELETE FROM config WHERE config_key = ?");
60.284 - this.pstmtSetConfigValue1 = conn.prepareStatement(
60.285 - "INSERT INTO config VALUES(?, ?)");
60.286 -
60.287 - // Prepare statements for method purgeGroup()
60.288 - this.pstmtPurgeGroup0 = conn.prepareStatement(
60.289 - "DELETE FROM peer_subscriptions WHERE group_id = ?");
60.290 - this.pstmtPurgeGroup1 = conn.prepareStatement(
60.291 - "DELETE FROM groups WHERE group_id = ?");
60.292 -
60.293 - // Prepare statement for method update(Group)
60.294 - this.pstmtUpdateGroup = conn.prepareStatement(
60.295 - "UPDATE groups SET flags = ?, name = ? WHERE group_id = ?");
60.296 - }
60.297 - catch(ClassNotFoundException ex)
60.298 - {
60.299 - throw new Error("JDBC Driver not found!", ex);
60.300 - }
60.301 - }
60.302 -
60.303 - /**
60.304 - * Adds an article to the database.
60.305 - * @param article
60.306 - * @return
60.307 - * @throws java.sql.SQLException
60.308 - */
60.309 - @Override
60.310 - public void addArticle(final Article article)
60.311 - throws StorageBackendException
60.312 - {
60.313 - try
60.314 - {
60.315 - this.conn.setAutoCommit(false);
60.316 -
60.317 - int newArticleID = getMaxArticleID() + 1;
60.318 -
60.319 - // Fill prepared statement with values;
60.320 - // writes body to article table
60.321 - pstmtAddArticle1.setInt(1, newArticleID);
60.322 - pstmtAddArticle1.setBytes(2, article.getBody());
60.323 - pstmtAddArticle1.execute();
60.324 -
60.325 - // Add headers
60.326 - Enumeration headers = article.getAllHeaders();
60.327 - for(int n = 0; headers.hasMoreElements(); n++)
60.328 - {
60.329 - Header header = (Header)headers.nextElement();
60.330 - pstmtAddArticle2.setInt(1, newArticleID);
60.331 - pstmtAddArticle2.setString(2, header.getName().toLowerCase());
60.332 - pstmtAddArticle2.setString(3,
60.333 - header.getValue().replaceAll("[\r\n]", ""));
60.334 - pstmtAddArticle2.setInt(4, n);
60.335 - pstmtAddArticle2.execute();
60.336 - }
60.337 -
60.338 - // For each newsgroup add a reference
60.339 - List<Group> groups = article.getGroups();
60.340 - for(Group group : groups)
60.341 - {
60.342 - pstmtAddArticle3.setLong(1, group.getInternalID());
60.343 - pstmtAddArticle3.setInt(2, newArticleID);
60.344 - pstmtAddArticle3.setLong(3, getMaxArticleIndex(group.getInternalID()) + 1);
60.345 - pstmtAddArticle3.execute();
60.346 - }
60.347 -
60.348 - // Write message-id to article_ids table
60.349 - this.pstmtAddArticle4.setInt(1, newArticleID);
60.350 - this.pstmtAddArticle4.setString(2, article.getMessageID());
60.351 - this.pstmtAddArticle4.execute();
60.352 -
60.353 - this.conn.commit();
60.354 - this.conn.setAutoCommit(true);
60.355 -
60.356 - this.restarts = 0; // Reset error count
60.357 - }
60.358 - catch(SQLException ex)
60.359 - {
60.360 - try
60.361 - {
60.362 - this.conn.rollback(); // Rollback changes
60.363 - }
60.364 - catch(SQLException ex2)
60.365 - {
60.366 - Log.get().severe("Rollback of addArticle() failed: " + ex2);
60.367 - }
60.368 -
60.369 - try
60.370 - {
60.371 - this.conn.setAutoCommit(true); // and release locks
60.372 - }
60.373 - catch(SQLException ex2)
60.374 - {
60.375 - Log.get().severe("setAutoCommit(true) of addArticle() failed: " + ex2);
60.376 - }
60.377 -
60.378 - restartConnection(ex);
60.379 - addArticle(article);
60.380 - }
60.381 - }
60.382 -
60.383 - /**
60.384 - * Adds a group to the JDBCDatabase. This method is not accessible via NNTP.
60.385 - * @param name
60.386 - * @throws java.sql.SQLException
60.387 - */
60.388 - @Override
60.389 - public void addGroup(String name, int flags)
60.390 - throws StorageBackendException
60.391 - {
60.392 - try
60.393 - {
60.394 - this.conn.setAutoCommit(false);
60.395 - pstmtAddGroup0.setString(1, name);
60.396 - pstmtAddGroup0.setInt(2, flags);
60.397 -
60.398 - pstmtAddGroup0.executeUpdate();
60.399 - this.conn.commit();
60.400 - this.conn.setAutoCommit(true);
60.401 - this.restarts = 0; // Reset error count
60.402 - }
60.403 - catch(SQLException ex)
60.404 - {
60.405 - try
60.406 - {
60.407 - this.conn.rollback();
60.408 - this.conn.setAutoCommit(true);
60.409 - }
60.410 - catch(SQLException ex2)
60.411 - {
60.412 - ex2.printStackTrace();
60.413 - }
60.414 -
60.415 - restartConnection(ex);
60.416 - addGroup(name, flags);
60.417 - }
60.418 - }
60.419 -
60.420 - @Override
60.421 - public void addEvent(long time, int type, long gid)
60.422 - throws StorageBackendException
60.423 - {
60.424 - try
60.425 - {
60.426 - this.conn.setAutoCommit(false);
60.427 - this.pstmtAddEvent.setLong(1, time);
60.428 - this.pstmtAddEvent.setInt(2, type);
60.429 - this.pstmtAddEvent.setLong(3, gid);
60.430 - this.pstmtAddEvent.executeUpdate();
60.431 - this.conn.commit();
60.432 - this.conn.setAutoCommit(true);
60.433 - this.restarts = 0;
60.434 - }
60.435 - catch(SQLException ex)
60.436 - {
60.437 - try
60.438 - {
60.439 - this.conn.rollback();
60.440 - this.conn.setAutoCommit(true);
60.441 - }
60.442 - catch(SQLException ex2)
60.443 - {
60.444 - ex2.printStackTrace();
60.445 - }
60.446 -
60.447 - restartConnection(ex);
60.448 - addEvent(time, type, gid);
60.449 - }
60.450 - }
60.451 -
60.452 - @Override
60.453 - public int countArticles()
60.454 - throws StorageBackendException
60.455 - {
60.456 - ResultSet rs = null;
60.457 -
60.458 - try
60.459 - {
60.460 - rs = this.pstmtCountArticles.executeQuery();
60.461 - if(rs.next())
60.462 - {
60.463 - return rs.getInt(1);
60.464 - }
60.465 - else
60.466 - {
60.467 - return -1;
60.468 - }
60.469 - }
60.470 - catch(SQLException ex)
60.471 - {
60.472 - restartConnection(ex);
60.473 - return countArticles();
60.474 - }
60.475 - finally
60.476 - {
60.477 - if(rs != null)
60.478 - {
60.479 - try
60.480 - {
60.481 - rs.close();
60.482 - }
60.483 - catch(SQLException ex)
60.484 - {
60.485 - ex.printStackTrace();
60.486 - }
60.487 - restarts = 0;
60.488 - }
60.489 - }
60.490 - }
60.491 -
60.492 - @Override
60.493 - public int countGroups()
60.494 - throws StorageBackendException
60.495 - {
60.496 - ResultSet rs = null;
60.497 -
60.498 - try
60.499 - {
60.500 - rs = this.pstmtCountGroups.executeQuery();
60.501 - if(rs.next())
60.502 - {
60.503 - return rs.getInt(1);
60.504 - }
60.505 - else
60.506 - {
60.507 - return -1;
60.508 - }
60.509 - }
60.510 - catch(SQLException ex)
60.511 - {
60.512 - restartConnection(ex);
60.513 - return countGroups();
60.514 - }
60.515 - finally
60.516 - {
60.517 - if(rs != null)
60.518 - {
60.519 - try
60.520 - {
60.521 - rs.close();
60.522 - }
60.523 - catch(SQLException ex)
60.524 - {
60.525 - ex.printStackTrace();
60.526 - }
60.527 - restarts = 0;
60.528 - }
60.529 - }
60.530 - }
60.531 -
60.532 - @Override
60.533 - public void delete(final String messageID)
60.534 - throws StorageBackendException
60.535 - {
60.536 - try
60.537 - {
60.538 - this.conn.setAutoCommit(false);
60.539 -
60.540 - this.pstmtDeleteArticle0.setString(1, messageID);
60.541 - int rs = this.pstmtDeleteArticle0.executeUpdate();
60.542 -
60.543 - // We do not trust the ON DELETE CASCADE functionality to delete
60.544 - // orphaned references...
60.545 - this.pstmtDeleteArticle1.setString(1, messageID);
60.546 - rs = this.pstmtDeleteArticle1.executeUpdate();
60.547 -
60.548 - this.pstmtDeleteArticle2.setString(1, messageID);
60.549 - rs = this.pstmtDeleteArticle2.executeUpdate();
60.550 -
60.551 - this.pstmtDeleteArticle3.setString(1, messageID);
60.552 - rs = this.pstmtDeleteArticle3.executeUpdate();
60.553 -
60.554 - this.conn.commit();
60.555 - this.conn.setAutoCommit(true);
60.556 - }
60.557 - catch(SQLException ex)
60.558 - {
60.559 - throw new StorageBackendException(ex);
60.560 - }
60.561 - }
60.562 -
60.563 - @Override
60.564 - public Article getArticle(String messageID)
60.565 - throws StorageBackendException
60.566 - {
60.567 - ResultSet rs = null;
60.568 - try
60.569 - {
60.570 - pstmtGetArticle0.setString(1, messageID);
60.571 - rs = pstmtGetArticle0.executeQuery();
60.572 -
60.573 - if(!rs.next())
60.574 - {
60.575 - return null;
60.576 - }
60.577 - else
60.578 - {
60.579 - byte[] body = rs.getBytes("body");
60.580 - String headers = getArticleHeaders(rs.getInt("article_id"));
60.581 - return new Article(headers, body);
60.582 - }
60.583 - }
60.584 - catch(SQLException ex)
60.585 - {
60.586 - restartConnection(ex);
60.587 - return getArticle(messageID);
60.588 - }
60.589 - finally
60.590 - {
60.591 - if(rs != null)
60.592 - {
60.593 - try
60.594 - {
60.595 - rs.close();
60.596 - }
60.597 - catch(SQLException ex)
60.598 - {
60.599 - ex.printStackTrace();
60.600 - }
60.601 - restarts = 0; // Reset error count
60.602 - }
60.603 - }
60.604 - }
60.605 -
60.606 - /**
60.607 - * Retrieves an article by its ID.
60.608 - * @param articleID
60.609 - * @return
60.610 - * @throws StorageBackendException
60.611 - */
60.612 - @Override
60.613 - public Article getArticle(long articleIndex, long gid)
60.614 - throws StorageBackendException
60.615 - {
60.616 - ResultSet rs = null;
60.617 -
60.618 - try
60.619 - {
60.620 - this.pstmtGetArticle1.setLong(1, articleIndex);
60.621 - this.pstmtGetArticle1.setLong(2, gid);
60.622 -
60.623 - rs = this.pstmtGetArticle1.executeQuery();
60.624 -
60.625 - if(rs.next())
60.626 - {
60.627 - byte[] body = rs.getBytes("body");
60.628 - String headers = getArticleHeaders(rs.getInt("article_id"));
60.629 - return new Article(headers, body);
60.630 - }
60.631 - else
60.632 - {
60.633 - return null;
60.634 - }
60.635 - }
60.636 - catch(SQLException ex)
60.637 - {
60.638 - restartConnection(ex);
60.639 - return getArticle(articleIndex, gid);
60.640 - }
60.641 - finally
60.642 - {
60.643 - if(rs != null)
60.644 - {
60.645 - try
60.646 - {
60.647 - rs.close();
60.648 - }
60.649 - catch(SQLException ex)
60.650 - {
60.651 - ex.printStackTrace();
60.652 - }
60.653 - restarts = 0;
60.654 - }
60.655 - }
60.656 - }
60.657 -
60.658 - /**
60.659 - * Searches for fitting header values using the given regular expression.
60.660 - * @param group
60.661 - * @param start
60.662 - * @param end
60.663 - * @param headerKey
60.664 - * @param pattern
60.665 - * @return
60.666 - * @throws StorageBackendException
60.667 - */
60.668 - @Override
60.669 - public List<Pair<Long, String>> getArticleHeaders(Channel group, long start,
60.670 - long end, String headerKey, String patStr)
60.671 - throws StorageBackendException, PatternSyntaxException
60.672 - {
60.673 - ResultSet rs = null;
60.674 - List<Pair<Long, String>> heads = new ArrayList<Pair<Long, String>>();
60.675 -
60.676 - try
60.677 - {
60.678 - this.pstmtGetArticleHeaders1.setString(1, group.getName());
60.679 - this.pstmtGetArticleHeaders1.setString(2, headerKey);
60.680 - this.pstmtGetArticleHeaders1.setLong(3, start);
60.681 -
60.682 - rs = this.pstmtGetArticleHeaders1.executeQuery();
60.683 -
60.684 - // Convert the "NNTP" regex to Java regex
60.685 - patStr = patStr.replace("*", ".*");
60.686 - Pattern pattern = Pattern.compile(patStr);
60.687 -
60.688 - while(rs.next())
60.689 - {
60.690 - Long articleIndex = rs.getLong(1);
60.691 - if(end < 0 || articleIndex <= end) // Match start is done via SQL
60.692 - {
60.693 - String headerValue = rs.getString(2);
60.694 - Matcher matcher = pattern.matcher(headerValue);
60.695 - if(matcher.matches())
60.696 - {
60.697 - heads.add(new Pair<Long, String>(articleIndex, headerValue));
60.698 - }
60.699 - }
60.700 - }
60.701 - }
60.702 - catch(SQLException ex)
60.703 - {
60.704 - restartConnection(ex);
60.705 - return getArticleHeaders(group, start, end, headerKey, patStr);
60.706 - }
60.707 - finally
60.708 - {
60.709 - if(rs != null)
60.710 - {
60.711 - try
60.712 - {
60.713 - rs.close();
60.714 - }
60.715 - catch(SQLException ex)
60.716 - {
60.717 - ex.printStackTrace();
60.718 - }
60.719 - }
60.720 - }
60.721 -
60.722 - return heads;
60.723 - }
60.724 -
60.725 - private String getArticleHeaders(long articleID)
60.726 - throws StorageBackendException
60.727 - {
60.728 - ResultSet rs = null;
60.729 -
60.730 - try
60.731 - {
60.732 - this.pstmtGetArticleHeaders0.setLong(1, articleID);
60.733 - rs = this.pstmtGetArticleHeaders0.executeQuery();
60.734 -
60.735 - StringBuilder buf = new StringBuilder();
60.736 - if(rs.next())
60.737 - {
60.738 - for(;;)
60.739 - {
60.740 - buf.append(rs.getString(1)); // key
60.741 - buf.append(": ");
60.742 - String foldedValue = MimeUtility.fold(0, rs.getString(2));
60.743 - buf.append(foldedValue); // value
60.744 - if(rs.next())
60.745 - {
60.746 - buf.append("\r\n");
60.747 - }
60.748 - else
60.749 - {
60.750 - break;
60.751 - }
60.752 - }
60.753 - }
60.754 -
60.755 - return buf.toString();
60.756 - }
60.757 - catch(SQLException ex)
60.758 - {
60.759 - restartConnection(ex);
60.760 - return getArticleHeaders(articleID);
60.761 - }
60.762 - finally
60.763 - {
60.764 - if(rs != null)
60.765 - {
60.766 - try
60.767 - {
60.768 - rs.close();
60.769 - }
60.770 - catch(SQLException ex)
60.771 - {
60.772 - ex.printStackTrace();
60.773 - }
60.774 - }
60.775 - }
60.776 - }
60.777 -
60.778 - @Override
60.779 - public long getArticleIndex(Article article, Group group)
60.780 - throws StorageBackendException
60.781 - {
60.782 - ResultSet rs = null;
60.783 -
60.784 - try
60.785 - {
60.786 - this.pstmtGetArticleIndex.setString(1, article.getMessageID());
60.787 - this.pstmtGetArticleIndex.setLong(2, group.getInternalID());
60.788 -
60.789 - rs = this.pstmtGetArticleIndex.executeQuery();
60.790 - if(rs.next())
60.791 - {
60.792 - return rs.getLong(1);
60.793 - }
60.794 - else
60.795 - {
60.796 - return -1;
60.797 - }
60.798 - }
60.799 - catch(SQLException ex)
60.800 - {
60.801 - restartConnection(ex);
60.802 - return getArticleIndex(article, group);
60.803 - }
60.804 - finally
60.805 - {
60.806 - if(rs != null)
60.807 - {
60.808 - try
60.809 - {
60.810 - rs.close();
60.811 - }
60.812 - catch(SQLException ex)
60.813 - {
60.814 - ex.printStackTrace();
60.815 - }
60.816 - }
60.817 - }
60.818 - }
60.819 -
60.820 - /**
60.821 - * Returns a list of Long/Article Pairs.
60.822 - * @throws java.sql.SQLException
60.823 - */
60.824 - @Override
60.825 - public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first,
60.826 - long last)
60.827 - throws StorageBackendException
60.828 - {
60.829 - ResultSet rs = null;
60.830 -
60.831 - try
60.832 - {
60.833 - this.pstmtGetArticleHeads.setLong(1, group.getInternalID());
60.834 - this.pstmtGetArticleHeads.setLong(2, first);
60.835 - this.pstmtGetArticleHeads.setLong(3, last);
60.836 - rs = pstmtGetArticleHeads.executeQuery();
60.837 -
60.838 - List<Pair<Long, ArticleHead>> articles
60.839 - = new ArrayList<Pair<Long, ArticleHead>>();
60.840 -
60.841 - while (rs.next())
60.842 - {
60.843 - long aid = rs.getLong("article_id");
60.844 - long aidx = rs.getLong("article_index");
60.845 - String headers = getArticleHeaders(aid);
60.846 - articles.add(new Pair<Long, ArticleHead>(aidx,
60.847 - new ArticleHead(headers)));
60.848 - }
60.849 -
60.850 - return articles;
60.851 - }
60.852 - catch(SQLException ex)
60.853 - {
60.854 - restartConnection(ex);
60.855 - return getArticleHeads(group, first, last);
60.856 - }
60.857 - finally
60.858 - {
60.859 - if(rs != null)
60.860 - {
60.861 - try
60.862 - {
60.863 - rs.close();
60.864 - }
60.865 - catch(SQLException ex)
60.866 - {
60.867 - ex.printStackTrace();
60.868 - }
60.869 - }
60.870 - }
60.871 - }
60.872 -
60.873 - @Override
60.874 - public List<Long> getArticleNumbers(long gid)
60.875 - throws StorageBackendException
60.876 - {
60.877 - ResultSet rs = null;
60.878 - try
60.879 - {
60.880 - List<Long> ids = new ArrayList<Long>();
60.881 - this.pstmtGetArticleIDs.setLong(1, gid);
60.882 - rs = this.pstmtGetArticleIDs.executeQuery();
60.883 - while(rs.next())
60.884 - {
60.885 - ids.add(rs.getLong(1));
60.886 - }
60.887 - return ids;
60.888 - }
60.889 - catch(SQLException ex)
60.890 - {
60.891 - restartConnection(ex);
60.892 - return getArticleNumbers(gid);
60.893 - }
60.894 - finally
60.895 - {
60.896 - if(rs != null)
60.897 - {
60.898 - try
60.899 - {
60.900 - rs.close();
60.901 - restarts = 0; // Clear the restart count after successful request
60.902 - }
60.903 - catch(SQLException ex)
60.904 - {
60.905 - ex.printStackTrace();
60.906 - }
60.907 - }
60.908 - }
60.909 - }
60.910 -
60.911 - @Override
60.912 - public String getConfigValue(String key)
60.913 - throws StorageBackendException
60.914 - {
60.915 - ResultSet rs = null;
60.916 - try
60.917 - {
60.918 - this.pstmtGetConfigValue.setString(1, key);
60.919 -
60.920 - rs = this.pstmtGetConfigValue.executeQuery();
60.921 - if(rs.next())
60.922 - {
60.923 - return rs.getString(1); // First data on index 1 not 0
60.924 - }
60.925 - else
60.926 - {
60.927 - return null;
60.928 - }
60.929 - }
60.930 - catch(SQLException ex)
60.931 - {
60.932 - restartConnection(ex);
60.933 - return getConfigValue(key);
60.934 - }
60.935 - finally
60.936 - {
60.937 - if(rs != null)
60.938 - {
60.939 - try
60.940 - {
60.941 - rs.close();
60.942 - }
60.943 - catch(SQLException ex)
60.944 - {
60.945 - ex.printStackTrace();
60.946 - }
60.947 - restarts = 0; // Clear the restart count after successful request
60.948 - }
60.949 - }
60.950 - }
60.951 -
60.952 - @Override
60.953 - public int getEventsCount(int type, long start, long end, Channel channel)
60.954 - throws StorageBackendException
60.955 - {
60.956 - ResultSet rs = null;
60.957 -
60.958 - try
60.959 - {
60.960 - if(channel == null)
60.961 - {
60.962 - this.pstmtGetEventsCount0.setInt(1, type);
60.963 - this.pstmtGetEventsCount0.setLong(2, start);
60.964 - this.pstmtGetEventsCount0.setLong(3, end);
60.965 - rs = this.pstmtGetEventsCount0.executeQuery();
60.966 - }
60.967 - else
60.968 - {
60.969 - this.pstmtGetEventsCount1.setInt(1, type);
60.970 - this.pstmtGetEventsCount1.setLong(2, start);
60.971 - this.pstmtGetEventsCount1.setLong(3, end);
60.972 - this.pstmtGetEventsCount1.setLong(4, channel.getInternalID());
60.973 - rs = this.pstmtGetEventsCount1.executeQuery();
60.974 - }
60.975 -
60.976 - if(rs.next())
60.977 - {
60.978 - return rs.getInt(1);
60.979 - }
60.980 - else
60.981 - {
60.982 - return -1;
60.983 - }
60.984 - }
60.985 - catch(SQLException ex)
60.986 - {
60.987 - restartConnection(ex);
60.988 - return getEventsCount(type, start, end, channel);
60.989 - }
60.990 - finally
60.991 - {
60.992 - if(rs != null)
60.993 - {
60.994 - try
60.995 - {
60.996 - rs.close();
60.997 - }
60.998 - catch(SQLException ex)
60.999 - {
60.1000 - ex.printStackTrace();
60.1001 - }
60.1002 - }
60.1003 - }
60.1004 - }
60.1005 -
60.1006 - /**
60.1007 - * Reads all Groups from the JDBCDatabase.
60.1008 - * @return
60.1009 - * @throws StorageBackendException
60.1010 - */
60.1011 - @Override
60.1012 - public List<Channel> getGroups()
60.1013 - throws StorageBackendException
60.1014 - {
60.1015 - ResultSet rs;
60.1016 - List<Channel> buffer = new ArrayList<Channel>();
60.1017 - Statement stmt = null;
60.1018 -
60.1019 - try
60.1020 - {
60.1021 - stmt = conn.createStatement();
60.1022 - rs = stmt.executeQuery("SELECT * FROM groups ORDER BY name");
60.1023 -
60.1024 - while(rs.next())
60.1025 - {
60.1026 - String name = rs.getString("name");
60.1027 - long id = rs.getLong("group_id");
60.1028 - int flags = rs.getInt("flags");
60.1029 -
60.1030 - Group group = new Group(name, id, flags);
60.1031 - buffer.add(group);
60.1032 - }
60.1033 -
60.1034 - return buffer;
60.1035 - }
60.1036 - catch(SQLException ex)
60.1037 - {
60.1038 - restartConnection(ex);
60.1039 - return getGroups();
60.1040 - }
60.1041 - finally
60.1042 - {
60.1043 - if(stmt != null)
60.1044 - {
60.1045 - try
60.1046 - {
60.1047 - stmt.close(); // Implicitely closes ResultSets
60.1048 - }
60.1049 - catch(SQLException ex)
60.1050 - {
60.1051 - ex.printStackTrace();
60.1052 - }
60.1053 - }
60.1054 - }
60.1055 - }
60.1056 -
60.1057 - @Override
60.1058 - public List<String> getGroupsForList(String listAddress)
60.1059 - throws StorageBackendException
60.1060 - {
60.1061 - ResultSet rs = null;
60.1062 -
60.1063 - try
60.1064 - {
60.1065 - this.pstmtGetGroupForList.setString(1, listAddress);
60.1066 -
60.1067 - rs = this.pstmtGetGroupForList.executeQuery();
60.1068 - List<String> groups = new ArrayList<String>();
60.1069 - while(rs.next())
60.1070 - {
60.1071 - String group = rs.getString(1);
60.1072 - groups.add(group);
60.1073 - }
60.1074 - return groups;
60.1075 - }
60.1076 - catch(SQLException ex)
60.1077 - {
60.1078 - restartConnection(ex);
60.1079 - return getGroupsForList(listAddress);
60.1080 - }
60.1081 - finally
60.1082 - {
60.1083 - if(rs != null)
60.1084 - {
60.1085 - try
60.1086 - {
60.1087 - rs.close();
60.1088 - }
60.1089 - catch(SQLException ex)
60.1090 - {
60.1091 - ex.printStackTrace();
60.1092 - }
60.1093 - }
60.1094 - }
60.1095 - }
60.1096 -
60.1097 - /**
60.1098 - * Returns the Group that is identified by the name.
60.1099 - * @param name
60.1100 - * @return
60.1101 - * @throws StorageBackendException
60.1102 - */
60.1103 - @Override
60.1104 - public Group getGroup(String name)
60.1105 - throws StorageBackendException
60.1106 - {
60.1107 - ResultSet rs = null;
60.1108 -
60.1109 - try
60.1110 - {
60.1111 - this.pstmtGetGroup0.setString(1, name);
60.1112 - rs = this.pstmtGetGroup0.executeQuery();
60.1113 -
60.1114 - if (!rs.next())
60.1115 - {
60.1116 - return null;
60.1117 - }
60.1118 - else
60.1119 - {
60.1120 - long id = rs.getLong("group_id");
60.1121 - int flags = rs.getInt("flags");
60.1122 - return new Group(name, id, flags);
60.1123 - }
60.1124 - }
60.1125 - catch(SQLException ex)
60.1126 - {
60.1127 - restartConnection(ex);
60.1128 - return getGroup(name);
60.1129 - }
60.1130 - finally
60.1131 - {
60.1132 - if(rs != null)
60.1133 - {
60.1134 - try
60.1135 - {
60.1136 - rs.close();
60.1137 - }
60.1138 - catch(SQLException ex)
60.1139 - {
60.1140 - ex.printStackTrace();
60.1141 - }
60.1142 - }
60.1143 - }
60.1144 - }
60.1145 -
60.1146 - @Override
60.1147 - public List<String> getListsForGroup(String group)
60.1148 - throws StorageBackendException
60.1149 - {
60.1150 - ResultSet rs = null;
60.1151 - List<String> lists = new ArrayList<String>();
60.1152 -
60.1153 - try
60.1154 - {
60.1155 - this.pstmtGetListForGroup.setString(1, group);
60.1156 - rs = this.pstmtGetListForGroup.executeQuery();
60.1157 -
60.1158 - while(rs.next())
60.1159 - {
60.1160 - lists.add(rs.getString(1));
60.1161 - }
60.1162 - return lists;
60.1163 - }
60.1164 - catch(SQLException ex)
60.1165 - {
60.1166 - restartConnection(ex);
60.1167 - return getListsForGroup(group);
60.1168 - }
60.1169 - finally
60.1170 - {
60.1171 - if(rs != null)
60.1172 - {
60.1173 - try
60.1174 - {
60.1175 - rs.close();
60.1176 - }
60.1177 - catch(SQLException ex)
60.1178 - {
60.1179 - ex.printStackTrace();
60.1180 - }
60.1181 - }
60.1182 - }
60.1183 - }
60.1184 -
60.1185 - private int getMaxArticleIndex(long groupID)
60.1186 - throws StorageBackendException
60.1187 - {
60.1188 - ResultSet rs = null;
60.1189 -
60.1190 - try
60.1191 - {
60.1192 - this.pstmtGetMaxArticleIndex.setLong(1, groupID);
60.1193 - rs = this.pstmtGetMaxArticleIndex.executeQuery();
60.1194 -
60.1195 - int maxIndex = 0;
60.1196 - if (rs.next())
60.1197 - {
60.1198 - maxIndex = rs.getInt(1);
60.1199 - }
60.1200 -
60.1201 - return maxIndex;
60.1202 - }
60.1203 - catch(SQLException ex)
60.1204 - {
60.1205 - restartConnection(ex);
60.1206 - return getMaxArticleIndex(groupID);
60.1207 - }
60.1208 - finally
60.1209 - {
60.1210 - if(rs != null)
60.1211 - {
60.1212 - try
60.1213 - {
60.1214 - rs.close();
60.1215 - }
60.1216 - catch(SQLException ex)
60.1217 - {
60.1218 - ex.printStackTrace();
60.1219 - }
60.1220 - }
60.1221 - }
60.1222 - }
60.1223 -
60.1224 - private int getMaxArticleID()
60.1225 - throws StorageBackendException
60.1226 - {
60.1227 - ResultSet rs = null;
60.1228 -
60.1229 - try
60.1230 - {
60.1231 - rs = this.pstmtGetMaxArticleID.executeQuery();
60.1232 -
60.1233 - int maxIndex = 0;
60.1234 - if (rs.next())
60.1235 - {
60.1236 - maxIndex = rs.getInt(1);
60.1237 - }
60.1238 -
60.1239 - return maxIndex;
60.1240 - }
60.1241 - catch(SQLException ex)
60.1242 - {
60.1243 - restartConnection(ex);
60.1244 - return getMaxArticleID();
60.1245 - }
60.1246 - finally
60.1247 - {
60.1248 - if(rs != null)
60.1249 - {
60.1250 - try
60.1251 - {
60.1252 - rs.close();
60.1253 - }
60.1254 - catch(SQLException ex)
60.1255 - {
60.1256 - ex.printStackTrace();
60.1257 - }
60.1258 - }
60.1259 - }
60.1260 - }
60.1261 -
60.1262 - @Override
60.1263 - public int getLastArticleNumber(Group group)
60.1264 - throws StorageBackendException
60.1265 - {
60.1266 - ResultSet rs = null;
60.1267 -
60.1268 - try
60.1269 - {
60.1270 - this.pstmtGetLastArticleNumber.setLong(1, group.getInternalID());
60.1271 - rs = this.pstmtGetLastArticleNumber.executeQuery();
60.1272 - if (rs.next())
60.1273 - {
60.1274 - return rs.getInt(1);
60.1275 - }
60.1276 - else
60.1277 - {
60.1278 - return 0;
60.1279 - }
60.1280 - }
60.1281 - catch(SQLException ex)
60.1282 - {
60.1283 - restartConnection(ex);
60.1284 - return getLastArticleNumber(group);
60.1285 - }
60.1286 - finally
60.1287 - {
60.1288 - if(rs != null)
60.1289 - {
60.1290 - try
60.1291 - {
60.1292 - rs.close();
60.1293 - }
60.1294 - catch(SQLException ex)
60.1295 - {
60.1296 - ex.printStackTrace();
60.1297 - }
60.1298 - }
60.1299 - }
60.1300 - }
60.1301 -
60.1302 - @Override
60.1303 - public int getFirstArticleNumber(Group group)
60.1304 - throws StorageBackendException
60.1305 - {
60.1306 - ResultSet rs = null;
60.1307 - try
60.1308 - {
60.1309 - this.pstmtGetFirstArticleNumber.setLong(1, group.getInternalID());
60.1310 - rs = this.pstmtGetFirstArticleNumber.executeQuery();
60.1311 - if(rs.next())
60.1312 - {
60.1313 - return rs.getInt(1);
60.1314 - }
60.1315 - else
60.1316 - {
60.1317 - return 0;
60.1318 - }
60.1319 - }
60.1320 - catch(SQLException ex)
60.1321 - {
60.1322 - restartConnection(ex);
60.1323 - return getFirstArticleNumber(group);
60.1324 - }
60.1325 - finally
60.1326 - {
60.1327 - if(rs != null)
60.1328 - {
60.1329 - try
60.1330 - {
60.1331 - rs.close();
60.1332 - }
60.1333 - catch(SQLException ex)
60.1334 - {
60.1335 - ex.printStackTrace();
60.1336 - }
60.1337 - }
60.1338 - }
60.1339 - }
60.1340 -
60.1341 - /**
60.1342 - * Returns a group name identified by the given id.
60.1343 - * @param id
60.1344 - * @return
60.1345 - * @throws StorageBackendException
60.1346 - */
60.1347 - public String getGroup(int id)
60.1348 - throws StorageBackendException
60.1349 - {
60.1350 - ResultSet rs = null;
60.1351 -
60.1352 - try
60.1353 - {
60.1354 - this.pstmtGetGroup1.setInt(1, id);
60.1355 - rs = this.pstmtGetGroup1.executeQuery();
60.1356 -
60.1357 - if (rs.next())
60.1358 - {
60.1359 - return rs.getString(1);
60.1360 - }
60.1361 - else
60.1362 - {
60.1363 - return null;
60.1364 - }
60.1365 - }
60.1366 - catch(SQLException ex)
60.1367 - {
60.1368 - restartConnection(ex);
60.1369 - return getGroup(id);
60.1370 - }
60.1371 - finally
60.1372 - {
60.1373 - if(rs != null)
60.1374 - {
60.1375 - try
60.1376 - {
60.1377 - rs.close();
60.1378 - }
60.1379 - catch(SQLException ex)
60.1380 - {
60.1381 - ex.printStackTrace();
60.1382 - }
60.1383 - }
60.1384 - }
60.1385 - }
60.1386 -
60.1387 - @Override
60.1388 - public double getEventsPerHour(int key, long gid)
60.1389 - throws StorageBackendException
60.1390 - {
60.1391 - String gidquery = "";
60.1392 - if(gid >= 0)
60.1393 - {
60.1394 - gidquery = " AND group_id = " + gid;
60.1395 - }
60.1396 -
60.1397 - Statement stmt = null;
60.1398 - ResultSet rs = null;
60.1399 -
60.1400 - try
60.1401 - {
60.1402 - stmt = this.conn.createStatement();
60.1403 - rs = stmt.executeQuery("SELECT Count(*) / (Max(event_time) - Min(event_time))" +
60.1404 - " * 1000 * 60 * 60 FROM events WHERE event_key = " + key + gidquery);
60.1405 -
60.1406 - if(rs.next())
60.1407 - {
60.1408 - restarts = 0; // reset error count
60.1409 - return rs.getDouble(1);
60.1410 - }
60.1411 - else
60.1412 - {
60.1413 - return Double.NaN;
60.1414 - }
60.1415 - }
60.1416 - catch(SQLException ex)
60.1417 - {
60.1418 - restartConnection(ex);
60.1419 - return getEventsPerHour(key, gid);
60.1420 - }
60.1421 - finally
60.1422 - {
60.1423 - try
60.1424 - {
60.1425 - if(stmt != null)
60.1426 - {
60.1427 - stmt.close(); // Implicitely closes the result sets
60.1428 - }
60.1429 - }
60.1430 - catch(SQLException ex)
60.1431 - {
60.1432 - ex.printStackTrace();
60.1433 - }
60.1434 - }
60.1435 - }
60.1436 -
60.1437 - @Override
60.1438 - public String getOldestArticle()
60.1439 - throws StorageBackendException
60.1440 - {
60.1441 - ResultSet rs = null;
60.1442 -
60.1443 - try
60.1444 - {
60.1445 - rs = this.pstmtGetOldestArticle.executeQuery();
60.1446 - if(rs.next())
60.1447 - {
60.1448 - return rs.getString(1);
60.1449 - }
60.1450 - else
60.1451 - {
60.1452 - return null;
60.1453 - }
60.1454 - }
60.1455 - catch(SQLException ex)
60.1456 - {
60.1457 - restartConnection(ex);
60.1458 - return getOldestArticle();
60.1459 - }
60.1460 - finally
60.1461 - {
60.1462 - if(rs != null)
60.1463 - {
60.1464 - try
60.1465 - {
60.1466 - rs.close();
60.1467 - }
60.1468 - catch(SQLException ex)
60.1469 - {
60.1470 - ex.printStackTrace();
60.1471 - }
60.1472 - }
60.1473 - }
60.1474 - }
60.1475 -
60.1476 - @Override
60.1477 - public int getPostingsCount(String groupname)
60.1478 - throws StorageBackendException
60.1479 - {
60.1480 - ResultSet rs = null;
60.1481 -
60.1482 - try
60.1483 - {
60.1484 - this.pstmtGetPostingsCount.setString(1, groupname);
60.1485 - rs = this.pstmtGetPostingsCount.executeQuery();
60.1486 - if(rs.next())
60.1487 - {
60.1488 - return rs.getInt(1);
60.1489 - }
60.1490 - else
60.1491 - {
60.1492 - Log.get().warning("Count on postings return nothing!");
60.1493 - return 0;
60.1494 - }
60.1495 - }
60.1496 - catch(SQLException ex)
60.1497 - {
60.1498 - restartConnection(ex);
60.1499 - return getPostingsCount(groupname);
60.1500 - }
60.1501 - finally
60.1502 - {
60.1503 - if(rs != null)
60.1504 - {
60.1505 - try
60.1506 - {
60.1507 - rs.close();
60.1508 - }
60.1509 - catch(SQLException ex)
60.1510 - {
60.1511 - ex.printStackTrace();
60.1512 - }
60.1513 - }
60.1514 - }
60.1515 - }
60.1516 -
60.1517 - @Override
60.1518 - public List<Subscription> getSubscriptions(int feedtype)
60.1519 - throws StorageBackendException
60.1520 - {
60.1521 - ResultSet rs = null;
60.1522 -
60.1523 - try
60.1524 - {
60.1525 - List<Subscription> subs = new ArrayList<Subscription>();
60.1526 - this.pstmtGetSubscriptions.setInt(1, feedtype);
60.1527 - rs = this.pstmtGetSubscriptions.executeQuery();
60.1528 -
60.1529 - while(rs.next())
60.1530 - {
60.1531 - String host = rs.getString("host");
60.1532 - String group = rs.getString("name");
60.1533 - int port = rs.getInt("port");
60.1534 - subs.add(new Subscription(host, port, feedtype, group));
60.1535 - }
60.1536 -
60.1537 - return subs;
60.1538 - }
60.1539 - catch(SQLException ex)
60.1540 - {
60.1541 - restartConnection(ex);
60.1542 - return getSubscriptions(feedtype);
60.1543 - }
60.1544 - finally
60.1545 - {
60.1546 - if(rs != null)
60.1547 - {
60.1548 - try
60.1549 - {
60.1550 - rs.close();
60.1551 - }
60.1552 - catch(SQLException ex)
60.1553 - {
60.1554 - ex.printStackTrace();
60.1555 - }
60.1556 - }
60.1557 - }
60.1558 - }
60.1559 -
60.1560 - /**
60.1561 - * Checks if there is an article with the given messageid in the JDBCDatabase.
60.1562 - * @param name
60.1563 - * @return
60.1564 - * @throws StorageBackendException
60.1565 - */
60.1566 - @Override
60.1567 - public boolean isArticleExisting(String messageID)
60.1568 - throws StorageBackendException
60.1569 - {
60.1570 - ResultSet rs = null;
60.1571 -
60.1572 - try
60.1573 - {
60.1574 - this.pstmtIsArticleExisting.setString(1, messageID);
60.1575 - rs = this.pstmtIsArticleExisting.executeQuery();
60.1576 - return rs.next() && rs.getInt(1) == 1;
60.1577 - }
60.1578 - catch(SQLException ex)
60.1579 - {
60.1580 - restartConnection(ex);
60.1581 - return isArticleExisting(messageID);
60.1582 - }
60.1583 - finally
60.1584 - {
60.1585 - if(rs != null)
60.1586 - {
60.1587 - try
60.1588 - {
60.1589 - rs.close();
60.1590 - }
60.1591 - catch(SQLException ex)
60.1592 - {
60.1593 - ex.printStackTrace();
60.1594 - }
60.1595 - }
60.1596 - }
60.1597 - }
60.1598 -
60.1599 - /**
60.1600 - * Checks if there is a group with the given name in the JDBCDatabase.
60.1601 - * @param name
60.1602 - * @return
60.1603 - * @throws StorageBackendException
60.1604 - */
60.1605 - @Override
60.1606 - public boolean isGroupExisting(String name)
60.1607 - throws StorageBackendException
60.1608 - {
60.1609 - ResultSet rs = null;
60.1610 -
60.1611 - try
60.1612 - {
60.1613 - this.pstmtIsGroupExisting.setString(1, name);
60.1614 - rs = this.pstmtIsGroupExisting.executeQuery();
60.1615 - return rs.next();
60.1616 - }
60.1617 - catch(SQLException ex)
60.1618 - {
60.1619 - restartConnection(ex);
60.1620 - return isGroupExisting(name);
60.1621 - }
60.1622 - finally
60.1623 - {
60.1624 - if(rs != null)
60.1625 - {
60.1626 - try
60.1627 - {
60.1628 - rs.close();
60.1629 - }
60.1630 - catch(SQLException ex)
60.1631 - {
60.1632 - ex.printStackTrace();
60.1633 - }
60.1634 - }
60.1635 - }
60.1636 - }
60.1637 -
60.1638 - @Override
60.1639 - public void setConfigValue(String key, String value)
60.1640 - throws StorageBackendException
60.1641 - {
60.1642 - try
60.1643 - {
60.1644 - conn.setAutoCommit(false);
60.1645 - this.pstmtSetConfigValue0.setString(1, key);
60.1646 - this.pstmtSetConfigValue0.execute();
60.1647 - this.pstmtSetConfigValue1.setString(1, key);
60.1648 - this.pstmtSetConfigValue1.setString(2, value);
60.1649 - this.pstmtSetConfigValue1.execute();
60.1650 - conn.commit();
60.1651 - conn.setAutoCommit(true);
60.1652 - }
60.1653 - catch(SQLException ex)
60.1654 - {
60.1655 - restartConnection(ex);
60.1656 - setConfigValue(key, value);
60.1657 - }
60.1658 - }
60.1659 -
60.1660 - /**
60.1661 - * Closes the JDBCDatabase connection.
60.1662 - */
60.1663 - public void shutdown()
60.1664 - throws StorageBackendException
60.1665 - {
60.1666 - try
60.1667 - {
60.1668 - if(this.conn != null)
60.1669 - {
60.1670 - this.conn.close();
60.1671 - }
60.1672 - }
60.1673 - catch(SQLException ex)
60.1674 - {
60.1675 - throw new StorageBackendException(ex);
60.1676 - }
60.1677 - }
60.1678 -
60.1679 - @Override
60.1680 - public void purgeGroup(Group group)
60.1681 - throws StorageBackendException
60.1682 - {
60.1683 - try
60.1684 - {
60.1685 - this.pstmtPurgeGroup0.setLong(1, group.getInternalID());
60.1686 - this.pstmtPurgeGroup0.executeUpdate();
60.1687 -
60.1688 - this.pstmtPurgeGroup1.setLong(1, group.getInternalID());
60.1689 - this.pstmtPurgeGroup1.executeUpdate();
60.1690 - }
60.1691 - catch(SQLException ex)
60.1692 - {
60.1693 - restartConnection(ex);
60.1694 - purgeGroup(group);
60.1695 - }
60.1696 - }
60.1697 -
60.1698 - private void restartConnection(SQLException cause)
60.1699 - throws StorageBackendException
60.1700 - {
60.1701 - restarts++;
60.1702 - Log.get().severe(Thread.currentThread()
60.1703 - + ": Database connection was closed (restart " + restarts + ").");
60.1704 -
60.1705 - if(restarts >= MAX_RESTARTS)
60.1706 - {
60.1707 - // Delete the current, probably broken JDBCDatabase instance.
60.1708 - // So no one can use the instance any more.
60.1709 - JDBCDatabaseProvider.instances.remove(Thread.currentThread());
60.1710 -
60.1711 - // Throw the exception upwards
60.1712 - throw new StorageBackendException(cause);
60.1713 - }
60.1714 -
60.1715 - try
60.1716 - {
60.1717 - Thread.sleep(1500L * restarts);
60.1718 - }
60.1719 - catch(InterruptedException ex)
60.1720 - {
60.1721 - Log.get().warning("Interrupted: " + ex.getMessage());
60.1722 - }
60.1723 -
60.1724 - // Try to properly close the old database connection
60.1725 - try
60.1726 - {
60.1727 - if(this.conn != null)
60.1728 - {
60.1729 - this.conn.close();
60.1730 - }
60.1731 - }
60.1732 - catch(SQLException ex)
60.1733 - {
60.1734 - Log.get().warning(ex.getMessage());
60.1735 - }
60.1736 -
60.1737 - try
60.1738 - {
60.1739 - // Try to reinitialize database connection
60.1740 - arise();
60.1741 - }
60.1742 - catch(SQLException ex)
60.1743 - {
60.1744 - Log.get().warning(ex.getMessage());
60.1745 - restartConnection(ex);
60.1746 - }
60.1747 - }
60.1748 -
60.1749 - @Override
60.1750 - public boolean update(Article article)
60.1751 - throws StorageBackendException
60.1752 - {
60.1753 - // DELETE FROM headers WHERE article_id = ?
60.1754 -
60.1755 - // INSERT INTO headers ...
60.1756 -
60.1757 - // SELECT * FROM postings WHERE article_id = ? AND group_id = ?
60.1758 - return false;
60.1759 - }
60.1760 -
60.1761 - /**
60.1762 - * Writes the flags and the name of the given group to the database.
60.1763 - * @param group
60.1764 - * @throws StorageBackendException
60.1765 - */
60.1766 - @Override
60.1767 - public boolean update(Group group)
60.1768 - throws StorageBackendException
60.1769 - {
60.1770 - try
60.1771 - {
60.1772 - this.pstmtUpdateGroup.setInt(1, group.getFlags());
60.1773 - this.pstmtUpdateGroup.setString(2, group.getName());
60.1774 - this.pstmtUpdateGroup.setLong(3, group.getInternalID());
60.1775 - int rs = this.pstmtUpdateGroup.executeUpdate();
60.1776 - return rs == 1;
60.1777 - }
60.1778 - catch(SQLException ex)
60.1779 - {
60.1780 - restartConnection(ex);
60.1781 - return update(group);
60.1782 - }
60.1783 - }
60.1784 -
60.1785 -}
61.1 --- a/org/sonews/storage/impl/JDBCDatabaseProvider.java Sun Aug 29 17:04:25 2010 +0200
61.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
61.3 @@ -1,69 +0,0 @@
61.4 -/*
61.5 - * SONEWS News Server
61.6 - * see AUTHORS for the list of contributors
61.7 - *
61.8 - * This program is free software: you can redistribute it and/or modify
61.9 - * it under the terms of the GNU General Public License as published by
61.10 - * the Free Software Foundation, either version 3 of the License, or
61.11 - * (at your option) any later version.
61.12 - *
61.13 - * This program is distributed in the hope that it will be useful,
61.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
61.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
61.16 - * GNU General Public License for more details.
61.17 - *
61.18 - * You should have received a copy of the GNU General Public License
61.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
61.20 - */
61.21 -
61.22 -package org.sonews.storage.impl;
61.23 -
61.24 -import java.sql.SQLException;
61.25 -import java.util.Map;
61.26 -import java.util.concurrent.ConcurrentHashMap;
61.27 -import org.sonews.storage.Storage;
61.28 -import org.sonews.storage.StorageBackendException;
61.29 -import org.sonews.storage.StorageProvider;
61.30 -
61.31 -/**
61.32 - *
61.33 - * @author Christian Lins
61.34 - * @since sonews/1.0
61.35 - */
61.36 -public class JDBCDatabaseProvider implements StorageProvider
61.37 -{
61.38 -
61.39 - protected static final Map<Thread, JDBCDatabase> instances
61.40 - = new ConcurrentHashMap<Thread, JDBCDatabase>();
61.41 -
61.42 - @Override
61.43 - public boolean isSupported(String uri)
61.44 - {
61.45 - throw new UnsupportedOperationException("Not supported yet.");
61.46 - }
61.47 -
61.48 - @Override
61.49 - public Storage storage(Thread thread)
61.50 - throws StorageBackendException
61.51 - {
61.52 - try
61.53 - {
61.54 - if(!instances.containsKey(Thread.currentThread()))
61.55 - {
61.56 - JDBCDatabase db = new JDBCDatabase();
61.57 - db.arise();
61.58 - instances.put(Thread.currentThread(), db);
61.59 - return db;
61.60 - }
61.61 - else
61.62 - {
61.63 - return instances.get(Thread.currentThread());
61.64 - }
61.65 - }
61.66 - catch(SQLException ex)
61.67 - {
61.68 - throw new StorageBackendException(ex);
61.69 - }
61.70 - }
61.71 -
61.72 -}
62.1 --- a/org/sonews/storage/package.html Sun Aug 29 17:04:25 2010 +0200
62.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
62.3 @@ -1,2 +0,0 @@
62.4 -Contains classes of the storage backend and the Group and Article
62.5 -abstraction.
62.6 \ No newline at end of file
63.1 --- a/org/sonews/util/DatabaseSetup.java Sun Aug 29 17:04:25 2010 +0200
63.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
63.3 @@ -1,127 +0,0 @@
63.4 -/*
63.5 - * SONEWS News Server
63.6 - * see AUTHORS for the list of contributors
63.7 - *
63.8 - * This program is free software: you can redistribute it and/or modify
63.9 - * it under the terms of the GNU General Public License as published by
63.10 - * the Free Software Foundation, either version 3 of the License, or
63.11 - * (at your option) any later version.
63.12 - *
63.13 - * This program is distributed in the hope that it will be useful,
63.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
63.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
63.16 - * GNU General Public License for more details.
63.17 - *
63.18 - * You should have received a copy of the GNU General Public License
63.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
63.20 - */
63.21 -
63.22 -package org.sonews.util;
63.23 -
63.24 -import java.io.BufferedReader;
63.25 -import java.io.InputStreamReader;
63.26 -import java.sql.Connection;
63.27 -import java.sql.DriverManager;
63.28 -import java.sql.Statement;
63.29 -import java.util.HashMap;
63.30 -import java.util.Map;
63.31 -import org.sonews.config.Config;
63.32 -import org.sonews.util.io.Resource;
63.33 -
63.34 -/**
63.35 - * Database setup utility class.
63.36 - * @author Christian Lins
63.37 - * @since sonews/0.5.0
63.38 - */
63.39 -public final class DatabaseSetup
63.40 -{
63.41 -
63.42 - private static final Map<String, String> templateMap
63.43 - = new HashMap<String, String>();
63.44 - private static final Map<String, StringTemplate> urlMap
63.45 - = new HashMap<String, StringTemplate>();
63.46 - private static final Map<String, String> driverMap
63.47 - = new HashMap<String, String>();
63.48 -
63.49 - static
63.50 - {
63.51 - templateMap.put("1", "helpers/database_mysql5_tmpl.sql");
63.52 - templateMap.put("2", "helpers/database_postgresql8_tmpl.sql");
63.53 -
63.54 - urlMap.put("1", new StringTemplate("jdbc:mysql://%HOSTNAME/%DB"));
63.55 - urlMap.put("2", new StringTemplate("jdbc:postgresql://%HOSTNAME/%DB"));
63.56 -
63.57 - driverMap.put("1", "com.mysql.jdbc.Driver");
63.58 - driverMap.put("2", "org.postgresql.Driver");
63.59 - }
63.60 -
63.61 - public static void main(String[] args)
63.62 - throws Exception
63.63 - {
63.64 - System.out.println("sonews Database setup helper");
63.65 - System.out.println("This program will create a initial database table structure");
63.66 - System.out.println("for the sonews Newsserver.");
63.67 - System.out.println("You need to create a database and a db user manually before!");
63.68 -
63.69 - System.out.println("Select DBMS type:");
63.70 - System.out.println("[1] MySQL 5.x or higher");
63.71 - System.out.println("[2] PostgreSQL 8.x or higher");
63.72 - System.out.print("Your choice: ");
63.73 -
63.74 - BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
63.75 - String dbmsType = in.readLine();
63.76 - String tmplName = templateMap.get(dbmsType);
63.77 - if(tmplName == null)
63.78 - {
63.79 - System.err.println("Invalid choice. Try again you fool!");
63.80 - main(args);
63.81 - return;
63.82 - }
63.83 -
63.84 - // Load JDBC Driver class
63.85 - Class.forName(driverMap.get(dbmsType));
63.86 -
63.87 - String tmpl = Resource.getAsString(tmplName, true);
63.88 -
63.89 - System.out.print("Database server hostname (e.g. localhost): ");
63.90 - String dbHostname = in.readLine();
63.91 -
63.92 - System.out.print("Database name: ");
63.93 - String dbName = in.readLine();
63.94 -
63.95 - System.out.print("Give name of DB user that can create tables: ");
63.96 - String dbUser = in.readLine();
63.97 -
63.98 - System.out.print("Password: ");
63.99 - String dbPassword = in.readLine();
63.100 -
63.101 - String url = urlMap.get(dbmsType)
63.102 - .set("HOSTNAME", dbHostname)
63.103 - .set("DB", dbName).toString();
63.104 -
63.105 - Connection conn =
63.106 - DriverManager.getConnection(url, dbUser, dbPassword);
63.107 - conn.setAutoCommit(false);
63.108 -
63.109 - String[] tmplChunks = tmpl.split(";");
63.110 -
63.111 - for(String chunk : tmplChunks)
63.112 - {
63.113 - if(chunk.trim().equals(""))
63.114 - {
63.115 - continue;
63.116 - }
63.117 -
63.118 - Statement stmt = conn.createStatement();
63.119 - stmt.execute(chunk);
63.120 - }
63.121 -
63.122 - conn.commit();
63.123 - conn.setAutoCommit(true);
63.124 -
63.125 - // Create config file
63.126 -
63.127 - System.out.println("Ok");
63.128 - }
63.129 -
63.130 -}
64.1 --- a/org/sonews/util/Log.java Sun Aug 29 17:04:25 2010 +0200
64.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
64.3 @@ -1,57 +0,0 @@
64.4 -/*
64.5 - * SONEWS News Server
64.6 - * see AUTHORS for the list of contributors
64.7 - *
64.8 - * This program is free software: you can redistribute it and/or modify
64.9 - * it under the terms of the GNU General Public License as published by
64.10 - * the Free Software Foundation, either version 3 of the License, or
64.11 - * (at your option) any later version.
64.12 - *
64.13 - * This program is distributed in the hope that it will be useful,
64.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
64.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
64.16 - * GNU General Public License for more details.
64.17 - *
64.18 - * You should have received a copy of the GNU General Public License
64.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
64.20 - */
64.21 -
64.22 -package org.sonews.util;
64.23 -
64.24 -import java.util.logging.Level;
64.25 -import java.util.logging.LogManager;
64.26 -import java.util.logging.Logger;
64.27 -import java.util.logging.SimpleFormatter;
64.28 -import java.util.logging.StreamHandler;
64.29 -import org.sonews.config.Config;
64.30 -
64.31 -/**
64.32 - * Provides logging and debugging methods.
64.33 - * @author Christian Lins
64.34 - * @since sonews/0.5.0
64.35 - */
64.36 -public class Log extends Logger
64.37 -{
64.38 -
64.39 - private static Log instance = new Log();
64.40 -
64.41 - private Log()
64.42 - {
64.43 - super("org.sonews", null);
64.44 -
64.45 - StreamHandler handler = new StreamHandler(System.out, new SimpleFormatter());
64.46 - Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
64.47 - handler.setLevel(level);
64.48 - addHandler(handler);
64.49 - setLevel(level);
64.50 - LogManager.getLogManager().addLogger(this);
64.51 - }
64.52 -
64.53 - public static Logger get()
64.54 - {
64.55 - Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
64.56 - instance.setLevel(level);
64.57 - return instance;
64.58 - }
64.59 -
64.60 -}
65.1 --- a/org/sonews/util/Pair.java Sun Aug 29 17:04:25 2010 +0200
65.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
65.3 @@ -1,48 +0,0 @@
65.4 -/*
65.5 - * SONEWS News Server
65.6 - * see AUTHORS for the list of contributors
65.7 - *
65.8 - * This program is free software: you can redistribute it and/or modify
65.9 - * it under the terms of the GNU General Public License as published by
65.10 - * the Free Software Foundation, either version 3 of the License, or
65.11 - * (at your option) any later version.
65.12 - *
65.13 - * This program is distributed in the hope that it will be useful,
65.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
65.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
65.16 - * GNU General Public License for more details.
65.17 - *
65.18 - * You should have received a copy of the GNU General Public License
65.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
65.20 - */
65.21 -
65.22 -package org.sonews.util;
65.23 -
65.24 -/**
65.25 - * A pair of two objects.
65.26 - * @author Christian Lins
65.27 - * @since sonews/0.5.0
65.28 - */
65.29 -public class Pair<T1, T2>
65.30 -{
65.31 -
65.32 - private T1 a;
65.33 - private T2 b;
65.34 -
65.35 - public Pair(T1 a, T2 b)
65.36 - {
65.37 - this.a = a;
65.38 - this.b = b;
65.39 - }
65.40 -
65.41 - public T1 getA()
65.42 - {
65.43 - return a;
65.44 - }
65.45 -
65.46 - public T2 getB()
65.47 - {
65.48 - return b;
65.49 - }
65.50 -
65.51 -}
66.1 --- a/org/sonews/util/Purger.java Sun Aug 29 17:04:25 2010 +0200
66.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
66.3 @@ -1,149 +0,0 @@
66.4 -/*
66.5 - * SONEWS News Server
66.6 - * see AUTHORS for the list of contributors
66.7 - *
66.8 - * This program is free software: you can redistribute it and/or modify
66.9 - * it under the terms of the GNU General Public License as published by
66.10 - * the Free Software Foundation, either version 3 of the License, or
66.11 - * (at your option) any later version.
66.12 - *
66.13 - * This program is distributed in the hope that it will be useful,
66.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
66.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
66.16 - * GNU General Public License for more details.
66.17 - *
66.18 - * You should have received a copy of the GNU General Public License
66.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
66.20 - */
66.21 -
66.22 -package org.sonews.util;
66.23 -
66.24 -import org.sonews.daemon.AbstractDaemon;
66.25 -import org.sonews.config.Config;
66.26 -import org.sonews.storage.Article;
66.27 -import org.sonews.storage.Headers;
66.28 -import java.util.Date;
66.29 -import java.util.List;
66.30 -import org.sonews.storage.Channel;
66.31 -import org.sonews.storage.Group;
66.32 -import org.sonews.storage.StorageBackendException;
66.33 -import org.sonews.storage.StorageManager;
66.34 -
66.35 -/**
66.36 - * The purger is started in configurable intervals to search
66.37 - * for messages that can be purged. A message must be deleted if its lifetime
66.38 - * has exceeded, if it was marked as deleted or if the maximum number of
66.39 - * articles in the database is reached.
66.40 - * @author Christian Lins
66.41 - * @since sonews/0.5.0
66.42 - */
66.43 -public class Purger extends AbstractDaemon
66.44 -{
66.45 -
66.46 - /**
66.47 - * Loops through all messages and deletes them if their time
66.48 - * has come.
66.49 - */
66.50 - @Override
66.51 - public void run()
66.52 - {
66.53 - try
66.54 - {
66.55 - while(isRunning())
66.56 - {
66.57 - purgeDeleted();
66.58 - purgeOutdated();
66.59 -
66.60 - Thread.sleep(120000); // Sleep for two minutes
66.61 - }
66.62 - }
66.63 - catch(StorageBackendException ex)
66.64 - {
66.65 - ex.printStackTrace();
66.66 - }
66.67 - catch(InterruptedException ex)
66.68 - {
66.69 - Log.get().warning("Purger interrupted: " + ex);
66.70 - }
66.71 - }
66.72 -
66.73 - private void purgeDeleted()
66.74 - throws StorageBackendException
66.75 - {
66.76 - List<Channel> groups = StorageManager.current().getGroups();
66.77 - for(Channel channel : groups)
66.78 - {
66.79 - if(!(channel instanceof Group))
66.80 - continue;
66.81 -
66.82 - Group group = (Group)channel;
66.83 - // Look for groups that are marked as deleted
66.84 - if(group.isDeleted())
66.85 - {
66.86 - List<Long> ids = StorageManager.current().getArticleNumbers(group.getInternalID());
66.87 - if(ids.size() == 0)
66.88 - {
66.89 - StorageManager.current().purgeGroup(group);
66.90 - Log.get().info("Group " + group.getName() + " purged.");
66.91 - }
66.92 -
66.93 - for(int n = 0; n < ids.size() && n < 10; n++)
66.94 - {
66.95 - Article art = StorageManager.current().getArticle(ids.get(n), group.getInternalID());
66.96 - StorageManager.current().delete(art.getMessageID());
66.97 - Log.get().info("Article " + art.getMessageID() + " purged.");
66.98 - }
66.99 - }
66.100 - }
66.101 - }
66.102 -
66.103 - private void purgeOutdated()
66.104 - throws InterruptedException, StorageBackendException
66.105 - {
66.106 - long articleMaximum =
66.107 - Config.inst().get("sonews.article.maxnum", Long.MAX_VALUE);
66.108 - long lifetime =
66.109 - Config.inst().get("sonews.article.lifetime", -1);
66.110 -
66.111 - if(lifetime > 0 || articleMaximum < Stats.getInstance().getNumberOfNews())
66.112 - {
66.113 - Log.get().info("Purging old messages...");
66.114 - String mid = StorageManager.current().getOldestArticle();
66.115 - if (mid == null) // No articles in the database
66.116 - {
66.117 - return;
66.118 - }
66.119 -
66.120 - Article art = StorageManager.current().getArticle(mid);
66.121 - long artDate = 0;
66.122 - String dateStr = art.getHeader(Headers.DATE)[0];
66.123 - try
66.124 - {
66.125 - artDate = Date.parse(dateStr) / 1000 / 60 / 60 / 24;
66.126 - }
66.127 - catch (IllegalArgumentException ex)
66.128 - {
66.129 - Log.get().warning("Could not parse date string: " + dateStr + " " + ex);
66.130 - }
66.131 -
66.132 - // Should we delete the message because of its age or because the
66.133 - // article maximum was reached?
66.134 - if (lifetime < 0 || artDate < (new Date().getTime() + lifetime))
66.135 - {
66.136 - StorageManager.current().delete(mid);
66.137 - System.out.println("Deleted: " + mid);
66.138 - }
66.139 - else
66.140 - {
66.141 - Thread.sleep(1000 * 60); // Wait 60 seconds
66.142 - return;
66.143 - }
66.144 - }
66.145 - else
66.146 - {
66.147 - Log.get().info("Lifetime purger is disabled");
66.148 - Thread.sleep(1000 * 60 * 30); // Wait 30 minutes
66.149 - }
66.150 - }
66.151 -
66.152 -}
67.1 --- a/org/sonews/util/Stats.java Sun Aug 29 17:04:25 2010 +0200
67.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
67.3 @@ -1,206 +0,0 @@
67.4 -/*
67.5 - * SONEWS News Server
67.6 - * see AUTHORS for the list of contributors
67.7 - *
67.8 - * This program is free software: you can redistribute it and/or modify
67.9 - * it under the terms of the GNU General Public License as published by
67.10 - * the Free Software Foundation, either version 3 of the License, or
67.11 - * (at your option) any later version.
67.12 - *
67.13 - * This program is distributed in the hope that it will be useful,
67.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
67.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
67.16 - * GNU General Public License for more details.
67.17 - *
67.18 - * You should have received a copy of the GNU General Public License
67.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
67.20 - */
67.21 -
67.22 -package org.sonews.util;
67.23 -
67.24 -import java.util.Calendar;
67.25 -import org.sonews.config.Config;
67.26 -import org.sonews.storage.Channel;
67.27 -import org.sonews.storage.StorageBackendException;
67.28 -import org.sonews.storage.StorageManager;
67.29 -
67.30 -/**
67.31 - * Class that capsulates statistical data gathering.
67.32 - * @author Christian Lins
67.33 - * @since sonews/0.5.0
67.34 - */
67.35 -public final class Stats
67.36 -{
67.37 -
67.38 - public static final byte CONNECTIONS = 1;
67.39 - public static final byte POSTED_NEWS = 2;
67.40 - public static final byte GATEWAYED_NEWS = 3;
67.41 - public static final byte FEEDED_NEWS = 4;
67.42 - public static final byte MLGW_RUNSTART = 5;
67.43 - public static final byte MLGW_RUNEND = 6;
67.44 -
67.45 - private static Stats instance = new Stats();
67.46 -
67.47 - public static Stats getInstance()
67.48 - {
67.49 - return Stats.instance;
67.50 - }
67.51 -
67.52 - private Stats() {}
67.53 -
67.54 - private volatile int connectedClients = 0;
67.55 -
67.56 - /**
67.57 - * A generic method that writes event data to the storage backend.
67.58 - * If event logging is disabled with sonews.eventlog=false this method
67.59 - * simply does nothing.
67.60 - * @param type
67.61 - * @param groupname
67.62 - */
67.63 - private void addEvent(byte type, String groupname)
67.64 - {
67.65 - try
67.66 - {
67.67 - if (Config.inst().get(Config.EVENTLOG, true))
67.68 - {
67.69 -
67.70 - Channel group = Channel.getByName(groupname);
67.71 - if (group != null)
67.72 - {
67.73 - StorageManager.current().addEvent(
67.74 - System.currentTimeMillis(), type, group.getInternalID());
67.75 - }
67.76 - }
67.77 - else
67.78 - {
67.79 - Log.get().info("Group " + groupname + " does not exist.");
67.80 - }
67.81 - }
67.82 - catch (StorageBackendException ex)
67.83 - {
67.84 - ex.printStackTrace();
67.85 - }
67.86 - }
67.87 -
67.88 - public void clientConnect()
67.89 - {
67.90 - this.connectedClients++;
67.91 - }
67.92 -
67.93 - public void clientDisconnect()
67.94 - {
67.95 - this.connectedClients--;
67.96 - }
67.97 -
67.98 - public int connectedClients()
67.99 - {
67.100 - return this.connectedClients;
67.101 - }
67.102 -
67.103 - public int getNumberOfGroups()
67.104 - {
67.105 - try
67.106 - {
67.107 - return StorageManager.current().countGroups();
67.108 - }
67.109 - catch(StorageBackendException ex)
67.110 - {
67.111 - ex.printStackTrace();
67.112 - return -1;
67.113 - }
67.114 - }
67.115 -
67.116 - public int getNumberOfNews()
67.117 - {
67.118 - try
67.119 - {
67.120 - return StorageManager.current().countArticles();
67.121 - }
67.122 - catch(StorageBackendException ex)
67.123 - {
67.124 - ex.printStackTrace();
67.125 - return -1;
67.126 - }
67.127 - }
67.128 -
67.129 - public int getYesterdaysEvents(final byte eventType, final int hour,
67.130 - final Channel group)
67.131 - {
67.132 - // Determine the timestamp values for yesterday and the given hour
67.133 - Calendar cal = Calendar.getInstance();
67.134 - int year = cal.get(Calendar.YEAR);
67.135 - int month = cal.get(Calendar.MONTH);
67.136 - int dayom = cal.get(Calendar.DAY_OF_MONTH) - 1; // Yesterday
67.137 -
67.138 - cal.set(year, month, dayom, hour, 0, 0);
67.139 - long startTimestamp = cal.getTimeInMillis();
67.140 -
67.141 - cal.set(year, month, dayom, hour + 1, 0, 0);
67.142 - long endTimestamp = cal.getTimeInMillis();
67.143 -
67.144 - try
67.145 - {
67.146 - return StorageManager.current()
67.147 - .getEventsCount(eventType, startTimestamp, endTimestamp, group);
67.148 - }
67.149 - catch(StorageBackendException ex)
67.150 - {
67.151 - ex.printStackTrace();
67.152 - return -1;
67.153 - }
67.154 - }
67.155 -
67.156 - public void mailPosted(String groupname)
67.157 - {
67.158 - addEvent(POSTED_NEWS, groupname);
67.159 - }
67.160 -
67.161 - public void mailGatewayed(String groupname)
67.162 - {
67.163 - addEvent(GATEWAYED_NEWS, groupname);
67.164 - }
67.165 -
67.166 - public void mailFeeded(String groupname)
67.167 - {
67.168 - addEvent(FEEDED_NEWS, groupname);
67.169 - }
67.170 -
67.171 - public void mlgwRunStart()
67.172 - {
67.173 - addEvent(MLGW_RUNSTART, "control");
67.174 - }
67.175 -
67.176 - public void mlgwRunEnd()
67.177 - {
67.178 - addEvent(MLGW_RUNEND, "control");
67.179 - }
67.180 -
67.181 - private double perHour(int key, long gid)
67.182 - {
67.183 - try
67.184 - {
67.185 - return StorageManager.current().getEventsPerHour(key, gid);
67.186 - }
67.187 - catch(StorageBackendException ex)
67.188 - {
67.189 - ex.printStackTrace();
67.190 - return -1;
67.191 - }
67.192 - }
67.193 -
67.194 - public double postedPerHour(long gid)
67.195 - {
67.196 - return perHour(POSTED_NEWS, gid);
67.197 - }
67.198 -
67.199 - public double gatewayedPerHour(long gid)
67.200 - {
67.201 - return perHour(GATEWAYED_NEWS, gid);
67.202 - }
67.203 -
67.204 - public double feededPerHour(long gid)
67.205 - {
67.206 - return perHour(FEEDED_NEWS, gid);
67.207 - }
67.208 -
67.209 -}
68.1 --- a/org/sonews/util/StringTemplate.java Sun Aug 29 17:04:25 2010 +0200
68.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
68.3 @@ -1,97 +0,0 @@
68.4 -/*
68.5 - * SONEWS News Server
68.6 - * see AUTHORS for the list of contributors
68.7 - *
68.8 - * This program is free software: you can redistribute it and/or modify
68.9 - * it under the terms of the GNU General Public License as published by
68.10 - * the Free Software Foundation, either version 3 of the License, or
68.11 - * (at your option) any later version.
68.12 - *
68.13 - * This program is distributed in the hope that it will be useful,
68.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
68.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
68.16 - * GNU General Public License for more details.
68.17 - *
68.18 - * You should have received a copy of the GNU General Public License
68.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
68.20 - */
68.21 -
68.22 -package org.sonews.util;
68.23 -
68.24 -import java.util.HashMap;
68.25 -import java.util.Map;
68.26 -
68.27 -/**
68.28 - * Class that allows simple String template handling.
68.29 - * @author Christian Lins
68.30 - * @since sonews/0.5.0
68.31 - */
68.32 -public class StringTemplate
68.33 -{
68.34 -
68.35 - private String str = null;
68.36 - private String templateDelimiter = "%";
68.37 - private Map<String, String> templateValues = new HashMap<String, String>();
68.38 -
68.39 - public StringTemplate(String str, final String templateDelimiter)
68.40 - {
68.41 - if(str == null || templateDelimiter == null)
68.42 - {
68.43 - throw new IllegalArgumentException("null arguments not allowed");
68.44 - }
68.45 -
68.46 - this.str = str;
68.47 - this.templateDelimiter = templateDelimiter;
68.48 - }
68.49 -
68.50 - public StringTemplate(String str)
68.51 - {
68.52 - this(str, "%");
68.53 - }
68.54 -
68.55 - public StringTemplate set(String template, String value)
68.56 - {
68.57 - if(template == null || value == null)
68.58 - {
68.59 - throw new IllegalArgumentException("null arguments not allowed");
68.60 - }
68.61 -
68.62 - this.templateValues.put(template, value);
68.63 - return this;
68.64 - }
68.65 -
68.66 - public StringTemplate set(String template, long value)
68.67 - {
68.68 - return set(template, Long.toString(value));
68.69 - }
68.70 -
68.71 - public StringTemplate set(String template, double value)
68.72 - {
68.73 - return set(template, Double.toString(value));
68.74 - }
68.75 -
68.76 - public StringTemplate set(String template, Object obj)
68.77 - {
68.78 - if(template == null || obj == null)
68.79 - {
68.80 - throw new IllegalArgumentException("null arguments not allowed");
68.81 - }
68.82 -
68.83 - return set(template, obj.toString());
68.84 - }
68.85 -
68.86 - @Override
68.87 - public String toString()
68.88 - {
68.89 - String ret = str;
68.90 -
68.91 - for(String key : this.templateValues.keySet())
68.92 - {
68.93 - String value = this.templateValues.get(key);
68.94 - ret = ret.replace(templateDelimiter + key, value);
68.95 - }
68.96 -
68.97 - return ret;
68.98 - }
68.99 -
68.100 -}
69.1 --- a/org/sonews/util/TimeoutMap.java Sun Aug 29 17:04:25 2010 +0200
69.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
69.3 @@ -1,145 +0,0 @@
69.4 -/*
69.5 - * SONEWS News Server
69.6 - * see AUTHORS for the list of contributors
69.7 - *
69.8 - * This program is free software: you can redistribute it and/or modify
69.9 - * it under the terms of the GNU General Public License as published by
69.10 - * the Free Software Foundation, either version 3 of the License, or
69.11 - * (at your option) any later version.
69.12 - *
69.13 - * This program is distributed in the hope that it will be useful,
69.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
69.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
69.16 - * GNU General Public License for more details.
69.17 - *
69.18 - * You should have received a copy of the GNU General Public License
69.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
69.20 - */
69.21 -
69.22 -package org.sonews.util;
69.23 -
69.24 -import java.util.HashMap;
69.25 -import java.util.HashSet;
69.26 -import java.util.Map;
69.27 -import java.util.Set;
69.28 -import java.util.concurrent.ConcurrentHashMap;
69.29 -
69.30 -/**
69.31 - * Implementation of a Map that will loose its stored values after a
69.32 - * configurable amount of time.
69.33 - * This class may be used to cache config values for example.
69.34 - * @author Christian Lins
69.35 - * @since sonews/0.5.0
69.36 - */
69.37 -public class TimeoutMap<K,V> extends ConcurrentHashMap<K, V>
69.38 -{
69.39 -
69.40 - private static final long serialVersionUID = 453453467700345L;
69.41 -
69.42 - private int timeout = 60000; // 60 sec
69.43 - private transient Map<K, Long> timeoutMap = new HashMap<K, Long>();
69.44 -
69.45 - /**
69.46 - * Constructor.
69.47 - * @param timeout Timeout in milliseconds
69.48 - */
69.49 - public TimeoutMap(final int timeout)
69.50 - {
69.51 - this.timeout = timeout;
69.52 - }
69.53 -
69.54 - /**
69.55 - * Uses default timeout (60 sec).
69.56 - */
69.57 - public TimeoutMap()
69.58 - {
69.59 - }
69.60 -
69.61 - /**
69.62 - *
69.63 - * @param key
69.64 - * @return true if key is still valid.
69.65 - */
69.66 - protected boolean checkTimeOut(Object key)
69.67 - {
69.68 - synchronized(this.timeoutMap)
69.69 - {
69.70 - if(this.timeoutMap.containsKey(key))
69.71 - {
69.72 - long keytime = this.timeoutMap.get(key);
69.73 - if((System.currentTimeMillis() - keytime) < this.timeout)
69.74 - {
69.75 - return true;
69.76 - }
69.77 - else
69.78 - {
69.79 - remove(key);
69.80 - return false;
69.81 - }
69.82 - }
69.83 - else
69.84 - {
69.85 - return false;
69.86 - }
69.87 - }
69.88 - }
69.89 -
69.90 - @Override
69.91 - public boolean containsKey(Object key)
69.92 - {
69.93 - return checkTimeOut(key);
69.94 - }
69.95 -
69.96 - @Override
69.97 - public synchronized V get(Object key)
69.98 - {
69.99 - if(checkTimeOut(key))
69.100 - {
69.101 - return super.get(key);
69.102 - }
69.103 - else
69.104 - {
69.105 - return null;
69.106 - }
69.107 - }
69.108 -
69.109 - @Override
69.110 - public V put(K key, V value)
69.111 - {
69.112 - synchronized(this.timeoutMap)
69.113 - {
69.114 - removeStaleKeys();
69.115 - this.timeoutMap.put(key, System.currentTimeMillis());
69.116 - return super.put(key, value);
69.117 - }
69.118 - }
69.119 -
69.120 - /**
69.121 - * @param arg0
69.122 - * @return
69.123 - */
69.124 - @Override
69.125 - public V remove(Object arg0)
69.126 - {
69.127 - synchronized(this.timeoutMap)
69.128 - {
69.129 - this.timeoutMap.remove(arg0);
69.130 - V val = super.remove(arg0);
69.131 - return val;
69.132 - }
69.133 - }
69.134 -
69.135 - protected void removeStaleKeys()
69.136 - {
69.137 - synchronized(this.timeoutMap)
69.138 - {
69.139 - Set<Object> keySet = new HashSet<Object>(this.timeoutMap.keySet());
69.140 - for(Object key : keySet)
69.141 - {
69.142 - // The key/value is removed by the checkTimeOut() method if true
69.143 - checkTimeOut(key);
69.144 - }
69.145 - }
69.146 - }
69.147 -
69.148 -}
70.1 --- a/org/sonews/util/io/ArticleInputStream.java Sun Aug 29 17:04:25 2010 +0200
70.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
70.3 @@ -1,71 +0,0 @@
70.4 -/*
70.5 - * SONEWS News Server
70.6 - * see AUTHORS for the list of contributors
70.7 - *
70.8 - * This program is free software: you can redistribute it and/or modify
70.9 - * it under the terms of the GNU General Public License as published by
70.10 - * the Free Software Foundation, either version 3 of the License, or
70.11 - * (at your option) any later version.
70.12 - *
70.13 - * This program is distributed in the hope that it will be useful,
70.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
70.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
70.16 - * GNU General Public License for more details.
70.17 - *
70.18 - * You should have received a copy of the GNU General Public License
70.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
70.20 - */
70.21 -
70.22 -package org.sonews.util.io;
70.23 -
70.24 -import java.io.ByteArrayOutputStream;
70.25 -import java.io.IOException;
70.26 -import java.io.InputStream;
70.27 -import java.io.UnsupportedEncodingException;
70.28 -import org.sonews.storage.Article;
70.29 -
70.30 -/**
70.31 - * Capsulates an Article to provide a raw InputStream.
70.32 - * @author Christian Lins
70.33 - * @since sonews/0.5.0
70.34 - */
70.35 -public class ArticleInputStream extends InputStream
70.36 -{
70.37 -
70.38 - private byte[] buf;
70.39 - private int pos = 0;
70.40 -
70.41 - public ArticleInputStream(final Article art)
70.42 - throws IOException, UnsupportedEncodingException
70.43 - {
70.44 - final ByteArrayOutputStream out = new ByteArrayOutputStream();
70.45 - out.write(art.getHeaderSource().getBytes("UTF-8"));
70.46 - out.write("\r\n\r\n".getBytes());
70.47 - out.write(art.getBody()); // Without CRLF
70.48 - out.flush();
70.49 - this.buf = out.toByteArray();
70.50 - }
70.51 -
70.52 - /**
70.53 - * This method reads one byte from the stream. The <code>pos</code>
70.54 - * counter is advanced to the next byte to be read. The byte read is
70.55 - * returned as an int in the range of 0-255. If the stream position
70.56 - * is already at the end of the buffer, no byte is read and a -1 is
70.57 - * returned in order to indicate the end of the stream.
70.58 - *
70.59 - * @return The byte read, or -1 if end of stream
70.60 - */
70.61 - @Override
70.62 - public synchronized int read()
70.63 - {
70.64 - if(pos < buf.length)
70.65 - {
70.66 - return ((int)buf[pos++]) & 0xFF;
70.67 - }
70.68 - else
70.69 - {
70.70 - return -1;
70.71 - }
70.72 - }
70.73 -
70.74 -}
71.1 --- a/org/sonews/util/io/ArticleReader.java Sun Aug 29 17:04:25 2010 +0200
71.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
71.3 @@ -1,135 +0,0 @@
71.4 -/*
71.5 - * SONEWS News Server
71.6 - * see AUTHORS for the list of contributors
71.7 - *
71.8 - * This program is free software: you can redistribute it and/or modify
71.9 - * it under the terms of the GNU General Public License as published by
71.10 - * the Free Software Foundation, either version 3 of the License, or
71.11 - * (at your option) any later version.
71.12 - *
71.13 - * This program is distributed in the hope that it will be useful,
71.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
71.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
71.16 - * GNU General Public License for more details.
71.17 - *
71.18 - * You should have received a copy of the GNU General Public License
71.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
71.20 - */
71.21 -
71.22 -package org.sonews.util.io;
71.23 -
71.24 -import java.io.BufferedInputStream;
71.25 -import java.io.BufferedOutputStream;
71.26 -import java.io.ByteArrayOutputStream;
71.27 -import java.io.IOException;
71.28 -import java.io.InputStream;
71.29 -import java.io.UnsupportedEncodingException;
71.30 -import java.net.Socket;
71.31 -import java.net.UnknownHostException;
71.32 -import org.sonews.config.Config;
71.33 -import org.sonews.util.Log;
71.34 -
71.35 -/**
71.36 - * Reads an news article from a NNTP server.
71.37 - * @author Christian Lins
71.38 - * @since sonews/0.5.0
71.39 - */
71.40 -public class ArticleReader
71.41 -{
71.42 -
71.43 - private BufferedOutputStream out;
71.44 - private BufferedInputStream in;
71.45 - private String messageID;
71.46 -
71.47 - public ArticleReader(String host, int port, String messageID)
71.48 - throws IOException, UnknownHostException
71.49 - {
71.50 - this.messageID = messageID;
71.51 -
71.52 - // Connect to NNTP server
71.53 - Socket socket = new Socket(host, port);
71.54 - this.out = new BufferedOutputStream(socket.getOutputStream());
71.55 - this.in = new BufferedInputStream(socket.getInputStream());
71.56 - String line = readln(this.in);
71.57 - if(!line.startsWith("200 "))
71.58 - {
71.59 - throw new IOException("Invalid hello from server: " + line);
71.60 - }
71.61 - }
71.62 -
71.63 - private boolean eofArticle(byte[] buf)
71.64 - {
71.65 - if(buf.length < 4)
71.66 - {
71.67 - return false;
71.68 - }
71.69 -
71.70 - int l = buf.length - 1;
71.71 - return buf[l-3] == 10 // '*\n'
71.72 - && buf[l-2] == '.' // '.'
71.73 - && buf[l-1] == 13 && buf[l] == 10; // '\r\n'
71.74 - }
71.75 -
71.76 - public byte[] getArticleData()
71.77 - throws IOException, UnsupportedEncodingException
71.78 - {
71.79 - long maxSize = Config.inst().get(Config.ARTICLE_MAXSIZE, 1024) * 1024L;
71.80 -
71.81 - try
71.82 - {
71.83 - this.out.write(("ARTICLE " + this.messageID + "\r\n").getBytes("UTF-8"));
71.84 - this.out.flush();
71.85 -
71.86 - String line = readln(this.in);
71.87 - if(line.startsWith("220 "))
71.88 - {
71.89 - ByteArrayOutputStream buf = new ByteArrayOutputStream();
71.90 -
71.91 - while(!eofArticle(buf.toByteArray()))
71.92 - {
71.93 - for(int b = in.read(); b != 10; b = in.read())
71.94 - {
71.95 - buf.write(b);
71.96 - }
71.97 -
71.98 - buf.write(10);
71.99 - if(buf.size() > maxSize)
71.100 - {
71.101 - Log.get().warning("Skipping message that is too large: " + buf.size());
71.102 - return null;
71.103 - }
71.104 - }
71.105 -
71.106 - return buf.toByteArray();
71.107 - }
71.108 - else
71.109 - {
71.110 - Log.get().warning("ArticleReader: " + line);
71.111 - return null;
71.112 - }
71.113 - }
71.114 - catch(IOException ex)
71.115 - {
71.116 - throw ex;
71.117 - }
71.118 - finally
71.119 - {
71.120 - this.out.write("QUIT\r\n".getBytes("UTF-8"));
71.121 - this.out.flush();
71.122 - this.out.close();
71.123 - }
71.124 - }
71.125 -
71.126 - private String readln(InputStream in)
71.127 - throws IOException
71.128 - {
71.129 - ByteArrayOutputStream buf = new ByteArrayOutputStream();
71.130 - for(int b = in.read(); b != 10 /* \n */; b = in.read())
71.131 - {
71.132 - buf.write(b);
71.133 - }
71.134 -
71.135 - return new String(buf.toByteArray());
71.136 - }
71.137 -
71.138 -}
72.1 --- a/org/sonews/util/io/ArticleWriter.java Sun Aug 29 17:04:25 2010 +0200
72.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
72.3 @@ -1,133 +0,0 @@
72.4 -/*
72.5 - * SONEWS News Server
72.6 - * see AUTHORS for the list of contributors
72.7 - *
72.8 - * This program is free software: you can redistribute it and/or modify
72.9 - * it under the terms of the GNU General Public License as published by
72.10 - * the Free Software Foundation, either version 3 of the License, or
72.11 - * (at your option) any later version.
72.12 - *
72.13 - * This program is distributed in the hope that it will be useful,
72.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
72.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
72.16 - * GNU General Public License for more details.
72.17 - *
72.18 - * You should have received a copy of the GNU General Public License
72.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
72.20 - */
72.21 -
72.22 -package org.sonews.util.io;
72.23 -
72.24 -import java.io.BufferedOutputStream;
72.25 -import java.io.BufferedReader;
72.26 -import java.io.IOException;
72.27 -import java.io.InputStreamReader;
72.28 -import java.io.UnsupportedEncodingException;
72.29 -import java.net.Socket;
72.30 -import java.net.UnknownHostException;
72.31 -import org.sonews.storage.Article;
72.32 -
72.33 -/**
72.34 - * Posts an Article to a NNTP server using the POST command.
72.35 - * @author Christian Lins
72.36 - * @since sonews/0.5.0
72.37 - */
72.38 -public class ArticleWriter
72.39 -{
72.40 -
72.41 - private BufferedOutputStream out;
72.42 - private BufferedReader inr;
72.43 -
72.44 - public ArticleWriter(String host, int port)
72.45 - throws IOException, UnknownHostException
72.46 - {
72.47 - // Connect to NNTP server
72.48 - Socket socket = new Socket(host, port);
72.49 - this.out = new BufferedOutputStream(socket.getOutputStream());
72.50 - this.inr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
72.51 - String line = inr.readLine();
72.52 - if(line == null || !line.startsWith("200 "))
72.53 - {
72.54 - throw new IOException("Invalid hello from server: " + line);
72.55 - }
72.56 - }
72.57 -
72.58 - public void close()
72.59 - throws IOException, UnsupportedEncodingException
72.60 - {
72.61 - this.out.write("QUIT\r\n".getBytes("UTF-8"));
72.62 - this.out.flush();
72.63 - }
72.64 -
72.65 - protected void finishPOST()
72.66 - throws IOException
72.67 - {
72.68 - this.out.write("\r\n.\r\n".getBytes());
72.69 - this.out.flush();
72.70 - String line = inr.readLine();
72.71 - if(line == null || !line.startsWith("240 ") || !line.startsWith("441 "))
72.72 - {
72.73 - throw new IOException(line);
72.74 - }
72.75 - }
72.76 -
72.77 - protected void preparePOST()
72.78 - throws IOException
72.79 - {
72.80 - this.out.write("POST\r\n".getBytes("UTF-8"));
72.81 - this.out.flush();
72.82 -
72.83 - String line = this.inr.readLine();
72.84 - if(line == null || !line.startsWith("340 "))
72.85 - {
72.86 - throw new IOException(line);
72.87 - }
72.88 - }
72.89 -
72.90 - public void writeArticle(Article article)
72.91 - throws IOException, UnsupportedEncodingException
72.92 - {
72.93 - byte[] buf = new byte[512];
72.94 - ArticleInputStream in = new ArticleInputStream(article);
72.95 -
72.96 - preparePOST();
72.97 -
72.98 - int len = in.read(buf);
72.99 - while(len != -1)
72.100 - {
72.101 - writeLine(buf, len);
72.102 - len = in.read(buf);
72.103 - }
72.104 -
72.105 - finishPOST();
72.106 - }
72.107 -
72.108 - /**
72.109 - * Writes the raw content of an article to the remote server. This method
72.110 - * does no charset conversion/handling of any kind so its the preferred
72.111 - * method for sending an article to remote peers.
72.112 - * @param rawArticle
72.113 - * @throws IOException
72.114 - */
72.115 - public void writeArticle(byte[] rawArticle)
72.116 - throws IOException
72.117 - {
72.118 - preparePOST();
72.119 - writeLine(rawArticle, rawArticle.length);
72.120 - finishPOST();
72.121 - }
72.122 -
72.123 - /**
72.124 - * Writes the given buffer to the connect remote server.
72.125 - * @param buffer
72.126 - * @param len
72.127 - * @throws IOException
72.128 - */
72.129 - protected void writeLine(byte[] buffer, int len)
72.130 - throws IOException
72.131 - {
72.132 - this.out.write(buffer, 0, len);
72.133 - this.out.flush();
72.134 - }
72.135 -
72.136 -}
73.1 --- a/org/sonews/util/io/Resource.java Sun Aug 29 17:04:25 2010 +0200
73.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
73.3 @@ -1,132 +0,0 @@
73.4 -/*
73.5 - * SONEWS News Server
73.6 - * see AUTHORS for the list of contributors
73.7 - *
73.8 - * This program is free software: you can redistribute it and/or modify
73.9 - * it under the terms of the GNU General Public License as published by
73.10 - * the Free Software Foundation, either version 3 of the License, or
73.11 - * (at your option) any later version.
73.12 - *
73.13 - * This program is distributed in the hope that it will be useful,
73.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
73.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
73.16 - * GNU General Public License for more details.
73.17 - *
73.18 - * You should have received a copy of the GNU General Public License
73.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
73.20 - */
73.21 -
73.22 -package org.sonews.util.io;
73.23 -
73.24 -import java.io.BufferedReader;
73.25 -import java.io.IOException;
73.26 -import java.io.InputStream;
73.27 -import java.io.InputStreamReader;
73.28 -import java.net.URL;
73.29 -import java.nio.charset.Charset;
73.30 -
73.31 -/**
73.32 - * Provides method for loading of resources.
73.33 - * @author Christian Lins
73.34 - * @since sonews/0.5.0
73.35 - */
73.36 -public final class Resource
73.37 -{
73.38 -
73.39 - /**
73.40 - * Loads a resource and returns it as URL reference.
73.41 - * The Resource's classloader is used to load the resource, not
73.42 - * the System's ClassLoader so it may be safe to use this method
73.43 - * in a sandboxed environment.
73.44 - * @return
73.45 - */
73.46 - public static URL getAsURL(final String name)
73.47 - {
73.48 - if(name == null)
73.49 - {
73.50 - return null;
73.51 - }
73.52 -
73.53 - return Resource.class.getClassLoader().getResource(name);
73.54 - }
73.55 -
73.56 - /**
73.57 - * Loads a resource and returns an InputStream to it.
73.58 - * @param name
73.59 - * @return
73.60 - */
73.61 - public static InputStream getAsStream(String name)
73.62 - {
73.63 - try
73.64 - {
73.65 - URL url = getAsURL(name);
73.66 - if(url == null)
73.67 - {
73.68 - return null;
73.69 - }
73.70 - else
73.71 - {
73.72 - return url.openStream();
73.73 - }
73.74 - }
73.75 - catch(IOException e)
73.76 - {
73.77 - e.printStackTrace();
73.78 - return null;
73.79 - }
73.80 - }
73.81 -
73.82 - /**
73.83 - * Loads a plain text resource.
73.84 - * @param withNewline If false all newlines are removed from the
73.85 - * return String
73.86 - */
73.87 - public static String getAsString(String name, boolean withNewline)
73.88 - {
73.89 - if(name == null)
73.90 - return null;
73.91 -
73.92 - BufferedReader in = null;
73.93 - try
73.94 - {
73.95 - InputStream ins = getAsStream(name);
73.96 - if(ins == null)
73.97 - return null;
73.98 -
73.99 - in = new BufferedReader(
73.100 - new InputStreamReader(ins, Charset.forName("UTF-8")));
73.101 - StringBuffer buf = new StringBuffer();
73.102 -
73.103 - for(;;)
73.104 - {
73.105 - String line = in.readLine();
73.106 - if(line == null)
73.107 - break;
73.108 -
73.109 - buf.append(line);
73.110 - if(withNewline)
73.111 - buf.append('\n');
73.112 - }
73.113 -
73.114 - return buf.toString();
73.115 - }
73.116 - catch(Exception e)
73.117 - {
73.118 - e.printStackTrace();
73.119 - return null;
73.120 - }
73.121 - finally
73.122 - {
73.123 - try
73.124 - {
73.125 - if(in != null)
73.126 - in.close();
73.127 - }
73.128 - catch(IOException ex)
73.129 - {
73.130 - ex.printStackTrace();
73.131 - }
73.132 - }
73.133 - }
73.134 -
73.135 -}
74.1 --- a/org/sonews/util/io/package.html Sun Aug 29 17:04:25 2010 +0200
74.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
74.3 @@ -1,1 +0,0 @@
74.4 -Contains I/O utilitiy classes.
74.5 \ No newline at end of file
75.1 --- a/org/sonews/util/package.html Sun Aug 29 17:04:25 2010 +0200
75.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
75.3 @@ -1,1 +0,0 @@
75.4 -Contains various utility classes.
75.5 \ No newline at end of file
76.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
76.2 +++ b/src/org/sonews/Main.java Sun Aug 29 17:28:58 2010 +0200
76.3 @@ -0,0 +1,198 @@
76.4 +/*
76.5 + * SONEWS News Server
76.6 + * see AUTHORS for the list of contributors
76.7 + *
76.8 + * This program is free software: you can redistribute it and/or modify
76.9 + * it under the terms of the GNU General Public License as published by
76.10 + * the Free Software Foundation, either version 3 of the License, or
76.11 + * (at your option) any later version.
76.12 + *
76.13 + * This program is distributed in the hope that it will be useful,
76.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
76.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
76.16 + * GNU General Public License for more details.
76.17 + *
76.18 + * You should have received a copy of the GNU General Public License
76.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
76.20 + */
76.21 +
76.22 +package org.sonews;
76.23 +
76.24 +import java.sql.Driver;
76.25 +import java.sql.DriverManager;
76.26 +import java.util.Enumeration;
76.27 +import java.util.Date;
76.28 +import java.util.logging.Level;
76.29 +import org.sonews.config.Config;
76.30 +import org.sonews.daemon.ChannelLineBuffers;
76.31 +import org.sonews.daemon.CommandSelector;
76.32 +import org.sonews.daemon.Connections;
76.33 +import org.sonews.daemon.NNTPDaemon;
76.34 +import org.sonews.feed.FeedManager;
76.35 +import org.sonews.mlgw.MailPoller;
76.36 +import org.sonews.storage.StorageBackendException;
76.37 +import org.sonews.storage.StorageManager;
76.38 +import org.sonews.storage.StorageProvider;
76.39 +import org.sonews.util.Log;
76.40 +import org.sonews.util.Purger;
76.41 +import org.sonews.util.io.Resource;
76.42 +
76.43 +/**
76.44 + * Startup class of the daemon.
76.45 + * @author Christian Lins
76.46 + * @since sonews/0.5.0
76.47 + */
76.48 +public final class Main
76.49 +{
76.50 +
76.51 + private Main()
76.52 + {
76.53 + }
76.54 +
76.55 + /** Version information of the sonews daemon */
76.56 + public static final String VERSION = "sonews/1.1.0";
76.57 + public static final Date STARTDATE = new Date();
76.58 +
76.59 + /**
76.60 + * The main entrypoint.
76.61 + * @param args
76.62 + * @throws Exception
76.63 + */
76.64 + public static void main(String[] args) throws Exception
76.65 + {
76.66 + System.out.println(VERSION);
76.67 + Thread.currentThread().setName("Mainthread");
76.68 +
76.69 + // Command line arguments
76.70 + boolean feed = false; // Enable feeding?
76.71 + boolean mlgw = false; // Enable Mailinglist gateway?
76.72 + int port = -1;
76.73 +
76.74 + for(int n = 0; n < args.length; n++)
76.75 + {
76.76 + if(args[n].equals("-c") || args[n].equals("-config"))
76.77 + {
76.78 + Config.inst().set(Config.LEVEL_CLI, Config.CONFIGFILE, args[++n]);
76.79 + System.out.println("Using config file " + args[n]);
76.80 + }
76.81 + else if(args[n].equals("-dumpjdbcdriver"))
76.82 + {
76.83 + System.out.println("Available JDBC drivers:");
76.84 + Enumeration<Driver> drvs = DriverManager.getDrivers();
76.85 + while(drvs.hasMoreElements())
76.86 + {
76.87 + System.out.println(drvs.nextElement());
76.88 + }
76.89 + return;
76.90 + }
76.91 + else if(args[n].equals("-feed"))
76.92 + {
76.93 + feed = true;
76.94 + }
76.95 + else if(args[n].equals("-h") || args[n].equals("-help"))
76.96 + {
76.97 + printArguments();
76.98 + return;
76.99 + }
76.100 + else if(args[n].equals("-mlgw"))
76.101 + {
76.102 + mlgw = true;
76.103 + }
76.104 + else if(args[n].equals("-p"))
76.105 + {
76.106 + port = Integer.parseInt(args[++n]);
76.107 + }
76.108 + else if(args[n].equals("-plugin"))
76.109 + {
76.110 + System.out.println("Warning: -plugin-storage is not implemented!");
76.111 + }
76.112 + else if(args[n].equals("-plugin-command"))
76.113 + {
76.114 + try
76.115 + {
76.116 + CommandSelector.addCommandHandler(args[++n]);
76.117 + }
76.118 + catch(Exception ex)
76.119 + {
76.120 + Log.get().warning("Could not load command plugin: " + args[n]);
76.121 + Log.get().log(Level.INFO, "Main.java", ex);
76.122 + }
76.123 + }
76.124 + else if(args[n].equals("-plugin-storage"))
76.125 + {
76.126 + System.out.println("Warning: -plugin-storage is not implemented!");
76.127 + }
76.128 + else if(args[n].equals("-v") || args[n].equals("-version"))
76.129 + {
76.130 + // Simply return as the version info is already printed above
76.131 + return;
76.132 + }
76.133 + }
76.134 +
76.135 + // Try to load the JDBCDatabase;
76.136 + // Do NOT USE BackendConfig or Log classes before this point because they require
76.137 + // a working JDBCDatabase connection.
76.138 + try
76.139 + {
76.140 + StorageProvider sprov =
76.141 + StorageManager.loadProvider("org.sonews.storage.impl.JDBCDatabaseProvider");
76.142 + StorageManager.enableProvider(sprov);
76.143 +
76.144 + // Make sure some elementary groups are existing
76.145 + if(!StorageManager.current().isGroupExisting("control"))
76.146 + {
76.147 + StorageManager.current().addGroup("control", 0);
76.148 + Log.get().info("Group 'control' created.");
76.149 + }
76.150 + }
76.151 + catch(StorageBackendException ex)
76.152 + {
76.153 + ex.printStackTrace();
76.154 + System.err.println("Database initialization failed with " + ex.toString());
76.155 + System.err.println("Make sure you have specified the correct database" +
76.156 + " settings in sonews.conf!");
76.157 + return;
76.158 + }
76.159 +
76.160 + ChannelLineBuffers.allocateDirect();
76.161 +
76.162 + // Add shutdown hook
76.163 + Runtime.getRuntime().addShutdownHook(new ShutdownHook());
76.164 +
76.165 + // Start the listening daemon
76.166 + if(port <= 0)
76.167 + {
76.168 + port = Config.inst().get(Config.PORT, 119);
76.169 + }
76.170 + final NNTPDaemon daemon = NNTPDaemon.createInstance(port);
76.171 + daemon.start();
76.172 +
76.173 + // Start Connections purger thread...
76.174 + Connections.getInstance().start();
76.175 +
76.176 + // Start mailinglist gateway...
76.177 + if(mlgw)
76.178 + {
76.179 + new MailPoller().start();
76.180 + }
76.181 +
76.182 + // Start feeds
76.183 + if(feed)
76.184 + {
76.185 + FeedManager.startFeeding();
76.186 + }
76.187 +
76.188 + Purger purger = new Purger();
76.189 + purger.start();
76.190 +
76.191 + // Wait for main thread to exit (setDaemon(false))
76.192 + daemon.join();
76.193 + }
76.194 +
76.195 + private static void printArguments()
76.196 + {
76.197 + String usage = Resource.getAsString("helpers/usage", true);
76.198 + System.out.println(usage);
76.199 + }
76.200 +
76.201 +}
77.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
77.2 +++ b/src/org/sonews/ShutdownHook.java Sun Aug 29 17:28:58 2010 +0200
77.3 @@ -0,0 +1,84 @@
77.4 +/*
77.5 + * SONEWS News Server
77.6 + * see AUTHORS for the list of contributors
77.7 + *
77.8 + * This program is free software: you can redistribute it and/or modify
77.9 + * it under the terms of the GNU General Public License as published by
77.10 + * the Free Software Foundation, either version 3 of the License, or
77.11 + * (at your option) any later version.
77.12 + *
77.13 + * This program is distributed in the hope that it will be useful,
77.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
77.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
77.16 + * GNU General Public License for more details.
77.17 + *
77.18 + * You should have received a copy of the GNU General Public License
77.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
77.20 + */
77.21 +
77.22 +package org.sonews;
77.23 +
77.24 +import java.sql.SQLException;
77.25 +import java.util.Map;
77.26 +import org.sonews.daemon.AbstractDaemon;
77.27 +
77.28 +/**
77.29 + * Will force all other threads to shutdown cleanly.
77.30 + * @author Christian Lins
77.31 + * @since sonews/0.5.0
77.32 + */
77.33 +class ShutdownHook extends Thread
77.34 +{
77.35 +
77.36 + /**
77.37 + * Called when the JVM exits.
77.38 + */
77.39 + @Override
77.40 + public void run()
77.41 + {
77.42 + System.out.println("sonews: Trying to shutdown all threads...");
77.43 +
77.44 + Map<Thread, StackTraceElement[]> threadsMap = Thread.getAllStackTraces();
77.45 + for(Thread thread : threadsMap.keySet())
77.46 + {
77.47 + // Interrupt the thread if it's a AbstractDaemon
77.48 + AbstractDaemon daemon;
77.49 + if(thread instanceof AbstractDaemon && thread.isAlive())
77.50 + {
77.51 + try
77.52 + {
77.53 + daemon = (AbstractDaemon)thread;
77.54 + daemon.shutdownNow();
77.55 + }
77.56 + catch(SQLException ex)
77.57 + {
77.58 + System.out.println("sonews: " + ex);
77.59 + }
77.60 + }
77.61 + }
77.62 +
77.63 + for(Thread thread : threadsMap.keySet())
77.64 + {
77.65 + AbstractDaemon daemon;
77.66 + if(thread instanceof AbstractDaemon && thread.isAlive())
77.67 + {
77.68 + daemon = (AbstractDaemon)thread;
77.69 + System.out.println("sonews: Waiting for " + daemon + " to exit...");
77.70 + try
77.71 + {
77.72 + daemon.join(500);
77.73 + }
77.74 + catch(InterruptedException ex)
77.75 + {
77.76 + System.out.println(ex.getLocalizedMessage());
77.77 + }
77.78 + }
77.79 + }
77.80 +
77.81 + // We have notified all not-sleeping AbstractDaemons of the shutdown;
77.82 + // all other threads can be simply purged on VM shutdown
77.83 +
77.84 + System.out.println("sonews: Clean shutdown.");
77.85 + }
77.86 +
77.87 +}
78.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
78.2 +++ b/src/org/sonews/acl/AccessControl.java Sun Aug 29 17:28:58 2010 +0200
78.3 @@ -0,0 +1,31 @@
78.4 +/*
78.5 + * SONEWS News Server
78.6 + * see AUTHORS for the list of contributors
78.7 + *
78.8 + * This program is free software: you can redistribute it and/or modify
78.9 + * it under the terms of the GNU General Public License as published by
78.10 + * the Free Software Foundation, either version 3 of the License, or
78.11 + * (at your option) any later version.
78.12 + *
78.13 + * This program is distributed in the hope that it will be useful,
78.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
78.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
78.16 + * GNU General Public License for more details.
78.17 + *
78.18 + * You should have received a copy of the GNU General Public License
78.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
78.20 + */
78.21 +
78.22 +package org.sonews.acl;
78.23 +
78.24 +/**
78.25 + *
78.26 + * @author Christian Lins
78.27 + * @since sonews/1.1
78.28 + */
78.29 +public interface AccessControl
78.30 +{
78.31 +
78.32 + boolean hasPermission(String user, char[] secret, String permission);
78.33 +
78.34 +}
79.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
79.2 +++ b/src/org/sonews/acl/AuthInfoCommand.java Sun Aug 29 17:28:58 2010 +0200
79.3 @@ -0,0 +1,64 @@
79.4 +/*
79.5 + * SONEWS News Server
79.6 + * see AUTHORS for the list of contributors
79.7 + *
79.8 + * This program is free software: you can redistribute it and/or modify
79.9 + * it under the terms of the GNU General Public License as published by
79.10 + * the Free Software Foundation, either version 3 of the License, or
79.11 + * (at your option) any later version.
79.12 + *
79.13 + * This program is distributed in the hope that it will be useful,
79.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
79.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
79.16 + * GNU General Public License for more details.
79.17 + *
79.18 + * You should have received a copy of the GNU General Public License
79.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
79.20 + */
79.21 +
79.22 +package org.sonews.acl;
79.23 +
79.24 +import java.io.IOException;
79.25 +import org.sonews.daemon.NNTPConnection;
79.26 +import org.sonews.daemon.command.Command;
79.27 +import org.sonews.storage.StorageBackendException;
79.28 +
79.29 +/**
79.30 + *
79.31 + * @author Christian Lins
79.32 + * @since sonews/1.1
79.33 + */
79.34 +public class AuthInfoCommand implements Command
79.35 +{
79.36 +
79.37 + @Override
79.38 + public String[] getSupportedCommandStrings()
79.39 + {
79.40 + throw new UnsupportedOperationException("Not supported yet.");
79.41 + }
79.42 +
79.43 + @Override
79.44 + public boolean hasFinished()
79.45 + {
79.46 + throw new UnsupportedOperationException("Not supported yet.");
79.47 + }
79.48 +
79.49 + @Override
79.50 + public String impliedCapability()
79.51 + {
79.52 + throw new UnsupportedOperationException("Not supported yet.");
79.53 + }
79.54 +
79.55 + @Override
79.56 + public boolean isStateful()
79.57 + {
79.58 + throw new UnsupportedOperationException("Not supported yet.");
79.59 + }
79.60 +
79.61 + @Override
79.62 + public void processLine(NNTPConnection conn, String line, byte[] rawLine) throws IOException, StorageBackendException
79.63 + {
79.64 + throw new UnsupportedOperationException("Not supported yet.");
79.65 + }
79.66 +
79.67 +}
80.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
80.2 +++ b/src/org/sonews/config/AbstractConfig.java Sun Aug 29 17:28:58 2010 +0200
80.3 @@ -0,0 +1,57 @@
80.4 +/*
80.5 + * SONEWS News Server
80.6 + * see AUTHORS for the list of contributors
80.7 + *
80.8 + * This program is free software: you can redistribute it and/or modify
80.9 + * it under the terms of the GNU General Public License as published by
80.10 + * the Free Software Foundation, either version 3 of the License, or
80.11 + * (at your option) any later version.
80.12 + *
80.13 + * This program is distributed in the hope that it will be useful,
80.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
80.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
80.16 + * GNU General Public License for more details.
80.17 + *
80.18 + * You should have received a copy of the GNU General Public License
80.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
80.20 + */
80.21 +
80.22 +package org.sonews.config;
80.23 +
80.24 +/**
80.25 + * Base class for Config and BootstrapConfig.
80.26 + * @author Christian Lins
80.27 + * @since sonews/0.5.0
80.28 + */
80.29 +public abstract class AbstractConfig
80.30 +{
80.31 +
80.32 + public abstract String get(String key, String defVal);
80.33 +
80.34 + public int get(final String key, final int defVal)
80.35 + {
80.36 + return Integer.parseInt(
80.37 + get(key, Integer.toString(defVal)));
80.38 + }
80.39 +
80.40 + public boolean get(String key, boolean defVal)
80.41 + {
80.42 + String val = get(key, Boolean.toString(defVal));
80.43 + return Boolean.parseBoolean(val);
80.44 + }
80.45 +
80.46 + /**
80.47 + * Returns a long config value specified via the given key.
80.48 + * @param key
80.49 + * @param defVal
80.50 + * @return
80.51 + */
80.52 + public long get(String key, long defVal)
80.53 + {
80.54 + String val = get(key, Long.toString(defVal));
80.55 + return Long.parseLong(val);
80.56 + }
80.57 +
80.58 + protected abstract void set(String key, String val);
80.59 +
80.60 +}
81.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
81.2 +++ b/src/org/sonews/config/BackendConfig.java Sun Aug 29 17:28:58 2010 +0200
81.3 @@ -0,0 +1,115 @@
81.4 +/*
81.5 + * SONEWS News Server
81.6 + * see AUTHORS for the list of contributors
81.7 + *
81.8 + * This program is free software: you can redistribute it and/or modify
81.9 + * it under the terms of the GNU General Public License as published by
81.10 + * the Free Software Foundation, either version 3 of the License, or
81.11 + * (at your option) any later version.
81.12 + *
81.13 + * This program is distributed in the hope that it will be useful,
81.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
81.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
81.16 + * GNU General Public License for more details.
81.17 + *
81.18 + * You should have received a copy of the GNU General Public License
81.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
81.20 + */
81.21 +
81.22 +package org.sonews.config;
81.23 +
81.24 +import java.util.logging.Level;
81.25 +import org.sonews.util.Log;
81.26 +import org.sonews.storage.StorageBackendException;
81.27 +import org.sonews.storage.StorageManager;
81.28 +import org.sonews.util.TimeoutMap;
81.29 +
81.30 +/**
81.31 + * Provides access to the program wide configuration that is stored within
81.32 + * the server's database.
81.33 + * @author Christian Lins
81.34 + * @since sonews/0.5.0
81.35 + */
81.36 +class BackendConfig extends AbstractConfig
81.37 +{
81.38 +
81.39 + private static BackendConfig instance = new BackendConfig();
81.40 +
81.41 + public static BackendConfig getInstance()
81.42 + {
81.43 + return instance;
81.44 + }
81.45 +
81.46 + private final TimeoutMap<String, String> values
81.47 + = new TimeoutMap<String, String>();
81.48 +
81.49 + private BackendConfig()
81.50 + {
81.51 + super();
81.52 + }
81.53 +
81.54 + /**
81.55 + * Returns the config value for the given key or the defaultValue if the
81.56 + * key is not found in config.
81.57 + * @param key
81.58 + * @param defaultValue
81.59 + * @return
81.60 + */
81.61 + @Override
81.62 + public String get(String key, String defaultValue)
81.63 + {
81.64 + try
81.65 + {
81.66 + String configValue = values.get(key);
81.67 + if(configValue == null)
81.68 + {
81.69 + if(StorageManager.current() == null)
81.70 + {
81.71 + Log.get().warning("BackendConfig not available, using default.");
81.72 + return defaultValue;
81.73 + }
81.74 +
81.75 + configValue = StorageManager.current().getConfigValue(key);
81.76 + if(configValue == null)
81.77 + {
81.78 + return defaultValue;
81.79 + }
81.80 + else
81.81 + {
81.82 + values.put(key, configValue);
81.83 + return configValue;
81.84 + }
81.85 + }
81.86 + else
81.87 + {
81.88 + return configValue;
81.89 + }
81.90 + }
81.91 + catch(StorageBackendException ex)
81.92 + {
81.93 + Log.get().log(Level.SEVERE, "Storage backend problem", ex);
81.94 + return defaultValue;
81.95 + }
81.96 + }
81.97 +
81.98 + /**
81.99 + * Sets the config value which is identified by the given key.
81.100 + * @param key
81.101 + * @param value
81.102 + */
81.103 + public void set(String key, String value)
81.104 + {
81.105 + values.put(key, value);
81.106 +
81.107 + try
81.108 + {
81.109 + // Write values to database
81.110 + StorageManager.current().setConfigValue(key, value);
81.111 + }
81.112 + catch(StorageBackendException ex)
81.113 + {
81.114 + ex.printStackTrace();
81.115 + }
81.116 + }
81.117 +
81.118 +}
82.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
82.2 +++ b/src/org/sonews/config/CommandLineConfig.java Sun Aug 29 17:28:58 2010 +0200
82.3 @@ -0,0 +1,64 @@
82.4 +/*
82.5 + * SONEWS News Server
82.6 + * see AUTHORS for the list of contributors
82.7 + *
82.8 + * This program is free software: you can redistribute it and/or modify
82.9 + * it under the terms of the GNU General Public License as published by
82.10 + * the Free Software Foundation, either version 3 of the License, or
82.11 + * (at your option) any later version.
82.12 + *
82.13 + * This program is distributed in the hope that it will be useful,
82.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
82.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
82.16 + * GNU General Public License for more details.
82.17 + *
82.18 + * You should have received a copy of the GNU General Public License
82.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
82.20 + */
82.21 +
82.22 +package org.sonews.config;
82.23 +
82.24 +import java.util.Map;
82.25 +import java.util.HashMap;
82.26 +
82.27 +/**
82.28 + *
82.29 + * @author Christian Lins
82.30 + */
82.31 +class CommandLineConfig extends AbstractConfig
82.32 +{
82.33 +
82.34 + private static final CommandLineConfig instance = new CommandLineConfig();
82.35 +
82.36 + public static CommandLineConfig getInstance()
82.37 + {
82.38 + return instance;
82.39 + }
82.40 +
82.41 + private final Map<String, String> values = new HashMap<String, String>();
82.42 +
82.43 + private CommandLineConfig() {}
82.44 +
82.45 + @Override
82.46 + public String get(String key, String def)
82.47 + {
82.48 + synchronized(this.values)
82.49 + {
82.50 + if(this.values.containsKey(key))
82.51 + {
82.52 + def = this.values.get(key);
82.53 + }
82.54 + }
82.55 + return def;
82.56 + }
82.57 +
82.58 + @Override
82.59 + public void set(String key, String val)
82.60 + {
82.61 + synchronized(this.values)
82.62 + {
82.63 + this.values.put(key, val);
82.64 + }
82.65 + }
82.66 +
82.67 +}
83.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
83.2 +++ b/src/org/sonews/config/Config.java Sun Aug 29 17:28:58 2010 +0200
83.3 @@ -0,0 +1,175 @@
83.4 +/*
83.5 + * SONEWS News Server
83.6 + * see AUTHORS for the list of contributors
83.7 + *
83.8 + * This program is free software: you can redistribute it and/or modify
83.9 + * it under the terms of the GNU General Public License as published by
83.10 + * the Free Software Foundation, either version 3 of the License, or
83.11 + * (at your option) any later version.
83.12 + *
83.13 + * This program is distributed in the hope that it will be useful,
83.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
83.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
83.16 + * GNU General Public License for more details.
83.17 + *
83.18 + * You should have received a copy of the GNU General Public License
83.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
83.20 + */
83.21 +
83.22 +package org.sonews.config;
83.23 +
83.24 +/**
83.25 + * Configuration facade class.
83.26 + * @author Christian Lins
83.27 + * @since sonews/1.0
83.28 + */
83.29 +public class Config extends AbstractConfig
83.30 +{
83.31 +
83.32 + public static final int LEVEL_CLI = 1;
83.33 + public static final int LEVEL_FILE = 2;
83.34 + public static final int LEVEL_BACKEND = 3;
83.35 +
83.36 + public static final String CONFIGFILE = "sonews.configfile";
83.37 +
83.38 + /** BackendConfig key constant. Value is the maximum article size in kilobytes. */
83.39 + public static final String ARTICLE_MAXSIZE = "sonews.article.maxsize";
83.40 +
83.41 + /** BackendConfig key constant. Value: Amount of news that are feeded per run. */
83.42 + public static final String EVENTLOG = "sonews.eventlog";
83.43 + public static final String FEED_NEWSPERRUN = "sonews.feed.newsperrun";
83.44 + public static final String FEED_PULLINTERVAL = "sonews.feed.pullinterval";
83.45 + public static final String HOSTNAME = "sonews.hostname";
83.46 + public static final String PORT = "sonews.port";
83.47 + public static final String TIMEOUT = "sonews.timeout";
83.48 + public static final String LOGLEVEL = "sonews.loglevel";
83.49 + public static final String MLPOLL_DELETEUNKNOWN = "sonews.mlpoll.deleteunknown";
83.50 + public static final String MLPOLL_HOST = "sonews.mlpoll.host";
83.51 + public static final String MLPOLL_PASSWORD = "sonews.mlpoll.password";
83.52 + public static final String MLPOLL_USER = "sonews.mlpoll.user";
83.53 + public static final String MLSEND_ADDRESS = "sonews.mlsend.address";
83.54 + public static final String MLSEND_RW_FROM = "sonews.mlsend.rewrite.from";
83.55 + public static final String MLSEND_RW_SENDER = "sonews.mlsend.rewrite.sender";
83.56 + public static final String MLSEND_HOST = "sonews.mlsend.host";
83.57 + public static final String MLSEND_PASSWORD = "sonews.mlsend.password";
83.58 + public static final String MLSEND_PORT = "sonews.mlsend.port";
83.59 + public static final String MLSEND_USER = "sonews.mlsend.user";
83.60 +
83.61 + /** Key constant. If value is "true" every I/O is written to logfile
83.62 + * (which is a lot!)
83.63 + */
83.64 + public static final String DEBUG = "sonews.debug";
83.65 +
83.66 + /** Key constant. Value is classname of the JDBC driver */
83.67 + public static final String STORAGE_DBMSDRIVER = "sonews.storage.dbmsdriver";
83.68 +
83.69 + /** Key constant. Value is JDBC connect String to the database. */
83.70 + public static final String STORAGE_DATABASE = "sonews.storage.database";
83.71 +
83.72 + /** Key constant. Value is the username for the DBMS. */
83.73 + public static final String STORAGE_USER = "sonews.storage.user";
83.74 +
83.75 + /** Key constant. Value is the password for the DBMS. */
83.76 + public static final String STORAGE_PASSWORD = "sonews.storage.password";
83.77 +
83.78 + /** Key constant. Value is the name of the host which is allowed to use the
83.79 + * XDAEMON command; default: "localhost" */
83.80 + public static final String XDAEMON_HOST = "sonews.xdaemon.host";
83.81 +
83.82 + /** The config key for the filename of the logfile */
83.83 + public static final String LOGFILE = "sonews.log";
83.84 +
83.85 + public static final String[] AVAILABLE_KEYS = {
83.86 + ARTICLE_MAXSIZE,
83.87 + EVENTLOG,
83.88 + FEED_NEWSPERRUN,
83.89 + FEED_PULLINTERVAL,
83.90 + HOSTNAME,
83.91 + MLPOLL_DELETEUNKNOWN,
83.92 + MLPOLL_HOST,
83.93 + MLPOLL_PASSWORD,
83.94 + MLPOLL_USER,
83.95 + MLSEND_ADDRESS,
83.96 + MLSEND_HOST,
83.97 + MLSEND_PASSWORD,
83.98 + MLSEND_PORT,
83.99 + MLSEND_RW_FROM,
83.100 + MLSEND_RW_SENDER,
83.101 + MLSEND_USER,
83.102 + PORT,
83.103 + TIMEOUT,
83.104 + XDAEMON_HOST
83.105 + };
83.106 +
83.107 + private static Config instance = new Config();
83.108 +
83.109 + public static Config inst()
83.110 + {
83.111 + return instance;
83.112 + }
83.113 +
83.114 + private Config(){}
83.115 +
83.116 + @Override
83.117 + public String get(String key, String def)
83.118 + {
83.119 + String val = CommandLineConfig.getInstance().get(key, null);
83.120 +
83.121 + if(val == null)
83.122 + {
83.123 + val = FileConfig.getInstance().get(key, null);
83.124 + }
83.125 +
83.126 + if(val == null)
83.127 + {
83.128 + val = BackendConfig.getInstance().get(key, def);
83.129 + }
83.130 +
83.131 + return val;
83.132 + }
83.133 +
83.134 + public String get(int maxLevel, String key, String def)
83.135 + {
83.136 + String val = CommandLineConfig.getInstance().get(key, null);
83.137 +
83.138 + if(val == null && maxLevel >= LEVEL_FILE)
83.139 + {
83.140 + val = FileConfig.getInstance().get(key, null);
83.141 + if(val == null && maxLevel >= LEVEL_BACKEND)
83.142 + {
83.143 + val = BackendConfig.getInstance().get(key, def);
83.144 + }
83.145 + }
83.146 +
83.147 + return val != null ? val : def;
83.148 + }
83.149 +
83.150 + @Override
83.151 + public void set(String key, String val)
83.152 + {
83.153 + set(LEVEL_BACKEND, key, val);
83.154 + }
83.155 +
83.156 + public void set(int level, String key, String val)
83.157 + {
83.158 + switch(level)
83.159 + {
83.160 + case LEVEL_CLI:
83.161 + {
83.162 + CommandLineConfig.getInstance().set(key, val);
83.163 + break;
83.164 + }
83.165 + case LEVEL_FILE:
83.166 + {
83.167 + FileConfig.getInstance().set(key, val);
83.168 + break;
83.169 + }
83.170 + case LEVEL_BACKEND:
83.171 + {
83.172 + BackendConfig.getInstance().set(key, val);
83.173 + break;
83.174 + }
83.175 + }
83.176 + }
83.177 +
83.178 +}
84.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
84.2 +++ b/src/org/sonews/config/FileConfig.java Sun Aug 29 17:28:58 2010 +0200
84.3 @@ -0,0 +1,170 @@
84.4 +/*
84.5 + * SONEWS News Server
84.6 + * see AUTHORS for the list of contributors
84.7 + *
84.8 + * This program is free software: you can redistribute it and/or modify
84.9 + * it under the terms of the GNU General Public License as published by
84.10 + * the Free Software Foundation, either version 3 of the License, or
84.11 + * (at your option) any later version.
84.12 + *
84.13 + * This program is distributed in the hope that it will be useful,
84.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
84.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
84.16 + * GNU General Public License for more details.
84.17 + *
84.18 + * You should have received a copy of the GNU General Public License
84.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
84.20 + */
84.21 +
84.22 +package org.sonews.config;
84.23 +
84.24 +import java.io.FileInputStream;
84.25 +import java.io.FileNotFoundException;
84.26 +import java.io.FileOutputStream;
84.27 +import java.io.IOException;
84.28 +import java.util.Properties;
84.29 +
84.30 +/**
84.31 + * Manages the bootstrap configuration. It MUST contain all config values
84.32 + * that are needed to establish a database connection.
84.33 + * For further configuration values use the Config class instead as that class
84.34 + * stores its values within the database.
84.35 + * @author Christian Lins
84.36 + * @since sonews/0.5.0
84.37 + */
84.38 +class FileConfig extends AbstractConfig
84.39 +{
84.40 +
84.41 + private static final Properties defaultConfig = new Properties();
84.42 +
84.43 + private static FileConfig instance = null;
84.44 +
84.45 + static
84.46 + {
84.47 + // Set some default values
84.48 + defaultConfig.setProperty(Config.STORAGE_DATABASE, "jdbc:mysql://localhost/sonews");
84.49 + defaultConfig.setProperty(Config.STORAGE_DBMSDRIVER, "com.mysql.jdbc.Driver");
84.50 + defaultConfig.setProperty(Config.STORAGE_USER, "sonews_user");
84.51 + defaultConfig.setProperty(Config.STORAGE_PASSWORD, "mysecret");
84.52 + defaultConfig.setProperty(Config.DEBUG, "false");
84.53 + }
84.54 +
84.55 + /**
84.56 + * Note: this method is not thread-safe
84.57 + * @return A Config instance
84.58 + */
84.59 + public static synchronized FileConfig getInstance()
84.60 + {
84.61 + if(instance == null)
84.62 + {
84.63 + instance = new FileConfig();
84.64 + }
84.65 + return instance;
84.66 + }
84.67 +
84.68 + // Every config instance is initialized with the default values.
84.69 + private final Properties settings = (Properties)defaultConfig.clone();
84.70 +
84.71 + /**
84.72 + * Config is a singelton class with only one instance at time.
84.73 + * So the constructor is private to prevent the creation of more
84.74 + * then one Config instance.
84.75 + * @see Config.getInstance() to retrieve an instance of Config
84.76 + */
84.77 + private FileConfig()
84.78 + {
84.79 + try
84.80 + {
84.81 + // Load settings from file
84.82 + load();
84.83 + }
84.84 + catch(IOException ex)
84.85 + {
84.86 + ex.printStackTrace();
84.87 + }
84.88 + }
84.89 +
84.90 + /**
84.91 + * Loads the configuration from the config file. By default this is done
84.92 + * by the (private) constructor but it can be useful to reload the config
84.93 + * by invoking this method.
84.94 + * @throws IOException
84.95 + */
84.96 + public void load()
84.97 + throws IOException
84.98 + {
84.99 + FileInputStream in = null;
84.100 +
84.101 + try
84.102 + {
84.103 + in = new FileInputStream(
84.104 + Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
84.105 + settings.load(in);
84.106 + }
84.107 + catch (FileNotFoundException e)
84.108 + {
84.109 + // MUST NOT use Log otherwise endless loop
84.110 + System.err.println(e.getMessage());
84.111 + save();
84.112 + }
84.113 + finally
84.114 + {
84.115 + if(in != null)
84.116 + in.close();
84.117 + }
84.118 + }
84.119 +
84.120 + /**
84.121 + * Saves this Config to the config file. By default this is done
84.122 + * at program end.
84.123 + * @throws FileNotFoundException
84.124 + * @throws IOException
84.125 + */
84.126 + public void save() throws FileNotFoundException, IOException
84.127 + {
84.128 + FileOutputStream out = null;
84.129 + try
84.130 + {
84.131 + out = new FileOutputStream(
84.132 + Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
84.133 + settings.store(out, "SONEWS Config File");
84.134 + out.flush();
84.135 + }
84.136 + catch(IOException ex)
84.137 + {
84.138 + throw ex;
84.139 + }
84.140 + finally
84.141 + {
84.142 + if(out != null)
84.143 + out.close();
84.144 + }
84.145 + }
84.146 +
84.147 + /**
84.148 + * Returns the value that is stored within this config
84.149 + * identified by the given key. If the key cannot be found
84.150 + * the default value is returned.
84.151 + * @param key Key to identify the value.
84.152 + * @param def The default value that is returned if the key
84.153 + * is not found in this Config.
84.154 + * @return
84.155 + */
84.156 + @Override
84.157 + public String get(String key, String def)
84.158 + {
84.159 + return settings.getProperty(key, def);
84.160 + }
84.161 +
84.162 + /**
84.163 + * Sets the value for a given key.
84.164 + * @param key
84.165 + * @param value
84.166 + */
84.167 + @Override
84.168 + public void set(final String key, final String value)
84.169 + {
84.170 + settings.setProperty(key, value);
84.171 + }
84.172 +
84.173 +}
85.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
85.2 +++ b/src/org/sonews/daemon/AbstractDaemon.java Sun Aug 29 17:28:58 2010 +0200
85.3 @@ -0,0 +1,101 @@
85.4 +/*
85.5 + * SONEWS News Server
85.6 + * see AUTHORS for the list of contributors
85.7 + *
85.8 + * This program is free software: you can redistribute it and/or modify
85.9 + * it under the terms of the GNU General Public License as published by
85.10 + * the Free Software Foundation, either version 3 of the License, or
85.11 + * (at your option) any later version.
85.12 + *
85.13 + * This program is distributed in the hope that it will be useful,
85.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
85.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
85.16 + * GNU General Public License for more details.
85.17 + *
85.18 + * You should have received a copy of the GNU General Public License
85.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
85.20 + */
85.21 +
85.22 +package org.sonews.daemon;
85.23 +
85.24 +import java.sql.SQLException;
85.25 +import org.sonews.storage.StorageManager;
85.26 +import org.sonews.util.Log;
85.27 +
85.28 +/**
85.29 + * Base class of all sonews threads.
85.30 + * Instances of this class will be automatically registered at the ShutdownHook
85.31 + * to be cleanly exited when the server is forced to exit.
85.32 + * @author Christian Lins
85.33 + * @since sonews/0.5.0
85.34 + */
85.35 +public abstract class AbstractDaemon extends Thread
85.36 +{
85.37 +
85.38 + /** This variable is write synchronized through setRunning */
85.39 + private boolean isRunning = false;
85.40 +
85.41 + /**
85.42 + * Protected constructor. Will be called by derived classes.
85.43 + */
85.44 + protected AbstractDaemon()
85.45 + {
85.46 + setDaemon(true); // VM will exit when all threads are daemons
85.47 + setName(getClass().getSimpleName());
85.48 + }
85.49 +
85.50 + /**
85.51 + * @return true if shutdown() was not yet called.
85.52 + */
85.53 + public boolean isRunning()
85.54 + {
85.55 + synchronized(this)
85.56 + {
85.57 + return this.isRunning;
85.58 + }
85.59 + }
85.60 +
85.61 + /**
85.62 + * Marks this thread to exit soon. Closes the associated JDBCDatabase connection
85.63 + * if available.
85.64 + * @throws java.sql.SQLException
85.65 + */
85.66 + public void shutdownNow()
85.67 + throws SQLException
85.68 + {
85.69 + synchronized(this)
85.70 + {
85.71 + this.isRunning = false;
85.72 + StorageManager.disableProvider();
85.73 + }
85.74 + }
85.75 +
85.76 + /**
85.77 + * Calls shutdownNow() but catches SQLExceptions if occurring.
85.78 + */
85.79 + public void shutdown()
85.80 + {
85.81 + try
85.82 + {
85.83 + shutdownNow();
85.84 + }
85.85 + catch(SQLException ex)
85.86 + {
85.87 + Log.get().warning(ex.toString());
85.88 + }
85.89 + }
85.90 +
85.91 + /**
85.92 + * Starts this daemon.
85.93 + */
85.94 + @Override
85.95 + public void start()
85.96 + {
85.97 + synchronized(this)
85.98 + {
85.99 + this.isRunning = true;
85.100 + }
85.101 + super.start();
85.102 + }
85.103 +
85.104 +}
86.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
86.2 +++ b/src/org/sonews/daemon/ChannelLineBuffers.java Sun Aug 29 17:28:58 2010 +0200
86.3 @@ -0,0 +1,283 @@
86.4 +/*
86.5 + * SONEWS News Server
86.6 + * see AUTHORS for the list of contributors
86.7 + *
86.8 + * This program is free software: you can redistribute it and/or modify
86.9 + * it under the terms of the GNU General Public License as published by
86.10 + * the Free Software Foundation, either version 3 of the License, or
86.11 + * (at your option) any later version.
86.12 + *
86.13 + * This program is distributed in the hope that it will be useful,
86.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
86.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
86.16 + * GNU General Public License for more details.
86.17 + *
86.18 + * You should have received a copy of the GNU General Public License
86.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
86.20 + */
86.21 +
86.22 +package org.sonews.daemon;
86.23 +
86.24 +import java.nio.ByteBuffer;
86.25 +import java.nio.channels.ClosedChannelException;
86.26 +import java.util.ArrayList;
86.27 +import java.util.List;
86.28 +
86.29 +/**
86.30 + * Class holding ByteBuffers for SocketChannels/NNTPConnection.
86.31 + * Due to the complex nature of AIO/NIO we must properly handle the line
86.32 + * buffers for the input and output of the SocketChannels.
86.33 + * @author Christian Lins
86.34 + * @since sonews/0.5.0
86.35 + */
86.36 +public class ChannelLineBuffers
86.37 +{
86.38 +
86.39 + /**
86.40 + * Size of one small buffer;
86.41 + * per default this is 512 bytes to fit one standard line.
86.42 + */
86.43 + public static final int BUFFER_SIZE = 512;
86.44 +
86.45 + private static int maxCachedBuffers = 2048; // Cached buffers maximum
86.46 +
86.47 + private static final List<ByteBuffer> freeSmallBuffers
86.48 + = new ArrayList<ByteBuffer>(maxCachedBuffers);
86.49 +
86.50 + /**
86.51 + * Allocates a predefined number of direct ByteBuffers (allocated via
86.52 + * ByteBuffer.allocateDirect()). This method is Thread-safe, but should only
86.53 + * called at startup.
86.54 + */
86.55 + public static void allocateDirect()
86.56 + {
86.57 + synchronized(freeSmallBuffers)
86.58 + {
86.59 + for(int n = 0; n < maxCachedBuffers; n++)
86.60 + {
86.61 + ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
86.62 + freeSmallBuffers.add(buffer);
86.63 + }
86.64 + }
86.65 + }
86.66 +
86.67 + private ByteBuffer inputBuffer = newLineBuffer();
86.68 + private List<ByteBuffer> outputBuffers = new ArrayList<ByteBuffer>();
86.69 +
86.70 + /**
86.71 + * Add the given ByteBuffer to the list of buffers to be send to the client.
86.72 + * This method is Thread-safe.
86.73 + * @param buffer
86.74 + * @throws java.nio.channels.ClosedChannelException If the client channel was
86.75 + * already closed.
86.76 + */
86.77 + public void addOutputBuffer(ByteBuffer buffer)
86.78 + throws ClosedChannelException
86.79 + {
86.80 + if(outputBuffers == null)
86.81 + {
86.82 + throw new ClosedChannelException();
86.83 + }
86.84 +
86.85 + synchronized(outputBuffers)
86.86 + {
86.87 + outputBuffers.add(buffer);
86.88 + }
86.89 + }
86.90 +
86.91 + /**
86.92 + * Currently a channel has only one input buffer. This *may* be a bottleneck
86.93 + * and should investigated in the future.
86.94 + * @param channel
86.95 + * @return The input buffer associated with given channel.
86.96 + */
86.97 + public ByteBuffer getInputBuffer()
86.98 + {
86.99 + return inputBuffer;
86.100 + }
86.101 +
86.102 + /**
86.103 + * Returns the current output buffer for writing(!) to SocketChannel.
86.104 + * @param channel
86.105 + * @return The next input buffer that contains unprocessed data or null
86.106 + * if the connection was closed or there are no more unprocessed buffers.
86.107 + */
86.108 + public ByteBuffer getOutputBuffer()
86.109 + {
86.110 + synchronized(outputBuffers)
86.111 + {
86.112 + if(outputBuffers == null || outputBuffers.isEmpty())
86.113 + {
86.114 + return null;
86.115 + }
86.116 + else
86.117 + {
86.118 + ByteBuffer buffer = outputBuffers.get(0);
86.119 + if(buffer.remaining() == 0)
86.120 + {
86.121 + outputBuffers.remove(0);
86.122 + // Add old buffers to the list of free buffers
86.123 + recycleBuffer(buffer);
86.124 + buffer = getOutputBuffer();
86.125 + }
86.126 + return buffer;
86.127 + }
86.128 + }
86.129 + }
86.130 +
86.131 + /**
86.132 + * @return false if there are output buffers pending to be written to the
86.133 + * client.
86.134 + */
86.135 + boolean isOutputBufferEmpty()
86.136 + {
86.137 + synchronized(outputBuffers)
86.138 + {
86.139 + return outputBuffers.isEmpty();
86.140 + }
86.141 + }
86.142 +
86.143 + /**
86.144 + * Goes through the input buffer of the given channel and searches
86.145 + * for next line terminator. If a '\n' is found, the bytes up to the
86.146 + * line terminator are returned as array of bytes (the line terminator
86.147 + * is omitted). If none is found the method returns null.
86.148 + * @param channel
86.149 + * @return A ByteBuffer wrapping the line.
86.150 + */
86.151 + ByteBuffer nextInputLine()
86.152 + {
86.153 + if(inputBuffer == null)
86.154 + {
86.155 + return null;
86.156 + }
86.157 +
86.158 + synchronized(inputBuffer)
86.159 + {
86.160 + ByteBuffer buffer = inputBuffer;
86.161 +
86.162 + // Mark the current write position
86.163 + int mark = buffer.position();
86.164 +
86.165 + // Set position to 0 and limit to current position
86.166 + buffer.flip();
86.167 +
86.168 + ByteBuffer lineBuffer = newLineBuffer();
86.169 +
86.170 + while (buffer.position() < buffer.limit())
86.171 + {
86.172 + byte b = buffer.get();
86.173 + if (b == 10) // '\n'
86.174 + {
86.175 + // The bytes between the buffer's current position and its limit,
86.176 + // if any, are copied to the beginning of the buffer. That is, the
86.177 + // byte at index p = position() is copied to index zero, the byte at
86.178 + // index p + 1 is copied to index one, and so forth until the byte
86.179 + // at index limit() - 1 is copied to index n = limit() - 1 - p.
86.180 + // The buffer's position is then set to n+1 and its limit is set to
86.181 + // its capacity.
86.182 + buffer.compact();
86.183 +
86.184 + lineBuffer.flip(); // limit to position, position to 0
86.185 + return lineBuffer;
86.186 + }
86.187 + else
86.188 + {
86.189 + lineBuffer.put(b);
86.190 + }
86.191 + }
86.192 +
86.193 + buffer.limit(BUFFER_SIZE);
86.194 + buffer.position(mark);
86.195 +
86.196 + if(buffer.hasRemaining())
86.197 + {
86.198 + return null;
86.199 + }
86.200 + else
86.201 + {
86.202 + // In the first 512 was no newline found, so the input is not standard
86.203 + // compliant. We return the current buffer as new line and add a space
86.204 + // to the beginning of the next line which corrects some overlong header
86.205 + // lines.
86.206 + inputBuffer = newLineBuffer();
86.207 + inputBuffer.put((byte)' ');
86.208 + buffer.flip();
86.209 + return buffer;
86.210 + }
86.211 + }
86.212 + }
86.213 +
86.214 + /**
86.215 + * Returns a at least 512 bytes long ByteBuffer ready for usage.
86.216 + * The method first try to reuse an already allocated (cached) buffer but
86.217 + * if that fails returns a newly allocated direct buffer.
86.218 + * Use recycleBuffer() method when you do not longer use the allocated buffer.
86.219 + */
86.220 + static ByteBuffer newLineBuffer()
86.221 + {
86.222 + ByteBuffer buf = null;
86.223 + synchronized(freeSmallBuffers)
86.224 + {
86.225 + if(!freeSmallBuffers.isEmpty())
86.226 + {
86.227 + buf = freeSmallBuffers.remove(0);
86.228 + }
86.229 + }
86.230 +
86.231 + if(buf == null)
86.232 + {
86.233 + // Allocate a non-direct buffer
86.234 + buf = ByteBuffer.allocate(BUFFER_SIZE);
86.235 + }
86.236 +
86.237 + assert buf.position() == 0;
86.238 + assert buf.limit() >= BUFFER_SIZE;
86.239 +
86.240 + return buf;
86.241 + }
86.242 +
86.243 + /**
86.244 + * Adds the given buffer to the list of free buffers if it is a valuable
86.245 + * direct allocated buffer.
86.246 + * @param buffer
86.247 + */
86.248 + public static void recycleBuffer(ByteBuffer buffer)
86.249 + {
86.250 + assert buffer != null;
86.251 +
86.252 + if(buffer.isDirect())
86.253 + {
86.254 + assert buffer.capacity() >= BUFFER_SIZE;
86.255 +
86.256 + // Add old buffers to the list of free buffers
86.257 + synchronized(freeSmallBuffers)
86.258 + {
86.259 + buffer.clear(); // Set position to 0 and limit to capacity
86.260 + freeSmallBuffers.add(buffer);
86.261 + }
86.262 + } // if(buffer.isDirect())
86.263 + }
86.264 +
86.265 + /**
86.266 + * Recycles all buffers of this ChannelLineBuffers object.
86.267 + */
86.268 + public void recycleBuffers()
86.269 + {
86.270 + synchronized(inputBuffer)
86.271 + {
86.272 + recycleBuffer(inputBuffer);
86.273 + this.inputBuffer = null;
86.274 + }
86.275 +
86.276 + synchronized(outputBuffers)
86.277 + {
86.278 + for(ByteBuffer buf : outputBuffers)
86.279 + {
86.280 + recycleBuffer(buf);
86.281 + }
86.282 + outputBuffers = null;
86.283 + }
86.284 + }
86.285 +
86.286 +}
87.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
87.2 +++ b/src/org/sonews/daemon/ChannelReader.java Sun Aug 29 17:28:58 2010 +0200
87.3 @@ -0,0 +1,202 @@
87.4 +/*
87.5 + * SONEWS News Server
87.6 + * see AUTHORS for the list of contributors
87.7 + *
87.8 + * This program is free software: you can redistribute it and/or modify
87.9 + * it under the terms of the GNU General Public License as published by
87.10 + * the Free Software Foundation, either version 3 of the License, or
87.11 + * (at your option) any later version.
87.12 + *
87.13 + * This program is distributed in the hope that it will be useful,
87.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
87.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
87.16 + * GNU General Public License for more details.
87.17 + *
87.18 + * You should have received a copy of the GNU General Public License
87.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
87.20 + */
87.21 +
87.22 +package org.sonews.daemon;
87.23 +
87.24 +import java.io.IOException;
87.25 +import java.nio.ByteBuffer;
87.26 +import java.nio.channels.CancelledKeyException;
87.27 +import java.nio.channels.SelectionKey;
87.28 +import java.nio.channels.Selector;
87.29 +import java.nio.channels.SocketChannel;
87.30 +import java.util.Iterator;
87.31 +import java.util.Set;
87.32 +import java.util.logging.Level;
87.33 +import org.sonews.util.Log;
87.34 +
87.35 +/**
87.36 + * A Thread task listening for OP_READ events from SocketChannels.
87.37 + * @author Christian Lins
87.38 + * @since sonews/0.5.0
87.39 + */
87.40 +class ChannelReader extends AbstractDaemon
87.41 +{
87.42 +
87.43 + private static ChannelReader instance = new ChannelReader();
87.44 +
87.45 + /**
87.46 + * @return Active ChannelReader instance.
87.47 + */
87.48 + public static ChannelReader getInstance()
87.49 + {
87.50 + return instance;
87.51 + }
87.52 +
87.53 + private Selector selector = null;
87.54 +
87.55 + protected ChannelReader()
87.56 + {
87.57 + }
87.58 +
87.59 + /**
87.60 + * Sets the selector which is used by this reader to determine the channel
87.61 + * to read from.
87.62 + * @param selector
87.63 + */
87.64 + public void setSelector(final Selector selector)
87.65 + {
87.66 + this.selector = selector;
87.67 + }
87.68 +
87.69 + /**
87.70 + * Run loop. Blocks until some data is available in a channel.
87.71 + */
87.72 + @Override
87.73 + public void run()
87.74 + {
87.75 + assert selector != null;
87.76 +
87.77 + while(isRunning())
87.78 + {
87.79 + try
87.80 + {
87.81 + // select() blocks until some SelectableChannels are ready for
87.82 + // processing. There is no need to lock the selector as we have only
87.83 + // one thread per selector.
87.84 + selector.select();
87.85 +
87.86 + // Get list of selection keys with pending events.
87.87 + // Note: the selected key set is not thread-safe
87.88 + SocketChannel channel = null;
87.89 + NNTPConnection conn = null;
87.90 + final Set<SelectionKey> selKeys = selector.selectedKeys();
87.91 + SelectionKey selKey = null;
87.92 +
87.93 + synchronized (selKeys)
87.94 + {
87.95 + Iterator it = selKeys.iterator();
87.96 +
87.97 + // Process the first pending event
87.98 + while (it.hasNext())
87.99 + {
87.100 + selKey = (SelectionKey) it.next();
87.101 + channel = (SocketChannel) selKey.channel();
87.102 + conn = Connections.getInstance().get(channel);
87.103 +
87.104 + // Because we cannot lock the selKey as that would cause a deadlock
87.105 + // we lock the connection. To preserve the order of the received
87.106 + // byte blocks a selection key for a connection that has pending
87.107 + // read events is skipped.
87.108 + if (conn == null || conn.tryReadLock())
87.109 + {
87.110 + // Remove from set to indicate that it's being processed
87.111 + it.remove();
87.112 + if (conn != null)
87.113 + {
87.114 + break; // End while loop
87.115 + }
87.116 + }
87.117 + else
87.118 + {
87.119 + selKey = null;
87.120 + channel = null;
87.121 + conn = null;
87.122 + }
87.123 + }
87.124 + }
87.125 +
87.126 + // Do not lock the selKeys while processing because this causes
87.127 + // a deadlock in sun.nio.ch.SelectorImpl.lockAndDoSelect()
87.128 + if (selKey != null && channel != null && conn != null)
87.129 + {
87.130 + processSelectionKey(conn, channel, selKey);
87.131 + conn.unlockReadLock();
87.132 + }
87.133 +
87.134 + }
87.135 + catch(CancelledKeyException ex)
87.136 + {
87.137 + Log.get().warning("ChannelReader.run(): " + ex);
87.138 + Log.get().log(Level.INFO, "", ex);
87.139 + }
87.140 + catch(Exception ex)
87.141 + {
87.142 + ex.printStackTrace();
87.143 + }
87.144 +
87.145 + // Eventually wait for a register operation
87.146 + synchronized (NNTPDaemon.RegisterGate)
87.147 + {
87.148 + // Do nothing; FindBugs may warn about an empty synchronized
87.149 + // statement, but we cannot use a wait()/notify() mechanism here.
87.150 + // If we used something like RegisterGate.wait() we block here
87.151 + // until the NNTPDaemon calls notify(). But the daemon only
87.152 + // calls notify() if itself is NOT blocked in the listening socket.
87.153 + }
87.154 + } // while(isRunning())
87.155 + }
87.156 +
87.157 + private void processSelectionKey(final NNTPConnection connection,
87.158 + final SocketChannel socketChannel, final SelectionKey selKey)
87.159 + throws InterruptedException, IOException
87.160 + {
87.161 + assert selKey != null;
87.162 + assert selKey.isReadable();
87.163 +
87.164 + // Some bytes are available for reading
87.165 + if(selKey.isValid())
87.166 + {
87.167 + // Lock the channel
87.168 + //synchronized(socketChannel)
87.169 + {
87.170 + // Read the data into the appropriate buffer
87.171 + ByteBuffer buf = connection.getInputBuffer();
87.172 + int read = -1;
87.173 + try
87.174 + {
87.175 + read = socketChannel.read(buf);
87.176 + }
87.177 + catch(IOException ex)
87.178 + {
87.179 + // The connection was probably closed by the remote host
87.180 + // in a non-clean fashion
87.181 + Log.get().info("ChannelReader.processSelectionKey(): " + ex);
87.182 + }
87.183 + catch(Exception ex)
87.184 + {
87.185 + Log.get().warning("ChannelReader.processSelectionKey(): " + ex);
87.186 + }
87.187 +
87.188 + if(read == -1) // End of stream
87.189 + {
87.190 + selKey.cancel();
87.191 + }
87.192 + else if(read > 0) // If some data was read
87.193 + {
87.194 + ConnectionWorker.addChannel(socketChannel);
87.195 + }
87.196 + }
87.197 + }
87.198 + else
87.199 + {
87.200 + // Should not happen
87.201 + Log.get().severe("Should not happen: " + selKey.toString());
87.202 + }
87.203 + }
87.204 +
87.205 +}
88.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
88.2 +++ b/src/org/sonews/daemon/ChannelWriter.java Sun Aug 29 17:28:58 2010 +0200
88.3 @@ -0,0 +1,210 @@
88.4 +/*
88.5 + * SONEWS News Server
88.6 + * see AUTHORS for the list of contributors
88.7 + *
88.8 + * This program is free software: you can redistribute it and/or modify
88.9 + * it under the terms of the GNU General Public License as published by
88.10 + * the Free Software Foundation, either version 3 of the License, or
88.11 + * (at your option) any later version.
88.12 + *
88.13 + * This program is distributed in the hope that it will be useful,
88.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
88.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
88.16 + * GNU General Public License for more details.
88.17 + *
88.18 + * You should have received a copy of the GNU General Public License
88.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
88.20 + */
88.21 +
88.22 +package org.sonews.daemon;
88.23 +
88.24 +import org.sonews.util.Log;
88.25 +import java.io.IOException;
88.26 +import java.nio.ByteBuffer;
88.27 +import java.nio.channels.CancelledKeyException;
88.28 +import java.nio.channels.SelectionKey;
88.29 +import java.nio.channels.Selector;
88.30 +import java.nio.channels.SocketChannel;
88.31 +import java.util.Iterator;
88.32 +
88.33 +/**
88.34 + * A Thread task that processes OP_WRITE events for SocketChannels.
88.35 + * @author Christian Lins
88.36 + * @since sonews/0.5.0
88.37 + */
88.38 +class ChannelWriter extends AbstractDaemon
88.39 +{
88.40 +
88.41 + private static ChannelWriter instance = new ChannelWriter();
88.42 +
88.43 + /**
88.44 + * @return Returns the active ChannelWriter instance.
88.45 + */
88.46 + public static ChannelWriter getInstance()
88.47 + {
88.48 + return instance;
88.49 + }
88.50 +
88.51 + private Selector selector = null;
88.52 +
88.53 + protected ChannelWriter()
88.54 + {
88.55 + }
88.56 +
88.57 + /**
88.58 + * @return Selector associated with this instance.
88.59 + */
88.60 + public Selector getSelector()
88.61 + {
88.62 + return this.selector;
88.63 + }
88.64 +
88.65 + /**
88.66 + * Sets the selector that is used by this ChannelWriter.
88.67 + * @param selector
88.68 + */
88.69 + public void setSelector(final Selector selector)
88.70 + {
88.71 + this.selector = selector;
88.72 + }
88.73 +
88.74 + /**
88.75 + * Run loop.
88.76 + */
88.77 + @Override
88.78 + public void run()
88.79 + {
88.80 + assert selector != null;
88.81 +
88.82 + while(isRunning())
88.83 + {
88.84 + try
88.85 + {
88.86 + SelectionKey selKey = null;
88.87 + SocketChannel socketChannel = null;
88.88 + NNTPConnection connection = null;
88.89 +
88.90 + // select() blocks until some SelectableChannels are ready for
88.91 + // processing. There is no need to synchronize the selector as we
88.92 + // have only one thread per selector.
88.93 + selector.select(); // The return value of select can be ignored
88.94 +
88.95 + // Get list of selection keys with pending OP_WRITE events.
88.96 + // The keySET is not thread-safe whereas the keys itself are.
88.97 + Iterator it = selector.selectedKeys().iterator();
88.98 +
88.99 + while (it.hasNext())
88.100 + {
88.101 + // We remove the first event from the set and store it for
88.102 + // later processing.
88.103 + selKey = (SelectionKey) it.next();
88.104 + socketChannel = (SocketChannel) selKey.channel();
88.105 + connection = Connections.getInstance().get(socketChannel);
88.106 +
88.107 + it.remove();
88.108 + if (connection != null)
88.109 + {
88.110 + break;
88.111 + }
88.112 + else
88.113 + {
88.114 + selKey = null;
88.115 + }
88.116 + }
88.117 +
88.118 + if (selKey != null)
88.119 + {
88.120 + try
88.121 + {
88.122 + // Process the selected key.
88.123 + // As there is only one OP_WRITE key for a given channel, we need
88.124 + // not to synchronize this processing to retain the order.
88.125 + processSelectionKey(connection, socketChannel, selKey);
88.126 + }
88.127 + catch (IOException ex)
88.128 + {
88.129 + Log.get().warning("Error writing to channel: " + ex);
88.130 +
88.131 + // Cancel write events for this channel
88.132 + selKey.cancel();
88.133 + connection.shutdownInput();
88.134 + connection.shutdownOutput();
88.135 + }
88.136 + }
88.137 +
88.138 + // Eventually wait for a register operation
88.139 + synchronized(NNTPDaemon.RegisterGate) { /* do nothing */ }
88.140 + }
88.141 + catch(CancelledKeyException ex)
88.142 + {
88.143 + Log.get().info("ChannelWriter.run(): " + ex);
88.144 + }
88.145 + catch(Exception ex)
88.146 + {
88.147 + ex.printStackTrace();
88.148 + }
88.149 + } // while(isRunning())
88.150 + }
88.151 +
88.152 + private void processSelectionKey(final NNTPConnection connection,
88.153 + final SocketChannel socketChannel, final SelectionKey selKey)
88.154 + throws InterruptedException, IOException
88.155 + {
88.156 + assert connection != null;
88.157 + assert socketChannel != null;
88.158 + assert selKey != null;
88.159 + assert selKey.isWritable();
88.160 +
88.161 + // SocketChannel is ready for writing
88.162 + if(selKey.isValid())
88.163 + {
88.164 + // Lock the socket channel
88.165 + synchronized(socketChannel)
88.166 + {
88.167 + // Get next output buffer
88.168 + ByteBuffer buf = connection.getOutputBuffer();
88.169 + if(buf == null)
88.170 + {
88.171 + // Currently we have nothing to write, so we stop the writeable
88.172 + // events until we have something to write to the socket channel
88.173 + //selKey.cancel();
88.174 + selKey.interestOps(0);
88.175 + // Update activity timestamp to prevent too early disconnects
88.176 + // on slow client connections
88.177 + connection.setLastActivity(System.currentTimeMillis());
88.178 + return;
88.179 + }
88.180 +
88.181 + while(buf != null) // There is data to be send
88.182 + {
88.183 + // Write buffer to socket channel; this method does not block
88.184 + if(socketChannel.write(buf) <= 0)
88.185 + {
88.186 + // Perhaps there is data to be written, but the SocketChannel's
88.187 + // buffer is full, so we stop writing to until the next event.
88.188 + break;
88.189 + }
88.190 + else
88.191 + {
88.192 + // Retrieve next buffer if available; method may return the same
88.193 + // buffer instance if it still have some bytes remaining
88.194 + buf = connection.getOutputBuffer();
88.195 + }
88.196 + }
88.197 + }
88.198 + }
88.199 + else
88.200 + {
88.201 + Log.get().warning("Invalid OP_WRITE key: " + selKey);
88.202 +
88.203 + if(socketChannel.socket().isClosed())
88.204 + {
88.205 + connection.shutdownInput();
88.206 + connection.shutdownOutput();
88.207 + socketChannel.close();
88.208 + Log.get().info("Connection closed.");
88.209 + }
88.210 + }
88.211 + }
88.212 +
88.213 +}
89.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
89.2 +++ b/src/org/sonews/daemon/CommandSelector.java Sun Aug 29 17:28:58 2010 +0200
89.3 @@ -0,0 +1,141 @@
89.4 +/*
89.5 + * SONEWS News Server
89.6 + * see AUTHORS for the list of contributors
89.7 + *
89.8 + * This program is free software: you can redistribute it and/or modify
89.9 + * it under the terms of the GNU General Public License as published by
89.10 + * the Free Software Foundation, either version 3 of the License, or
89.11 + * (at your option) any later version.
89.12 + *
89.13 + * This program is distributed in the hope that it will be useful,
89.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
89.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
89.16 + * GNU General Public License for more details.
89.17 + *
89.18 + * You should have received a copy of the GNU General Public License
89.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
89.20 + */
89.21 +
89.22 +package org.sonews.daemon;
89.23 +
89.24 +import java.util.HashMap;
89.25 +import java.util.Map;
89.26 +import java.util.Set;
89.27 +import java.util.concurrent.ConcurrentHashMap;
89.28 +import org.sonews.daemon.command.Command;
89.29 +import org.sonews.daemon.command.UnsupportedCommand;
89.30 +import org.sonews.util.Log;
89.31 +import org.sonews.util.io.Resource;
89.32 +
89.33 +/**
89.34 + * Selects the correct command processing class.
89.35 + * @author Christian Lins
89.36 + * @since sonews/1.0
89.37 + */
89.38 +public class CommandSelector
89.39 +{
89.40 +
89.41 + private static Map<Thread, CommandSelector> instances
89.42 + = new ConcurrentHashMap<Thread, CommandSelector>();
89.43 + private static Map<String, Class<?>> commandClassesMapping
89.44 + = new ConcurrentHashMap<String, Class<?>>();
89.45 +
89.46 + static
89.47 + {
89.48 + String[] classes = Resource.getAsString("helpers/commands.list", true).split("\n");
89.49 + for(String className : classes)
89.50 + {
89.51 + if(className.charAt(0) == '#')
89.52 + {
89.53 + // Skip comments
89.54 + continue;
89.55 + }
89.56 +
89.57 + try
89.58 + {
89.59 + addCommandHandler(className);
89.60 + }
89.61 + catch(ClassNotFoundException ex)
89.62 + {
89.63 + Log.get().warning("Could not load command class: " + ex);
89.64 + }
89.65 + catch(InstantiationException ex)
89.66 + {
89.67 + Log.get().severe("Could not instantiate command class: " + ex);
89.68 + }
89.69 + catch(IllegalAccessException ex)
89.70 + {
89.71 + Log.get().severe("Could not access command class: " + ex);
89.72 + }
89.73 + }
89.74 + }
89.75 +
89.76 + public static void addCommandHandler(String className)
89.77 + throws ClassNotFoundException, InstantiationException, IllegalAccessException
89.78 + {
89.79 + Class<?> clazz = Class.forName(className);
89.80 + Command cmd = (Command)clazz.newInstance();
89.81 + String[] cmdStrs = cmd.getSupportedCommandStrings();
89.82 + for (String cmdStr : cmdStrs)
89.83 + {
89.84 + commandClassesMapping.put(cmdStr, clazz);
89.85 + }
89.86 + }
89.87 +
89.88 + public static Set<String> getCommandNames()
89.89 + {
89.90 + return commandClassesMapping.keySet();
89.91 + }
89.92 +
89.93 + public static CommandSelector getInstance()
89.94 + {
89.95 + CommandSelector csel = instances.get(Thread.currentThread());
89.96 + if(csel == null)
89.97 + {
89.98 + csel = new CommandSelector();
89.99 + instances.put(Thread.currentThread(), csel);
89.100 + }
89.101 + return csel;
89.102 + }
89.103 +
89.104 + private Map<String, Command> commandMapping = new HashMap<String, Command>();
89.105 + private Command unsupportedCmd = new UnsupportedCommand();
89.106 +
89.107 + private CommandSelector()
89.108 + {}
89.109 +
89.110 + public Command get(String commandName)
89.111 + {
89.112 + try
89.113 + {
89.114 + commandName = commandName.toUpperCase();
89.115 + Command cmd = this.commandMapping.get(commandName);
89.116 +
89.117 + if(cmd == null)
89.118 + {
89.119 + Class<?> clazz = commandClassesMapping.get(commandName);
89.120 + if(clazz == null)
89.121 + {
89.122 + cmd = this.unsupportedCmd;
89.123 + }
89.124 + else
89.125 + {
89.126 + cmd = (Command)clazz.newInstance();
89.127 + this.commandMapping.put(commandName, cmd);
89.128 + }
89.129 + }
89.130 + else if(cmd.isStateful())
89.131 + {
89.132 + cmd = cmd.getClass().newInstance();
89.133 + }
89.134 +
89.135 + return cmd;
89.136 + }
89.137 + catch(Exception ex)
89.138 + {
89.139 + ex.printStackTrace();
89.140 + return this.unsupportedCmd;
89.141 + }
89.142 + }
89.143 +
89.144 +}
90.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
90.2 +++ b/src/org/sonews/daemon/ConnectionWorker.java Sun Aug 29 17:28:58 2010 +0200
90.3 @@ -0,0 +1,102 @@
90.4 +/*
90.5 + * SONEWS News Server
90.6 + * see AUTHORS for the list of contributors
90.7 + *
90.8 + * This program is free software: you can redistribute it and/or modify
90.9 + * it under the terms of the GNU General Public License as published by
90.10 + * the Free Software Foundation, either version 3 of the License, or
90.11 + * (at your option) any later version.
90.12 + *
90.13 + * This program is distributed in the hope that it will be useful,
90.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
90.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
90.16 + * GNU General Public License for more details.
90.17 + *
90.18 + * You should have received a copy of the GNU General Public License
90.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
90.20 + */
90.21 +
90.22 +package org.sonews.daemon;
90.23 +
90.24 +import org.sonews.util.Log;
90.25 +import java.nio.ByteBuffer;
90.26 +import java.nio.channels.SocketChannel;
90.27 +import java.util.concurrent.ArrayBlockingQueue;
90.28 +
90.29 +/**
90.30 + * Does most of the work: parsing input, talking to client and Database.
90.31 + * @author Christian Lins
90.32 + * @since sonews/0.5.0
90.33 + */
90.34 +class ConnectionWorker extends AbstractDaemon
90.35 +{
90.36 +
90.37 + // 256 pending events should be enough
90.38 + private static ArrayBlockingQueue<SocketChannel> pendingChannels
90.39 + = new ArrayBlockingQueue<SocketChannel>(256, true);
90.40 +
90.41 + /**
90.42 + * Registers the given channel for further event processing.
90.43 + * @param channel
90.44 + */
90.45 + public static void addChannel(SocketChannel channel)
90.46 + throws InterruptedException
90.47 + {
90.48 + pendingChannels.put(channel);
90.49 + }
90.50 +
90.51 + /**
90.52 + * Processing loop.
90.53 + */
90.54 + @Override
90.55 + public void run()
90.56 + {
90.57 + while(isRunning())
90.58 + {
90.59 + try
90.60 + {
90.61 + // Retrieve and remove if available, otherwise wait.
90.62 + SocketChannel channel = pendingChannels.take();
90.63 +
90.64 + if(channel != null)
90.65 + {
90.66 + // Connections.getInstance().get() MAY return null
90.67 + NNTPConnection conn = Connections.getInstance().get(channel);
90.68 +
90.69 + // Try to lock the connection object
90.70 + if(conn != null && conn.tryReadLock())
90.71 + {
90.72 + ByteBuffer buf = conn.getBuffers().nextInputLine();
90.73 + while(buf != null) // Complete line was received
90.74 + {
90.75 + final byte[] line = new byte[buf.limit()];
90.76 + buf.get(line);
90.77 + ChannelLineBuffers.recycleBuffer(buf);
90.78 +
90.79 + // Here is the actual work done
90.80 + conn.lineReceived(line);
90.81 +
90.82 + // Read next line as we could have already received the next line
90.83 + buf = conn.getBuffers().nextInputLine();
90.84 + }
90.85 + conn.unlockReadLock();
90.86 + }
90.87 + else
90.88 + {
90.89 + addChannel(channel);
90.90 + }
90.91 + }
90.92 + }
90.93 + catch(InterruptedException ex)
90.94 + {
90.95 + Log.get().info("ConnectionWorker interrupted: " + ex);
90.96 + }
90.97 + catch(Exception ex)
90.98 + {
90.99 + Log.get().severe("Exception in ConnectionWorker: " + ex);
90.100 + ex.printStackTrace();
90.101 + }
90.102 + } // end while(isRunning())
90.103 + }
90.104 +
90.105 +}
91.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
91.2 +++ b/src/org/sonews/daemon/Connections.java Sun Aug 29 17:28:58 2010 +0200
91.3 @@ -0,0 +1,181 @@
91.4 +/*
91.5 + * SONEWS News Server
91.6 + * see AUTHORS for the list of contributors
91.7 + *
91.8 + * This program is free software: you can redistribute it and/or modify
91.9 + * it under the terms of the GNU General Public License as published by
91.10 + * the Free Software Foundation, either version 3 of the License, or
91.11 + * (at your option) any later version.
91.12 + *
91.13 + * This program is distributed in the hope that it will be useful,
91.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
91.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
91.16 + * GNU General Public License for more details.
91.17 + *
91.18 + * You should have received a copy of the GNU General Public License
91.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
91.20 + */
91.21 +
91.22 +package org.sonews.daemon;
91.23 +
91.24 +import org.sonews.config.Config;
91.25 +import org.sonews.util.Log;
91.26 +import org.sonews.util.Stats;
91.27 +import java.io.IOException;
91.28 +import java.net.InetSocketAddress;
91.29 +import java.net.Socket;
91.30 +import java.nio.channels.SocketChannel;
91.31 +import java.util.ArrayList;
91.32 +import java.util.HashMap;
91.33 +import java.util.List;
91.34 +import java.util.ListIterator;
91.35 +import java.util.Map;
91.36 +
91.37 +/**
91.38 + * Daemon thread collecting all NNTPConnection instances. The thread
91.39 + * checks periodically if there are stale/timed out connections and
91.40 + * removes and purges them properly.
91.41 + * @author Christian Lins
91.42 + * @since sonews/0.5.0
91.43 + */
91.44 +public final class Connections extends AbstractDaemon
91.45 +{
91.46 +
91.47 + private static final Connections instance = new Connections();
91.48 +
91.49 + /**
91.50 + * @return Active Connections instance.
91.51 + */
91.52 + public static Connections getInstance()
91.53 + {
91.54 + return Connections.instance;
91.55 + }
91.56 +
91.57 + private final List<NNTPConnection> connections
91.58 + = new ArrayList<NNTPConnection>();
91.59 + private final Map<SocketChannel, NNTPConnection> connByChannel
91.60 + = new HashMap<SocketChannel, NNTPConnection>();
91.61 +
91.62 + private Connections()
91.63 + {
91.64 + setName("Connections");
91.65 + }
91.66 +
91.67 + /**
91.68 + * Adds the given NNTPConnection to the Connections management.
91.69 + * @param conn
91.70 + * @see org.sonews.daemon.NNTPConnection
91.71 + */
91.72 + public void add(final NNTPConnection conn)
91.73 + {
91.74 + synchronized(this.connections)
91.75 + {
91.76 + this.connections.add(conn);
91.77 + this.connByChannel.put(conn.getSocketChannel(), conn);
91.78 + }
91.79 + }
91.80 +
91.81 + /**
91.82 + * @param channel
91.83 + * @return NNTPConnection instance that is associated with the given
91.84 + * SocketChannel.
91.85 + */
91.86 + public NNTPConnection get(final SocketChannel channel)
91.87 + {
91.88 + synchronized(this.connections)
91.89 + {
91.90 + return this.connByChannel.get(channel);
91.91 + }
91.92 + }
91.93 +
91.94 + int getConnectionCount(String remote)
91.95 + {
91.96 + int cnt = 0;
91.97 + synchronized(this.connections)
91.98 + {
91.99 + for(NNTPConnection conn : this.connections)
91.100 + {
91.101 + assert conn != null;
91.102 + assert conn.getSocketChannel() != null;
91.103 +
91.104 + Socket socket = conn.getSocketChannel().socket();
91.105 + if(socket != null)
91.106 + {
91.107 + InetSocketAddress sockAddr = (InetSocketAddress)socket.getRemoteSocketAddress();
91.108 + if(sockAddr != null)
91.109 + {
91.110 + if(sockAddr.getHostName().equals(remote))
91.111 + {
91.112 + cnt++;
91.113 + }
91.114 + }
91.115 + } // if(socket != null)
91.116 + }
91.117 + }
91.118 + return cnt;
91.119 + }
91.120 +
91.121 + /**
91.122 + * Run loops. Checks periodically for timed out connections and purged them
91.123 + * from the lists.
91.124 + */
91.125 + @Override
91.126 + public void run()
91.127 + {
91.128 + while(isRunning())
91.129 + {
91.130 + int timeoutMillis = 1000 * Config.inst().get(Config.TIMEOUT, 180);
91.131 +
91.132 + synchronized (this.connections)
91.133 + {
91.134 + final ListIterator<NNTPConnection> iter = this.connections.listIterator();
91.135 + NNTPConnection conn;
91.136 +
91.137 + while (iter.hasNext())
91.138 + {
91.139 + conn = iter.next();
91.140 + if((System.currentTimeMillis() - conn.getLastActivity()) > timeoutMillis
91.141 + && conn.getBuffers().isOutputBufferEmpty())
91.142 + {
91.143 + // A connection timeout has occurred so purge the connection
91.144 + iter.remove();
91.145 +
91.146 + // Close and remove the channel
91.147 + SocketChannel channel = conn.getSocketChannel();
91.148 + connByChannel.remove(channel);
91.149 +
91.150 + try
91.151 + {
91.152 + assert channel != null;
91.153 + assert channel.socket() != null;
91.154 +
91.155 + // Close the channel; implicitely cancels all selectionkeys
91.156 + channel.close();
91.157 + Log.get().info("Disconnected: " + channel.socket().getRemoteSocketAddress() +
91.158 + " (timeout)");
91.159 + }
91.160 + catch(IOException ex)
91.161 + {
91.162 + Log.get().warning("Connections.run(): " + ex);
91.163 + }
91.164 +
91.165 + // Recycle the used buffers
91.166 + conn.getBuffers().recycleBuffers();
91.167 +
91.168 + Stats.getInstance().clientDisconnect();
91.169 + }
91.170 + }
91.171 + }
91.172 +
91.173 + try
91.174 + {
91.175 + Thread.sleep(10000); // Sleep ten seconds
91.176 + }
91.177 + catch(InterruptedException ex)
91.178 + {
91.179 + Log.get().warning("Connections Thread was interrupted: " + ex.getMessage());
91.180 + }
91.181 + }
91.182 + }
91.183 +
91.184 +}
92.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
92.2 +++ b/src/org/sonews/daemon/LineEncoder.java Sun Aug 29 17:28:58 2010 +0200
92.3 @@ -0,0 +1,80 @@
92.4 +/*
92.5 + * SONEWS News Server
92.6 + * see AUTHORS for the list of contributors
92.7 + *
92.8 + * This program is free software: you can redistribute it and/or modify
92.9 + * it under the terms of the GNU General Public License as published by
92.10 + * the Free Software Foundation, either version 3 of the License, or
92.11 + * (at your option) any later version.
92.12 + *
92.13 + * This program is distributed in the hope that it will be useful,
92.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
92.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
92.16 + * GNU General Public License for more details.
92.17 + *
92.18 + * You should have received a copy of the GNU General Public License
92.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
92.20 + */
92.21 +
92.22 +package org.sonews.daemon;
92.23 +
92.24 +import java.nio.ByteBuffer;
92.25 +import java.nio.CharBuffer;
92.26 +import java.nio.channels.ClosedChannelException;
92.27 +import java.nio.charset.Charset;
92.28 +import java.nio.charset.CharsetEncoder;
92.29 +import java.nio.charset.CoderResult;
92.30 +
92.31 +/**
92.32 + * Encodes a line to buffers using the correct charset.
92.33 + * @author Christian Lins
92.34 + * @since sonews/0.5.0
92.35 + */
92.36 +class LineEncoder
92.37 +{
92.38 +
92.39 + private CharBuffer characters;
92.40 + private Charset charset;
92.41 +
92.42 + /**
92.43 + * Constructs new LineEncoder.
92.44 + * @param characters
92.45 + * @param charset
92.46 + */
92.47 + public LineEncoder(CharBuffer characters, Charset charset)
92.48 + {
92.49 + this.characters = characters;
92.50 + this.charset = charset;
92.51 + }
92.52 +
92.53 + /**
92.54 + * Encodes the characters of this instance to the given ChannelLineBuffers
92.55 + * using the Charset of this instance.
92.56 + * @param buffer
92.57 + * @throws java.nio.channels.ClosedChannelException
92.58 + */
92.59 + public void encode(ChannelLineBuffers buffer)
92.60 + throws ClosedChannelException
92.61 + {
92.62 + CharsetEncoder encoder = charset.newEncoder();
92.63 + while (characters.hasRemaining())
92.64 + {
92.65 + ByteBuffer buf = ChannelLineBuffers.newLineBuffer();
92.66 + assert buf.position() == 0;
92.67 + assert buf.capacity() >= 512;
92.68 +
92.69 + CoderResult res = encoder.encode(characters, buf, true);
92.70 +
92.71 + // Set limit to current position and current position to 0;
92.72 + // means make ready for read from buffer
92.73 + buf.flip();
92.74 + buffer.addOutputBuffer(buf);
92.75 +
92.76 + if (res.isUnderflow()) // All input processed
92.77 + {
92.78 + break;
92.79 + }
92.80 + }
92.81 + }
92.82 +
92.83 +}
93.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
93.2 +++ b/src/org/sonews/daemon/NNTPConnection.java Sun Aug 29 17:28:58 2010 +0200
93.3 @@ -0,0 +1,428 @@
93.4 +/*
93.5 + * SONEWS News Server
93.6 + * see AUTHORS for the list of contributors
93.7 + *
93.8 + * This program is free software: you can redistribute it and/or modify
93.9 + * it under the terms of the GNU General Public License as published by
93.10 + * the Free Software Foundation, either version 3 of the License, or
93.11 + * (at your option) any later version.
93.12 + *
93.13 + * This program is distributed in the hope that it will be useful,
93.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
93.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
93.16 + * GNU General Public License for more details.
93.17 + *
93.18 + * You should have received a copy of the GNU General Public License
93.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
93.20 + */
93.21 +
93.22 +package org.sonews.daemon;
93.23 +
93.24 +import java.io.IOException;
93.25 +import java.net.InetSocketAddress;
93.26 +import java.net.SocketException;
93.27 +import java.nio.ByteBuffer;
93.28 +import java.nio.CharBuffer;
93.29 +import java.nio.channels.ClosedChannelException;
93.30 +import java.nio.channels.SelectionKey;
93.31 +import java.nio.channels.SocketChannel;
93.32 +import java.nio.charset.Charset;
93.33 +import java.util.Arrays;
93.34 +import java.util.Timer;
93.35 +import java.util.TimerTask;
93.36 +import org.sonews.daemon.command.Command;
93.37 +import org.sonews.storage.Article;
93.38 +import org.sonews.storage.Channel;
93.39 +import org.sonews.storage.StorageBackendException;
93.40 +import org.sonews.util.Log;
93.41 +import org.sonews.util.Stats;
93.42 +
93.43 +/**
93.44 + * For every SocketChannel (so TCP/IP connection) there is an instance of
93.45 + * this class.
93.46 + * @author Christian Lins
93.47 + * @since sonews/0.5.0
93.48 + */
93.49 +public final class NNTPConnection
93.50 +{
93.51 +
93.52 + public static final String NEWLINE = "\r\n"; // RFC defines this as newline
93.53 + public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
93.54 +
93.55 + private static final Timer cancelTimer = new Timer(true); // Thread-safe? True for run as daemon
93.56 +
93.57 + /** SocketChannel is generally thread-safe */
93.58 + private SocketChannel channel = null;
93.59 + private Charset charset = Charset.forName("UTF-8");
93.60 + private Command command = null;
93.61 + private Article currentArticle = null;
93.62 + private Channel currentGroup = null;
93.63 + private volatile long lastActivity = System.currentTimeMillis();
93.64 + private ChannelLineBuffers lineBuffers = new ChannelLineBuffers();
93.65 + private int readLock = 0;
93.66 + private final Object readLockGate = new Object();
93.67 + private SelectionKey writeSelKey = null;
93.68 +
93.69 + public NNTPConnection(final SocketChannel channel)
93.70 + throws IOException
93.71 + {
93.72 + if(channel == null)
93.73 + {
93.74 + throw new IllegalArgumentException("channel is null");
93.75 + }
93.76 +
93.77 + this.channel = channel;
93.78 + Stats.getInstance().clientConnect();
93.79 + }
93.80 +
93.81 + /**
93.82 + * Tries to get the read lock for this NNTPConnection. This method is Thread-
93.83 + * safe and returns true of the read lock was successfully set. If the lock
93.84 + * is still hold by another Thread the method returns false.
93.85 + */
93.86 + boolean tryReadLock()
93.87 + {
93.88 + // As synchronizing simple types may cause deadlocks,
93.89 + // we use a gate object.
93.90 + synchronized(readLockGate)
93.91 + {
93.92 + if(readLock != 0)
93.93 + {
93.94 + return false;
93.95 + }
93.96 + else
93.97 + {
93.98 + readLock = Thread.currentThread().hashCode();
93.99 + return true;
93.100 + }
93.101 + }
93.102 + }
93.103 +
93.104 + /**
93.105 + * Releases the read lock in a Thread-safe way.
93.106 + * @throws IllegalMonitorStateException if a Thread not holding the lock
93.107 + * tries to release it.
93.108 + */
93.109 + void unlockReadLock()
93.110 + {
93.111 + synchronized(readLockGate)
93.112 + {
93.113 + if(readLock == Thread.currentThread().hashCode())
93.114 + {
93.115 + readLock = 0;
93.116 + }
93.117 + else
93.118 + {
93.119 + throw new IllegalMonitorStateException();
93.120 + }
93.121 + }
93.122 + }
93.123 +
93.124 + /**
93.125 + * @return Current input buffer of this NNTPConnection instance.
93.126 + */
93.127 + public ByteBuffer getInputBuffer()
93.128 + {
93.129 + return this.lineBuffers.getInputBuffer();
93.130 + }
93.131 +
93.132 + /**
93.133 + * @return Output buffer of this NNTPConnection which has at least one byte
93.134 + * free storage.
93.135 + */
93.136 + public ByteBuffer getOutputBuffer()
93.137 + {
93.138 + return this.lineBuffers.getOutputBuffer();
93.139 + }
93.140 +
93.141 + /**
93.142 + * @return ChannelLineBuffers instance associated with this NNTPConnection.
93.143 + */
93.144 + public ChannelLineBuffers getBuffers()
93.145 + {
93.146 + return this.lineBuffers;
93.147 + }
93.148 +
93.149 + /**
93.150 + * @return true if this connection comes from a local remote address.
93.151 + */
93.152 + public boolean isLocalConnection()
93.153 + {
93.154 + return ((InetSocketAddress)this.channel.socket().getRemoteSocketAddress())
93.155 + .getHostName().equalsIgnoreCase("localhost");
93.156 + }
93.157 +
93.158 + void setWriteSelectionKey(SelectionKey selKey)
93.159 + {
93.160 + this.writeSelKey = selKey;
93.161 + }
93.162 +
93.163 + public void shutdownInput()
93.164 + {
93.165 + try
93.166 + {
93.167 + // Closes the input line of the channel's socket, so no new data
93.168 + // will be received and a timeout can be triggered.
93.169 + this.channel.socket().shutdownInput();
93.170 + }
93.171 + catch(IOException ex)
93.172 + {
93.173 + Log.get().warning("Exception in NNTPConnection.shutdownInput(): " + ex);
93.174 + }
93.175 + }
93.176 +
93.177 + public void shutdownOutput()
93.178 + {
93.179 + cancelTimer.schedule(new TimerTask()
93.180 + {
93.181 + @Override
93.182 + public void run()
93.183 + {
93.184 + try
93.185 + {
93.186 + // Closes the output line of the channel's socket.
93.187 + channel.socket().shutdownOutput();
93.188 + channel.close();
93.189 + }
93.190 + catch(SocketException ex)
93.191 + {
93.192 + // Socket was already disconnected
93.193 + Log.get().info("NNTPConnection.shutdownOutput(): " + ex);
93.194 + }
93.195 + catch(Exception ex)
93.196 + {
93.197 + Log.get().warning("NNTPConnection.shutdownOutput(): " + ex);
93.198 + }
93.199 + }
93.200 + }, 3000);
93.201 + }
93.202 +
93.203 + public SocketChannel getSocketChannel()
93.204 + {
93.205 + return this.channel;
93.206 + }
93.207 +
93.208 + public Article getCurrentArticle()
93.209 + {
93.210 + return this.currentArticle;
93.211 + }
93.212 +
93.213 + public Charset getCurrentCharset()
93.214 + {
93.215 + return this.charset;
93.216 + }
93.217 +
93.218 + /**
93.219 + * @return The currently selected communication channel (not SocketChannel)
93.220 + */
93.221 + public Channel getCurrentChannel()
93.222 + {
93.223 + return this.currentGroup;
93.224 + }
93.225 +
93.226 + public void setCurrentArticle(final Article article)
93.227 + {
93.228 + this.currentArticle = article;
93.229 + }
93.230 +
93.231 + public void setCurrentGroup(final Channel group)
93.232 + {
93.233 + this.currentGroup = group;
93.234 + }
93.235 +
93.236 + public long getLastActivity()
93.237 + {
93.238 + return this.lastActivity;
93.239 + }
93.240 +
93.241 + /**
93.242 + * Due to the readLockGate there is no need to synchronize this method.
93.243 + * @param raw
93.244 + * @throws IllegalArgumentException if raw is null.
93.245 + * @throws IllegalStateException if calling thread does not own the readLock.
93.246 + */
93.247 + void lineReceived(byte[] raw)
93.248 + {
93.249 + if(raw == null)
93.250 + {
93.251 + throw new IllegalArgumentException("raw is null");
93.252 + }
93.253 +
93.254 + if(readLock == 0 || readLock != Thread.currentThread().hashCode())
93.255 + {
93.256 + throw new IllegalStateException("readLock not properly set");
93.257 + }
93.258 +
93.259 + this.lastActivity = System.currentTimeMillis();
93.260 +
93.261 + String line = new String(raw, this.charset);
93.262 +
93.263 + // There might be a trailing \r, but trim() is a bad idea
93.264 + // as it removes also leading spaces from long header lines.
93.265 + if(line.endsWith("\r"))
93.266 + {
93.267 + line = line.substring(0, line.length() - 1);
93.268 + raw = Arrays.copyOf(raw, raw.length - 1);
93.269 + }
93.270 +
93.271 + Log.get().fine("<< " + line);
93.272 +
93.273 + if(command == null)
93.274 + {
93.275 + command = parseCommandLine(line);
93.276 + assert command != null;
93.277 + }
93.278 +
93.279 + try
93.280 + {
93.281 + // The command object will process the line we just received
93.282 + try
93.283 + {
93.284 + command.processLine(this, line, raw);
93.285 + }
93.286 + catch(StorageBackendException ex)
93.287 + {
93.288 + Log.get().info("Retry command processing after StorageBackendException");
93.289 +
93.290 + // Try it a second time, so that the backend has time to recover
93.291 + command.processLine(this, line, raw);
93.292 + }
93.293 + }
93.294 + catch(ClosedChannelException ex0)
93.295 + {
93.296 + try
93.297 + {
93.298 + Log.get().info("Connection to " + channel.socket().getRemoteSocketAddress()
93.299 + + " closed: " + ex0);
93.300 + }
93.301 + catch(Exception ex0a)
93.302 + {
93.303 + ex0a.printStackTrace();
93.304 + }
93.305 + }
93.306 + catch(Exception ex1) // This will catch a second StorageBackendException
93.307 + {
93.308 + try
93.309 + {
93.310 + command = null;
93.311 + ex1.printStackTrace();
93.312 + println("500 Internal server error");
93.313 + }
93.314 + catch(Exception ex2)
93.315 + {
93.316 + ex2.printStackTrace();
93.317 + }
93.318 + }
93.319 +
93.320 + if(command == null || command.hasFinished())
93.321 + {
93.322 + command = null;
93.323 + charset = Charset.forName("UTF-8"); // Reset to default
93.324 + }
93.325 + }
93.326 +
93.327 + /**
93.328 + * This method determines the fitting command processing class.
93.329 + * @param line
93.330 + * @return
93.331 + */
93.332 + private Command parseCommandLine(String line)
93.333 + {
93.334 + String cmdStr = line.split(" ")[0];
93.335 + return CommandSelector.getInstance().get(cmdStr);
93.336 + }
93.337 +
93.338 + /**
93.339 + * Puts the given line into the output buffer, adds a newline character
93.340 + * and returns. The method returns immediately and does not block until
93.341 + * the line was sent. If line is longer than 510 octets it is split up in
93.342 + * several lines. Each line is terminated by \r\n (NNTPConnection.NEWLINE).
93.343 + * @param line
93.344 + */
93.345 + public void println(final CharSequence line, final Charset charset)
93.346 + throws IOException
93.347 + {
93.348 + writeToChannel(CharBuffer.wrap(line), charset, line);
93.349 + writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
93.350 + }
93.351 +
93.352 + /**
93.353 + * Writes the given raw lines to the output buffers and finishes with
93.354 + * a newline character (\r\n).
93.355 + * @param rawLines
93.356 + */
93.357 + public void println(final byte[] rawLines)
93.358 + throws IOException
93.359 + {
93.360 + this.lineBuffers.addOutputBuffer(ByteBuffer.wrap(rawLines));
93.361 + writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
93.362 + }
93.363 +
93.364 + /**
93.365 + * Encodes the given CharBuffer using the given Charset to a bunch of
93.366 + * ByteBuffers (each 512 bytes large) and enqueues them for writing at the
93.367 + * connected SocketChannel.
93.368 + * @throws java.io.IOException
93.369 + */
93.370 + private void writeToChannel(CharBuffer characters, final Charset charset,
93.371 + CharSequence debugLine)
93.372 + throws IOException
93.373 + {
93.374 + if(!charset.canEncode())
93.375 + {
93.376 + Log.get().severe("FATAL: Charset " + charset + " cannot encode!");
93.377 + return;
93.378 + }
93.379 +
93.380 + // Write characters to output buffers
93.381 + LineEncoder lenc = new LineEncoder(characters, charset);
93.382 + lenc.encode(lineBuffers);
93.383 +
93.384 + enableWriteEvents(debugLine);
93.385 + }
93.386 +
93.387 + private void enableWriteEvents(CharSequence debugLine)
93.388 + {
93.389 + // Enable OP_WRITE events so that the buffers are processed
93.390 + try
93.391 + {
93.392 + this.writeSelKey.interestOps(SelectionKey.OP_WRITE);
93.393 + ChannelWriter.getInstance().getSelector().wakeup();
93.394 + }
93.395 + catch(Exception ex) // CancelledKeyException and ChannelCloseException
93.396 + {
93.397 + Log.get().warning("NNTPConnection.writeToChannel(): " + ex);
93.398 + return;
93.399 + }
93.400 +
93.401 + // Update last activity timestamp
93.402 + this.lastActivity = System.currentTimeMillis();
93.403 + if(debugLine != null)
93.404 + {
93.405 + Log.get().fine(">> " + debugLine);
93.406 + }
93.407 + }
93.408 +
93.409 + public void println(final CharSequence line)
93.410 + throws IOException
93.411 + {
93.412 + println(line, charset);
93.413 + }
93.414 +
93.415 + public void print(final String line)
93.416 + throws IOException
93.417 + {
93.418 + writeToChannel(CharBuffer.wrap(line), charset, line);
93.419 + }
93.420 +
93.421 + public void setCurrentCharset(final Charset charset)
93.422 + {
93.423 + this.charset = charset;
93.424 + }
93.425 +
93.426 + void setLastActivity(long timestamp)
93.427 + {
93.428 + this.lastActivity = timestamp;
93.429 + }
93.430 +
93.431 +}
94.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
94.2 +++ b/src/org/sonews/daemon/NNTPDaemon.java Sun Aug 29 17:28:58 2010 +0200
94.3 @@ -0,0 +1,197 @@
94.4 +/*
94.5 + * SONEWS News Server
94.6 + * see AUTHORS for the list of contributors
94.7 + *
94.8 + * This program is free software: you can redistribute it and/or modify
94.9 + * it under the terms of the GNU General Public License as published by
94.10 + * the Free Software Foundation, either version 3 of the License, or
94.11 + * (at your option) any later version.
94.12 + *
94.13 + * This program is distributed in the hope that it will be useful,
94.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
94.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
94.16 + * GNU General Public License for more details.
94.17 + *
94.18 + * You should have received a copy of the GNU General Public License
94.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
94.20 + */
94.21 +
94.22 +package org.sonews.daemon;
94.23 +
94.24 +import org.sonews.config.Config;
94.25 +import org.sonews.Main;
94.26 +import org.sonews.util.Log;
94.27 +import java.io.IOException;
94.28 +import java.net.BindException;
94.29 +import java.net.InetSocketAddress;
94.30 +import java.net.ServerSocket;
94.31 +import java.nio.channels.CancelledKeyException;
94.32 +import java.nio.channels.ClosedChannelException;
94.33 +import java.nio.channels.SelectionKey;
94.34 +import java.nio.channels.Selector;
94.35 +import java.nio.channels.ServerSocketChannel;
94.36 +import java.nio.channels.SocketChannel;
94.37 +
94.38 +/**
94.39 + * NNTP daemon using SelectableChannels.
94.40 + * @author Christian Lins
94.41 + * @since sonews/0.5.0
94.42 + */
94.43 +public final class NNTPDaemon extends AbstractDaemon
94.44 +{
94.45 +
94.46 + public static final Object RegisterGate = new Object();
94.47 +
94.48 + private static NNTPDaemon instance = null;
94.49 +
94.50 + public static synchronized NNTPDaemon createInstance(int port)
94.51 + {
94.52 + if(instance == null)
94.53 + {
94.54 + instance = new NNTPDaemon(port);
94.55 + return instance;
94.56 + }
94.57 + else
94.58 + {
94.59 + throw new RuntimeException("NNTPDaemon.createInstance() called twice");
94.60 + }
94.61 + }
94.62 +
94.63 + private int port;
94.64 +
94.65 + private NNTPDaemon(final int port)
94.66 + {
94.67 + Log.get().info("Server listening on port " + port);
94.68 + this.port = port;
94.69 + }
94.70 +
94.71 + @Override
94.72 + public void run()
94.73 + {
94.74 + try
94.75 + {
94.76 + // Create a Selector that handles the SocketChannel multiplexing
94.77 + final Selector readSelector = Selector.open();
94.78 + final Selector writeSelector = Selector.open();
94.79 +
94.80 + // Start working threads
94.81 + final int workerThreads = Runtime.getRuntime().availableProcessors() * 4;
94.82 + ConnectionWorker[] cworkers = new ConnectionWorker[workerThreads];
94.83 + for(int n = 0; n < workerThreads; n++)
94.84 + {
94.85 + cworkers[n] = new ConnectionWorker();
94.86 + cworkers[n].start();
94.87 + }
94.88 +
94.89 + ChannelWriter.getInstance().setSelector(writeSelector);
94.90 + ChannelReader.getInstance().setSelector(readSelector);
94.91 + ChannelWriter.getInstance().start();
94.92 + ChannelReader.getInstance().start();
94.93 +
94.94 + final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
94.95 + serverSocketChannel.configureBlocking(true); // Set to blocking mode
94.96 +
94.97 + // Configure ServerSocket; bind to socket...
94.98 + final ServerSocket serverSocket = serverSocketChannel.socket();
94.99 + serverSocket.bind(new InetSocketAddress(this.port));
94.100 +
94.101 + while(isRunning())
94.102 + {
94.103 + SocketChannel socketChannel;
94.104 +
94.105 + try
94.106 + {
94.107 + // As we set the server socket channel to blocking mode the accept()
94.108 + // method will block.
94.109 + socketChannel = serverSocketChannel.accept();
94.110 + socketChannel.configureBlocking(false);
94.111 + assert socketChannel.isConnected();
94.112 + assert socketChannel.finishConnect();
94.113 + }
94.114 + catch(IOException ex)
94.115 + {
94.116 + // Under heavy load an IOException "Too many open files may
94.117 + // be thrown. It most cases we should slow down the connection
94.118 + // accepting, to give the worker threads some time to process work.
94.119 + Log.get().severe("IOException while accepting connection: " + ex.getMessage());
94.120 + Log.get().info("Connection accepting sleeping for seconds...");
94.121 + Thread.sleep(5000); // 5 seconds
94.122 + continue;
94.123 + }
94.124 +
94.125 + final NNTPConnection conn;
94.126 + try
94.127 + {
94.128 + conn = new NNTPConnection(socketChannel);
94.129 + Connections.getInstance().add(conn);
94.130 + }
94.131 + catch(IOException ex)
94.132 + {
94.133 + Log.get().warning(ex.toString());
94.134 + socketChannel.close();
94.135 + continue;
94.136 + }
94.137 +
94.138 + try
94.139 + {
94.140 + SelectionKey selKeyWrite =
94.141 + registerSelector(writeSelector, socketChannel, SelectionKey.OP_WRITE);
94.142 + registerSelector(readSelector, socketChannel, SelectionKey.OP_READ);
94.143 +
94.144 + Log.get().info("Connected: " + socketChannel.socket().getRemoteSocketAddress());
94.145 +
94.146 + // Set write selection key and send hello to client
94.147 + conn.setWriteSelectionKey(selKeyWrite);
94.148 + conn.println("200 " + Config.inst().get(Config.HOSTNAME, "localhost")
94.149 + + " " + Main.VERSION + " news server ready - (posting ok).");
94.150 + }
94.151 + catch(CancelledKeyException cke)
94.152 + {
94.153 + Log.get().warning("CancelledKeyException " + cke.getMessage() + " was thrown: "
94.154 + + socketChannel.socket());
94.155 + }
94.156 + catch(ClosedChannelException cce)
94.157 + {
94.158 + Log.get().warning("ClosedChannelException " + cce.getMessage() + " was thrown: "
94.159 + + socketChannel.socket());
94.160 + }
94.161 + }
94.162 + }
94.163 + catch(BindException ex)
94.164 + {
94.165 + // Could not bind to socket; this is a fatal problem; so perform shutdown
94.166 + ex.printStackTrace();
94.167 + System.exit(1);
94.168 + }
94.169 + catch(IOException ex)
94.170 + {
94.171 + ex.printStackTrace();
94.172 + }
94.173 + catch(Exception ex)
94.174 + {
94.175 + ex.printStackTrace();
94.176 + }
94.177 + }
94.178 +
94.179 + public static SelectionKey registerSelector(final Selector selector,
94.180 + final SocketChannel channel, final int op)
94.181 + throws CancelledKeyException, ClosedChannelException
94.182 + {
94.183 + // Register the selector at the channel, so that it will be notified
94.184 + // on the socket's events
94.185 + synchronized(RegisterGate)
94.186 + {
94.187 + // Wakeup the currently blocking reader/writer thread; we have locked
94.188 + // the RegisterGate to prevent the awakened thread to block again
94.189 + selector.wakeup();
94.190 +
94.191 + // Lock the selector to prevent the waiting worker threads going into
94.192 + // selector.select() which would block the selector.
94.193 + synchronized (selector)
94.194 + {
94.195 + return channel.register(selector, op, null);
94.196 + }
94.197 + }
94.198 + }
94.199 +
94.200 +}
95.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
95.2 +++ b/src/org/sonews/daemon/command/ArticleCommand.java Sun Aug 29 17:28:58 2010 +0200
95.3 @@ -0,0 +1,174 @@
95.4 +/*
95.5 + * SONEWS News Server
95.6 + * see AUTHORS for the list of contributors
95.7 + *
95.8 + * This program is free software: you can redistribute it and/or modify
95.9 + * it under the terms of the GNU General Public License as published by
95.10 + * the Free Software Foundation, either version 3 of the License, or
95.11 + * (at your option) any later version.
95.12 + *
95.13 + * This program is distributed in the hope that it will be useful,
95.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
95.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
95.16 + * GNU General Public License for more details.
95.17 + *
95.18 + * You should have received a copy of the GNU General Public License
95.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
95.20 + */
95.21 +
95.22 +package org.sonews.daemon.command;
95.23 +
95.24 +import java.io.IOException;
95.25 +import org.sonews.storage.Article;
95.26 +import org.sonews.daemon.NNTPConnection;
95.27 +import org.sonews.storage.Channel;
95.28 +import org.sonews.storage.StorageBackendException;
95.29 +
95.30 +/**
95.31 + * Class handling the ARTICLE, BODY and HEAD commands.
95.32 + * @author Christian Lins
95.33 + * @author Dennis Schwerdel
95.34 + * @since n3tpd/0.1
95.35 + */
95.36 +public class ArticleCommand implements Command
95.37 +{
95.38 +
95.39 + @Override
95.40 + public String[] getSupportedCommandStrings()
95.41 + {
95.42 + return new String[] {"ARTICLE", "BODY", "HEAD"};
95.43 + }
95.44 +
95.45 + @Override
95.46 + public boolean hasFinished()
95.47 + {
95.48 + return true;
95.49 + }
95.50 +
95.51 + @Override
95.52 + public String impliedCapability()
95.53 + {
95.54 + return null;
95.55 + }
95.56 +
95.57 + @Override
95.58 + public boolean isStateful()
95.59 + {
95.60 + return false;
95.61 + }
95.62 +
95.63 + // TODO: Refactor this method to reduce its complexity!
95.64 + @Override
95.65 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
95.66 + throws IOException
95.67 + {
95.68 + final String[] command = line.split(" ");
95.69 +
95.70 + Article article = null;
95.71 + long artIndex = -1;
95.72 + if (command.length == 1)
95.73 + {
95.74 + article = conn.getCurrentArticle();
95.75 + if (article == null)
95.76 + {
95.77 + conn.println("420 no current article has been selected");
95.78 + return;
95.79 + }
95.80 + }
95.81 + else if (command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN))
95.82 + {
95.83 + // Message-ID
95.84 + article = Article.getByMessageID(command[1]);
95.85 + if (article == null)
95.86 + {
95.87 + conn.println("430 no such article found");
95.88 + return;
95.89 + }
95.90 + }
95.91 + else
95.92 + {
95.93 + // Message Number
95.94 + try
95.95 + {
95.96 + Channel currentGroup = conn.getCurrentChannel();
95.97 + if(currentGroup == null)
95.98 + {
95.99 + conn.println("400 no group selected");
95.100 + return;
95.101 + }
95.102 +
95.103 + artIndex = Long.parseLong(command[1]);
95.104 + article = currentGroup.getArticle(artIndex);
95.105 + }
95.106 + catch(NumberFormatException ex)
95.107 + {
95.108 + ex.printStackTrace();
95.109 + }
95.110 + catch(StorageBackendException ex)
95.111 + {
95.112 + ex.printStackTrace();
95.113 + }
95.114 +
95.115 + if (article == null)
95.116 + {
95.117 + conn.println("423 no such article number in this group");
95.118 + return;
95.119 + }
95.120 + conn.setCurrentArticle(article);
95.121 + }
95.122 +
95.123 + if(command[0].equalsIgnoreCase("ARTICLE"))
95.124 + {
95.125 + conn.println("220 " + artIndex + " " + article.getMessageID()
95.126 + + " article retrieved - head and body follow");
95.127 + conn.println(article.getHeaderSource());
95.128 + conn.println("");
95.129 + conn.println(article.getBody());
95.130 + conn.println(".");
95.131 + }
95.132 + else if(command[0].equalsIgnoreCase("BODY"))
95.133 + {
95.134 + conn.println("222 " + artIndex + " " + article.getMessageID() + " body");
95.135 + conn.println(article.getBody());
95.136 + conn.println(".");
95.137 + }
95.138 +
95.139 + /*
95.140 + * HEAD: This command is mandatory.
95.141 + *
95.142 + * Syntax
95.143 + * HEAD message-id
95.144 + * HEAD number
95.145 + * HEAD
95.146 + *
95.147 + * Responses
95.148 + *
95.149 + * First form (message-id specified)
95.150 + * 221 0|n message-id Headers follow (multi-line)
95.151 + * 430 No article with that message-id
95.152 + *
95.153 + * Second form (article number specified)
95.154 + * 221 n message-id Headers follow (multi-line)
95.155 + * 412 No newsgroup selected
95.156 + * 423 No article with that number
95.157 + *
95.158 + * Third form (current article number used)
95.159 + * 221 n message-id Headers follow (multi-line)
95.160 + * 412 No newsgroup selected
95.161 + * 420 Current article number is invalid
95.162 + *
95.163 + * Parameters
95.164 + * number Requested article number
95.165 + * n Returned article number
95.166 + * message-id Article message-id
95.167 + */
95.168 + else if(command[0].equalsIgnoreCase("HEAD"))
95.169 + {
95.170 + conn.println("221 " + artIndex + " " + article.getMessageID()
95.171 + + " Headers follow (multi-line)");
95.172 + conn.println(article.getHeaderSource());
95.173 + conn.println(".");
95.174 + }
95.175 + }
95.176 +
95.177 +}
96.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
96.2 +++ b/src/org/sonews/daemon/command/CapabilitiesCommand.java Sun Aug 29 17:28:58 2010 +0200
96.3 @@ -0,0 +1,93 @@
96.4 +/*
96.5 + * SONEWS News Server
96.6 + * see AUTHORS for the list of contributors
96.7 + *
96.8 + * This program is free software: you can redistribute it and/or modify
96.9 + * it under the terms of the GNU General Public License as published by
96.10 + * the Free Software Foundation, either version 3 of the License, or
96.11 + * (at your option) any later version.
96.12 + *
96.13 + * This program is distributed in the hope that it will be useful,
96.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
96.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
96.16 + * GNU General Public License for more details.
96.17 + *
96.18 + * You should have received a copy of the GNU General Public License
96.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
96.20 + */
96.21 +
96.22 +package org.sonews.daemon.command;
96.23 +
96.24 +import java.io.IOException;
96.25 +import org.sonews.daemon.NNTPConnection;
96.26 +
96.27 +/**
96.28 + * <pre>
96.29 + * The CAPABILITIES command allows a client to determine the
96.30 + * capabilities of the server at any given time.
96.31 + *
96.32 + * This command MAY be issued at any time; the server MUST NOT require
96.33 + * it to be issued in order to make use of any capability. The response
96.34 + * generated by this command MAY change during a session because of
96.35 + * other state information (which, in turn, may be changed by the
96.36 + * effects of other commands or by external events). An NNTP client is
96.37 + * only able to get the current and correct information concerning
96.38 + * available capabilities at any point during a session by issuing a
96.39 + * CAPABILITIES command at that point of that session and processing the
96.40 + * response.
96.41 + * </pre>
96.42 + * @author Christian Lins
96.43 + * @since sonews/0.5.0
96.44 + */
96.45 +public class CapabilitiesCommand implements Command
96.46 +{
96.47 +
96.48 + static final String[] CAPABILITIES = new String[]
96.49 + {
96.50 + "VERSION 2", // MUST be the first one; VERSION 2 refers to RFC3977
96.51 + "READER", // Server implements commands for reading
96.52 + "POST", // Server implements POST command
96.53 + "OVER" // Server implements OVER command
96.54 + };
96.55 +
96.56 + @Override
96.57 + public String[] getSupportedCommandStrings()
96.58 + {
96.59 + return new String[] {"CAPABILITIES"};
96.60 + }
96.61 +
96.62 + /**
96.63 + * First called after one call to processLine().
96.64 + * @return
96.65 + */
96.66 + @Override
96.67 + public boolean hasFinished()
96.68 + {
96.69 + return true;
96.70 + }
96.71 +
96.72 + @Override
96.73 + public String impliedCapability()
96.74 + {
96.75 + return null;
96.76 + }
96.77 +
96.78 + @Override
96.79 + public boolean isStateful()
96.80 + {
96.81 + return false;
96.82 + }
96.83 +
96.84 + @Override
96.85 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
96.86 + throws IOException
96.87 + {
96.88 + conn.println("101 Capabilities list:");
96.89 + for(String cap : CAPABILITIES)
96.90 + {
96.91 + conn.println(cap);
96.92 + }
96.93 + conn.println(".");
96.94 + }
96.95 +
96.96 +}
97.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
97.2 +++ b/src/org/sonews/daemon/command/Command.java Sun Aug 29 17:28:58 2010 +0200
97.3 @@ -0,0 +1,51 @@
97.4 +/*
97.5 + * SONEWS News Server
97.6 + * see AUTHORS for the list of contributors
97.7 + *
97.8 + * This program is free software: you can redistribute it and/or modify
97.9 + * it under the terms of the GNU General Public License as published by
97.10 + * the Free Software Foundation, either version 3 of the License, or
97.11 + * (at your option) any later version.
97.12 + *
97.13 + * This program is distributed in the hope that it will be useful,
97.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
97.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
97.16 + * GNU General Public License for more details.
97.17 + *
97.18 + * You should have received a copy of the GNU General Public License
97.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
97.20 + */
97.21 +
97.22 +package org.sonews.daemon.command;
97.23 +
97.24 +import java.io.IOException;
97.25 +import org.sonews.daemon.NNTPConnection;
97.26 +import org.sonews.storage.StorageBackendException;
97.27 +
97.28 +/**
97.29 + * Interface for pluggable NNTP commands handling classes.
97.30 + * @author Christian Lins
97.31 + * @since sonews/0.6.0
97.32 + */
97.33 +public interface Command
97.34 +{
97.35 +
97.36 + /**
97.37 + * @return true if this instance can be reused.
97.38 + */
97.39 + boolean hasFinished();
97.40 +
97.41 + /**
97.42 + * Returns capability string that is implied by this command class.
97.43 + * MAY return null if the command is required by the NNTP standard.
97.44 + */
97.45 + String impliedCapability();
97.46 +
97.47 + boolean isStateful();
97.48 +
97.49 + String[] getSupportedCommandStrings();
97.50 +
97.51 + void processLine(NNTPConnection conn, String line, byte[] rawLine)
97.52 + throws IOException, StorageBackendException;
97.53 +
97.54 +}
98.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
98.2 +++ b/src/org/sonews/daemon/command/GroupCommand.java Sun Aug 29 17:28:58 2010 +0200
98.3 @@ -0,0 +1,102 @@
98.4 +/*
98.5 + * SONEWS News Server
98.6 + * see AUTHORS for the list of contributors
98.7 + *
98.8 + * This program is free software: you can redistribute it and/or modify
98.9 + * it under the terms of the GNU General Public License as published by
98.10 + * the Free Software Foundation, either version 3 of the License, or
98.11 + * (at your option) any later version.
98.12 + *
98.13 + * This program is distributed in the hope that it will be useful,
98.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
98.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
98.16 + * GNU General Public License for more details.
98.17 + *
98.18 + * You should have received a copy of the GNU General Public License
98.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
98.20 + */
98.21 +
98.22 +package org.sonews.daemon.command;
98.23 +
98.24 +import java.io.IOException;
98.25 +import org.sonews.daemon.NNTPConnection;
98.26 +import org.sonews.storage.Channel;
98.27 +import org.sonews.storage.StorageBackendException;
98.28 +
98.29 +/**
98.30 + * Class handling the GROUP command.
98.31 + * <pre>
98.32 + * Syntax
98.33 + * GROUP group
98.34 + *
98.35 + * Responses
98.36 + * 211 number low high group Group successfully selected
98.37 + * 411 No such newsgroup
98.38 + *
98.39 + * Parameters
98.40 + * group Name of newsgroup
98.41 + * number Estimated number of articles in the group
98.42 + * low Reported low water mark
98.43 + * high Reported high water mark
98.44 + * </pre>
98.45 + * (from RFC 3977)
98.46 + *
98.47 + * @author Christian Lins
98.48 + * @author Dennis Schwerdel
98.49 + * @since n3tpd/0.1
98.50 + */
98.51 +public class GroupCommand implements Command
98.52 +{
98.53 +
98.54 + @Override
98.55 + public String[] getSupportedCommandStrings()
98.56 + {
98.57 + return new String[]{"GROUP"};
98.58 + }
98.59 +
98.60 + @Override
98.61 + public boolean hasFinished()
98.62 + {
98.63 + return true;
98.64 + }
98.65 +
98.66 + @Override
98.67 + public String impliedCapability()
98.68 + {
98.69 + return null;
98.70 + }
98.71 +
98.72 + @Override
98.73 + public boolean isStateful()
98.74 + {
98.75 + return true;
98.76 + }
98.77 +
98.78 + @Override
98.79 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
98.80 + throws IOException, StorageBackendException
98.81 + {
98.82 + final String[] command = line.split(" ");
98.83 +
98.84 + Channel group;
98.85 + if(command.length >= 2)
98.86 + {
98.87 + group = Channel.getByName(command[1]);
98.88 + if(group == null || group.isDeleted())
98.89 + {
98.90 + conn.println("411 no such news group");
98.91 + }
98.92 + else
98.93 + {
98.94 + conn.setCurrentGroup(group);
98.95 + conn.println("211 " + group.getPostingsCount() + " " + group.getFirstArticleNumber()
98.96 + + " " + group.getLastArticleNumber() + " " + group.getName() + " group selected");
98.97 + }
98.98 + }
98.99 + else
98.100 + {
98.101 + conn.println("500 no group name given");
98.102 + }
98.103 + }
98.104 +
98.105 +}
99.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
99.2 +++ b/src/org/sonews/daemon/command/HelpCommand.java Sun Aug 29 17:28:58 2010 +0200
99.3 @@ -0,0 +1,100 @@
99.4 +/*
99.5 + * SONEWS News Server
99.6 + * see AUTHORS for the list of contributors
99.7 + *
99.8 + * This program is free software: you can redistribute it and/or modify
99.9 + * it under the terms of the GNU General Public License as published by
99.10 + * the Free Software Foundation, either version 3 of the License, or
99.11 + * (at your option) any later version.
99.12 + *
99.13 + * This program is distributed in the hope that it will be useful,
99.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
99.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
99.16 + * GNU General Public License for more details.
99.17 + *
99.18 + * You should have received a copy of the GNU General Public License
99.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
99.20 + */
99.21 +
99.22 +package org.sonews.daemon.command;
99.23 +
99.24 +import java.io.IOException;
99.25 +import java.util.Set;
99.26 +import org.sonews.daemon.CommandSelector;
99.27 +import org.sonews.daemon.NNTPConnection;
99.28 +import org.sonews.util.io.Resource;
99.29 +
99.30 +/**
99.31 + * This command provides a short summary of the commands that are
99.32 + * understood by this implementation of the server. The help text will
99.33 + * be presented as a multi-line data block following the 100 response
99.34 + * code (taken from RFC).
99.35 + * @author Christian Lins
99.36 + * @since sonews/0.5.0
99.37 + */
99.38 +public class HelpCommand implements Command
99.39 +{
99.40 +
99.41 + @Override
99.42 + public boolean hasFinished()
99.43 + {
99.44 + return true;
99.45 + }
99.46 +
99.47 + @Override
99.48 + public String impliedCapability()
99.49 + {
99.50 + return null;
99.51 + }
99.52 +
99.53 + @Override
99.54 + public boolean isStateful()
99.55 + {
99.56 + return true;
99.57 + }
99.58 +
99.59 + @Override
99.60 + public String[] getSupportedCommandStrings()
99.61 + {
99.62 + return new String[]{"HELP"};
99.63 + }
99.64 +
99.65 + @Override
99.66 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
99.67 + throws IOException
99.68 + {
99.69 + final String[] command = line.split(" ");
99.70 + conn.println("100 help text follows");
99.71 +
99.72 + if(line.length() <= 1)
99.73 + {
99.74 + final String[] help = Resource
99.75 + .getAsString("helpers/helptext", true).split("\n");
99.76 + for(String hstr : help)
99.77 + {
99.78 + conn.println(hstr);
99.79 + }
99.80 +
99.81 + Set<String> commandNames = CommandSelector.getCommandNames();
99.82 + for(String cmdName : commandNames)
99.83 + {
99.84 + conn.println(cmdName);
99.85 + }
99.86 + }
99.87 + else
99.88 + {
99.89 + Command cmd = CommandSelector.getInstance().get(command[1]);
99.90 + if(cmd instanceof HelpfulCommand)
99.91 + {
99.92 + conn.println(((HelpfulCommand)cmd).getHelpString());
99.93 + }
99.94 + else
99.95 + {
99.96 + conn.println("No further help information available.");
99.97 + }
99.98 + }
99.99 +
99.100 + conn.println(".");
99.101 + }
99.102 +
99.103 +}
100.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
100.2 +++ b/src/org/sonews/daemon/command/HelpfulCommand.java Sun Aug 29 17:28:58 2010 +0200
100.3 @@ -0,0 +1,35 @@
100.4 +/*
100.5 + * SONEWS News Server
100.6 + * see AUTHORS for the list of contributors
100.7 + *
100.8 + * This program is free software: you can redistribute it and/or modify
100.9 + * it under the terms of the GNU General Public License as published by
100.10 + * the Free Software Foundation, either version 3 of the License, or
100.11 + * (at your option) any later version.
100.12 + *
100.13 + * This program is distributed in the hope that it will be useful,
100.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
100.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
100.16 + * GNU General Public License for more details.
100.17 + *
100.18 + * You should have received a copy of the GNU General Public License
100.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
100.20 + */
100.21 +
100.22 +package org.sonews.daemon.command;
100.23 +
100.24 +/**
100.25 + *
100.26 + * @since sonews/1.1
100.27 + * @author Christian Lins
100.28 + */
100.29 +public interface HelpfulCommand extends Command
100.30 +{
100.31 +
100.32 + /**
100.33 + * @return A short description of this command, that is
100.34 + * used within the output of the HELP command.
100.35 + */
100.36 + String getHelpString();
100.37 +
100.38 +}
101.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
101.2 +++ b/src/org/sonews/daemon/command/ListCommand.java Sun Aug 29 17:28:58 2010 +0200
101.3 @@ -0,0 +1,153 @@
101.4 +/*
101.5 + * SONEWS News Server
101.6 + * see AUTHORS for the list of contributors
101.7 + *
101.8 + * This program is free software: you can redistribute it and/or modify
101.9 + * it under the terms of the GNU General Public License as published by
101.10 + * the Free Software Foundation, either version 3 of the License, or
101.11 + * (at your option) any later version.
101.12 + *
101.13 + * This program is distributed in the hope that it will be useful,
101.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
101.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
101.16 + * GNU General Public License for more details.
101.17 + *
101.18 + * You should have received a copy of the GNU General Public License
101.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
101.20 + */
101.21 +
101.22 +package org.sonews.daemon.command;
101.23 +
101.24 +import java.io.IOException;
101.25 +import java.util.List;
101.26 +import java.util.regex.Matcher;
101.27 +import java.util.regex.Pattern;
101.28 +import java.util.regex.PatternSyntaxException;
101.29 +import org.sonews.daemon.NNTPConnection;
101.30 +import org.sonews.storage.Channel;
101.31 +import org.sonews.storage.StorageBackendException;
101.32 +import org.sonews.util.Log;
101.33 +
101.34 +/**
101.35 + * Class handling the LIST command.
101.36 + * @author Christian Lins
101.37 + * @author Dennis Schwerdel
101.38 + * @since n3tpd/0.1
101.39 + */
101.40 +public class ListCommand implements Command
101.41 +{
101.42 +
101.43 + @Override
101.44 + public String[] getSupportedCommandStrings()
101.45 + {
101.46 + return new String[]{"LIST"};
101.47 + }
101.48 +
101.49 + @Override
101.50 + public boolean hasFinished()
101.51 + {
101.52 + return true;
101.53 + }
101.54 +
101.55 + @Override
101.56 + public String impliedCapability()
101.57 + {
101.58 + return null;
101.59 + }
101.60 +
101.61 + @Override
101.62 + public boolean isStateful()
101.63 + {
101.64 + return false;
101.65 + }
101.66 +
101.67 + @Override
101.68 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
101.69 + throws IOException, StorageBackendException
101.70 + {
101.71 + final String[] command = line.split(" ");
101.72 +
101.73 + if(command.length >= 2)
101.74 + {
101.75 + if(command[1].equalsIgnoreCase("OVERVIEW.FMT"))
101.76 + {
101.77 + conn.println("215 information follows");
101.78 + conn.println("Subject:\nFrom:\nDate:\nMessage-ID:\nReferences:\nBytes:\nLines:\nXref");
101.79 + conn.println(".");
101.80 + }
101.81 + else if(command[1].equalsIgnoreCase("NEWSGROUPS"))
101.82 + {
101.83 + conn.println("215 information follows");
101.84 + final List<Channel> list = Channel.getAll();
101.85 + for (Channel g : list)
101.86 + {
101.87 + conn.println(g.getName() + "\t" + "-");
101.88 + }
101.89 + conn.println(".");
101.90 + }
101.91 + else if(command[1].equalsIgnoreCase("SUBSCRIPTIONS"))
101.92 + {
101.93 + conn.println("215 information follows");
101.94 + conn.println(".");
101.95 + }
101.96 + else if(command[1].equalsIgnoreCase("EXTENSIONS"))
101.97 + {
101.98 + conn.println("202 Supported NNTP extensions.");
101.99 + conn.println("LISTGROUP");
101.100 + conn.println("XDAEMON");
101.101 + conn.println("XPAT");
101.102 + conn.println(".");
101.103 + }
101.104 + else if(command[1].equalsIgnoreCase("ACTIVE"))
101.105 + {
101.106 + String pattern = command.length == 2
101.107 + ? null : command[2].replace("*", "\\w*");
101.108 + printGroupInfo(conn, pattern);
101.109 + }
101.110 + else
101.111 + {
101.112 + conn.println("500 unknown argument to LIST command");
101.113 + }
101.114 + }
101.115 + else
101.116 + {
101.117 + printGroupInfo(conn, null);
101.118 + }
101.119 + }
101.120 +
101.121 + private void printGroupInfo(NNTPConnection conn, String pattern)
101.122 + throws IOException, StorageBackendException
101.123 + {
101.124 + final List<Channel> groups = Channel.getAll();
101.125 + if(groups != null)
101.126 + {
101.127 + conn.println("215 list of newsgroups follows");
101.128 + for(Channel g : groups)
101.129 + {
101.130 + try
101.131 + {
101.132 + Matcher matcher = pattern == null ?
101.133 + null : Pattern.compile(pattern).matcher(g.getName());
101.134 + if(!g.isDeleted() &&
101.135 + (matcher == null || matcher.find()))
101.136 + {
101.137 + String writeable = g.isWriteable() ? " y" : " n";
101.138 + // Indeed first the higher article number then the lower
101.139 + conn.println(g.getName() + " " + g.getLastArticleNumber() + " "
101.140 + + g.getFirstArticleNumber() + writeable);
101.141 + }
101.142 + }
101.143 + catch(PatternSyntaxException ex)
101.144 + {
101.145 + Log.get().info(ex.toString());
101.146 + }
101.147 + }
101.148 + conn.println(".");
101.149 + }
101.150 + else
101.151 + {
101.152 + conn.println("500 server backend malfunction");
101.153 + }
101.154 + }
101.155 +
101.156 +}
102.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
102.2 +++ b/src/org/sonews/daemon/command/ListGroupCommand.java Sun Aug 29 17:28:58 2010 +0200
102.3 @@ -0,0 +1,94 @@
102.4 +/*
102.5 + * SONEWS News Server
102.6 + * see AUTHORS for the list of contributors
102.7 + *
102.8 + * This program is free software: you can redistribute it and/or modify
102.9 + * it under the terms of the GNU General Public License as published by
102.10 + * the Free Software Foundation, either version 3 of the License, or
102.11 + * (at your option) any later version.
102.12 + *
102.13 + * This program is distributed in the hope that it will be useful,
102.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
102.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
102.16 + * GNU General Public License for more details.
102.17 + *
102.18 + * You should have received a copy of the GNU General Public License
102.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
102.20 + */
102.21 +
102.22 +package org.sonews.daemon.command;
102.23 +
102.24 +import java.io.IOException;
102.25 +import java.util.List;
102.26 +import org.sonews.daemon.NNTPConnection;
102.27 +import org.sonews.storage.Channel;
102.28 +import org.sonews.storage.StorageBackendException;
102.29 +
102.30 +/**
102.31 + * Class handling the LISTGROUP command.
102.32 + * @author Christian Lins
102.33 + * @author Dennis Schwerdel
102.34 + * @since n3tpd/0.1
102.35 + */
102.36 +public class ListGroupCommand implements Command
102.37 +{
102.38 +
102.39 + @Override
102.40 + public String[] getSupportedCommandStrings()
102.41 + {
102.42 + return new String[]{"LISTGROUP"};
102.43 + }
102.44 +
102.45 + @Override
102.46 + public boolean hasFinished()
102.47 + {
102.48 + return true;
102.49 + }
102.50 +
102.51 + @Override
102.52 + public String impliedCapability()
102.53 + {
102.54 + return null;
102.55 + }
102.56 +
102.57 + @Override
102.58 + public boolean isStateful()
102.59 + {
102.60 + return false;
102.61 + }
102.62 +
102.63 + @Override
102.64 + public void processLine(NNTPConnection conn, final String commandName, byte[] raw)
102.65 + throws IOException, StorageBackendException
102.66 + {
102.67 + final String[] command = commandName.split(" ");
102.68 +
102.69 + Channel group;
102.70 + if(command.length >= 2)
102.71 + {
102.72 + group = Channel.getByName(command[1]);
102.73 + }
102.74 + else
102.75 + {
102.76 + group = conn.getCurrentChannel();
102.77 + }
102.78 +
102.79 + if (group == null)
102.80 + {
102.81 + conn.println("412 no group selected; use GROUP <group> command");
102.82 + return;
102.83 + }
102.84 +
102.85 + List<Long> ids = group.getArticleNumbers();
102.86 + conn.println("211 " + ids.size() + " " +
102.87 + group.getFirstArticleNumber() + " " +
102.88 + group.getLastArticleNumber() + " list of article numbers follow");
102.89 + for(long id : ids)
102.90 + {
102.91 + // One index number per line
102.92 + conn.println(Long.toString(id));
102.93 + }
102.94 + conn.println(".");
102.95 + }
102.96 +
102.97 +}
103.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
103.2 +++ b/src/org/sonews/daemon/command/ModeReaderCommand.java Sun Aug 29 17:28:58 2010 +0200
103.3 @@ -0,0 +1,72 @@
103.4 +/*
103.5 + * SONEWS News Server
103.6 + * see AUTHORS for the list of contributors
103.7 + *
103.8 + * This program is free software: you can redistribute it and/or modify
103.9 + * it under the terms of the GNU General Public License as published by
103.10 + * the Free Software Foundation, either version 3 of the License, or
103.11 + * (at your option) any later version.
103.12 + *
103.13 + * This program is distributed in the hope that it will be useful,
103.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
103.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
103.16 + * GNU General Public License for more details.
103.17 + *
103.18 + * You should have received a copy of the GNU General Public License
103.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
103.20 + */
103.21 +
103.22 +package org.sonews.daemon.command;
103.23 +
103.24 +import java.io.IOException;
103.25 +import org.sonews.daemon.NNTPConnection;
103.26 +import org.sonews.storage.StorageBackendException;
103.27 +
103.28 +/**
103.29 + * Class handling the MODE READER command. This command actually does nothing
103.30 + * but returning a success status code.
103.31 + * @author Christian Lins
103.32 + * @since sonews/0.5.0
103.33 + */
103.34 +public class ModeReaderCommand implements Command
103.35 +{
103.36 +
103.37 + @Override
103.38 + public String[] getSupportedCommandStrings()
103.39 + {
103.40 + return new String[]{"MODE"};
103.41 + }
103.42 +
103.43 + @Override
103.44 + public boolean hasFinished()
103.45 + {
103.46 + return true;
103.47 + }
103.48 +
103.49 + @Override
103.50 + public String impliedCapability()
103.51 + {
103.52 + return null;
103.53 + }
103.54 +
103.55 + @Override
103.56 + public boolean isStateful()
103.57 + {
103.58 + return false;
103.59 + }
103.60 +
103.61 + @Override
103.62 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
103.63 + throws IOException, StorageBackendException
103.64 + {
103.65 + if(line.equalsIgnoreCase("MODE READER"))
103.66 + {
103.67 + conn.println("200 hello you can post");
103.68 + }
103.69 + else
103.70 + {
103.71 + conn.println("500 I do not know this mode command");
103.72 + }
103.73 + }
103.74 +
103.75 +}
104.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
104.2 +++ b/src/org/sonews/daemon/command/NewGroupsCommand.java Sun Aug 29 17:28:58 2010 +0200
104.3 @@ -0,0 +1,78 @@
104.4 +/*
104.5 + * SONEWS News Server
104.6 + * see AUTHORS for the list of contributors
104.7 + *
104.8 + * This program is free software: you can redistribute it and/or modify
104.9 + * it under the terms of the GNU General Public License as published by
104.10 + * the Free Software Foundation, either version 3 of the License, or
104.11 + * (at your option) any later version.
104.12 + *
104.13 + * This program is distributed in the hope that it will be useful,
104.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
104.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
104.16 + * GNU General Public License for more details.
104.17 + *
104.18 + * You should have received a copy of the GNU General Public License
104.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
104.20 + */
104.21 +
104.22 +package org.sonews.daemon.command;
104.23 +
104.24 +import java.io.IOException;
104.25 +import org.sonews.daemon.NNTPConnection;
104.26 +import org.sonews.storage.StorageBackendException;
104.27 +
104.28 +/**
104.29 + * Class handling the NEWGROUPS command.
104.30 + * @author Christian Lins
104.31 + * @author Dennis Schwerdel
104.32 + * @since n3tpd/0.1
104.33 + */
104.34 +public class NewGroupsCommand implements Command
104.35 +{
104.36 +
104.37 + @Override
104.38 + public String[] getSupportedCommandStrings()
104.39 + {
104.40 + return new String[]{"NEWGROUPS"};
104.41 + }
104.42 +
104.43 + @Override
104.44 + public boolean hasFinished()
104.45 + {
104.46 + return true;
104.47 + }
104.48 +
104.49 + @Override
104.50 + public String impliedCapability()
104.51 + {
104.52 + return null;
104.53 + }
104.54 +
104.55 + @Override
104.56 + public boolean isStateful()
104.57 + {
104.58 + return false;
104.59 + }
104.60 +
104.61 + @Override
104.62 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
104.63 + throws IOException, StorageBackendException
104.64 + {
104.65 + final String[] command = line.split(" ");
104.66 +
104.67 + if(command.length == 3)
104.68 + {
104.69 + conn.println("231 list of new newsgroups follows");
104.70 +
104.71 + // Currently we do not store a group's creation date;
104.72 + // so we return an empty list which is a valid response
104.73 + conn.println(".");
104.74 + }
104.75 + else
104.76 + {
104.77 + conn.println("500 invalid command usage");
104.78 + }
104.79 + }
104.80 +
104.81 +}
105.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
105.2 +++ b/src/org/sonews/daemon/command/NextPrevCommand.java Sun Aug 29 17:28:58 2010 +0200
105.3 @@ -0,0 +1,116 @@
105.4 +/*
105.5 + * SONEWS News Server
105.6 + * see AUTHORS for the list of contributors
105.7 + *
105.8 + * This program is free software: you can redistribute it and/or modify
105.9 + * it under the terms of the GNU General Public License as published by
105.10 + * the Free Software Foundation, either version 3 of the License, or
105.11 + * (at your option) any later version.
105.12 + *
105.13 + * This program is distributed in the hope that it will be useful,
105.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
105.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
105.16 + * GNU General Public License for more details.
105.17 + *
105.18 + * You should have received a copy of the GNU General Public License
105.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
105.20 + */
105.21 +
105.22 +package org.sonews.daemon.command;
105.23 +
105.24 +import java.io.IOException;
105.25 +import org.sonews.daemon.NNTPConnection;
105.26 +import org.sonews.storage.Article;
105.27 +import org.sonews.storage.Channel;
105.28 +import org.sonews.storage.StorageBackendException;
105.29 +
105.30 +/**
105.31 + * Class handling the NEXT and LAST command.
105.32 + * @author Christian Lins
105.33 + * @author Dennis Schwerdel
105.34 + * @since n3tpd/0.1
105.35 + */
105.36 +public class NextPrevCommand implements Command
105.37 +{
105.38 +
105.39 + @Override
105.40 + public String[] getSupportedCommandStrings()
105.41 + {
105.42 + return new String[]{"NEXT", "PREV"};
105.43 + }
105.44 +
105.45 + @Override
105.46 + public boolean hasFinished()
105.47 + {
105.48 + return true;
105.49 + }
105.50 +
105.51 + @Override
105.52 + public String impliedCapability()
105.53 + {
105.54 + return null;
105.55 + }
105.56 +
105.57 + @Override
105.58 + public boolean isStateful()
105.59 + {
105.60 + return false;
105.61 + }
105.62 +
105.63 + @Override
105.64 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
105.65 + throws IOException, StorageBackendException
105.66 + {
105.67 + final Article currA = conn.getCurrentArticle();
105.68 + final Channel currG = conn.getCurrentChannel();
105.69 +
105.70 + if (currA == null)
105.71 + {
105.72 + conn.println("420 no current article has been selected");
105.73 + return;
105.74 + }
105.75 +
105.76 + if (currG == null)
105.77 + {
105.78 + conn.println("412 no newsgroup selected");
105.79 + return;
105.80 + }
105.81 +
105.82 + final String[] command = line.split(" ");
105.83 +
105.84 + if(command[0].equalsIgnoreCase("NEXT"))
105.85 + {
105.86 + selectNewArticle(conn, currA, currG, 1);
105.87 + }
105.88 + else if(command[0].equalsIgnoreCase("PREV"))
105.89 + {
105.90 + selectNewArticle(conn, currA, currG, -1);
105.91 + }
105.92 + else
105.93 + {
105.94 + conn.println("500 internal server error");
105.95 + }
105.96 + }
105.97 +
105.98 + private void selectNewArticle(NNTPConnection conn, Article article, Channel grp,
105.99 + final int delta)
105.100 + throws IOException, StorageBackendException
105.101 + {
105.102 + assert article != null;
105.103 +
105.104 + article = grp.getArticle(grp.getIndexOf(article) + delta);
105.105 +
105.106 + if(article == null)
105.107 + {
105.108 + conn.println("421 no next article in this group");
105.109 + }
105.110 + else
105.111 + {
105.112 + conn.setCurrentArticle(article);
105.113 + conn.println("223 " + conn.getCurrentChannel().getIndexOf(article)
105.114 + + " " + article.getMessageID()
105.115 + + " article retrieved - request text separately");
105.116 + }
105.117 + }
105.118 +
105.119 +}
106.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
106.2 +++ b/src/org/sonews/daemon/command/OverCommand.java Sun Aug 29 17:28:58 2010 +0200
106.3 @@ -0,0 +1,294 @@
106.4 +/*
106.5 + * SONEWS News Server
106.6 + * see AUTHORS for the list of contributors
106.7 + *
106.8 + * This program is free software: you can redistribute it and/or modify
106.9 + * it under the terms of the GNU General Public License as published by
106.10 + * the Free Software Foundation, either version 3 of the License, or
106.11 + * (at your option) any later version.
106.12 + *
106.13 + * This program is distributed in the hope that it will be useful,
106.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
106.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
106.16 + * GNU General Public License for more details.
106.17 + *
106.18 + * You should have received a copy of the GNU General Public License
106.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
106.20 + */
106.21 +
106.22 +package org.sonews.daemon.command;
106.23 +
106.24 +import java.io.IOException;
106.25 +import java.util.List;
106.26 +import org.sonews.util.Log;
106.27 +import org.sonews.daemon.NNTPConnection;
106.28 +import org.sonews.storage.Article;
106.29 +import org.sonews.storage.ArticleHead;
106.30 +import org.sonews.storage.Headers;
106.31 +import org.sonews.storage.StorageBackendException;
106.32 +import org.sonews.util.Pair;
106.33 +
106.34 +/**
106.35 + * Class handling the OVER/XOVER command.
106.36 + *
106.37 + * Description of the XOVER command:
106.38 + * <pre>
106.39 + * XOVER [range]
106.40 + *
106.41 + * The XOVER command returns information from the overview
106.42 + * database for the article(s) specified.
106.43 + *
106.44 + * The optional range argument may be any of the following:
106.45 + * an article number
106.46 + * an article number followed by a dash to indicate
106.47 + * all following
106.48 + * an article number followed by a dash followed by
106.49 + * another article number
106.50 + *
106.51 + * If no argument is specified, then information from the
106.52 + * current article is displayed. Successful responses start
106.53 + * with a 224 response followed by the overview information
106.54 + * for all matched messages. Once the output is complete, a
106.55 + * period is sent on a line by itself. If no argument is
106.56 + * specified, the information for the current article is
106.57 + * returned. A news group must have been selected earlier,
106.58 + * else a 412 error response is returned. If no articles are
106.59 + * in the range specified, a 420 error response is returned
106.60 + * by the server. A 502 response will be returned if the
106.61 + * client only has permission to transfer articles.
106.62 + *
106.63 + * Each line of output will be formatted with the article number,
106.64 + * followed by each of the headers in the overview database or the
106.65 + * article itself (when the data is not available in the overview
106.66 + * database) for that article separated by a tab character. The
106.67 + * sequence of fields must be in this order: subject, author,
106.68 + * date, message-id, references, byte count, and line count. Other
106.69 + * optional fields may follow line count. Other optional fields may
106.70 + * follow line count. These fields are specified by examining the
106.71 + * response to the LIST OVERVIEW.FMT command. Where no data exists,
106.72 + * a null field must be provided (i.e. the output will have two tab
106.73 + * characters adjacent to each other). Servers should not output
106.74 + * fields for articles that have been removed since the XOVER database
106.75 + * was created.
106.76 + *
106.77 + * The LIST OVERVIEW.FMT command should be implemented if XOVER
106.78 + * is implemented. A client can use LIST OVERVIEW.FMT to determine
106.79 + * what optional fields and in which order all fields will be
106.80 + * supplied by the XOVER command.
106.81 + *
106.82 + * Note that any tab and end-of-line characters in any header
106.83 + * data that is returned will be converted to a space character.
106.84 + *
106.85 + * Responses:
106.86 + *
106.87 + * 224 Overview information follows
106.88 + * 412 No news group current selected
106.89 + * 420 No article(s) selected
106.90 + * 502 no permission
106.91 + *
106.92 + * OVER defines additional responses:
106.93 + *
106.94 + * First form (message-id specified)
106.95 + * 224 Overview information follows (multi-line)
106.96 + * 430 No article with that message-id
106.97 + *
106.98 + * Second form (range specified)
106.99 + * 224 Overview information follows (multi-line)
106.100 + * 412 No newsgroup selected
106.101 + * 423 No articles in that range
106.102 + *
106.103 + * Third form (current article number used)
106.104 + * 224 Overview information follows (multi-line)
106.105 + * 412 No newsgroup selected
106.106 + * 420 Current article number is invalid
106.107 + *
106.108 + * </pre>
106.109 + * @author Christian Lins
106.110 + * @since sonews/0.5.0
106.111 + */
106.112 +public class OverCommand implements Command
106.113 +{
106.114 +
106.115 + public static final int MAX_LINES_PER_DBREQUEST = 200;
106.116 +
106.117 + @Override
106.118 + public String[] getSupportedCommandStrings()
106.119 + {
106.120 + return new String[]{"OVER", "XOVER"};
106.121 + }
106.122 +
106.123 + @Override
106.124 + public boolean hasFinished()
106.125 + {
106.126 + return true;
106.127 + }
106.128 +
106.129 + @Override
106.130 + public String impliedCapability()
106.131 + {
106.132 + return null;
106.133 + }
106.134 +
106.135 + @Override
106.136 + public boolean isStateful()
106.137 + {
106.138 + return false;
106.139 + }
106.140 +
106.141 + @Override
106.142 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
106.143 + throws IOException, StorageBackendException
106.144 + {
106.145 + if(conn.getCurrentChannel() == null)
106.146 + {
106.147 + conn.println("412 no newsgroup selected");
106.148 + }
106.149 + else
106.150 + {
106.151 + String[] command = line.split(" ");
106.152 +
106.153 + // If no parameter was specified, show information about
106.154 + // the currently selected article(s)
106.155 + if(command.length == 1)
106.156 + {
106.157 + final Article art = conn.getCurrentArticle();
106.158 + if(art == null)
106.159 + {
106.160 + conn.println("420 no article(s) selected");
106.161 + return;
106.162 + }
106.163 +
106.164 + conn.println(buildOverview(art, -1));
106.165 + }
106.166 + // otherwise print information about the specified range
106.167 + else
106.168 + {
106.169 + long artStart;
106.170 + long artEnd = conn.getCurrentChannel().getLastArticleNumber();
106.171 + String[] nums = command[1].split("-");
106.172 + if(nums.length >= 1)
106.173 + {
106.174 + try
106.175 + {
106.176 + artStart = Integer.parseInt(nums[0]);
106.177 + }
106.178 + catch(NumberFormatException e)
106.179 + {
106.180 + Log.get().info(e.getMessage());
106.181 + artStart = Integer.parseInt(command[1]);
106.182 + }
106.183 + }
106.184 + else
106.185 + {
106.186 + artStart = conn.getCurrentChannel().getFirstArticleNumber();
106.187 + }
106.188 +
106.189 + if(nums.length >=2)
106.190 + {
106.191 + try
106.192 + {
106.193 + artEnd = Integer.parseInt(nums[1]);
106.194 + }
106.195 + catch(NumberFormatException e)
106.196 + {
106.197 + e.printStackTrace();
106.198 + }
106.199 + }
106.200 +
106.201 + if(artStart > artEnd)
106.202 + {
106.203 + if(command[0].equalsIgnoreCase("OVER"))
106.204 + {
106.205 + conn.println("423 no articles in that range");
106.206 + }
106.207 + else
106.208 + {
106.209 + conn.println("224 (empty) overview information follows:");
106.210 + conn.println(".");
106.211 + }
106.212 + }
106.213 + else
106.214 + {
106.215 + for(long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST)
106.216 + {
106.217 + long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
106.218 + List<Pair<Long, ArticleHead>> articleHeads = conn.getCurrentChannel()
106.219 + .getArticleHeads(n, nEnd);
106.220 + if(articleHeads.isEmpty() && n == artStart
106.221 + && command[0].equalsIgnoreCase("OVER"))
106.222 + {
106.223 + // This reply is only valid for OVER, not for XOVER command
106.224 + conn.println("423 no articles in that range");
106.225 + return;
106.226 + }
106.227 + else if(n == artStart)
106.228 + {
106.229 + // XOVER replies this although there is no data available
106.230 + conn.println("224 overview information follows");
106.231 + }
106.232 +
106.233 + for(Pair<Long, ArticleHead> article : articleHeads)
106.234 + {
106.235 + String overview = buildOverview(article.getB(), article.getA());
106.236 + conn.println(overview);
106.237 + }
106.238 + } // for
106.239 + conn.println(".");
106.240 + }
106.241 + }
106.242 + }
106.243 + }
106.244 +
106.245 + private String buildOverview(ArticleHead art, long nr)
106.246 + {
106.247 + StringBuilder overview = new StringBuilder();
106.248 + overview.append(nr);
106.249 + overview.append('\t');
106.250 +
106.251 + String subject = art.getHeader(Headers.SUBJECT)[0];
106.252 + if("".equals(subject))
106.253 + {
106.254 + subject = "<empty>";
106.255 + }
106.256 + overview.append(escapeString(subject));
106.257 + overview.append('\t');
106.258 +
106.259 + overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
106.260 + overview.append('\t');
106.261 + overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
106.262 + overview.append('\t');
106.263 + overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
106.264 + overview.append('\t');
106.265 + overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
106.266 + overview.append('\t');
106.267 +
106.268 + String bytes = art.getHeader(Headers.BYTES)[0];
106.269 + if("".equals(bytes))
106.270 + {
106.271 + bytes = "0";
106.272 + }
106.273 + overview.append(escapeString(bytes));
106.274 + overview.append('\t');
106.275 +
106.276 + String lines = art.getHeader(Headers.LINES)[0];
106.277 + if("".equals(lines))
106.278 + {
106.279 + lines = "0";
106.280 + }
106.281 + overview.append(escapeString(lines));
106.282 + overview.append('\t');
106.283 + overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
106.284 +
106.285 + // Remove trailing tabs if some data is empty
106.286 + return overview.toString().trim();
106.287 + }
106.288 +
106.289 + private String escapeString(String str)
106.290 + {
106.291 + String nstr = str.replace("\r", "");
106.292 + nstr = nstr.replace('\n', ' ');
106.293 + nstr = nstr.replace('\t', ' ');
106.294 + return nstr.trim();
106.295 + }
106.296 +
106.297 +}
107.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
107.2 +++ b/src/org/sonews/daemon/command/PostCommand.java Sun Aug 29 17:28:58 2010 +0200
107.3 @@ -0,0 +1,332 @@
107.4 +/*
107.5 + * SONEWS News Server
107.6 + * see AUTHORS for the list of contributors
107.7 + *
107.8 + * This program is free software: you can redistribute it and/or modify
107.9 + * it under the terms of the GNU General Public License as published by
107.10 + * the Free Software Foundation, either version 3 of the License, or
107.11 + * (at your option) any later version.
107.12 + *
107.13 + * This program is distributed in the hope that it will be useful,
107.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
107.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
107.16 + * GNU General Public License for more details.
107.17 + *
107.18 + * You should have received a copy of the GNU General Public License
107.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
107.20 + */
107.21 +
107.22 +package org.sonews.daemon.command;
107.23 +
107.24 +import java.io.IOException;
107.25 +import java.io.ByteArrayInputStream;
107.26 +import java.io.ByteArrayOutputStream;
107.27 +import java.sql.SQLException;
107.28 +import java.util.Arrays;
107.29 +import javax.mail.MessagingException;
107.30 +import javax.mail.internet.AddressException;
107.31 +import javax.mail.internet.InternetHeaders;
107.32 +import org.sonews.config.Config;
107.33 +import org.sonews.util.Log;
107.34 +import org.sonews.mlgw.Dispatcher;
107.35 +import org.sonews.storage.Article;
107.36 +import org.sonews.storage.Group;
107.37 +import org.sonews.daemon.NNTPConnection;
107.38 +import org.sonews.storage.Headers;
107.39 +import org.sonews.storage.StorageBackendException;
107.40 +import org.sonews.storage.StorageManager;
107.41 +import org.sonews.feed.FeedManager;
107.42 +import org.sonews.util.Stats;
107.43 +
107.44 +/**
107.45 + * Implementation of the POST command. This command requires multiple lines
107.46 + * from the client, so the handling of asynchronous reading is a little tricky
107.47 + * to handle.
107.48 + * @author Christian Lins
107.49 + * @since sonews/0.5.0
107.50 + */
107.51 +public class PostCommand implements Command
107.52 +{
107.53 +
107.54 + private final Article article = new Article();
107.55 + private int lineCount = 0;
107.56 + private long bodySize = 0;
107.57 + private InternetHeaders headers = null;
107.58 + private long maxBodySize =
107.59 + Config.inst().get(Config.ARTICLE_MAXSIZE, 128) * 1024L; // Size in bytes
107.60 + private PostState state = PostState.WaitForLineOne;
107.61 + private final ByteArrayOutputStream bufBody = new ByteArrayOutputStream();
107.62 + private final StringBuilder strHead = new StringBuilder();
107.63 +
107.64 + @Override
107.65 + public String[] getSupportedCommandStrings()
107.66 + {
107.67 + return new String[]{"POST"};
107.68 + }
107.69 +
107.70 + @Override
107.71 + public boolean hasFinished()
107.72 + {
107.73 + return this.state == PostState.Finished;
107.74 + }
107.75 +
107.76 + @Override
107.77 + public String impliedCapability()
107.78 + {
107.79 + return null;
107.80 + }
107.81 +
107.82 + @Override
107.83 + public boolean isStateful()
107.84 + {
107.85 + return true;
107.86 + }
107.87 +
107.88 + /**
107.89 + * Process the given line String. line.trim() was called by NNTPConnection.
107.90 + * @param line
107.91 + * @throws java.io.IOException
107.92 + * @throws java.sql.SQLException
107.93 + */
107.94 + @Override // TODO: Refactor this method to reduce complexity!
107.95 + public void processLine(NNTPConnection conn, String line, byte[] raw)
107.96 + throws IOException, StorageBackendException
107.97 + {
107.98 + switch(state)
107.99 + {
107.100 + case WaitForLineOne:
107.101 + {
107.102 + if(line.equalsIgnoreCase("POST"))
107.103 + {
107.104 + conn.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
107.105 + state = PostState.ReadingHeaders;
107.106 + }
107.107 + else
107.108 + {
107.109 + conn.println("500 invalid command usage");
107.110 + }
107.111 + break;
107.112 + }
107.113 + case ReadingHeaders:
107.114 + {
107.115 + strHead.append(line);
107.116 + strHead.append(NNTPConnection.NEWLINE);
107.117 +
107.118 + if("".equals(line) || ".".equals(line))
107.119 + {
107.120 + // we finally met the blank line
107.121 + // separating headers from body
107.122 +
107.123 + try
107.124 + {
107.125 + // Parse the header using the InternetHeader class from JavaMail API
107.126 + headers = new InternetHeaders(
107.127 + new ByteArrayInputStream(strHead.toString().trim()
107.128 + .getBytes(conn.getCurrentCharset())));
107.129 +
107.130 + // add the header entries for the article
107.131 + article.setHeaders(headers);
107.132 + }
107.133 + catch (MessagingException e)
107.134 + {
107.135 + e.printStackTrace();
107.136 + conn.println("500 posting failed - invalid header");
107.137 + state = PostState.Finished;
107.138 + break;
107.139 + }
107.140 +
107.141 + // Change charset for reading body;
107.142 + // for multipart messages UTF-8 is returned
107.143 + //conn.setCurrentCharset(article.getBodyCharset());
107.144 +
107.145 + state = PostState.ReadingBody;
107.146 +
107.147 + if(".".equals(line))
107.148 + {
107.149 + // Post an article without body
107.150 + postArticle(conn, article);
107.151 + state = PostState.Finished;
107.152 + }
107.153 + }
107.154 + break;
107.155 + }
107.156 + case ReadingBody:
107.157 + {
107.158 + if(".".equals(line))
107.159 + {
107.160 + // Set some headers needed for Over command
107.161 + headers.setHeader(Headers.LINES, Integer.toString(lineCount));
107.162 + headers.setHeader(Headers.BYTES, Long.toString(bodySize));
107.163 +
107.164 + byte[] body = bufBody.toByteArray();
107.165 + if(body.length >= 2)
107.166 + {
107.167 + // Remove trailing CRLF
107.168 + body = Arrays.copyOf(body, body.length - 2);
107.169 + }
107.170 + article.setBody(body); // set the article body
107.171 +
107.172 + postArticle(conn, article);
107.173 + state = PostState.Finished;
107.174 + }
107.175 + else
107.176 + {
107.177 + bodySize += line.length() + 1;
107.178 + lineCount++;
107.179 +
107.180 + // Add line to body buffer
107.181 + bufBody.write(raw, 0, raw.length);
107.182 + bufBody.write(NNTPConnection.NEWLINE.getBytes());
107.183 +
107.184 + if(bodySize > maxBodySize)
107.185 + {
107.186 + conn.println("500 article is too long");
107.187 + state = PostState.Finished;
107.188 + break;
107.189 + }
107.190 + }
107.191 + break;
107.192 + }
107.193 + default:
107.194 + {
107.195 + // Should never happen
107.196 + Log.get().severe("PostCommand::processLine(): already finished...");
107.197 + }
107.198 + }
107.199 + }
107.200 +
107.201 + /**
107.202 + * Article is a control message and needs special handling.
107.203 + * @param article
107.204 + */
107.205 + private void controlMessage(NNTPConnection conn, Article article)
107.206 + throws IOException
107.207 + {
107.208 + String[] ctrl = article.getHeader(Headers.CONTROL)[0].split(" ");
107.209 + if(ctrl.length == 2) // "cancel <mid>"
107.210 + {
107.211 + try
107.212 + {
107.213 + StorageManager.current().delete(ctrl[1]);
107.214 +
107.215 + // Move cancel message to "control" group
107.216 + article.setHeader(Headers.NEWSGROUPS, "control");
107.217 + StorageManager.current().addArticle(article);
107.218 + conn.println("240 article cancelled");
107.219 + }
107.220 + catch(StorageBackendException ex)
107.221 + {
107.222 + Log.get().severe(ex.toString());
107.223 + conn.println("500 internal server error");
107.224 + }
107.225 + }
107.226 + else
107.227 + {
107.228 + conn.println("441 unknown control header");
107.229 + }
107.230 + }
107.231 +
107.232 + private void supersedeMessage(NNTPConnection conn, Article article)
107.233 + throws IOException
107.234 + {
107.235 + try
107.236 + {
107.237 + String oldMsg = article.getHeader(Headers.SUPERSEDES)[0];
107.238 + StorageManager.current().delete(oldMsg);
107.239 + StorageManager.current().addArticle(article);
107.240 + conn.println("240 article replaced");
107.241 + }
107.242 + catch(StorageBackendException ex)
107.243 + {
107.244 + Log.get().severe(ex.toString());
107.245 + conn.println("500 internal server error");
107.246 + }
107.247 + }
107.248 +
107.249 + private void postArticle(NNTPConnection conn, Article article)
107.250 + throws IOException
107.251 + {
107.252 + if(article.getHeader(Headers.CONTROL)[0].length() > 0)
107.253 + {
107.254 + controlMessage(conn, article);
107.255 + }
107.256 + else if(article.getHeader(Headers.SUPERSEDES)[0].length() > 0)
107.257 + {
107.258 + supersedeMessage(conn, article);
107.259 + }
107.260 + else // Post the article regularily
107.261 + {
107.262 + // Circle check; note that Path can already contain the hostname here
107.263 + String host = Config.inst().get(Config.HOSTNAME, "localhost");
107.264 + if(article.getHeader(Headers.PATH)[0].indexOf(host + "!", 1) > 0)
107.265 + {
107.266 + Log.get().info(article.getMessageID() + " skipped for host " + host);
107.267 + conn.println("441 I know this article already");
107.268 + return;
107.269 + }
107.270 +
107.271 + // Try to create the article in the database or post it to
107.272 + // appropriate mailing list
107.273 + try
107.274 + {
107.275 + boolean success = false;
107.276 + String[] groupnames = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
107.277 + for(String groupname : groupnames)
107.278 + {
107.279 + Group group = StorageManager.current().getGroup(groupname);
107.280 + if(group != null && !group.isDeleted())
107.281 + {
107.282 + if(group.isMailingList() && !conn.isLocalConnection())
107.283 + {
107.284 + // Send to mailing list; the Dispatcher writes
107.285 + // statistics to database
107.286 + Dispatcher.toList(article, group.getName());
107.287 + success = true;
107.288 + }
107.289 + else
107.290 + {
107.291 + // Store in database
107.292 + if(!StorageManager.current().isArticleExisting(article.getMessageID()))
107.293 + {
107.294 + StorageManager.current().addArticle(article);
107.295 +
107.296 + // Log this posting to statistics
107.297 + Stats.getInstance().mailPosted(
107.298 + article.getHeader(Headers.NEWSGROUPS)[0]);
107.299 + }
107.300 + success = true;
107.301 + }
107.302 + }
107.303 + } // end for
107.304 +
107.305 + if(success)
107.306 + {
107.307 + conn.println("240 article posted ok");
107.308 + FeedManager.queueForPush(article);
107.309 + }
107.310 + else
107.311 + {
107.312 + conn.println("441 newsgroup not found");
107.313 + }
107.314 + }
107.315 + catch(AddressException ex)
107.316 + {
107.317 + Log.get().warning(ex.getMessage());
107.318 + conn.println("441 invalid sender address");
107.319 + }
107.320 + catch(MessagingException ex)
107.321 + {
107.322 + // A MessageException is thrown when the sender email address is
107.323 + // invalid or something is wrong with the SMTP server.
107.324 + System.err.println(ex.getLocalizedMessage());
107.325 + conn.println("441 " + ex.getClass().getCanonicalName() + ": " + ex.getLocalizedMessage());
107.326 + }
107.327 + catch(StorageBackendException ex)
107.328 + {
107.329 + ex.printStackTrace();
107.330 + conn.println("500 internal server error");
107.331 + }
107.332 + }
107.333 + }
107.334 +
107.335 +}
108.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
108.2 +++ b/src/org/sonews/daemon/command/PostState.java Sun Aug 29 17:28:58 2010 +0200
108.3 @@ -0,0 +1,29 @@
108.4 +/*
108.5 + * SONEWS News Server
108.6 + * see AUTHORS for the list of contributors
108.7 + *
108.8 + * This program is free software: you can redistribute it and/or modify
108.9 + * it under the terms of the GNU General Public License as published by
108.10 + * the Free Software Foundation, either version 3 of the License, or
108.11 + * (at your option) any later version.
108.12 + *
108.13 + * This program is distributed in the hope that it will be useful,
108.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
108.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
108.16 + * GNU General Public License for more details.
108.17 + *
108.18 + * You should have received a copy of the GNU General Public License
108.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
108.20 + */
108.21 +
108.22 +package org.sonews.daemon.command;
108.23 +
108.24 +/**
108.25 + * States of the POST command's finite state machine.
108.26 + * @author Christian Lins
108.27 + * @since sonews/0.5.0
108.28 + */
108.29 +enum PostState
108.30 +{
108.31 + WaitForLineOne, ReadingHeaders, ReadingBody, Finished
108.32 +}
109.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
109.2 +++ b/src/org/sonews/daemon/command/QuitCommand.java Sun Aug 29 17:28:58 2010 +0200
109.3 @@ -0,0 +1,67 @@
109.4 +/*
109.5 + * SONEWS News Server
109.6 + * see AUTHORS for the list of contributors
109.7 + *
109.8 + * This program is free software: you can redistribute it and/or modify
109.9 + * it under the terms of the GNU General Public License as published by
109.10 + * the Free Software Foundation, either version 3 of the License, or
109.11 + * (at your option) any later version.
109.12 + *
109.13 + * This program is distributed in the hope that it will be useful,
109.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
109.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
109.16 + * GNU General Public License for more details.
109.17 + *
109.18 + * You should have received a copy of the GNU General Public License
109.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
109.20 + */
109.21 +
109.22 +package org.sonews.daemon.command;
109.23 +
109.24 +import java.io.IOException;
109.25 +import org.sonews.daemon.NNTPConnection;
109.26 +import org.sonews.storage.StorageBackendException;
109.27 +
109.28 +/**
109.29 + * Implementation of the QUIT command; client wants to shutdown the connection.
109.30 + * @author Christian Lins
109.31 + * @since sonews/0.5.0
109.32 + */
109.33 +public class QuitCommand implements Command
109.34 +{
109.35 +
109.36 + @Override
109.37 + public String[] getSupportedCommandStrings()
109.38 + {
109.39 + return new String[]{"QUIT"};
109.40 + }
109.41 +
109.42 + @Override
109.43 + public boolean hasFinished()
109.44 + {
109.45 + return true;
109.46 + }
109.47 +
109.48 + @Override
109.49 + public String impliedCapability()
109.50 + {
109.51 + return null;
109.52 + }
109.53 +
109.54 + @Override
109.55 + public boolean isStateful()
109.56 + {
109.57 + return false;
109.58 + }
109.59 +
109.60 + @Override
109.61 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
109.62 + throws IOException, StorageBackendException
109.63 + {
109.64 + conn.println("205 cya");
109.65 +
109.66 + conn.shutdownInput();
109.67 + conn.shutdownOutput();
109.68 + }
109.69 +
109.70 +}
110.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
110.2 +++ b/src/org/sonews/daemon/command/StatCommand.java Sun Aug 29 17:28:58 2010 +0200
110.3 @@ -0,0 +1,114 @@
110.4 +/*
110.5 + * SONEWS News Server
110.6 + * see AUTHORS for the list of contributors
110.7 + *
110.8 + * This program is free software: you can redistribute it and/or modify
110.9 + * it under the terms of the GNU General Public License as published by
110.10 + * the Free Software Foundation, either version 3 of the License, or
110.11 + * (at your option) any later version.
110.12 + *
110.13 + * This program is distributed in the hope that it will be useful,
110.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
110.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
110.16 + * GNU General Public License for more details.
110.17 + *
110.18 + * You should have received a copy of the GNU General Public License
110.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
110.20 + */
110.21 +
110.22 +package org.sonews.daemon.command;
110.23 +
110.24 +import java.io.IOException;
110.25 +import org.sonews.storage.Article;
110.26 +import org.sonews.daemon.NNTPConnection;
110.27 +import org.sonews.storage.StorageBackendException;
110.28 +
110.29 +/**
110.30 + * Implementation of the STAT command.
110.31 + * @author Christian Lins
110.32 + * @since sonews/0.5.0
110.33 + */
110.34 +public class StatCommand implements Command
110.35 +{
110.36 +
110.37 + @Override
110.38 + public String[] getSupportedCommandStrings()
110.39 + {
110.40 + return new String[]{"STAT"};
110.41 + }
110.42 +
110.43 + @Override
110.44 + public boolean hasFinished()
110.45 + {
110.46 + return true;
110.47 + }
110.48 +
110.49 + @Override
110.50 + public String impliedCapability()
110.51 + {
110.52 + return null;
110.53 + }
110.54 +
110.55 + @Override
110.56 + public boolean isStateful()
110.57 + {
110.58 + return false;
110.59 + }
110.60 +
110.61 + // TODO: Method has various exit points => Refactor!
110.62 + @Override
110.63 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
110.64 + throws IOException, StorageBackendException
110.65 + {
110.66 + final String[] command = line.split(" ");
110.67 +
110.68 + Article article = null;
110.69 + if(command.length == 1)
110.70 + {
110.71 + article = conn.getCurrentArticle();
110.72 + if(article == null)
110.73 + {
110.74 + conn.println("420 no current article has been selected");
110.75 + return;
110.76 + }
110.77 + }
110.78 + else if(command[1].matches(NNTPConnection.MESSAGE_ID_PATTERN))
110.79 + {
110.80 + // Message-ID
110.81 + article = Article.getByMessageID(command[1]);
110.82 + if (article == null)
110.83 + {
110.84 + conn.println("430 no such article found");
110.85 + return;
110.86 + }
110.87 + }
110.88 + else
110.89 + {
110.90 + // Message Number
110.91 + try
110.92 + {
110.93 + long aid = Long.parseLong(command[1]);
110.94 + article = conn.getCurrentChannel().getArticle(aid);
110.95 + }
110.96 + catch(NumberFormatException ex)
110.97 + {
110.98 + ex.printStackTrace();
110.99 + }
110.100 + catch(StorageBackendException ex)
110.101 + {
110.102 + ex.printStackTrace();
110.103 + }
110.104 + if (article == null)
110.105 + {
110.106 + conn.println("423 no such article number in this group");
110.107 + return;
110.108 + }
110.109 + conn.setCurrentArticle(article);
110.110 + }
110.111 +
110.112 + conn.println("223 " + conn.getCurrentChannel().getIndexOf(article) + " "
110.113 + + article.getMessageID()
110.114 + + " article retrieved - request text separately");
110.115 + }
110.116 +
110.117 +}
111.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
111.2 +++ b/src/org/sonews/daemon/command/UnsupportedCommand.java Sun Aug 29 17:28:58 2010 +0200
111.3 @@ -0,0 +1,67 @@
111.4 +/*
111.5 + * SONEWS News Server
111.6 + * see AUTHORS for the list of contributors
111.7 + *
111.8 + * This program is free software: you can redistribute it and/or modify
111.9 + * it under the terms of the GNU General Public License as published by
111.10 + * the Free Software Foundation, either version 3 of the License, or
111.11 + * (at your option) any later version.
111.12 + *
111.13 + * This program is distributed in the hope that it will be useful,
111.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
111.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
111.16 + * GNU General Public License for more details.
111.17 + *
111.18 + * You should have received a copy of the GNU General Public License
111.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
111.20 + */
111.21 +
111.22 +package org.sonews.daemon.command;
111.23 +
111.24 +import java.io.IOException;
111.25 +import org.sonews.daemon.NNTPConnection;
111.26 +
111.27 +/**
111.28 + * A default "Unsupported Command". Simply returns error code 500 and a
111.29 + * "command not supported" message.
111.30 + * @author Christian Lins
111.31 + * @since sonews/0.5.0
111.32 + */
111.33 +public class UnsupportedCommand implements Command
111.34 +{
111.35 +
111.36 + /**
111.37 + * @return Always returns null.
111.38 + */
111.39 + @Override
111.40 + public String[] getSupportedCommandStrings()
111.41 + {
111.42 + return null;
111.43 + }
111.44 +
111.45 + @Override
111.46 + public boolean hasFinished()
111.47 + {
111.48 + return true;
111.49 + }
111.50 +
111.51 + @Override
111.52 + public String impliedCapability()
111.53 + {
111.54 + return null;
111.55 + }
111.56 +
111.57 + @Override
111.58 + public boolean isStateful()
111.59 + {
111.60 + return false;
111.61 + }
111.62 +
111.63 + @Override
111.64 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
111.65 + throws IOException
111.66 + {
111.67 + conn.println("500 command not supported");
111.68 + }
111.69 +
111.70 +}
112.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
112.2 +++ b/src/org/sonews/daemon/command/XDaemonCommand.java Sun Aug 29 17:28:58 2010 +0200
112.3 @@ -0,0 +1,270 @@
112.4 +/*
112.5 + * SONEWS News Server
112.6 + * see AUTHORS for the list of contributors
112.7 + *
112.8 + * This program is free software: you can redistribute it and/or modify
112.9 + * it under the terms of the GNU General Public License as published by
112.10 + * the Free Software Foundation, either version 3 of the License, or
112.11 + * (at your option) any later version.
112.12 + *
112.13 + * This program is distributed in the hope that it will be useful,
112.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
112.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
112.16 + * GNU General Public License for more details.
112.17 + *
112.18 + * You should have received a copy of the GNU General Public License
112.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
112.20 + */
112.21 +
112.22 +package org.sonews.daemon.command;
112.23 +
112.24 +import java.io.IOException;
112.25 +import java.net.InetSocketAddress;
112.26 +import java.util.List;
112.27 +import org.sonews.config.Config;
112.28 +import org.sonews.daemon.NNTPConnection;
112.29 +import org.sonews.storage.StorageBackendException;
112.30 +import org.sonews.storage.StorageManager;
112.31 +import org.sonews.feed.FeedManager;
112.32 +import org.sonews.feed.Subscription;
112.33 +import org.sonews.storage.Channel;
112.34 +import org.sonews.storage.Group;
112.35 +import org.sonews.util.Stats;
112.36 +
112.37 +/**
112.38 + * The XDAEMON command allows a client to get/set properties of the
112.39 + * running server daemon. Only locally connected clients are allowed to
112.40 + * use this command.
112.41 + * The restriction to localhost connection can be suppressed by overriding
112.42 + * the sonews.xdaemon.host bootstrap config property.
112.43 + * @author Christian Lins
112.44 + * @since sonews/0.5.0
112.45 + */
112.46 +public class XDaemonCommand implements Command
112.47 +{
112.48 +
112.49 + @Override
112.50 + public String[] getSupportedCommandStrings()
112.51 + {
112.52 + return new String[]{"XDAEMON"};
112.53 + }
112.54 +
112.55 + @Override
112.56 + public boolean hasFinished()
112.57 + {
112.58 + return true;
112.59 + }
112.60 +
112.61 + @Override
112.62 + public String impliedCapability()
112.63 + {
112.64 + return null;
112.65 + }
112.66 +
112.67 + @Override
112.68 + public boolean isStateful()
112.69 + {
112.70 + return false;
112.71 + }
112.72 +
112.73 + private void channelAdd(String[] commands, NNTPConnection conn)
112.74 + throws IOException, StorageBackendException
112.75 + {
112.76 + String groupName = commands[2];
112.77 + if(StorageManager.current().isGroupExisting(groupName))
112.78 + {
112.79 + conn.println("400 group " + groupName + " already existing!");
112.80 + }
112.81 + else
112.82 + {
112.83 + StorageManager.current().addGroup(groupName, Integer.parseInt(commands[3]));
112.84 + conn.println("200 group " + groupName + " created");
112.85 + }
112.86 + }
112.87 +
112.88 + // TODO: Refactor this method to reduce complexity!
112.89 + @Override
112.90 + public void processLine(NNTPConnection conn, String line, byte[] raw)
112.91 + throws IOException, StorageBackendException
112.92 + {
112.93 + InetSocketAddress addr = (InetSocketAddress)conn.getSocketChannel().socket()
112.94 + .getRemoteSocketAddress();
112.95 + if(addr.getHostName().equals(
112.96 + Config.inst().get(Config.XDAEMON_HOST, "localhost")))
112.97 + {
112.98 + String[] commands = line.split(" ", 4);
112.99 + if(commands.length == 3 && commands[1].equalsIgnoreCase("LIST"))
112.100 + {
112.101 + if(commands[2].equalsIgnoreCase("CONFIGKEYS"))
112.102 + {
112.103 + conn.println("100 list of available config keys follows");
112.104 + for(String key : Config.AVAILABLE_KEYS)
112.105 + {
112.106 + conn.println(key);
112.107 + }
112.108 + conn.println(".");
112.109 + }
112.110 + else if(commands[2].equalsIgnoreCase("PEERINGRULES"))
112.111 + {
112.112 + List<Subscription> pull =
112.113 + StorageManager.current().getSubscriptions(FeedManager.TYPE_PULL);
112.114 + List<Subscription> push =
112.115 + StorageManager.current().getSubscriptions(FeedManager.TYPE_PUSH);
112.116 + conn.println("100 list of peering rules follows");
112.117 + for(Subscription sub : pull)
112.118 + {
112.119 + conn.println("PULL " + sub.getHost() + ":" + sub.getPort()
112.120 + + " " + sub.getGroup());
112.121 + }
112.122 + for(Subscription sub : push)
112.123 + {
112.124 + conn.println("PUSH " + sub.getHost() + ":" + sub.getPort()
112.125 + + " " + sub.getGroup());
112.126 + }
112.127 + conn.println(".");
112.128 + }
112.129 + else
112.130 + {
112.131 + conn.println("401 unknown sub command");
112.132 + }
112.133 + }
112.134 + else if(commands.length == 3 && commands[1].equalsIgnoreCase("DELETE"))
112.135 + {
112.136 + StorageManager.current().delete(commands[2]);
112.137 + conn.println("200 article " + commands[2] + " deleted");
112.138 + }
112.139 + else if(commands.length == 4 && commands[1].equalsIgnoreCase("GROUPADD"))
112.140 + {
112.141 + channelAdd(commands, conn);
112.142 + }
112.143 + else if(commands.length == 3 && commands[1].equalsIgnoreCase("GROUPDEL"))
112.144 + {
112.145 + Group group = StorageManager.current().getGroup(commands[2]);
112.146 + if(group == null)
112.147 + {
112.148 + conn.println("400 group not found");
112.149 + }
112.150 + else
112.151 + {
112.152 + group.setFlag(Group.DELETED);
112.153 + group.update();
112.154 + conn.println("200 group " + commands[2] + " marked as deleted");
112.155 + }
112.156 + }
112.157 + else if(commands.length == 4 && commands[1].equalsIgnoreCase("SET"))
112.158 + {
112.159 + String key = commands[2];
112.160 + String val = commands[3];
112.161 + Config.inst().set(key, val);
112.162 + conn.println("200 new config value set");
112.163 + }
112.164 + else if(commands.length == 3 && commands[1].equalsIgnoreCase("GET"))
112.165 + {
112.166 + String key = commands[2];
112.167 + String val = Config.inst().get(key, null);
112.168 + if(val != null)
112.169 + {
112.170 + conn.println("100 config value for " + key + " follows");
112.171 + conn.println(val);
112.172 + conn.println(".");
112.173 + }
112.174 + else
112.175 + {
112.176 + conn.println("400 config value not set");
112.177 + }
112.178 + }
112.179 + else if(commands.length >= 3 && commands[1].equalsIgnoreCase("LOG"))
112.180 + {
112.181 + Group group = null;
112.182 + if(commands.length > 3)
112.183 + {
112.184 + group = (Group)Channel.getByName(commands[3]);
112.185 + }
112.186 +
112.187 + if(commands[2].equalsIgnoreCase("CONNECTED_CLIENTS"))
112.188 + {
112.189 + conn.println("100 number of connections follow");
112.190 + conn.println(Integer.toString(Stats.getInstance().connectedClients()));
112.191 + conn.println(".");
112.192 + }
112.193 + else if(commands[2].equalsIgnoreCase("POSTED_NEWS"))
112.194 + {
112.195 + conn.println("100 hourly numbers of posted news yesterday");
112.196 + for(int n = 0; n < 24; n++)
112.197 + {
112.198 + conn.println(n + " " + Stats.getInstance()
112.199 + .getYesterdaysEvents(Stats.POSTED_NEWS, n, group));
112.200 + }
112.201 + conn.println(".");
112.202 + }
112.203 + else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS"))
112.204 + {
112.205 + conn.println("100 hourly numbers of gatewayed news yesterday");
112.206 + for(int n = 0; n < 24; n++)
112.207 + {
112.208 + conn.println(n + " " + Stats.getInstance()
112.209 + .getYesterdaysEvents(Stats.GATEWAYED_NEWS, n, group));
112.210 + }
112.211 + conn.println(".");
112.212 + }
112.213 + else if(commands[2].equalsIgnoreCase("TRANSMITTED_NEWS"))
112.214 + {
112.215 + conn.println("100 hourly numbers of news transmitted to peers yesterday");
112.216 + for(int n = 0; n < 24; n++)
112.217 + {
112.218 + conn.println(n + " " + Stats.getInstance()
112.219 + .getYesterdaysEvents(Stats.FEEDED_NEWS, n, group));
112.220 + }
112.221 + conn.println(".");
112.222 + }
112.223 + else if(commands[2].equalsIgnoreCase("HOSTED_NEWS"))
112.224 + {
112.225 + conn.println("100 number of overall hosted news");
112.226 + conn.println(Integer.toString(Stats.getInstance().getNumberOfNews()));
112.227 + conn.println(".");
112.228 + }
112.229 + else if(commands[2].equalsIgnoreCase("HOSTED_GROUPS"))
112.230 + {
112.231 + conn.println("100 number of hosted groups");
112.232 + conn.println(Integer.toString(Stats.getInstance().getNumberOfGroups()));
112.233 + conn.println(".");
112.234 + }
112.235 + else if(commands[2].equalsIgnoreCase("POSTED_NEWS_PER_HOUR"))
112.236 + {
112.237 + conn.println("100 posted news per hour");
112.238 + conn.println(Double.toString(Stats.getInstance().postedPerHour(-1)));
112.239 + conn.println(".");
112.240 + }
112.241 + else if(commands[2].equalsIgnoreCase("FEEDED_NEWS_PER_HOUR"))
112.242 + {
112.243 + conn.println("100 feeded news per hour");
112.244 + conn.println(Double.toString(Stats.getInstance().feededPerHour(-1)));
112.245 + conn.println(".");
112.246 + }
112.247 + else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS_PER_HOUR"))
112.248 + {
112.249 + conn.println("100 gatewayed news per hour");
112.250 + conn.println(Double.toString(Stats.getInstance().gatewayedPerHour(-1)));
112.251 + conn.println(".");
112.252 + }
112.253 + else
112.254 + {
112.255 + conn.println("401 unknown sub command");
112.256 + }
112.257 + }
112.258 + else if(commands.length >= 3 && commands[1].equalsIgnoreCase("PLUGIN"))
112.259 + {
112.260 +
112.261 + }
112.262 + else
112.263 + {
112.264 + conn.println("400 invalid command usage");
112.265 + }
112.266 + }
112.267 + else
112.268 + {
112.269 + conn.println("501 not allowed");
112.270 + }
112.271 + }
112.272 +
112.273 +}
113.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
113.2 +++ b/src/org/sonews/daemon/command/XPatCommand.java Sun Aug 29 17:28:58 2010 +0200
113.3 @@ -0,0 +1,170 @@
113.4 +/*
113.5 + * SONEWS News Server
113.6 + * see AUTHORS for the list of contributors
113.7 + *
113.8 + * This program is free software: you can redistribute it and/or modify
113.9 + * it under the terms of the GNU General Public License as published by
113.10 + * the Free Software Foundation, either version 3 of the License, or
113.11 + * (at your option) any later version.
113.12 + *
113.13 + * This program is distributed in the hope that it will be useful,
113.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
113.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
113.16 + * GNU General Public License for more details.
113.17 + *
113.18 + * You should have received a copy of the GNU General Public License
113.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
113.20 + */
113.21 +
113.22 +package org.sonews.daemon.command;
113.23 +
113.24 +import java.io.IOException;
113.25 +import java.util.List;
113.26 +import java.util.Locale;
113.27 +import java.util.regex.PatternSyntaxException;
113.28 +import org.sonews.daemon.NNTPConnection;
113.29 +import org.sonews.storage.StorageBackendException;
113.30 +import org.sonews.storage.StorageManager;
113.31 +import org.sonews.util.Pair;
113.32 +
113.33 +/**
113.34 + * <pre>
113.35 + * XPAT header range|<message-id> pat [pat...]
113.36 + *
113.37 + * The XPAT command is used to retrieve specific headers from
113.38 + * specific articles, based on pattern matching on the contents of
113.39 + * the header. This command was first available in INN.
113.40 + *
113.41 + * The required header parameter is the name of a header line (e.g.
113.42 + * "subject") in a news group article. See RFC-1036 for a list
113.43 + * of valid header lines. The required range argument may be
113.44 + * any of the following:
113.45 + * an article number
113.46 + * an article number followed by a dash to indicate
113.47 + * all following
113.48 + * an article number followed by a dash followed by
113.49 + * another article number
113.50 + *
113.51 + * The required message-id argument indicates a specific
113.52 + * article. The range and message-id arguments are mutually
113.53 + * exclusive. At least one pattern in wildmat must be specified
113.54 + * as well. If there are additional arguments the are joined
113.55 + * together separated by a single space to form one complete
113.56 + * pattern. Successful responses start with a 221 response
113.57 + * followed by a the headers from all messages in which the
113.58 + * pattern matched the contents of the specified header line. This
113.59 + * includes an empty list. Once the output is complete, a period
113.60 + * is sent on a line by itself. If the optional argument is a
113.61 + * message-id and no such article exists, the 430 error response
113.62 + * is returned. A 502 response will be returned if the client only
113.63 + * has permission to transfer articles.
113.64 + *
113.65 + * Responses
113.66 + *
113.67 + * 221 Header follows
113.68 + * 430 no such article
113.69 + * 502 no permission
113.70 + *
113.71 + * Response Data:
113.72 + *
113.73 + * art_nr fitting_header_value
113.74 + *
113.75 + * </pre>
113.76 + * [Source:"draft-ietf-nntp-imp-02.txt"] [Copyright: 1998 S. Barber]
113.77 + *
113.78 + * @author Christian Lins
113.79 + * @since sonews/0.5.0
113.80 + */
113.81 +public class XPatCommand implements Command
113.82 +{
113.83 +
113.84 + @Override
113.85 + public String[] getSupportedCommandStrings()
113.86 + {
113.87 + return new String[]{"XPAT"};
113.88 + }
113.89 +
113.90 + @Override
113.91 + public boolean hasFinished()
113.92 + {
113.93 + return true;
113.94 + }
113.95 +
113.96 + @Override
113.97 + public String impliedCapability()
113.98 + {
113.99 + return null;
113.100 + }
113.101 +
113.102 + @Override
113.103 + public boolean isStateful()
113.104 + {
113.105 + return false;
113.106 + }
113.107 +
113.108 + @Override
113.109 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
113.110 + throws IOException, StorageBackendException
113.111 + {
113.112 + if(conn.getCurrentChannel() == null)
113.113 + {
113.114 + conn.println("430 no group selected");
113.115 + return;
113.116 + }
113.117 +
113.118 + String[] command = line.split("\\p{Space}+");
113.119 +
113.120 + // There may be multiple patterns and Thunderbird produces
113.121 + // additional spaces between range and pattern
113.122 + if(command.length >= 4)
113.123 + {
113.124 + String header = command[1].toLowerCase(Locale.US);
113.125 + String range = command[2];
113.126 + String pattern = command[3];
113.127 +
113.128 + long start = -1;
113.129 + long end = -1;
113.130 + if(range.contains("-"))
113.131 + {
113.132 + String[] rsplit = range.split("-", 2);
113.133 + start = Long.parseLong(rsplit[0]);
113.134 + if(rsplit[1].length() > 0)
113.135 + {
113.136 + end = Long.parseLong(rsplit[1]);
113.137 + }
113.138 + }
113.139 + else // TODO: Handle Message-IDs
113.140 + {
113.141 + start = Long.parseLong(range);
113.142 + }
113.143 +
113.144 + try
113.145 + {
113.146 + List<Pair<Long, String>> heads = StorageManager.current().
113.147 + getArticleHeaders(conn.getCurrentChannel(), start, end, header, pattern);
113.148 +
113.149 + conn.println("221 header follows");
113.150 + for(Pair<Long, String> head : heads)
113.151 + {
113.152 + conn.println(head.getA() + " " + head.getB());
113.153 + }
113.154 + conn.println(".");
113.155 + }
113.156 + catch(PatternSyntaxException ex)
113.157 + {
113.158 + ex.printStackTrace();
113.159 + conn.println("500 invalid pattern syntax");
113.160 + }
113.161 + catch(StorageBackendException ex)
113.162 + {
113.163 + ex.printStackTrace();
113.164 + conn.println("500 internal server error");
113.165 + }
113.166 + }
113.167 + else
113.168 + {
113.169 + conn.println("430 invalid command usage");
113.170 + }
113.171 + }
113.172 +
113.173 +}
114.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
114.2 +++ b/src/org/sonews/daemon/command/package.html Sun Aug 29 17:28:58 2010 +0200
114.3 @@ -0,0 +1,1 @@
114.4 +Contains a class for every NNTP command.
114.5 \ No newline at end of file
115.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
115.2 +++ b/src/org/sonews/daemon/package.html Sun Aug 29 17:28:58 2010 +0200
115.3 @@ -0,0 +1,1 @@
115.4 +Contains basic classes of the daemon.
115.5 \ No newline at end of file
116.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
116.2 +++ b/src/org/sonews/feed/FeedManager.java Sun Aug 29 17:28:58 2010 +0200
116.3 @@ -0,0 +1,54 @@
116.4 +/*
116.5 + * SONEWS News Server
116.6 + * see AUTHORS for the list of contributors
116.7 + *
116.8 + * This program is free software: you can redistribute it and/or modify
116.9 + * it under the terms of the GNU General Public License as published by
116.10 + * the Free Software Foundation, either version 3 of the License, or
116.11 + * (at your option) any later version.
116.12 + *
116.13 + * This program is distributed in the hope that it will be useful,
116.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
116.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
116.16 + * GNU General Public License for more details.
116.17 + *
116.18 + * You should have received a copy of the GNU General Public License
116.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
116.20 + */
116.21 +
116.22 +package org.sonews.feed;
116.23 +
116.24 +import org.sonews.storage.Article;
116.25 +
116.26 +/**
116.27 + * Controlls push and pull feeder.
116.28 + * @author Christian Lins
116.29 + * @since sonews/0.5.0
116.30 + */
116.31 +public final class FeedManager
116.32 +{
116.33 +
116.34 + public static final int TYPE_PULL = 0;
116.35 + public static final int TYPE_PUSH = 1;
116.36 +
116.37 + private static PullFeeder pullFeeder = new PullFeeder();
116.38 + private static PushFeeder pushFeeder = new PushFeeder();
116.39 +
116.40 + /**
116.41 + * Reads the peer subscriptions from database and starts the appropriate
116.42 + * PullFeeder or PushFeeder.
116.43 + */
116.44 + public static synchronized void startFeeding()
116.45 + {
116.46 + pullFeeder.start();
116.47 + pushFeeder.start();
116.48 + }
116.49 +
116.50 + public static void queueForPush(Article article)
116.51 + {
116.52 + pushFeeder.queueForPush(article);
116.53 + }
116.54 +
116.55 + private FeedManager() {}
116.56 +
116.57 +}
117.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
117.2 +++ b/src/org/sonews/feed/PullFeeder.java Sun Aug 29 17:28:58 2010 +0200
117.3 @@ -0,0 +1,276 @@
117.4 +/*
117.5 + * SONEWS News Server
117.6 + * see AUTHORS for the list of contributors
117.7 + *
117.8 + * This program is free software: you can redistribute it and/or modify
117.9 + * it under the terms of the GNU General Public License as published by
117.10 + * the Free Software Foundation, either version 3 of the License, or
117.11 + * (at your option) any later version.
117.12 + *
117.13 + * This program is distributed in the hope that it will be useful,
117.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
117.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
117.16 + * GNU General Public License for more details.
117.17 + *
117.18 + * You should have received a copy of the GNU General Public License
117.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
117.20 + */
117.21 +
117.22 +package org.sonews.feed;
117.23 +
117.24 +import java.io.BufferedReader;
117.25 +import java.io.IOException;
117.26 +import java.io.InputStreamReader;
117.27 +import java.io.PrintWriter;
117.28 +import java.net.Socket;
117.29 +import java.net.SocketException;
117.30 +import java.net.UnknownHostException;
117.31 +import java.util.ArrayList;
117.32 +import java.util.HashMap;
117.33 +import java.util.HashSet;
117.34 +import java.util.List;
117.35 +import java.util.Map;
117.36 +import java.util.Set;
117.37 +import java.util.logging.Level;
117.38 +import org.sonews.config.Config;
117.39 +import org.sonews.daemon.AbstractDaemon;
117.40 +import org.sonews.util.Log;
117.41 +import org.sonews.storage.StorageBackendException;
117.42 +import org.sonews.storage.StorageManager;
117.43 +import org.sonews.util.Stats;
117.44 +import org.sonews.util.io.ArticleReader;
117.45 +import org.sonews.util.io.ArticleWriter;
117.46 +
117.47 +/**
117.48 + * The PullFeeder class regularily checks another Newsserver for new
117.49 + * messages.
117.50 + * @author Christian Lins
117.51 + * @since sonews/0.5.0
117.52 + */
117.53 +class PullFeeder extends AbstractDaemon
117.54 +{
117.55 +
117.56 + private Map<Subscription, Integer> highMarks = new HashMap<Subscription, Integer>();
117.57 + private BufferedReader in;
117.58 + private PrintWriter out;
117.59 + private Set<Subscription> subscriptions = new HashSet<Subscription>();
117.60 +
117.61 + private void addSubscription(final Subscription sub)
117.62 + {
117.63 + subscriptions.add(sub);
117.64 +
117.65 + if(!highMarks.containsKey(sub))
117.66 + {
117.67 + // Set a initial highMark
117.68 + this.highMarks.put(sub, 0);
117.69 + }
117.70 + }
117.71 +
117.72 + /**
117.73 + * Changes to the given group and returns its high mark.
117.74 + * @param groupName
117.75 + * @return
117.76 + */
117.77 + private int changeGroup(String groupName)
117.78 + throws IOException
117.79 + {
117.80 + this.out.print("GROUP " + groupName + "\r\n");
117.81 + this.out.flush();
117.82 +
117.83 + String line = this.in.readLine();
117.84 + if(line.startsWith("211 "))
117.85 + {
117.86 + int highmark = Integer.parseInt(line.split(" ")[3]);
117.87 + return highmark;
117.88 + }
117.89 + else
117.90 + {
117.91 + throw new IOException("GROUP " + groupName + " returned: " + line);
117.92 + }
117.93 + }
117.94 +
117.95 + private void connectTo(final String host, final int port)
117.96 + throws IOException, UnknownHostException
117.97 + {
117.98 + Socket socket = new Socket(host, port);
117.99 + this.out = new PrintWriter(socket.getOutputStream());
117.100 + this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
117.101 +
117.102 + String line = in.readLine();
117.103 + if(!(line.charAt(0) == '2')) // Could be 200 or 2xx if posting is not allowed
117.104 + {
117.105 + throw new IOException(line);
117.106 + }
117.107 +
117.108 + // Send MODE READER to peer, some newsservers are friendlier then
117.109 + this.out.println("MODE READER\r\n");
117.110 + this.out.flush();
117.111 + line = this.in.readLine();
117.112 + }
117.113 +
117.114 + private void disconnect()
117.115 + throws IOException
117.116 + {
117.117 + this.out.print("QUIT\r\n");
117.118 + this.out.flush();
117.119 + this.out.close();
117.120 + this.in.close();
117.121 +
117.122 + this.out = null;
117.123 + this.in = null;
117.124 + }
117.125 +
117.126 + /**
117.127 + * Uses the OVER or XOVER command to get a list of message overviews that
117.128 + * may be unknown to this feeder and are about to be peered.
117.129 + * @param start
117.130 + * @param end
117.131 + * @return A list of message ids with potentially interesting messages.
117.132 + */
117.133 + private List<String> over(int start, int end)
117.134 + throws IOException
117.135 + {
117.136 + this.out.print("OVER " + start + "-" + end + "\r\n");
117.137 + this.out.flush();
117.138 +
117.139 + String line = this.in.readLine();
117.140 + if(line.startsWith("500 ")) // OVER not supported
117.141 + {
117.142 + this.out.print("XOVER " + start + "-" + end + "\r\n");
117.143 + this.out.flush();
117.144 +
117.145 + line = this.in.readLine();
117.146 + }
117.147 +
117.148 + if(line.startsWith("224 "))
117.149 + {
117.150 + List<String> messages = new ArrayList<String>();
117.151 + line = this.in.readLine();
117.152 + while(!".".equals(line))
117.153 + {
117.154 + String mid = line.split("\t")[4]; // 5th should be the Message-ID
117.155 + messages.add(mid);
117.156 + line = this.in.readLine();
117.157 + }
117.158 + return messages;
117.159 + }
117.160 + else
117.161 + {
117.162 + throw new IOException("Server return for OVER/XOVER: " + line);
117.163 + }
117.164 + }
117.165 +
117.166 + @Override
117.167 + public void run()
117.168 + {
117.169 + while(isRunning())
117.170 + {
117.171 + int pullInterval = 1000 *
117.172 + Config.inst().get(Config.FEED_PULLINTERVAL, 3600);
117.173 + String host = "localhost";
117.174 + int port = 119;
117.175 +
117.176 + Log.get().info("Start PullFeeder run...");
117.177 +
117.178 + try
117.179 + {
117.180 + this.subscriptions.clear();
117.181 + List<Subscription> subsPull = StorageManager.current()
117.182 + .getSubscriptions(FeedManager.TYPE_PULL);
117.183 + for(Subscription sub : subsPull)
117.184 + {
117.185 + addSubscription(sub);
117.186 + }
117.187 + }
117.188 + catch(StorageBackendException ex)
117.189 + {
117.190 + Log.get().log(Level.SEVERE, host, ex);
117.191 + }
117.192 +
117.193 + try
117.194 + {
117.195 + for(Subscription sub : this.subscriptions)
117.196 + {
117.197 + host = sub.getHost();
117.198 + port = sub.getPort();
117.199 +
117.200 + try
117.201 + {
117.202 + Log.get().info("Feeding " + sub.getGroup() + " from " + sub.getHost());
117.203 + try
117.204 + {
117.205 + connectTo(host, port);
117.206 + }
117.207 + catch(SocketException ex)
117.208 + {
117.209 + Log.get().info("Skipping " + sub.getHost() + ": " + ex);
117.210 + continue;
117.211 + }
117.212 +
117.213 + int oldMark = this.highMarks.get(sub);
117.214 + int newMark = changeGroup(sub.getGroup());
117.215 +
117.216 + if(oldMark != newMark)
117.217 + {
117.218 + List<String> messageIDs = over(oldMark, newMark);
117.219 +
117.220 + for(String messageID : messageIDs)
117.221 + {
117.222 + if(!StorageManager.current().isArticleExisting(messageID))
117.223 + {
117.224 + try
117.225 + {
117.226 + // Post the message via common socket connection
117.227 + ArticleReader aread =
117.228 + new ArticleReader(sub.getHost(), sub.getPort(), messageID);
117.229 + byte[] abuf = aread.getArticleData();
117.230 + if(abuf == null)
117.231 + {
117.232 + Log.get().warning("Could not feed " + messageID
117.233 + + " from " + sub.getHost());
117.234 + }
117.235 + else
117.236 + {
117.237 + Log.get().info("Feeding " + messageID);
117.238 + ArticleWriter awrite = new ArticleWriter(
117.239 + "localhost", Config.inst().get(Config.PORT, 119));
117.240 + awrite.writeArticle(abuf);
117.241 + awrite.close();
117.242 + }
117.243 + Stats.getInstance().mailFeeded(sub.getGroup());
117.244 + }
117.245 + catch(IOException ex)
117.246 + {
117.247 + // There may be a temporary network failure
117.248 + ex.printStackTrace();
117.249 + Log.get().warning("Skipping mail " + messageID + " due to exception.");
117.250 + }
117.251 + }
117.252 + } // for(;;)
117.253 + this.highMarks.put(sub, newMark);
117.254 + }
117.255 +
117.256 + disconnect();
117.257 + }
117.258 + catch(StorageBackendException ex)
117.259 + {
117.260 + ex.printStackTrace();
117.261 + }
117.262 + catch(IOException ex)
117.263 + {
117.264 + ex.printStackTrace();
117.265 + Log.get().severe("PullFeeder run stopped due to exception.");
117.266 + }
117.267 + } // for(Subscription sub : subscriptions)
117.268 +
117.269 + Log.get().info("PullFeeder run ended. Waiting " + pullInterval / 1000 + "s");
117.270 + Thread.sleep(pullInterval);
117.271 + }
117.272 + catch(InterruptedException ex)
117.273 + {
117.274 + Log.get().warning(ex.getMessage());
117.275 + }
117.276 + }
117.277 + }
117.278 +
117.279 +}
118.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
118.2 +++ b/src/org/sonews/feed/PushFeeder.java Sun Aug 29 17:28:58 2010 +0200
118.3 @@ -0,0 +1,118 @@
118.4 +/*
118.5 + * SONEWS News Server
118.6 + * see AUTHORS for the list of contributors
118.7 + *
118.8 + * This program is free software: you can redistribute it and/or modify
118.9 + * it under the terms of the GNU General Public License as published by
118.10 + * the Free Software Foundation, either version 3 of the License, or
118.11 + * (at your option) any later version.
118.12 + *
118.13 + * This program is distributed in the hope that it will be useful,
118.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
118.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
118.16 + * GNU General Public License for more details.
118.17 + *
118.18 + * You should have received a copy of the GNU General Public License
118.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
118.20 + */
118.21 +
118.22 +package org.sonews.feed;
118.23 +
118.24 +import java.io.IOException;
118.25 +import java.util.List;
118.26 +import java.util.concurrent.ConcurrentLinkedQueue;
118.27 +import org.sonews.daemon.AbstractDaemon;
118.28 +import org.sonews.storage.Article;
118.29 +import org.sonews.storage.Headers;
118.30 +import org.sonews.storage.StorageBackendException;
118.31 +import org.sonews.storage.StorageManager;
118.32 +import org.sonews.util.Log;
118.33 +import org.sonews.util.io.ArticleWriter;
118.34 +
118.35 +/**
118.36 + * Pushes new articles to remote newsservers. This feeder sleeps until a new
118.37 + * message is posted to the sonews instance.
118.38 + * @author Christian Lins
118.39 + * @since sonews/0.5.0
118.40 + */
118.41 +class PushFeeder extends AbstractDaemon
118.42 +{
118.43 +
118.44 + private ConcurrentLinkedQueue<Article> articleQueue =
118.45 + new ConcurrentLinkedQueue<Article>();
118.46 +
118.47 + @Override
118.48 + public void run()
118.49 + {
118.50 + while(isRunning())
118.51 + {
118.52 + try
118.53 + {
118.54 + synchronized(this)
118.55 + {
118.56 + this.wait();
118.57 + }
118.58 +
118.59 + List<Subscription> subscriptions = StorageManager.current()
118.60 + .getSubscriptions(FeedManager.TYPE_PUSH);
118.61 +
118.62 + Article article = this.articleQueue.poll();
118.63 + String[] groups = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
118.64 + Log.get().info("PushFeed: " + article.getMessageID());
118.65 + for(Subscription sub : subscriptions)
118.66 + {
118.67 + // Circle check
118.68 + if(article.getHeader(Headers.PATH)[0].contains(sub.getHost()))
118.69 + {
118.70 + Log.get().info(article.getMessageID() + " skipped for host "
118.71 + + sub.getHost());
118.72 + continue;
118.73 + }
118.74 +
118.75 + try
118.76 + {
118.77 + for(String group : groups)
118.78 + {
118.79 + if(sub.getGroup().equals(group))
118.80 + {
118.81 + // Delete headers that may cause problems
118.82 + article.removeHeader(Headers.NNTP_POSTING_DATE);
118.83 + article.removeHeader(Headers.NNTP_POSTING_HOST);
118.84 + article.removeHeader(Headers.X_COMPLAINTS_TO);
118.85 + article.removeHeader(Headers.X_TRACE);
118.86 + article.removeHeader(Headers.XREF);
118.87 +
118.88 + // POST the message to remote server
118.89 + ArticleWriter awriter = new ArticleWriter(sub.getHost(), sub.getPort());
118.90 + awriter.writeArticle(article);
118.91 + break;
118.92 + }
118.93 + }
118.94 + }
118.95 + catch(IOException ex)
118.96 + {
118.97 + Log.get().warning(ex.toString());
118.98 + }
118.99 + }
118.100 + }
118.101 + catch(StorageBackendException ex)
118.102 + {
118.103 + Log.get().severe(ex.toString());
118.104 + }
118.105 + catch(InterruptedException ex)
118.106 + {
118.107 + Log.get().warning("PushFeeder interrupted: " + ex);
118.108 + }
118.109 + }
118.110 + }
118.111 +
118.112 + public void queueForPush(Article article)
118.113 + {
118.114 + this.articleQueue.add(article);
118.115 + synchronized(this)
118.116 + {
118.117 + this.notifyAll();
118.118 + }
118.119 + }
118.120 +
118.121 +}
119.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
119.2 +++ b/src/org/sonews/feed/Subscription.java Sun Aug 29 17:28:58 2010 +0200
119.3 @@ -0,0 +1,84 @@
119.4 +/*
119.5 + * SONEWS News Server
119.6 + * see AUTHORS for the list of contributors
119.7 + *
119.8 + * This program is free software: you can redistribute it and/or modify
119.9 + * it under the terms of the GNU General Public License as published by
119.10 + * the Free Software Foundation, either version 3 of the License, or
119.11 + * (at your option) any later version.
119.12 + *
119.13 + * This program is distributed in the hope that it will be useful,
119.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
119.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
119.16 + * GNU General Public License for more details.
119.17 + *
119.18 + * You should have received a copy of the GNU General Public License
119.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
119.20 + */
119.21 +
119.22 +package org.sonews.feed;
119.23 +
119.24 +/**
119.25 + * For every group that is synchronized with or from a remote newsserver
119.26 + * a Subscription instance exists.
119.27 + * @author Christian Lins
119.28 + * @since sonews/0.5.0
119.29 + */
119.30 +public class Subscription
119.31 +{
119.32 +
119.33 + private String host;
119.34 + private int port;
119.35 + private int feedtype;
119.36 + private String group;
119.37 +
119.38 + public Subscription(String host, int port, int feedtype, String group)
119.39 + {
119.40 + this.host = host;
119.41 + this.port = port;
119.42 + this.feedtype = feedtype;
119.43 + this.group = group;
119.44 + }
119.45 +
119.46 + @Override
119.47 + public boolean equals(Object obj)
119.48 + {
119.49 + if(obj instanceof Subscription)
119.50 + {
119.51 + Subscription sub = (Subscription)obj;
119.52 + return sub.host.equals(host) && sub.group.equals(group)
119.53 + && sub.port == port && sub.feedtype == feedtype;
119.54 + }
119.55 + else
119.56 + {
119.57 + return false;
119.58 + }
119.59 + }
119.60 +
119.61 + @Override
119.62 + public int hashCode()
119.63 + {
119.64 + return host.hashCode() + port + feedtype + group.hashCode();
119.65 + }
119.66 +
119.67 + public int getFeedtype()
119.68 + {
119.69 + return feedtype;
119.70 + }
119.71 +
119.72 + public String getGroup()
119.73 + {
119.74 + return group;
119.75 + }
119.76 +
119.77 + public String getHost()
119.78 + {
119.79 + return host;
119.80 + }
119.81 +
119.82 + public int getPort()
119.83 + {
119.84 + return port;
119.85 + }
119.86 +
119.87 +}
120.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
120.2 +++ b/src/org/sonews/feed/package.html Sun Aug 29 17:28:58 2010 +0200
120.3 @@ -0,0 +1,2 @@
120.4 +Contains classes for the peering functionality, e.g. pulling and pushing
120.5 +mails from and to remote newsservers.
120.6 \ No newline at end of file
121.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
121.2 +++ b/src/org/sonews/mlgw/Dispatcher.java Sun Aug 29 17:28:58 2010 +0200
121.3 @@ -0,0 +1,301 @@
121.4 +/*
121.5 + * SONEWS News Server
121.6 + * see AUTHORS for the list of contributors
121.7 + *
121.8 + * This program is free software: you can redistribute it and/or modify
121.9 + * it under the terms of the GNU General Public License as published by
121.10 + * the Free Software Foundation, either version 3 of the License, or
121.11 + * (at your option) any later version.
121.12 + *
121.13 + * This program is distributed in the hope that it will be useful,
121.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
121.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
121.16 + * GNU General Public License for more details.
121.17 + *
121.18 + * You should have received a copy of the GNU General Public License
121.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
121.20 + */
121.21 +
121.22 +package org.sonews.mlgw;
121.23 +
121.24 +import java.io.IOException;
121.25 +import java.util.ArrayList;
121.26 +import java.util.List;
121.27 +import java.util.regex.Matcher;
121.28 +import java.util.regex.Pattern;
121.29 +import javax.mail.Address;
121.30 +import javax.mail.Authenticator;
121.31 +import javax.mail.Message;
121.32 +import javax.mail.MessagingException;
121.33 +import javax.mail.PasswordAuthentication;
121.34 +import javax.mail.internet.InternetAddress;
121.35 +import org.sonews.config.Config;
121.36 +import org.sonews.storage.Article;
121.37 +import org.sonews.storage.Group;
121.38 +import org.sonews.storage.Headers;
121.39 +import org.sonews.storage.StorageBackendException;
121.40 +import org.sonews.storage.StorageManager;
121.41 +import org.sonews.util.Log;
121.42 +import org.sonews.util.Stats;
121.43 +
121.44 +/**
121.45 + * Dispatches messages from mailing list to newsserver or vice versa.
121.46 + * @author Christian Lins
121.47 + * @since sonews/0.5.0
121.48 + */
121.49 +public class Dispatcher
121.50 +{
121.51 +
121.52 + static class PasswordAuthenticator extends Authenticator
121.53 + {
121.54 +
121.55 + @Override
121.56 + public PasswordAuthentication getPasswordAuthentication()
121.57 + {
121.58 + final String username =
121.59 + Config.inst().get(Config.MLSEND_USER, "user");
121.60 + final String password =
121.61 + Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
121.62 +
121.63 + return new PasswordAuthentication(username, password);
121.64 + }
121.65 +
121.66 + }
121.67 +
121.68 + /**
121.69 + * Chunks out the email address of the full List-Post header field.
121.70 + * @param listPostValue
121.71 + * @return The matching email address or null
121.72 + */
121.73 + private static String chunkListPost(String listPostValue)
121.74 + {
121.75 + // listPostValue is of form "<mailto:dev@openoffice.org>"
121.76 + Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
121.77 + Matcher mailMatcher = mailPattern.matcher(listPostValue);
121.78 + if(mailMatcher.find())
121.79 + {
121.80 + return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
121.81 + }
121.82 + else
121.83 + {
121.84 + return null;
121.85 + }
121.86 + }
121.87 +
121.88 + /**
121.89 + * This method inspects the header of the given message, trying
121.90 + * to find the most appropriate recipient.
121.91 + * @param msg
121.92 + * @param fallback If this is false only List-Post and X-List-Post headers
121.93 + * are examined.
121.94 + * @return null or fitting group name for the given message.
121.95 + */
121.96 + private static List<String> getGroupFor(final Message msg, final boolean fallback)
121.97 + throws MessagingException, StorageBackendException
121.98 + {
121.99 + List<String> groups = null;
121.100 +
121.101 + // Is there a List-Post header?
121.102 + String[] listPost = msg.getHeader(Headers.LIST_POST);
121.103 + InternetAddress listPostAddr;
121.104 +
121.105 + if(listPost == null || listPost.length == 0 || "".equals(listPost[0]))
121.106 + {
121.107 + // Is there a X-List-Post header?
121.108 + listPost = msg.getHeader(Headers.X_LIST_POST);
121.109 + }
121.110 +
121.111 + if(listPost != null && listPost.length > 0
121.112 + && !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null)
121.113 + {
121.114 + // listPost[0] is of form "<mailto:dev@openoffice.org>"
121.115 + listPost[0] = chunkListPost(listPost[0]);
121.116 + listPostAddr = new InternetAddress(listPost[0], false);
121.117 + groups = StorageManager.current().getGroupsForList(listPostAddr.getAddress());
121.118 + }
121.119 + else if(fallback)
121.120 + {
121.121 + Log.get().info("Using fallback recipient discovery for: " + msg.getSubject());
121.122 + groups = new ArrayList<String>();
121.123 + // Fallback to TO/CC/BCC addresses
121.124 + Address[] to = msg.getAllRecipients();
121.125 + for(Address toa : to) // Address can have '<' '>' around
121.126 + {
121.127 + if(toa instanceof InternetAddress)
121.128 + {
121.129 + List<String> g = StorageManager.current()
121.130 + .getGroupsForList(((InternetAddress)toa).getAddress());
121.131 + groups.addAll(g);
121.132 + }
121.133 + }
121.134 + }
121.135 +
121.136 + return groups;
121.137 + }
121.138 +
121.139 + /**
121.140 + * Posts a message that was received from a mailing list to the
121.141 + * appropriate newsgroup.
121.142 + * If the message already exists in the storage, this message checks
121.143 + * if it must be posted in an additional group. This can happen for
121.144 + * crosspostings in different mailing lists.
121.145 + * @param msg
121.146 + */
121.147 + public static boolean toGroup(final Message msg)
121.148 + {
121.149 + try
121.150 + {
121.151 + // Create new Article object
121.152 + Article article = new Article(msg);
121.153 + boolean posted = false;
121.154 +
121.155 + // Check if this mail is already existing the storage
121.156 + boolean updateReq =
121.157 + StorageManager.current().isArticleExisting(article.getMessageID());
121.158 +
121.159 + List<String> newsgroups = getGroupFor(msg, !updateReq);
121.160 + List<String> oldgroups = new ArrayList<String>();
121.161 + if(updateReq)
121.162 + {
121.163 + // Check for duplicate entries of the same group
121.164 + Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
121.165 + List<Group> oldGroups = oldArticle.getGroups();
121.166 + for(Group oldGroup : oldGroups)
121.167 + {
121.168 + if(!newsgroups.contains(oldGroup.getName()))
121.169 + {
121.170 + oldgroups.add(oldGroup.getName());
121.171 + }
121.172 + }
121.173 + }
121.174 +
121.175 + if(newsgroups.size() > 0)
121.176 + {
121.177 + newsgroups.addAll(oldgroups);
121.178 + StringBuilder groups = new StringBuilder();
121.179 + for(int n = 0; n < newsgroups.size(); n++)
121.180 + {
121.181 + groups.append(newsgroups.get(n));
121.182 + if (n + 1 != newsgroups.size())
121.183 + {
121.184 + groups.append(',');
121.185 + }
121.186 + }
121.187 + Log.get().info("Posting to group " + groups.toString());
121.188 +
121.189 + article.setGroup(groups.toString());
121.190 + //article.removeHeader(Headers.REPLY_TO);
121.191 + //article.removeHeader(Headers.TO);
121.192 +
121.193 + // Write article to database
121.194 + if(updateReq)
121.195 + {
121.196 + Log.get().info("Updating " + article.getMessageID()
121.197 + + " with additional groups");
121.198 + StorageManager.current().delete(article.getMessageID());
121.199 + StorageManager.current().addArticle(article);
121.200 + }
121.201 + else
121.202 + {
121.203 + Log.get().info("Gatewaying " + article.getMessageID() + " to "
121.204 + + article.getHeader(Headers.NEWSGROUPS)[0]);
121.205 + StorageManager.current().addArticle(article);
121.206 + Stats.getInstance().mailGatewayed(
121.207 + article.getHeader(Headers.NEWSGROUPS)[0]);
121.208 + }
121.209 + posted = true;
121.210 + }
121.211 + else
121.212 + {
121.213 + StringBuilder buf = new StringBuilder();
121.214 + for (Address toa : msg.getAllRecipients())
121.215 + {
121.216 + buf.append(' ');
121.217 + buf.append(toa.toString());
121.218 + }
121.219 + buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
121.220 + Log.get().warning("No group for" + buf.toString());
121.221 + }
121.222 + return posted;
121.223 + }
121.224 + catch(Exception ex)
121.225 + {
121.226 + ex.printStackTrace();
121.227 + return false;
121.228 + }
121.229 + }
121.230 +
121.231 + /**
121.232 + * Mails a message received through NNTP to the appropriate mailing list.
121.233 + * This method MAY be called several times by PostCommand for the same
121.234 + * article.
121.235 + */
121.236 + public static void toList(Article article, String group)
121.237 + throws IOException, MessagingException, StorageBackendException
121.238 + {
121.239 + // Get mailing lists for the group of this article
121.240 + List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
121.241 +
121.242 + if(rcptAddresses == null || rcptAddresses.size() == 0)
121.243 + {
121.244 + Log.get().warning("No ML-address for " + group + " found.");
121.245 + return;
121.246 + }
121.247 +
121.248 + for(String rcptAddress : rcptAddresses)
121.249 + {
121.250 + // Compose message and send it via given SMTP-Host
121.251 + String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
121.252 + int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
121.253 + String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
121.254 + String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
121.255 + String smtpFrom = Config.inst().get(
121.256 + Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
121.257 +
121.258 + // TODO: Make Article cloneable()
121.259 + article.getMessageID(); // Make sure an ID is existing
121.260 + article.removeHeader(Headers.NEWSGROUPS);
121.261 + article.removeHeader(Headers.PATH);
121.262 + article.removeHeader(Headers.LINES);
121.263 + article.removeHeader(Headers.BYTES);
121.264 +
121.265 + article.setHeader("To", rcptAddress);
121.266 + //article.setHeader("Reply-To", listAddress);
121.267 +
121.268 + if (Config.inst().get(Config.MLSEND_RW_SENDER, false))
121.269 + {
121.270 + rewriteSenderAddress(article); // Set the SENDER address
121.271 + }
121.272 +
121.273 + SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
121.274 + smtpTransport.send(article, smtpFrom, rcptAddress);
121.275 + smtpTransport.close();
121.276 +
121.277 + Stats.getInstance().mailGatewayed(group);
121.278 + Log.get().info("MLGateway: Mail " + article.getHeader("Subject")[0]
121.279 + + " was delivered to " + rcptAddress + ".");
121.280 + }
121.281 + }
121.282 +
121.283 + /**
121.284 + * Sets the SENDER header of the given MimeMessage. This might be necessary
121.285 + * for moderated groups that does not allow the "normal" FROM sender.
121.286 + * @param msg
121.287 + * @throws javax.mail.MessagingException
121.288 + */
121.289 + private static void rewriteSenderAddress(Article msg)
121.290 + throws MessagingException
121.291 + {
121.292 + String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
121.293 +
121.294 + if(mlAddress != null)
121.295 + {
121.296 + msg.setHeader(Headers.SENDER, mlAddress);
121.297 + }
121.298 + else
121.299 + {
121.300 + throw new MessagingException("Cannot rewrite SENDER header!");
121.301 + }
121.302 + }
121.303 +
121.304 +}
122.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
122.2 +++ b/src/org/sonews/mlgw/MailPoller.java Sun Aug 29 17:28:58 2010 +0200
122.3 @@ -0,0 +1,151 @@
122.4 +/*
122.5 + * SONEWS News Server
122.6 + * see AUTHORS for the list of contributors
122.7 + *
122.8 + * This program is free software: you can redistribute it and/or modify
122.9 + * it under the terms of the GNU General Public License as published by
122.10 + * the Free Software Foundation, either version 3 of the License, or
122.11 + * (at your option) any later version.
122.12 + *
122.13 + * This program is distributed in the hope that it will be useful,
122.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
122.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
122.16 + * GNU General Public License for more details.
122.17 + *
122.18 + * You should have received a copy of the GNU General Public License
122.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
122.20 + */
122.21 +
122.22 +package org.sonews.mlgw;
122.23 +
122.24 +import java.util.Properties;
122.25 +import javax.mail.AuthenticationFailedException;
122.26 +import javax.mail.Authenticator;
122.27 +import javax.mail.Flags.Flag;
122.28 +import javax.mail.Folder;
122.29 +import javax.mail.Message;
122.30 +import javax.mail.MessagingException;
122.31 +import javax.mail.NoSuchProviderException;
122.32 +import javax.mail.PasswordAuthentication;
122.33 +import javax.mail.Session;
122.34 +import javax.mail.Store;
122.35 +import org.sonews.config.Config;
122.36 +import org.sonews.daemon.AbstractDaemon;
122.37 +import org.sonews.util.Log;
122.38 +import org.sonews.util.Stats;
122.39 +
122.40 +/**
122.41 + * Daemon polling for new mails in a POP3 account to be delivered to newsgroups.
122.42 + * @author Christian Lins
122.43 + * @since sonews/0.5.0
122.44 + */
122.45 +public class MailPoller extends AbstractDaemon
122.46 +{
122.47 +
122.48 + static class PasswordAuthenticator extends Authenticator
122.49 + {
122.50 +
122.51 + @Override
122.52 + public PasswordAuthentication getPasswordAuthentication()
122.53 + {
122.54 + final String username =
122.55 + Config.inst().get(Config.MLPOLL_USER, "user");
122.56 + final String password =
122.57 + Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
122.58 +
122.59 + return new PasswordAuthentication(username, password);
122.60 + }
122.61 +
122.62 + }
122.63 +
122.64 + @Override
122.65 + public void run()
122.66 + {
122.67 + Log.get().info("Starting Mailinglist Poller...");
122.68 + int errors = 0;
122.69 + while(isRunning())
122.70 + {
122.71 + try
122.72 + {
122.73 + // Wait some time between runs. At the beginning has advantages,
122.74 + // because the wait is not skipped if an exception occurs.
122.75 + Thread.sleep(60000 * (errors + 1)); // one minute * errors
122.76 +
122.77 + final String host =
122.78 + Config.inst().get(Config.MLPOLL_HOST, "samplehost");
122.79 + final String username =
122.80 + Config.inst().get(Config.MLPOLL_USER, "user");
122.81 + final String password =
122.82 + Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
122.83 +
122.84 + Stats.getInstance().mlgwRunStart();
122.85 +
122.86 + // Create empty properties
122.87 + Properties props = System.getProperties();
122.88 + props.put("mail.pop3.host", host);
122.89 + props.put("mail.mime.address.strict", "false");
122.90 +
122.91 + // Get session
122.92 + Session session = Session.getInstance(props);
122.93 +
122.94 + // Get the store
122.95 + Store store = session.getStore("pop3");
122.96 + store.connect(host, 110, username, password);
122.97 +
122.98 + // Get folder
122.99 + Folder folder = store.getFolder("INBOX");
122.100 + folder.open(Folder.READ_WRITE);
122.101 +
122.102 + // Get directory
122.103 + Message[] messages = folder.getMessages();
122.104 +
122.105 + // Dispatch messages and delete it afterwards on the inbox
122.106 + for(Message message : messages)
122.107 + {
122.108 + if(Dispatcher.toGroup(message)
122.109 + || Config.inst().get(Config.MLPOLL_DELETEUNKNOWN, false))
122.110 + {
122.111 + // Delete the message
122.112 + message.setFlag(Flag.DELETED, true);
122.113 + }
122.114 + }
122.115 +
122.116 + // Close connection
122.117 + folder.close(true); // true to expunge deleted messages
122.118 + store.close();
122.119 + errors = 0;
122.120 +
122.121 + Stats.getInstance().mlgwRunEnd();
122.122 + }
122.123 + catch(NoSuchProviderException ex)
122.124 + {
122.125 + Log.get().severe(ex.toString());
122.126 + shutdown();
122.127 + }
122.128 + catch(AuthenticationFailedException ex)
122.129 + {
122.130 + // AuthentificationFailedException may be thrown if credentials are
122.131 + // bad or if the Mailbox is in use (locked).
122.132 + ex.printStackTrace();
122.133 + errors = errors < 5 ? errors + 1 : errors;
122.134 + }
122.135 + catch(InterruptedException ex)
122.136 + {
122.137 + System.out.println("sonews: " + this + " returns: " + ex);
122.138 + return;
122.139 + }
122.140 + catch(MessagingException ex)
122.141 + {
122.142 + ex.printStackTrace();
122.143 + errors = errors < 5 ? errors + 1 : errors;
122.144 + }
122.145 + catch(Exception ex)
122.146 + {
122.147 + ex.printStackTrace();
122.148 + errors = errors < 5 ? errors + 1 : errors;
122.149 + }
122.150 + }
122.151 + Log.get().severe("MailPoller exited.");
122.152 + }
122.153 +
122.154 +}
123.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
123.2 +++ b/src/org/sonews/mlgw/SMTPTransport.java Sun Aug 29 17:28:58 2010 +0200
123.3 @@ -0,0 +1,133 @@
123.4 +/*
123.5 + * SONEWS News Server
123.6 + * see AUTHORS for the list of contributors
123.7 + *
123.8 + * This program is free software: you can redistribute it and/or modify
123.9 + * it under the terms of the GNU General Public License as published by
123.10 + * the Free Software Foundation, either version 3 of the License, or
123.11 + * (at your option) any later version.
123.12 + *
123.13 + * This program is distributed in the hope that it will be useful,
123.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
123.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
123.16 + * GNU General Public License for more details.
123.17 + *
123.18 + * You should have received a copy of the GNU General Public License
123.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
123.20 + */
123.21 +
123.22 +package org.sonews.mlgw;
123.23 +
123.24 +import java.io.BufferedOutputStream;
123.25 +import java.io.BufferedReader;
123.26 +import java.io.IOException;
123.27 +import java.io.InputStreamReader;
123.28 +import java.net.Socket;
123.29 +import java.net.UnknownHostException;
123.30 +import org.sonews.config.Config;
123.31 +import org.sonews.storage.Article;
123.32 +import org.sonews.util.io.ArticleInputStream;
123.33 +
123.34 +/**
123.35 + * Connects to a SMTP server and sends a given Article to it.
123.36 + * @author Christian Lins
123.37 + * @since sonews/1.0
123.38 + */
123.39 +class SMTPTransport
123.40 +{
123.41 +
123.42 + protected BufferedReader in;
123.43 + protected BufferedOutputStream out;
123.44 + protected Socket socket;
123.45 +
123.46 + public SMTPTransport(String host, int port)
123.47 + throws IOException, UnknownHostException
123.48 + {
123.49 + socket = new Socket(host, port);
123.50 + this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
123.51 + this.out = new BufferedOutputStream(socket.getOutputStream());
123.52 +
123.53 + // Read helo from server
123.54 + String line = this.in.readLine();
123.55 + if(line == null || !line.startsWith("220 "))
123.56 + {
123.57 + throw new IOException("Invalid helo from server: " + line);
123.58 + }
123.59 +
123.60 + // Send HELO to server
123.61 + this.out.write(
123.62 + ("HELO " + Config.inst().get(Config.HOSTNAME, "localhost") + "\r\n").getBytes("UTF-8"));
123.63 + this.out.flush();
123.64 + line = this.in.readLine();
123.65 + if(line == null || !line.startsWith("250 "))
123.66 + {
123.67 + throw new IOException("Unexpected reply: " + line);
123.68 + }
123.69 + }
123.70 +
123.71 + public SMTPTransport(String host)
123.72 + throws IOException
123.73 + {
123.74 + this(host, 25);
123.75 + }
123.76 +
123.77 + public void close()
123.78 + throws IOException
123.79 + {
123.80 + this.out.write("QUIT".getBytes("UTF-8"));
123.81 + this.out.flush();
123.82 + this.in.readLine();
123.83 +
123.84 + this.socket.close();
123.85 + }
123.86 +
123.87 + public void send(Article article, String mailFrom, String rcptTo)
123.88 + throws IOException
123.89 + {
123.90 + assert(article != null);
123.91 + assert(mailFrom != null);
123.92 + assert(rcptTo != null);
123.93 +
123.94 + this.out.write(("MAIL FROM: " + mailFrom).getBytes("UTF-8"));
123.95 + this.out.flush();
123.96 + String line = this.in.readLine();
123.97 + if(line == null || !line.startsWith("250 "))
123.98 + {
123.99 + throw new IOException("Unexpected reply: " + line);
123.100 + }
123.101 +
123.102 + this.out.write(("RCPT TO: " + rcptTo).getBytes("UTF-8"));
123.103 + this.out.flush();
123.104 + line = this.in.readLine();
123.105 + if(line == null || !line.startsWith("250 "))
123.106 + {
123.107 + throw new IOException("Unexpected reply: " + line);
123.108 + }
123.109 +
123.110 + this.out.write("DATA".getBytes("UTF-8"));
123.111 + this.out.flush();
123.112 + line = this.in.readLine();
123.113 + if(line == null || !line.startsWith("354 "))
123.114 + {
123.115 + throw new IOException("Unexpected reply: " + line);
123.116 + }
123.117 +
123.118 + ArticleInputStream artStream = new ArticleInputStream(article);
123.119 + for(int b = artStream.read(); b >= 0; b = artStream.read())
123.120 + {
123.121 + this.out.write(b);
123.122 + }
123.123 +
123.124 + // Flush the binary stream; important because otherwise the output
123.125 + // will be mixed with the PrintWriter.
123.126 + this.out.flush();
123.127 + this.out.write("\r\n.\r\n".getBytes("UTF-8"));
123.128 + this.out.flush();
123.129 + line = this.in.readLine();
123.130 + if(line == null || !line.startsWith("250 "))
123.131 + {
123.132 + throw new IOException("Unexpected reply: " + line);
123.133 + }
123.134 + }
123.135 +
123.136 +}
124.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
124.2 +++ b/src/org/sonews/mlgw/package.html Sun Aug 29 17:28:58 2010 +0200
124.3 @@ -0,0 +1,1 @@
124.4 +Contains classes of the Mailinglist Gateway.
124.5 \ No newline at end of file
125.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
125.2 +++ b/src/org/sonews/plugin/Plugin.java Sun Aug 29 17:28:58 2010 +0200
125.3 @@ -0,0 +1,42 @@
125.4 +/*
125.5 + * SONEWS News Server
125.6 + * see AUTHORS for the list of contributors
125.7 + *
125.8 + * This program is free software: you can redistribute it and/or modify
125.9 + * it under the terms of the GNU General Public License as published by
125.10 + * the Free Software Foundation, either version 3 of the License, or
125.11 + * (at your option) any later version.
125.12 + *
125.13 + * This program is distributed in the hope that it will be useful,
125.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
125.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
125.16 + * GNU General Public License for more details.
125.17 + *
125.18 + * You should have received a copy of the GNU General Public License
125.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
125.20 + */
125.21 +
125.22 +package org.sonews.plugin;
125.23 +
125.24 +/**
125.25 + * A generic Plugin for sonews. Implementing classes do not really add new
125.26 + * functionality to sonews but can use this interface as convenient procedure
125.27 + * for installing functionality plugins, e.g. Command-Plugins or Storage-Plugins.
125.28 + * @author Christian Lins
125.29 + * @since sonews/1.1
125.30 + */
125.31 +public interface Plugin
125.32 +{
125.33 +
125.34 + /**
125.35 + * Called when the Plugin is loaded by sonews. This method can be used
125.36 + * by implementing classes to install additional or required plugins.
125.37 + */
125.38 + void load();
125.39 +
125.40 + /**
125.41 + * Called when the Plugin is unloaded by sonews.
125.42 + */
125.43 + void unload();
125.44 +
125.45 +}
126.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
126.2 +++ b/src/org/sonews/storage/Article.java Sun Aug 29 17:28:58 2010 +0200
126.3 @@ -0,0 +1,253 @@
126.4 +/*
126.5 + * SONEWS News Server
126.6 + * see AUTHORS for the list of contributors
126.7 + *
126.8 + * This program is free software: you can redistribute it and/or modify
126.9 + * it under the terms of the GNU General Public License as published by
126.10 + * the Free Software Foundation, either version 3 of the License, or
126.11 + * (at your option) any later version.
126.12 + *
126.13 + * This program is distributed in the hope that it will be useful,
126.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
126.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
126.16 + * GNU General Public License for more details.
126.17 + *
126.18 + * You should have received a copy of the GNU General Public License
126.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
126.20 + */
126.21 +
126.22 +package org.sonews.storage;
126.23 +
126.24 +import java.io.ByteArrayInputStream;
126.25 +import java.io.ByteArrayOutputStream;
126.26 +import java.io.IOException;
126.27 +import java.security.MessageDigest;
126.28 +import java.security.NoSuchAlgorithmException;
126.29 +import java.util.UUID;
126.30 +import java.util.ArrayList;
126.31 +import java.util.Enumeration;
126.32 +import java.util.List;
126.33 +import javax.mail.Header;
126.34 +import javax.mail.Message;
126.35 +import javax.mail.MessagingException;
126.36 +import javax.mail.internet.InternetHeaders;
126.37 +import org.sonews.config.Config;
126.38 +
126.39 +/**
126.40 + * Represents a newsgroup article.
126.41 + * @author Christian Lins
126.42 + * @author Denis Schwerdel
126.43 + * @since n3tpd/0.1
126.44 + */
126.45 +public class Article extends ArticleHead
126.46 +{
126.47 +
126.48 + /**
126.49 + * Loads the Article identified by the given ID from the JDBCDatabase.
126.50 + * @param messageID
126.51 + * @return null if Article is not found or if an error occurred.
126.52 + */
126.53 + public static Article getByMessageID(final String messageID)
126.54 + {
126.55 + try
126.56 + {
126.57 + return StorageManager.current().getArticle(messageID);
126.58 + }
126.59 + catch(StorageBackendException ex)
126.60 + {
126.61 + ex.printStackTrace();
126.62 + return null;
126.63 + }
126.64 + }
126.65 +
126.66 + private byte[] body = new byte[0];
126.67 +
126.68 + /**
126.69 + * Default constructor.
126.70 + */
126.71 + public Article()
126.72 + {
126.73 + }
126.74 +
126.75 + /**
126.76 + * Creates a new Article object using the date from the given
126.77 + * raw data.
126.78 + */
126.79 + public Article(String headers, byte[] body)
126.80 + {
126.81 + try
126.82 + {
126.83 + this.body = body;
126.84 +
126.85 + // Parse the header
126.86 + this.headers = new InternetHeaders(
126.87 + new ByteArrayInputStream(headers.getBytes()));
126.88 +
126.89 + this.headerSrc = headers;
126.90 + }
126.91 + catch(MessagingException ex)
126.92 + {
126.93 + ex.printStackTrace();
126.94 + }
126.95 + }
126.96 +
126.97 + /**
126.98 + * Creates an Article instance using the data from the javax.mail.Message
126.99 + * object. This constructor is called by the Mailinglist gateway.
126.100 + * @see javax.mail.Message
126.101 + * @param msg
126.102 + * @throws IOException
126.103 + * @throws MessagingException
126.104 + */
126.105 + public Article(final Message msg)
126.106 + throws IOException, MessagingException
126.107 + {
126.108 + this.headers = new InternetHeaders();
126.109 +
126.110 + for(Enumeration e = msg.getAllHeaders() ; e.hasMoreElements();)
126.111 + {
126.112 + final Header header = (Header)e.nextElement();
126.113 + this.headers.addHeader(header.getName(), header.getValue());
126.114 + }
126.115 +
126.116 + // Reads the raw byte body using Message.writeTo(OutputStream out)
126.117 + this.body = readContent(msg);
126.118 +
126.119 + // Validate headers
126.120 + validateHeaders();
126.121 + }
126.122 +
126.123 + /**
126.124 + * Reads from the given Message into a byte array.
126.125 + * @param in
126.126 + * @return
126.127 + * @throws IOException
126.128 + */
126.129 + private byte[] readContent(Message in)
126.130 + throws IOException, MessagingException
126.131 + {
126.132 + ByteArrayOutputStream out = new ByteArrayOutputStream();
126.133 + in.writeTo(out);
126.134 + return out.toByteArray();
126.135 + }
126.136 +
126.137 + /**
126.138 + * Removes the header identified by the given key.
126.139 + * @param headerKey
126.140 + */
126.141 + public void removeHeader(final String headerKey)
126.142 + {
126.143 + this.headers.removeHeader(headerKey);
126.144 + this.headerSrc = null;
126.145 + }
126.146 +
126.147 + /**
126.148 + * Generates a message id for this article and sets it into
126.149 + * the header object. You have to update the JDBCDatabase manually to make this
126.150 + * change persistent.
126.151 + * Note: a Message-ID should never be changed and only generated once.
126.152 + */
126.153 + private String generateMessageID()
126.154 + {
126.155 + String randomString;
126.156 + MessageDigest md5;
126.157 + try
126.158 + {
126.159 + md5 = MessageDigest.getInstance("MD5");
126.160 + md5.reset();
126.161 + md5.update(getBody());
126.162 + md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
126.163 + md5.update(getHeader(Headers.FROM)[0].getBytes());
126.164 + byte[] result = md5.digest();
126.165 + StringBuffer hexString = new StringBuffer();
126.166 + for (int i = 0; i < result.length; i++)
126.167 + {
126.168 + hexString.append(Integer.toHexString(0xFF & result[i]));
126.169 + }
126.170 + randomString = hexString.toString();
126.171 + }
126.172 + catch (NoSuchAlgorithmException e)
126.173 + {
126.174 + e.printStackTrace();
126.175 + randomString = UUID.randomUUID().toString();
126.176 + }
126.177 + String msgID = "<" + randomString + "@"
126.178 + + Config.inst().get(Config.HOSTNAME, "localhost") + ">";
126.179 +
126.180 + this.headers.setHeader(Headers.MESSAGE_ID, msgID);
126.181 +
126.182 + return msgID;
126.183 + }
126.184 +
126.185 + /**
126.186 + * Returns the body string.
126.187 + */
126.188 + public byte[] getBody()
126.189 + {
126.190 + return body;
126.191 + }
126.192 +
126.193 + /**
126.194 + * @return Numerical IDs of the newsgroups this Article belongs to.
126.195 + */
126.196 + public List<Group> getGroups()
126.197 + {
126.198 + String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
126.199 + ArrayList<Group> groups = new ArrayList<Group>();
126.200 +
126.201 + try
126.202 + {
126.203 + for(String newsgroup : groupnames)
126.204 + {
126.205 + newsgroup = newsgroup.trim();
126.206 + Group group = StorageManager.current().getGroup(newsgroup);
126.207 + if(group != null && // If the server does not provide the group, ignore it
126.208 + !groups.contains(group)) // Yes, there may be duplicates
126.209 + {
126.210 + groups.add(group);
126.211 + }
126.212 + }
126.213 + }
126.214 + catch(StorageBackendException ex)
126.215 + {
126.216 + ex.printStackTrace();
126.217 + return null;
126.218 + }
126.219 + return groups;
126.220 + }
126.221 +
126.222 + public void setBody(byte[] body)
126.223 + {
126.224 + this.body = body;
126.225 + }
126.226 +
126.227 + /**
126.228 + *
126.229 + * @param groupname Name(s) of newsgroups
126.230 + */
126.231 + public void setGroup(String groupname)
126.232 + {
126.233 + this.headers.setHeader(Headers.NEWSGROUPS, groupname);
126.234 + }
126.235 +
126.236 + /**
126.237 + * Returns the Message-ID of this Article. If the appropriate header
126.238 + * is empty, a new Message-ID is created.
126.239 + * @return Message-ID of this Article.
126.240 + */
126.241 + public String getMessageID()
126.242 + {
126.243 + String[] msgID = getHeader(Headers.MESSAGE_ID);
126.244 + return msgID[0].equals("") ? generateMessageID() : msgID[0];
126.245 + }
126.246 +
126.247 + /**
126.248 + * @return String containing the Message-ID.
126.249 + */
126.250 + @Override
126.251 + public String toString()
126.252 + {
126.253 + return getMessageID();
126.254 + }
126.255 +
126.256 +}
127.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
127.2 +++ b/src/org/sonews/storage/ArticleHead.java Sun Aug 29 17:28:58 2010 +0200
127.3 @@ -0,0 +1,161 @@
127.4 +/*
127.5 + * SONEWS News Server
127.6 + * see AUTHORS for the list of contributors
127.7 + *
127.8 + * This program is free software: you can redistribute it and/or modify
127.9 + * it under the terms of the GNU General Public License as published by
127.10 + * the Free Software Foundation, either version 3 of the License, or
127.11 + * (at your option) any later version.
127.12 + *
127.13 + * This program is distributed in the hope that it will be useful,
127.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
127.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
127.16 + * GNU General Public License for more details.
127.17 + *
127.18 + * You should have received a copy of the GNU General Public License
127.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
127.20 + */
127.21 +
127.22 +package org.sonews.storage;
127.23 +
127.24 +import java.io.ByteArrayInputStream;
127.25 +import java.util.Enumeration;
127.26 +import javax.mail.Header;
127.27 +import javax.mail.MessagingException;
127.28 +import javax.mail.internet.InternetHeaders;
127.29 +import javax.mail.internet.MimeUtility;
127.30 +import org.sonews.config.Config;
127.31 +
127.32 +/**
127.33 + * An article with no body only headers.
127.34 + * @author Christian Lins
127.35 + * @since sonews/0.5.0
127.36 + */
127.37 +public class ArticleHead
127.38 +{
127.39 +
127.40 + protected InternetHeaders headers = null;
127.41 + protected String headerSrc = null;
127.42 +
127.43 + protected ArticleHead()
127.44 + {
127.45 + }
127.46 +
127.47 + public ArticleHead(String headers)
127.48 + {
127.49 + try
127.50 + {
127.51 + // Parse the header
127.52 + this.headers = new InternetHeaders(
127.53 + new ByteArrayInputStream(headers.getBytes()));
127.54 + }
127.55 + catch(MessagingException ex)
127.56 + {
127.57 + ex.printStackTrace();
127.58 + }
127.59 + }
127.60 +
127.61 + /**
127.62 + * Returns the header field with given name.
127.63 + * @param name Name of the header field(s).
127.64 + * @param returnNull If set to true, this method will return null instead
127.65 + * of an empty array if there is no header field found.
127.66 + * @return Header values or empty string.
127.67 + */
127.68 + public String[] getHeader(String name, boolean returnNull)
127.69 + {
127.70 + String[] ret = this.headers.getHeader(name);
127.71 + if(ret == null && !returnNull)
127.72 + {
127.73 + ret = new String[]{""};
127.74 + }
127.75 + return ret;
127.76 + }
127.77 +
127.78 + public String[] getHeader(String name)
127.79 + {
127.80 + return getHeader(name, false);
127.81 + }
127.82 +
127.83 + /**
127.84 + * Sets the header value identified through the header name.
127.85 + * @param name
127.86 + * @param value
127.87 + */
127.88 + public void setHeader(String name, String value)
127.89 + {
127.90 + this.headers.setHeader(name, value);
127.91 + this.headerSrc = null;
127.92 + }
127.93 +
127.94 + public Enumeration getAllHeaders()
127.95 + {
127.96 + return this.headers.getAllHeaders();
127.97 + }
127.98 +
127.99 + /**
127.100 + * @return Header source code of this Article.
127.101 + */
127.102 + public String getHeaderSource()
127.103 + {
127.104 + if(this.headerSrc != null)
127.105 + {
127.106 + return this.headerSrc;
127.107 + }
127.108 +
127.109 + StringBuffer buf = new StringBuffer();
127.110 +
127.111 + for(Enumeration en = this.headers.getAllHeaders(); en.hasMoreElements();)
127.112 + {
127.113 + Header entry = (Header)en.nextElement();
127.114 +
127.115 + String value = entry.getValue().replaceAll("[\r\n]", " ");
127.116 + buf.append(entry.getName());
127.117 + buf.append(": ");
127.118 + buf.append(MimeUtility.fold(entry.getName().length() + 2, value));
127.119 +
127.120 + if(en.hasMoreElements())
127.121 + {
127.122 + buf.append("\r\n");
127.123 + }
127.124 + }
127.125 +
127.126 + this.headerSrc = buf.toString();
127.127 + return this.headerSrc;
127.128 + }
127.129 +
127.130 + /**
127.131 + * Sets the headers of this Article. If headers contain no
127.132 + * Message-Id a new one is created.
127.133 + * @param headers
127.134 + */
127.135 + public void setHeaders(InternetHeaders headers)
127.136 + {
127.137 + this.headers = headers;
127.138 + this.headerSrc = null;
127.139 + validateHeaders();
127.140 + }
127.141 +
127.142 + /**
127.143 + * Checks some headers for their validity and generates an
127.144 + * appropriate Path-header for this host if not yet existing.
127.145 + * This method is called by some Article constructors and the
127.146 + * method setHeaders().
127.147 + * @return true if something on the headers was changed.
127.148 + */
127.149 + protected void validateHeaders()
127.150 + {
127.151 + // Check for valid Path-header
127.152 + final String path = getHeader(Headers.PATH)[0];
127.153 + final String host = Config.inst().get(Config.HOSTNAME, "localhost");
127.154 + if(!path.startsWith(host))
127.155 + {
127.156 + StringBuffer pathBuf = new StringBuffer();
127.157 + pathBuf.append(host);
127.158 + pathBuf.append('!');
127.159 + pathBuf.append(path);
127.160 + this.headers.setHeader(Headers.PATH, pathBuf.toString());
127.161 + }
127.162 + }
127.163 +
127.164 +}
128.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
128.2 +++ b/src/org/sonews/storage/Channel.java Sun Aug 29 17:28:58 2010 +0200
128.3 @@ -0,0 +1,111 @@
128.4 +/*
128.5 + * SONEWS News Server
128.6 + * see AUTHORS for the list of contributors
128.7 + *
128.8 + * This program is free software: you can redistribute it and/or modify
128.9 + * it under the terms of the GNU General Public License as published by
128.10 + * the Free Software Foundation, either version 3 of the License, or
128.11 + * (at your option) any later version.
128.12 + *
128.13 + * This program is distributed in the hope that it will be useful,
128.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
128.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
128.16 + * GNU General Public License for more details.
128.17 + *
128.18 + * You should have received a copy of the GNU General Public License
128.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
128.20 + */
128.21 +
128.22 +package org.sonews.storage;
128.23 +
128.24 +import java.util.ArrayList;
128.25 +import java.util.List;
128.26 +import org.sonews.util.Pair;
128.27 +
128.28 +/**
128.29 + * A logical communication Channel is the a generic structural element for sets
128.30 + * of messages; e.g. a Newsgroup for a set of Articles.
128.31 + * A Channel can either be a real set of messages or an aggregated set of
128.32 + * several subsets.
128.33 + * @author Christian Lins
128.34 + * @since sonews/1.0
128.35 + */
128.36 +public abstract class Channel
128.37 +{
128.38 +
128.39 + /**
128.40 + * If this flag is set the Group is no real newsgroup but a mailing list
128.41 + * mirror. In that case every posting and receiving mails must go through
128.42 + * the mailing list gateway.
128.43 + */
128.44 + public static final int MAILINGLIST = 0x1;
128.45 +
128.46 + /**
128.47 + * If this flag is set the Group is marked as readonly and the posting
128.48 + * is prohibited. This can be useful for groups that are synced only in
128.49 + * one direction.
128.50 + */
128.51 + public static final int READONLY = 0x2;
128.52 +
128.53 + /**
128.54 + * If this flag is set the Group is marked as deleted and must not occur
128.55 + * in any output. The deletion is done lazily by a low priority daemon.
128.56 + */
128.57 + public static final int DELETED = 0x80;
128.58 +
128.59 + public static List<Channel> getAll()
128.60 + {
128.61 + List<Channel> all = new ArrayList<Channel>();
128.62 +
128.63 + /*List<Channel> agroups = AggregatedGroup.getAll();
128.64 + if(agroups != null)
128.65 + {
128.66 + all.addAll(agroups);
128.67 + }*/
128.68 +
128.69 + List<Channel> groups = Group.getAll();
128.70 + if(groups != null)
128.71 + {
128.72 + all.addAll(groups);
128.73 + }
128.74 +
128.75 + return all;
128.76 + }
128.77 +
128.78 + public static Channel getByName(String name)
128.79 + throws StorageBackendException
128.80 + {
128.81 + return StorageManager.current().getGroup(name);
128.82 + }
128.83 +
128.84 + public abstract Article getArticle(long idx)
128.85 + throws StorageBackendException;
128.86 +
128.87 + public abstract List<Pair<Long, ArticleHead>> getArticleHeads(
128.88 + final long first, final long last)
128.89 + throws StorageBackendException;
128.90 +
128.91 + public abstract List<Long> getArticleNumbers()
128.92 + throws StorageBackendException;
128.93 +
128.94 + public abstract long getFirstArticleNumber()
128.95 + throws StorageBackendException;
128.96 +
128.97 + public abstract long getIndexOf(Article art)
128.98 + throws StorageBackendException;
128.99 +
128.100 + public abstract long getInternalID();
128.101 +
128.102 + public abstract long getLastArticleNumber()
128.103 + throws StorageBackendException;
128.104 +
128.105 + public abstract String getName();
128.106 +
128.107 + public abstract long getPostingsCount()
128.108 + throws StorageBackendException;
128.109 +
128.110 + public abstract boolean isDeleted();
128.111 +
128.112 + public abstract boolean isWriteable();
128.113 +
128.114 +}
129.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
129.2 +++ b/src/org/sonews/storage/Group.java Sun Aug 29 17:28:58 2010 +0200
129.3 @@ -0,0 +1,184 @@
129.4 +/*
129.5 + * SONEWS News Server
129.6 + * see AUTHORS for the list of contributors
129.7 + *
129.8 + * This program is free software: you can redistribute it and/or modify
129.9 + * it under the terms of the GNU General Public License as published by
129.10 + * the Free Software Foundation, either version 3 of the License, or
129.11 + * (at your option) any later version.
129.12 + *
129.13 + * This program is distributed in the hope that it will be useful,
129.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
129.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
129.16 + * GNU General Public License for more details.
129.17 + *
129.18 + * You should have received a copy of the GNU General Public License
129.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
129.20 + */
129.21 +
129.22 +package org.sonews.storage;
129.23 +
129.24 +import java.sql.SQLException;
129.25 +import java.util.List;
129.26 +import org.sonews.util.Log;
129.27 +import org.sonews.util.Pair;
129.28 +
129.29 +/**
129.30 + * Represents a logical Group within this newsserver.
129.31 + * @author Christian Lins
129.32 + * @since sonews/0.5.0
129.33 + */
129.34 +// TODO: This class should not be public!
129.35 +public class Group extends Channel
129.36 +{
129.37 +
129.38 + private long id = 0;
129.39 + private int flags = -1;
129.40 + private String name = null;
129.41 +
129.42 + /**
129.43 + * @return List of all groups this server handles.
129.44 + */
129.45 + public static List<Channel> getAll()
129.46 + {
129.47 + try
129.48 + {
129.49 + return StorageManager.current().getGroups();
129.50 + }
129.51 + catch(StorageBackendException ex)
129.52 + {
129.53 + Log.get().severe(ex.getMessage());
129.54 + return null;
129.55 + }
129.56 + }
129.57 +
129.58 + /**
129.59 + * @param name
129.60 + * @param id
129.61 + */
129.62 + public Group(final String name, final long id, final int flags)
129.63 + {
129.64 + this.id = id;
129.65 + this.flags = flags;
129.66 + this.name = name;
129.67 + }
129.68 +
129.69 + @Override
129.70 + public boolean equals(Object obj)
129.71 + {
129.72 + if(obj instanceof Group)
129.73 + {
129.74 + return ((Group)obj).id == this.id;
129.75 + }
129.76 + else
129.77 + {
129.78 + return false;
129.79 + }
129.80 + }
129.81 +
129.82 + public Article getArticle(long idx)
129.83 + throws StorageBackendException
129.84 + {
129.85 + return StorageManager.current().getArticle(idx, this.id);
129.86 + }
129.87 +
129.88 + public List<Pair<Long, ArticleHead>> getArticleHeads(final long first, final long last)
129.89 + throws StorageBackendException
129.90 + {
129.91 + return StorageManager.current().getArticleHeads(this, first, last);
129.92 + }
129.93 +
129.94 + public List<Long> getArticleNumbers()
129.95 + throws StorageBackendException
129.96 + {
129.97 + return StorageManager.current().getArticleNumbers(id);
129.98 + }
129.99 +
129.100 + public long getFirstArticleNumber()
129.101 + throws StorageBackendException
129.102 + {
129.103 + return StorageManager.current().getFirstArticleNumber(this);
129.104 + }
129.105 +
129.106 + public int getFlags()
129.107 + {
129.108 + return this.flags;
129.109 + }
129.110 +
129.111 + public long getIndexOf(Article art)
129.112 + throws StorageBackendException
129.113 + {
129.114 + return StorageManager.current().getArticleIndex(art, this);
129.115 + }
129.116 +
129.117 + /**
129.118 + * Returns the group id.
129.119 + */
129.120 + public long getInternalID()
129.121 + {
129.122 + assert id > 0;
129.123 +
129.124 + return id;
129.125 + }
129.126 +
129.127 + public boolean isDeleted()
129.128 + {
129.129 + return (this.flags & DELETED) != 0;
129.130 + }
129.131 +
129.132 + public boolean isMailingList()
129.133 + {
129.134 + return (this.flags & MAILINGLIST) != 0;
129.135 + }
129.136 +
129.137 + public boolean isWriteable()
129.138 + {
129.139 + return true;
129.140 + }
129.141 +
129.142 + public long getLastArticleNumber()
129.143 + throws StorageBackendException
129.144 + {
129.145 + return StorageManager.current().getLastArticleNumber(this);
129.146 + }
129.147 +
129.148 + public String getName()
129.149 + {
129.150 + return name;
129.151 + }
129.152 +
129.153 + /**
129.154 + * Performs this.flags |= flag to set a specified flag and updates the data
129.155 + * in the JDBCDatabase.
129.156 + * @param flag
129.157 + */
129.158 + public void setFlag(final int flag)
129.159 + {
129.160 + this.flags |= flag;
129.161 + }
129.162 +
129.163 + public void setName(final String name)
129.164 + {
129.165 + this.name = name;
129.166 + }
129.167 +
129.168 + /**
129.169 + * @return Number of posted articles in this group.
129.170 + * @throws java.sql.SQLException
129.171 + */
129.172 + public long getPostingsCount()
129.173 + throws StorageBackendException
129.174 + {
129.175 + return StorageManager.current().getPostingsCount(this.name);
129.176 + }
129.177 +
129.178 + /**
129.179 + * Updates flags and name in the backend.
129.180 + */
129.181 + public void update()
129.182 + throws StorageBackendException
129.183 + {
129.184 + StorageManager.current().update(this);
129.185 + }
129.186 +
129.187 +}
130.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
130.2 +++ b/src/org/sonews/storage/Headers.java Sun Aug 29 17:28:58 2010 +0200
130.3 @@ -0,0 +1,56 @@
130.4 +/*
130.5 + * SONEWS News Server
130.6 + * see AUTHORS for the list of contributors
130.7 + *
130.8 + * This program is free software: you can redistribute it and/or modify
130.9 + * it under the terms of the GNU General Public License as published by
130.10 + * the Free Software Foundation, either version 3 of the License, or
130.11 + * (at your option) any later version.
130.12 + *
130.13 + * This program is distributed in the hope that it will be useful,
130.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
130.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
130.16 + * GNU General Public License for more details.
130.17 + *
130.18 + * You should have received a copy of the GNU General Public License
130.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
130.20 + */
130.21 +
130.22 +package org.sonews.storage;
130.23 +
130.24 +/**
130.25 + * Contains header constants. These header keys are no way complete but all
130.26 + * headers that are relevant for sonews.
130.27 + * @author Christian Lins
130.28 + * @since sonews/0.5.0
130.29 + */
130.30 +public final class Headers
130.31 +{
130.32 +
130.33 + public static final String BYTES = "bytes";
130.34 + public static final String CONTENT_TYPE = "content-type";
130.35 + public static final String CONTROL = "control";
130.36 + public static final String DATE = "date";
130.37 + public static final String FROM = "from";
130.38 + public static final String LINES = "lines";
130.39 + public static final String LIST_POST = "list-post";
130.40 + public static final String MESSAGE_ID = "message-id";
130.41 + public static final String NEWSGROUPS = "newsgroups";
130.42 + public static final String NNTP_POSTING_DATE = "nntp-posting-date";
130.43 + public static final String NNTP_POSTING_HOST = "nntp-posting-host";
130.44 + public static final String PATH = "path";
130.45 + public static final String REFERENCES = "references";
130.46 + public static final String REPLY_TO = "reply-to";
130.47 + public static final String SENDER = "sender";
130.48 + public static final String SUBJECT = "subject";
130.49 + public static final String SUPERSEDES = "subersedes";
130.50 + public static final String TO = "to";
130.51 + public static final String X_COMPLAINTS_TO = "x-complaints-to";
130.52 + public static final String X_LIST_POST = "x-list-post";
130.53 + public static final String X_TRACE = "x-trace";
130.54 + public static final String XREF = "xref";
130.55 +
130.56 + private Headers()
130.57 + {}
130.58 +
130.59 +}
131.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
131.2 +++ b/src/org/sonews/storage/Storage.java Sun Aug 29 17:28:58 2010 +0200
131.3 @@ -0,0 +1,150 @@
131.4 +/*
131.5 + * SONEWS News Server
131.6 + * see AUTHORS for the list of contributors
131.7 + *
131.8 + * This program is free software: you can redistribute it and/or modify
131.9 + * it under the terms of the GNU General Public License as published by
131.10 + * the Free Software Foundation, either version 3 of the License, or
131.11 + * (at your option) any later version.
131.12 + *
131.13 + * This program is distributed in the hope that it will be useful,
131.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
131.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
131.16 + * GNU General Public License for more details.
131.17 + *
131.18 + * You should have received a copy of the GNU General Public License
131.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
131.20 + */
131.21 +
131.22 +package org.sonews.storage;
131.23 +
131.24 +import java.util.List;
131.25 +import org.sonews.feed.Subscription;
131.26 +import org.sonews.util.Pair;
131.27 +
131.28 +/**
131.29 + * A generic storage backend interface.
131.30 + * @author Christian Lins
131.31 + * @since sonews/1.0
131.32 + */
131.33 +public interface Storage
131.34 +{
131.35 +
131.36 + /**
131.37 + * Stores the given Article in the storage.
131.38 + * @param art
131.39 + * @throws StorageBackendException
131.40 + */
131.41 + void addArticle(Article art)
131.42 + throws StorageBackendException;
131.43 +
131.44 + void addEvent(long timestamp, int type, long groupID)
131.45 + throws StorageBackendException;
131.46 +
131.47 + void addGroup(String groupname, int flags)
131.48 + throws StorageBackendException;
131.49 +
131.50 + int countArticles()
131.51 + throws StorageBackendException;
131.52 +
131.53 + int countGroups()
131.54 + throws StorageBackendException;
131.55 +
131.56 + void delete(String messageID)
131.57 + throws StorageBackendException;
131.58 +
131.59 + Article getArticle(String messageID)
131.60 + throws StorageBackendException;
131.61 +
131.62 + Article getArticle(long articleIndex, long groupID)
131.63 + throws StorageBackendException;
131.64 +
131.65 + List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first, long last)
131.66 + throws StorageBackendException;
131.67 +
131.68 + List<Pair<Long, String>> getArticleHeaders(Channel channel, long start, long end,
131.69 + String header, String pattern)
131.70 + throws StorageBackendException;
131.71 +
131.72 + long getArticleIndex(Article art, Group group)
131.73 + throws StorageBackendException;
131.74 +
131.75 + List<Long> getArticleNumbers(long groupID)
131.76 + throws StorageBackendException;
131.77 +
131.78 + String getConfigValue(String key)
131.79 + throws StorageBackendException;
131.80 +
131.81 + int getEventsCount(int eventType, long startTimestamp, long endTimestamp,
131.82 + Channel channel)
131.83 + throws StorageBackendException;
131.84 +
131.85 + double getEventsPerHour(int key, long gid)
131.86 + throws StorageBackendException;
131.87 +
131.88 + int getFirstArticleNumber(Group group)
131.89 + throws StorageBackendException;
131.90 +
131.91 + Group getGroup(String name)
131.92 + throws StorageBackendException;
131.93 +
131.94 + List<Channel> getGroups()
131.95 + throws StorageBackendException;
131.96 +
131.97 + /**
131.98 + * Retrieves the collection of groupnames that are associated with the
131.99 + * given list address.
131.100 + * @param inetaddress
131.101 + * @return
131.102 + * @throws StorageBackendException
131.103 + */
131.104 + List<String> getGroupsForList(String listAddress)
131.105 + throws StorageBackendException;
131.106 +
131.107 + int getLastArticleNumber(Group group)
131.108 + throws StorageBackendException;
131.109 +
131.110 + /**
131.111 + * Returns a list of email addresses that are related to the given
131.112 + * groupname. In most cases the list may contain only one entry.
131.113 + * @param groupname
131.114 + * @return
131.115 + * @throws StorageBackendException
131.116 + */
131.117 + List<String> getListsForGroup(String groupname)
131.118 + throws StorageBackendException;
131.119 +
131.120 + String getOldestArticle()
131.121 + throws StorageBackendException;
131.122 +
131.123 + int getPostingsCount(String groupname)
131.124 + throws StorageBackendException;
131.125 +
131.126 + List<Subscription> getSubscriptions(int type)
131.127 + throws StorageBackendException;
131.128 +
131.129 + boolean isArticleExisting(String messageID)
131.130 + throws StorageBackendException;
131.131 +
131.132 + boolean isGroupExisting(String groupname)
131.133 + throws StorageBackendException;
131.134 +
131.135 + void purgeGroup(Group group)
131.136 + throws StorageBackendException;
131.137 +
131.138 + void setConfigValue(String key, String value)
131.139 + throws StorageBackendException;
131.140 +
131.141 + /**
131.142 + * Updates headers and channel references of the given article.
131.143 + * @param article
131.144 + * @return
131.145 + * @throws StorageBackendException
131.146 + */
131.147 + boolean update(Article article)
131.148 + throws StorageBackendException;
131.149 +
131.150 + boolean update(Group group)
131.151 + throws StorageBackendException;
131.152 +
131.153 +}
132.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
132.2 +++ b/src/org/sonews/storage/StorageBackendException.java Sun Aug 29 17:28:58 2010 +0200
132.3 @@ -0,0 +1,39 @@
132.4 +/*
132.5 + * SONEWS News Server
132.6 + * see AUTHORS for the list of contributors
132.7 + *
132.8 + * This program is free software: you can redistribute it and/or modify
132.9 + * it under the terms of the GNU General Public License as published by
132.10 + * the Free Software Foundation, either version 3 of the License, or
132.11 + * (at your option) any later version.
132.12 + *
132.13 + * This program is distributed in the hope that it will be useful,
132.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
132.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
132.16 + * GNU General Public License for more details.
132.17 + *
132.18 + * You should have received a copy of the GNU General Public License
132.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
132.20 + */
132.21 +
132.22 +package org.sonews.storage;
132.23 +
132.24 +/**
132.25 + *
132.26 + * @author Christian Lins
132.27 + * @since sonews/1.0
132.28 + */
132.29 +public class StorageBackendException extends Exception
132.30 +{
132.31 +
132.32 + public StorageBackendException(Throwable cause)
132.33 + {
132.34 + super(cause);
132.35 + }
132.36 +
132.37 + public StorageBackendException(String msg)
132.38 + {
132.39 + super(msg);
132.40 + }
132.41 +
132.42 +}
133.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
133.2 +++ b/src/org/sonews/storage/StorageManager.java Sun Aug 29 17:28:58 2010 +0200
133.3 @@ -0,0 +1,89 @@
133.4 +/*
133.5 + * SONEWS News Server
133.6 + * see AUTHORS for the list of contributors
133.7 + *
133.8 + * This program is free software: you can redistribute it and/or modify
133.9 + * it under the terms of the GNU General Public License as published by
133.10 + * the Free Software Foundation, either version 3 of the License, or
133.11 + * (at your option) any later version.
133.12 + *
133.13 + * This program is distributed in the hope that it will be useful,
133.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
133.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
133.16 + * GNU General Public License for more details.
133.17 + *
133.18 + * You should have received a copy of the GNU General Public License
133.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
133.20 + */
133.21 +
133.22 +package org.sonews.storage;
133.23 +
133.24 +/**
133.25 + *
133.26 + * @author Christian Lins
133.27 + * @since sonews/1.0
133.28 + */
133.29 +public final class StorageManager
133.30 +{
133.31 +
133.32 + private static StorageProvider provider;
133.33 +
133.34 + public static Storage current()
133.35 + throws StorageBackendException
133.36 + {
133.37 + synchronized(StorageManager.class)
133.38 + {
133.39 + if(provider == null)
133.40 + {
133.41 + return null;
133.42 + }
133.43 + else
133.44 + {
133.45 + return provider.storage(Thread.currentThread());
133.46 + }
133.47 + }
133.48 + }
133.49 +
133.50 + public static StorageProvider loadProvider(String pluginClassName)
133.51 + {
133.52 + try
133.53 + {
133.54 + Class<?> clazz = Class.forName(pluginClassName);
133.55 + Object inst = clazz.newInstance();
133.56 + return (StorageProvider)inst;
133.57 + }
133.58 + catch(Exception ex)
133.59 + {
133.60 + System.err.println(ex);
133.61 + return null;
133.62 + }
133.63 + }
133.64 +
133.65 + /**
133.66 + * Sets the current storage provider.
133.67 + * @param provider
133.68 + */
133.69 + public static void enableProvider(StorageProvider provider)
133.70 + {
133.71 + synchronized(StorageManager.class)
133.72 + {
133.73 + if(StorageManager.provider != null)
133.74 + {
133.75 + disableProvider();
133.76 + }
133.77 + StorageManager.provider = provider;
133.78 + }
133.79 + }
133.80 +
133.81 + /**
133.82 + * Disables the current provider.
133.83 + */
133.84 + public static void disableProvider()
133.85 + {
133.86 + synchronized(StorageManager.class)
133.87 + {
133.88 + provider = null;
133.89 + }
133.90 + }
133.91 +
133.92 +}
134.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
134.2 +++ b/src/org/sonews/storage/StorageProvider.java Sun Aug 29 17:28:58 2010 +0200
134.3 @@ -0,0 +1,40 @@
134.4 +/*
134.5 + * SONEWS News Server
134.6 + * see AUTHORS for the list of contributors
134.7 + *
134.8 + * This program is free software: you can redistribute it and/or modify
134.9 + * it under the terms of the GNU General Public License as published by
134.10 + * the Free Software Foundation, either version 3 of the License, or
134.11 + * (at your option) any later version.
134.12 + *
134.13 + * This program is distributed in the hope that it will be useful,
134.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
134.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
134.16 + * GNU General Public License for more details.
134.17 + *
134.18 + * You should have received a copy of the GNU General Public License
134.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
134.20 + */
134.21 +
134.22 +package org.sonews.storage;
134.23 +
134.24 +/**
134.25 + * Provides access to storage backend instances.
134.26 + * @author Christian Lins
134.27 + * @since sonews/1.0
134.28 + */
134.29 +public interface StorageProvider
134.30 +{
134.31 +
134.32 + public boolean isSupported(String uri);
134.33 +
134.34 + /**
134.35 + * This method returns the reference to the associated storage.
134.36 + * The reference MAY be unique for each thread. In any case it MUST be
134.37 + * thread-safe to use this method.
134.38 + * @return The reference to the associated Storage.
134.39 + */
134.40 + public Storage storage(Thread thread)
134.41 + throws StorageBackendException;
134.42 +
134.43 +}
135.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
135.2 +++ b/src/org/sonews/storage/impl/JDBCDatabase.java Sun Aug 29 17:28:58 2010 +0200
135.3 @@ -0,0 +1,1782 @@
135.4 +/*
135.5 + * SONEWS News Server
135.6 + * see AUTHORS for the list of contributors
135.7 + *
135.8 + * This program is free software: you can redistribute it and/or modify
135.9 + * it under the terms of the GNU General Public License as published by
135.10 + * the Free Software Foundation, either version 3 of the License, or
135.11 + * (at your option) any later version.
135.12 + *
135.13 + * This program is distributed in the hope that it will be useful,
135.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
135.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
135.16 + * GNU General Public License for more details.
135.17 + *
135.18 + * You should have received a copy of the GNU General Public License
135.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
135.20 + */
135.21 +
135.22 +package org.sonews.storage.impl;
135.23 +
135.24 +import java.sql.Connection;
135.25 +import java.sql.DriverManager;
135.26 +import java.sql.ResultSet;
135.27 +import java.sql.SQLException;
135.28 +import java.sql.Statement;
135.29 +import java.sql.PreparedStatement;
135.30 +import java.util.ArrayList;
135.31 +import java.util.Enumeration;
135.32 +import java.util.List;
135.33 +import java.util.regex.Matcher;
135.34 +import java.util.regex.Pattern;
135.35 +import java.util.regex.PatternSyntaxException;
135.36 +import javax.mail.Header;
135.37 +import javax.mail.internet.MimeUtility;
135.38 +import org.sonews.config.Config;
135.39 +import org.sonews.util.Log;
135.40 +import org.sonews.feed.Subscription;
135.41 +import org.sonews.storage.Article;
135.42 +import org.sonews.storage.ArticleHead;
135.43 +import org.sonews.storage.Channel;
135.44 +import org.sonews.storage.Group;
135.45 +import org.sonews.storage.Storage;
135.46 +import org.sonews.storage.StorageBackendException;
135.47 +import org.sonews.util.Pair;
135.48 +
135.49 +/**
135.50 + * JDBCDatabase facade class.
135.51 + * @author Christian Lins
135.52 + * @since sonews/0.5.0
135.53 + */
135.54 +// TODO: Refactor this class to reduce size (e.g. ArticleDatabase GroupDatabase)
135.55 +public class JDBCDatabase implements Storage
135.56 +{
135.57 +
135.58 + public static final int MAX_RESTARTS = 2;
135.59 +
135.60 + private Connection conn = null;
135.61 + private PreparedStatement pstmtAddArticle1 = null;
135.62 + private PreparedStatement pstmtAddArticle2 = null;
135.63 + private PreparedStatement pstmtAddArticle3 = null;
135.64 + private PreparedStatement pstmtAddArticle4 = null;
135.65 + private PreparedStatement pstmtAddGroup0 = null;
135.66 + private PreparedStatement pstmtAddEvent = null;
135.67 + private PreparedStatement pstmtCountArticles = null;
135.68 + private PreparedStatement pstmtCountGroups = null;
135.69 + private PreparedStatement pstmtDeleteArticle0 = null;
135.70 + private PreparedStatement pstmtDeleteArticle1 = null;
135.71 + private PreparedStatement pstmtDeleteArticle2 = null;
135.72 + private PreparedStatement pstmtDeleteArticle3 = null;
135.73 + private PreparedStatement pstmtGetArticle0 = null;
135.74 + private PreparedStatement pstmtGetArticle1 = null;
135.75 + private PreparedStatement pstmtGetArticleHeaders0 = null;
135.76 + private PreparedStatement pstmtGetArticleHeaders1 = null;
135.77 + private PreparedStatement pstmtGetArticleHeads = null;
135.78 + private PreparedStatement pstmtGetArticleIDs = null;
135.79 + private PreparedStatement pstmtGetArticleIndex = null;
135.80 + private PreparedStatement pstmtGetConfigValue = null;
135.81 + private PreparedStatement pstmtGetEventsCount0 = null;
135.82 + private PreparedStatement pstmtGetEventsCount1 = null;
135.83 + private PreparedStatement pstmtGetGroupForList = null;
135.84 + private PreparedStatement pstmtGetGroup0 = null;
135.85 + private PreparedStatement pstmtGetGroup1 = null;
135.86 + private PreparedStatement pstmtGetFirstArticleNumber = null;
135.87 + private PreparedStatement pstmtGetListForGroup = null;
135.88 + private PreparedStatement pstmtGetLastArticleNumber = null;
135.89 + private PreparedStatement pstmtGetMaxArticleID = null;
135.90 + private PreparedStatement pstmtGetMaxArticleIndex = null;
135.91 + private PreparedStatement pstmtGetOldestArticle = null;
135.92 + private PreparedStatement pstmtGetPostingsCount = null;
135.93 + private PreparedStatement pstmtGetSubscriptions = null;
135.94 + private PreparedStatement pstmtIsArticleExisting = null;
135.95 + private PreparedStatement pstmtIsGroupExisting = null;
135.96 + private PreparedStatement pstmtPurgeGroup0 = null;
135.97 + private PreparedStatement pstmtPurgeGroup1 = null;
135.98 + private PreparedStatement pstmtSetConfigValue0 = null;
135.99 + private PreparedStatement pstmtSetConfigValue1 = null;
135.100 + private PreparedStatement pstmtUpdateGroup = null;
135.101 +
135.102 + /** How many times the database connection was reinitialized */
135.103 + private int restarts = 0;
135.104 +
135.105 + /**
135.106 + * Rises the database: reconnect and recreate all prepared statements.
135.107 + * @throws java.lang.SQLException
135.108 + */
135.109 + protected void arise()
135.110 + throws SQLException
135.111 + {
135.112 + try
135.113 + {
135.114 + // Load database driver
135.115 + Class.forName(
135.116 + Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DBMSDRIVER, "java.lang.Object"));
135.117 +
135.118 + // Establish database connection
135.119 + this.conn = DriverManager.getConnection(
135.120 + Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DATABASE, "<not specified>"),
135.121 + Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_USER, "root"),
135.122 + Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_PASSWORD, ""));
135.123 +
135.124 + this.conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
135.125 + if(this.conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE)
135.126 + {
135.127 + Log.get().warning("Database is NOT fully serializable!");
135.128 + }
135.129 +
135.130 + // Prepare statements for method addArticle()
135.131 + this.pstmtAddArticle1 = conn.prepareStatement(
135.132 + "INSERT INTO articles (article_id, body) VALUES(?, ?)");
135.133 + this.pstmtAddArticle2 = conn.prepareStatement(
135.134 + "INSERT INTO headers (article_id, header_key, header_value, header_index) " +
135.135 + "VALUES (?, ?, ?, ?)");
135.136 + this.pstmtAddArticle3 = conn.prepareStatement(
135.137 + "INSERT INTO postings (group_id, article_id, article_index)" +
135.138 + "VALUES (?, ?, ?)");
135.139 + this.pstmtAddArticle4 = conn.prepareStatement(
135.140 + "INSERT INTO article_ids (article_id, message_id) VALUES (?, ?)");
135.141 +
135.142 + // Prepare statement for method addStatValue()
135.143 + this.pstmtAddEvent = conn.prepareStatement(
135.144 + "INSERT INTO events VALUES (?, ?, ?)");
135.145 +
135.146 + // Prepare statement for method addGroup()
135.147 + this.pstmtAddGroup0 = conn.prepareStatement(
135.148 + "INSERT INTO groups (name, flags) VALUES (?, ?)");
135.149 +
135.150 + // Prepare statement for method countArticles()
135.151 + this.pstmtCountArticles = conn.prepareStatement(
135.152 + "SELECT Count(article_id) FROM article_ids");
135.153 +
135.154 + // Prepare statement for method countGroups()
135.155 + this.pstmtCountGroups = conn.prepareStatement(
135.156 + "SELECT Count(group_id) FROM groups WHERE " +
135.157 + "flags & " + Channel.DELETED + " = 0");
135.158 +
135.159 + // Prepare statements for method delete(article)
135.160 + this.pstmtDeleteArticle0 = conn.prepareStatement(
135.161 + "DELETE FROM articles WHERE article_id = " +
135.162 + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
135.163 + this.pstmtDeleteArticle1 = conn.prepareStatement(
135.164 + "DELETE FROM headers WHERE article_id = " +
135.165 + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
135.166 + this.pstmtDeleteArticle2 = conn.prepareStatement(
135.167 + "DELETE FROM postings WHERE article_id = " +
135.168 + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
135.169 + this.pstmtDeleteArticle3 = conn.prepareStatement(
135.170 + "DELETE FROM article_ids WHERE message_id = ?");
135.171 +
135.172 + // Prepare statements for methods getArticle()
135.173 + this.pstmtGetArticle0 = conn.prepareStatement(
135.174 + "SELECT * FROM articles WHERE article_id = " +
135.175 + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
135.176 + this.pstmtGetArticle1 = conn.prepareStatement(
135.177 + "SELECT * FROM articles WHERE article_id = " +
135.178 + "(SELECT article_id FROM postings WHERE " +
135.179 + "article_index = ? AND group_id = ?)");
135.180 +
135.181 + // Prepare statement for method getArticleHeaders()
135.182 + this.pstmtGetArticleHeaders0 = conn.prepareStatement(
135.183 + "SELECT header_key, header_value FROM headers WHERE article_id = ? " +
135.184 + "ORDER BY header_index ASC");
135.185 +
135.186 + // Prepare statement for method getArticleHeaders(regular expr pattern)
135.187 + this.pstmtGetArticleHeaders1 = conn.prepareStatement(
135.188 + "SELECT p.article_index, h.header_value FROM headers h " +
135.189 + "INNER JOIN postings p ON h.article_id = p.article_id " +
135.190 + "INNER JOIN groups g ON p.group_id = g.group_id " +
135.191 + "WHERE g.name = ? AND " +
135.192 + "h.header_key = ? AND " +
135.193 + "p.article_index >= ? " +
135.194 + "ORDER BY p.article_index ASC");
135.195 +
135.196 + this.pstmtGetArticleIDs = conn.prepareStatement(
135.197 + "SELECT article_index FROM postings WHERE group_id = ?");
135.198 +
135.199 + // Prepare statement for method getArticleIndex
135.200 + this.pstmtGetArticleIndex = conn.prepareStatement(
135.201 + "SELECT article_index FROM postings WHERE " +
135.202 + "article_id = (SELECT article_id FROM article_ids " +
135.203 + "WHERE message_id = ?) " +
135.204 + " AND group_id = ?");
135.205 +
135.206 + // Prepare statements for method getArticleHeads()
135.207 + this.pstmtGetArticleHeads = conn.prepareStatement(
135.208 + "SELECT article_id, article_index FROM postings WHERE " +
135.209 + "postings.group_id = ? AND article_index >= ? AND " +
135.210 + "article_index <= ?");
135.211 +
135.212 + // Prepare statements for method getConfigValue()
135.213 + this.pstmtGetConfigValue = conn.prepareStatement(
135.214 + "SELECT config_value FROM config WHERE config_key = ?");
135.215 +
135.216 + // Prepare statements for method getEventsCount()
135.217 + this.pstmtGetEventsCount0 = conn.prepareStatement(
135.218 + "SELECT Count(*) FROM events WHERE event_key = ? AND " +
135.219 + "event_time >= ? AND event_time < ?");
135.220 +
135.221 + this.pstmtGetEventsCount1 = conn.prepareStatement(
135.222 + "SELECT Count(*) FROM events WHERE event_key = ? AND " +
135.223 + "event_time >= ? AND event_time < ? AND group_id = ?");
135.224 +
135.225 + // Prepare statement for method getGroupForList()
135.226 + this.pstmtGetGroupForList = conn.prepareStatement(
135.227 + "SELECT name FROM groups INNER JOIN groups2list " +
135.228 + "ON groups.group_id = groups2list.group_id " +
135.229 + "WHERE groups2list.listaddress = ?");
135.230 +
135.231 + // Prepare statement for method getGroup()
135.232 + this.pstmtGetGroup0 = conn.prepareStatement(
135.233 + "SELECT group_id, flags FROM groups WHERE Name = ?");
135.234 + this.pstmtGetGroup1 = conn.prepareStatement(
135.235 + "SELECT name FROM groups WHERE group_id = ?");
135.236 +
135.237 + // Prepare statement for method getLastArticleNumber()
135.238 + this.pstmtGetLastArticleNumber = conn.prepareStatement(
135.239 + "SELECT Max(article_index) FROM postings WHERE group_id = ?");
135.240 +
135.241 + // Prepare statement for method getListForGroup()
135.242 + this.pstmtGetListForGroup = conn.prepareStatement(
135.243 + "SELECT listaddress FROM groups2list INNER JOIN groups " +
135.244 + "ON groups.group_id = groups2list.group_id WHERE name = ?");
135.245 +
135.246 + // Prepare statement for method getMaxArticleID()
135.247 + this.pstmtGetMaxArticleID = conn.prepareStatement(
135.248 + "SELECT Max(article_id) FROM articles");
135.249 +
135.250 + // Prepare statement for method getMaxArticleIndex()
135.251 + this.pstmtGetMaxArticleIndex = conn.prepareStatement(
135.252 + "SELECT Max(article_index) FROM postings WHERE group_id = ?");
135.253 +
135.254 + // Prepare statement for method getOldestArticle()
135.255 + this.pstmtGetOldestArticle = conn.prepareStatement(
135.256 + "SELECT message_id FROM article_ids WHERE article_id = " +
135.257 + "(SELECT Min(article_id) FROM article_ids)");
135.258 +
135.259 + // Prepare statement for method getFirstArticleNumber()
135.260 + this.pstmtGetFirstArticleNumber = conn.prepareStatement(
135.261 + "SELECT Min(article_index) FROM postings WHERE group_id = ?");
135.262 +
135.263 + // Prepare statement for method getPostingsCount()
135.264 + this.pstmtGetPostingsCount = conn.prepareStatement(
135.265 + "SELECT Count(*) FROM postings NATURAL JOIN groups " +
135.266 + "WHERE groups.name = ?");
135.267 +
135.268 + // Prepare statement for method getSubscriptions()
135.269 + this.pstmtGetSubscriptions = conn.prepareStatement(
135.270 + "SELECT host, port, name FROM peers NATURAL JOIN " +
135.271 + "peer_subscriptions NATURAL JOIN groups WHERE feedtype = ?");
135.272 +
135.273 + // Prepare statement for method isArticleExisting()
135.274 + this.pstmtIsArticleExisting = conn.prepareStatement(
135.275 + "SELECT Count(article_id) FROM article_ids WHERE message_id = ?");
135.276 +
135.277 + // Prepare statement for method isGroupExisting()
135.278 + this.pstmtIsGroupExisting = conn.prepareStatement(
135.279 + "SELECT * FROM groups WHERE name = ?");
135.280 +
135.281 + // Prepare statement for method setConfigValue()
135.282 + this.pstmtSetConfigValue0 = conn.prepareStatement(
135.283 + "DELETE FROM config WHERE config_key = ?");
135.284 + this.pstmtSetConfigValue1 = conn.prepareStatement(
135.285 + "INSERT INTO config VALUES(?, ?)");
135.286 +
135.287 + // Prepare statements for method purgeGroup()
135.288 + this.pstmtPurgeGroup0 = conn.prepareStatement(
135.289 + "DELETE FROM peer_subscriptions WHERE group_id = ?");
135.290 + this.pstmtPurgeGroup1 = conn.prepareStatement(
135.291 + "DELETE FROM groups WHERE group_id = ?");
135.292 +
135.293 + // Prepare statement for method update(Group)
135.294 + this.pstmtUpdateGroup = conn.prepareStatement(
135.295 + "UPDATE groups SET flags = ?, name = ? WHERE group_id = ?");
135.296 + }
135.297 + catch(ClassNotFoundException ex)
135.298 + {
135.299 + throw new Error("JDBC Driver not found!", ex);
135.300 + }
135.301 + }
135.302 +
135.303 + /**
135.304 + * Adds an article to the database.
135.305 + * @param article
135.306 + * @return
135.307 + * @throws java.sql.SQLException
135.308 + */
135.309 + @Override
135.310 + public void addArticle(final Article article)
135.311 + throws StorageBackendException
135.312 + {
135.313 + try
135.314 + {
135.315 + this.conn.setAutoCommit(false);
135.316 +
135.317 + int newArticleID = getMaxArticleID() + 1;
135.318 +
135.319 + // Fill prepared statement with values;
135.320 + // writes body to article table
135.321 + pstmtAddArticle1.setInt(1, newArticleID);
135.322 + pstmtAddArticle1.setBytes(2, article.getBody());
135.323 + pstmtAddArticle1.execute();
135.324 +
135.325 + // Add headers
135.326 + Enumeration headers = article.getAllHeaders();
135.327 + for(int n = 0; headers.hasMoreElements(); n++)
135.328 + {
135.329 + Header header = (Header)headers.nextElement();
135.330 + pstmtAddArticle2.setInt(1, newArticleID);
135.331 + pstmtAddArticle2.setString(2, header.getName().toLowerCase());
135.332 + pstmtAddArticle2.setString(3,
135.333 + header.getValue().replaceAll("[\r\n]", ""));
135.334 + pstmtAddArticle2.setInt(4, n);
135.335 + pstmtAddArticle2.execute();
135.336 + }
135.337 +
135.338 + // For each newsgroup add a reference
135.339 + List<Group> groups = article.getGroups();
135.340 + for(Group group : groups)
135.341 + {
135.342 + pstmtAddArticle3.setLong(1, group.getInternalID());
135.343 + pstmtAddArticle3.setInt(2, newArticleID);
135.344 + pstmtAddArticle3.setLong(3, getMaxArticleIndex(group.getInternalID()) + 1);
135.345 + pstmtAddArticle3.execute();
135.346 + }
135.347 +
135.348 + // Write message-id to article_ids table
135.349 + this.pstmtAddArticle4.setInt(1, newArticleID);
135.350 + this.pstmtAddArticle4.setString(2, article.getMessageID());
135.351 + this.pstmtAddArticle4.execute();
135.352 +
135.353 + this.conn.commit();
135.354 + this.conn.setAutoCommit(true);
135.355 +
135.356 + this.restarts = 0; // Reset error count
135.357 + }
135.358 + catch(SQLException ex)
135.359 + {
135.360 + try
135.361 + {
135.362 + this.conn.rollback(); // Rollback changes
135.363 + }
135.364 + catch(SQLException ex2)
135.365 + {
135.366 + Log.get().severe("Rollback of addArticle() failed: " + ex2);
135.367 + }
135.368 +
135.369 + try
135.370 + {
135.371 + this.conn.setAutoCommit(true); // and release locks
135.372 + }
135.373 + catch(SQLException ex2)
135.374 + {
135.375 + Log.get().severe("setAutoCommit(true) of addArticle() failed: " + ex2);
135.376 + }
135.377 +
135.378 + restartConnection(ex);
135.379 + addArticle(article);
135.380 + }
135.381 + }
135.382 +
135.383 + /**
135.384 + * Adds a group to the JDBCDatabase. This method is not accessible via NNTP.
135.385 + * @param name
135.386 + * @throws java.sql.SQLException
135.387 + */
135.388 + @Override
135.389 + public void addGroup(String name, int flags)
135.390 + throws StorageBackendException
135.391 + {
135.392 + try
135.393 + {
135.394 + this.conn.setAutoCommit(false);
135.395 + pstmtAddGroup0.setString(1, name);
135.396 + pstmtAddGroup0.setInt(2, flags);
135.397 +
135.398 + pstmtAddGroup0.executeUpdate();
135.399 + this.conn.commit();
135.400 + this.conn.setAutoCommit(true);
135.401 + this.restarts = 0; // Reset error count
135.402 + }
135.403 + catch(SQLException ex)
135.404 + {
135.405 + try
135.406 + {
135.407 + this.conn.rollback();
135.408 + this.conn.setAutoCommit(true);
135.409 + }
135.410 + catch(SQLException ex2)
135.411 + {
135.412 + ex2.printStackTrace();
135.413 + }
135.414 +
135.415 + restartConnection(ex);
135.416 + addGroup(name, flags);
135.417 + }
135.418 + }
135.419 +
135.420 + @Override
135.421 + public void addEvent(long time, int type, long gid)
135.422 + throws StorageBackendException
135.423 + {
135.424 + try
135.425 + {
135.426 + this.conn.setAutoCommit(false);
135.427 + this.pstmtAddEvent.setLong(1, time);
135.428 + this.pstmtAddEvent.setInt(2, type);
135.429 + this.pstmtAddEvent.setLong(3, gid);
135.430 + this.pstmtAddEvent.executeUpdate();
135.431 + this.conn.commit();
135.432 + this.conn.setAutoCommit(true);
135.433 + this.restarts = 0;
135.434 + }
135.435 + catch(SQLException ex)
135.436 + {
135.437 + try
135.438 + {
135.439 + this.conn.rollback();
135.440 + this.conn.setAutoCommit(true);
135.441 + }
135.442 + catch(SQLException ex2)
135.443 + {
135.444 + ex2.printStackTrace();
135.445 + }
135.446 +
135.447 + restartConnection(ex);
135.448 + addEvent(time, type, gid);
135.449 + }
135.450 + }
135.451 +
135.452 + @Override
135.453 + public int countArticles()
135.454 + throws StorageBackendException
135.455 + {
135.456 + ResultSet rs = null;
135.457 +
135.458 + try
135.459 + {
135.460 + rs = this.pstmtCountArticles.executeQuery();
135.461 + if(rs.next())
135.462 + {
135.463 + return rs.getInt(1);
135.464 + }
135.465 + else
135.466 + {
135.467 + return -1;
135.468 + }
135.469 + }
135.470 + catch(SQLException ex)
135.471 + {
135.472 + restartConnection(ex);
135.473 + return countArticles();
135.474 + }
135.475 + finally
135.476 + {
135.477 + if(rs != null)
135.478 + {
135.479 + try
135.480 + {
135.481 + rs.close();
135.482 + }
135.483 + catch(SQLException ex)
135.484 + {
135.485 + ex.printStackTrace();
135.486 + }
135.487 + restarts = 0;
135.488 + }
135.489 + }
135.490 + }
135.491 +
135.492 + @Override
135.493 + public int countGroups()
135.494 + throws StorageBackendException
135.495 + {
135.496 + ResultSet rs = null;
135.497 +
135.498 + try
135.499 + {
135.500 + rs = this.pstmtCountGroups.executeQuery();
135.501 + if(rs.next())
135.502 + {
135.503 + return rs.getInt(1);
135.504 + }
135.505 + else
135.506 + {
135.507 + return -1;
135.508 + }
135.509 + }
135.510 + catch(SQLException ex)
135.511 + {
135.512 + restartConnection(ex);
135.513 + return countGroups();
135.514 + }
135.515 + finally
135.516 + {
135.517 + if(rs != null)
135.518 + {
135.519 + try
135.520 + {
135.521 + rs.close();
135.522 + }
135.523 + catch(SQLException ex)
135.524 + {
135.525 + ex.printStackTrace();
135.526 + }
135.527 + restarts = 0;
135.528 + }
135.529 + }
135.530 + }
135.531 +
135.532 + @Override
135.533 + public void delete(final String messageID)
135.534 + throws StorageBackendException
135.535 + {
135.536 + try
135.537 + {
135.538 + this.conn.setAutoCommit(false);
135.539 +
135.540 + this.pstmtDeleteArticle0.setString(1, messageID);
135.541 + int rs = this.pstmtDeleteArticle0.executeUpdate();
135.542 +
135.543 + // We do not trust the ON DELETE CASCADE functionality to delete
135.544 + // orphaned references...
135.545 + this.pstmtDeleteArticle1.setString(1, messageID);
135.546 + rs = this.pstmtDeleteArticle1.executeUpdate();
135.547 +
135.548 + this.pstmtDeleteArticle2.setString(1, messageID);
135.549 + rs = this.pstmtDeleteArticle2.executeUpdate();
135.550 +
135.551 + this.pstmtDeleteArticle3.setString(1, messageID);
135.552 + rs = this.pstmtDeleteArticle3.executeUpdate();
135.553 +
135.554 + this.conn.commit();
135.555 + this.conn.setAutoCommit(true);
135.556 + }
135.557 + catch(SQLException ex)
135.558 + {
135.559 + throw new StorageBackendException(ex);
135.560 + }
135.561 + }
135.562 +
135.563 + @Override
135.564 + public Article getArticle(String messageID)
135.565 + throws StorageBackendException
135.566 + {
135.567 + ResultSet rs = null;
135.568 + try
135.569 + {
135.570 + pstmtGetArticle0.setString(1, messageID);
135.571 + rs = pstmtGetArticle0.executeQuery();
135.572 +
135.573 + if(!rs.next())
135.574 + {
135.575 + return null;
135.576 + }
135.577 + else
135.578 + {
135.579 + byte[] body = rs.getBytes("body");
135.580 + String headers = getArticleHeaders(rs.getInt("article_id"));
135.581 + return new Article(headers, body);
135.582 + }
135.583 + }
135.584 + catch(SQLException ex)
135.585 + {
135.586 + restartConnection(ex);
135.587 + return getArticle(messageID);
135.588 + }
135.589 + finally
135.590 + {
135.591 + if(rs != null)
135.592 + {
135.593 + try
135.594 + {
135.595 + rs.close();
135.596 + }
135.597 + catch(SQLException ex)
135.598 + {
135.599 + ex.printStackTrace();
135.600 + }
135.601 + restarts = 0; // Reset error count
135.602 + }
135.603 + }
135.604 + }
135.605 +
135.606 + /**
135.607 + * Retrieves an article by its ID.
135.608 + * @param articleID
135.609 + * @return
135.610 + * @throws StorageBackendException
135.611 + */
135.612 + @Override
135.613 + public Article getArticle(long articleIndex, long gid)
135.614 + throws StorageBackendException
135.615 + {
135.616 + ResultSet rs = null;
135.617 +
135.618 + try
135.619 + {
135.620 + this.pstmtGetArticle1.setLong(1, articleIndex);
135.621 + this.pstmtGetArticle1.setLong(2, gid);
135.622 +
135.623 + rs = this.pstmtGetArticle1.executeQuery();
135.624 +
135.625 + if(rs.next())
135.626 + {
135.627 + byte[] body = rs.getBytes("body");
135.628 + String headers = getArticleHeaders(rs.getInt("article_id"));
135.629 + return new Article(headers, body);
135.630 + }
135.631 + else
135.632 + {
135.633 + return null;
135.634 + }
135.635 + }
135.636 + catch(SQLException ex)
135.637 + {
135.638 + restartConnection(ex);
135.639 + return getArticle(articleIndex, gid);
135.640 + }
135.641 + finally
135.642 + {
135.643 + if(rs != null)
135.644 + {
135.645 + try
135.646 + {
135.647 + rs.close();
135.648 + }
135.649 + catch(SQLException ex)
135.650 + {
135.651 + ex.printStackTrace();
135.652 + }
135.653 + restarts = 0;
135.654 + }
135.655 + }
135.656 + }
135.657 +
135.658 + /**
135.659 + * Searches for fitting header values using the given regular expression.
135.660 + * @param group
135.661 + * @param start
135.662 + * @param end
135.663 + * @param headerKey
135.664 + * @param pattern
135.665 + * @return
135.666 + * @throws StorageBackendException
135.667 + */
135.668 + @Override
135.669 + public List<Pair<Long, String>> getArticleHeaders(Channel group, long start,
135.670 + long end, String headerKey, String patStr)
135.671 + throws StorageBackendException, PatternSyntaxException
135.672 + {
135.673 + ResultSet rs = null;
135.674 + List<Pair<Long, String>> heads = new ArrayList<Pair<Long, String>>();
135.675 +
135.676 + try
135.677 + {
135.678 + this.pstmtGetArticleHeaders1.setString(1, group.getName());
135.679 + this.pstmtGetArticleHeaders1.setString(2, headerKey);
135.680 + this.pstmtGetArticleHeaders1.setLong(3, start);
135.681 +
135.682 + rs = this.pstmtGetArticleHeaders1.executeQuery();
135.683 +
135.684 + // Convert the "NNTP" regex to Java regex
135.685 + patStr = patStr.replace("*", ".*");
135.686 + Pattern pattern = Pattern.compile(patStr);
135.687 +
135.688 + while(rs.next())
135.689 + {
135.690 + Long articleIndex = rs.getLong(1);
135.691 + if(end < 0 || articleIndex <= end) // Match start is done via SQL
135.692 + {
135.693 + String headerValue = rs.getString(2);
135.694 + Matcher matcher = pattern.matcher(headerValue);
135.695 + if(matcher.matches())
135.696 + {
135.697 + heads.add(new Pair<Long, String>(articleIndex, headerValue));
135.698 + }
135.699 + }
135.700 + }
135.701 + }
135.702 + catch(SQLException ex)
135.703 + {
135.704 + restartConnection(ex);
135.705 + return getArticleHeaders(group, start, end, headerKey, patStr);
135.706 + }
135.707 + finally
135.708 + {
135.709 + if(rs != null)
135.710 + {
135.711 + try
135.712 + {
135.713 + rs.close();
135.714 + }
135.715 + catch(SQLException ex)
135.716 + {
135.717 + ex.printStackTrace();
135.718 + }
135.719 + }
135.720 + }
135.721 +
135.722 + return heads;
135.723 + }
135.724 +
135.725 + private String getArticleHeaders(long articleID)
135.726 + throws StorageBackendException
135.727 + {
135.728 + ResultSet rs = null;
135.729 +
135.730 + try
135.731 + {
135.732 + this.pstmtGetArticleHeaders0.setLong(1, articleID);
135.733 + rs = this.pstmtGetArticleHeaders0.executeQuery();
135.734 +
135.735 + StringBuilder buf = new StringBuilder();
135.736 + if(rs.next())
135.737 + {
135.738 + for(;;)
135.739 + {
135.740 + buf.append(rs.getString(1)); // key
135.741 + buf.append(": ");
135.742 + String foldedValue = MimeUtility.fold(0, rs.getString(2));
135.743 + buf.append(foldedValue); // value
135.744 + if(rs.next())
135.745 + {
135.746 + buf.append("\r\n");
135.747 + }
135.748 + else
135.749 + {
135.750 + break;
135.751 + }
135.752 + }
135.753 + }
135.754 +
135.755 + return buf.toString();
135.756 + }
135.757 + catch(SQLException ex)
135.758 + {
135.759 + restartConnection(ex);
135.760 + return getArticleHeaders(articleID);
135.761 + }
135.762 + finally
135.763 + {
135.764 + if(rs != null)
135.765 + {
135.766 + try
135.767 + {
135.768 + rs.close();
135.769 + }
135.770 + catch(SQLException ex)
135.771 + {
135.772 + ex.printStackTrace();
135.773 + }
135.774 + }
135.775 + }
135.776 + }
135.777 +
135.778 + @Override
135.779 + public long getArticleIndex(Article article, Group group)
135.780 + throws StorageBackendException
135.781 + {
135.782 + ResultSet rs = null;
135.783 +
135.784 + try
135.785 + {
135.786 + this.pstmtGetArticleIndex.setString(1, article.getMessageID());
135.787 + this.pstmtGetArticleIndex.setLong(2, group.getInternalID());
135.788 +
135.789 + rs = this.pstmtGetArticleIndex.executeQuery();
135.790 + if(rs.next())
135.791 + {
135.792 + return rs.getLong(1);
135.793 + }
135.794 + else
135.795 + {
135.796 + return -1;
135.797 + }
135.798 + }
135.799 + catch(SQLException ex)
135.800 + {
135.801 + restartConnection(ex);
135.802 + return getArticleIndex(article, group);
135.803 + }
135.804 + finally
135.805 + {
135.806 + if(rs != null)
135.807 + {
135.808 + try
135.809 + {
135.810 + rs.close();
135.811 + }
135.812 + catch(SQLException ex)
135.813 + {
135.814 + ex.printStackTrace();
135.815 + }
135.816 + }
135.817 + }
135.818 + }
135.819 +
135.820 + /**
135.821 + * Returns a list of Long/Article Pairs.
135.822 + * @throws java.sql.SQLException
135.823 + */
135.824 + @Override
135.825 + public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first,
135.826 + long last)
135.827 + throws StorageBackendException
135.828 + {
135.829 + ResultSet rs = null;
135.830 +
135.831 + try
135.832 + {
135.833 + this.pstmtGetArticleHeads.setLong(1, group.getInternalID());
135.834 + this.pstmtGetArticleHeads.setLong(2, first);
135.835 + this.pstmtGetArticleHeads.setLong(3, last);
135.836 + rs = pstmtGetArticleHeads.executeQuery();
135.837 +
135.838 + List<Pair<Long, ArticleHead>> articles
135.839 + = new ArrayList<Pair<Long, ArticleHead>>();
135.840 +
135.841 + while (rs.next())
135.842 + {
135.843 + long aid = rs.getLong("article_id");
135.844 + long aidx = rs.getLong("article_index");
135.845 + String headers = getArticleHeaders(aid);
135.846 + articles.add(new Pair<Long, ArticleHead>(aidx,
135.847 + new ArticleHead(headers)));
135.848 + }
135.849 +
135.850 + return articles;
135.851 + }
135.852 + catch(SQLException ex)
135.853 + {
135.854 + restartConnection(ex);
135.855 + return getArticleHeads(group, first, last);
135.856 + }
135.857 + finally
135.858 + {
135.859 + if(rs != null)
135.860 + {
135.861 + try
135.862 + {
135.863 + rs.close();
135.864 + }
135.865 + catch(SQLException ex)
135.866 + {
135.867 + ex.printStackTrace();
135.868 + }
135.869 + }
135.870 + }
135.871 + }
135.872 +
135.873 + @Override
135.874 + public List<Long> getArticleNumbers(long gid)
135.875 + throws StorageBackendException
135.876 + {
135.877 + ResultSet rs = null;
135.878 + try
135.879 + {
135.880 + List<Long> ids = new ArrayList<Long>();
135.881 + this.pstmtGetArticleIDs.setLong(1, gid);
135.882 + rs = this.pstmtGetArticleIDs.executeQuery();
135.883 + while(rs.next())
135.884 + {
135.885 + ids.add(rs.getLong(1));
135.886 + }
135.887 + return ids;
135.888 + }
135.889 + catch(SQLException ex)
135.890 + {
135.891 + restartConnection(ex);
135.892 + return getArticleNumbers(gid);
135.893 + }
135.894 + finally
135.895 + {
135.896 + if(rs != null)
135.897 + {
135.898 + try
135.899 + {
135.900 + rs.close();
135.901 + restarts = 0; // Clear the restart count after successful request
135.902 + }
135.903 + catch(SQLException ex)
135.904 + {
135.905 + ex.printStackTrace();
135.906 + }
135.907 + }
135.908 + }
135.909 + }
135.910 +
135.911 + @Override
135.912 + public String getConfigValue(String key)
135.913 + throws StorageBackendException
135.914 + {
135.915 + ResultSet rs = null;
135.916 + try
135.917 + {
135.918 + this.pstmtGetConfigValue.setString(1, key);
135.919 +
135.920 + rs = this.pstmtGetConfigValue.executeQuery();
135.921 + if(rs.next())
135.922 + {
135.923 + return rs.getString(1); // First data on index 1 not 0
135.924 + }
135.925 + else
135.926 + {
135.927 + return null;
135.928 + }
135.929 + }
135.930 + catch(SQLException ex)
135.931 + {
135.932 + restartConnection(ex);
135.933 + return getConfigValue(key);
135.934 + }
135.935 + finally
135.936 + {
135.937 + if(rs != null)
135.938 + {
135.939 + try
135.940 + {
135.941 + rs.close();
135.942 + }
135.943 + catch(SQLException ex)
135.944 + {
135.945 + ex.printStackTrace();
135.946 + }
135.947 + restarts = 0; // Clear the restart count after successful request
135.948 + }
135.949 + }
135.950 + }
135.951 +
135.952 + @Override
135.953 + public int getEventsCount(int type, long start, long end, Channel channel)
135.954 + throws StorageBackendException
135.955 + {
135.956 + ResultSet rs = null;
135.957 +
135.958 + try
135.959 + {
135.960 + if(channel == null)
135.961 + {
135.962 + this.pstmtGetEventsCount0.setInt(1, type);
135.963 + this.pstmtGetEventsCount0.setLong(2, start);
135.964 + this.pstmtGetEventsCount0.setLong(3, end);
135.965 + rs = this.pstmtGetEventsCount0.executeQuery();
135.966 + }
135.967 + else
135.968 + {
135.969 + this.pstmtGetEventsCount1.setInt(1, type);
135.970 + this.pstmtGetEventsCount1.setLong(2, start);
135.971 + this.pstmtGetEventsCount1.setLong(3, end);
135.972 + this.pstmtGetEventsCount1.setLong(4, channel.getInternalID());
135.973 + rs = this.pstmtGetEventsCount1.executeQuery();
135.974 + }
135.975 +
135.976 + if(rs.next())
135.977 + {
135.978 + return rs.getInt(1);
135.979 + }
135.980 + else
135.981 + {
135.982 + return -1;
135.983 + }
135.984 + }
135.985 + catch(SQLException ex)
135.986 + {
135.987 + restartConnection(ex);
135.988 + return getEventsCount(type, start, end, channel);
135.989 + }
135.990 + finally
135.991 + {
135.992 + if(rs != null)
135.993 + {
135.994 + try
135.995 + {
135.996 + rs.close();
135.997 + }
135.998 + catch(SQLException ex)
135.999 + {
135.1000 + ex.printStackTrace();
135.1001 + }
135.1002 + }
135.1003 + }
135.1004 + }
135.1005 +
135.1006 + /**
135.1007 + * Reads all Groups from the JDBCDatabase.
135.1008 + * @return
135.1009 + * @throws StorageBackendException
135.1010 + */
135.1011 + @Override
135.1012 + public List<Channel> getGroups()
135.1013 + throws StorageBackendException
135.1014 + {
135.1015 + ResultSet rs;
135.1016 + List<Channel> buffer = new ArrayList<Channel>();
135.1017 + Statement stmt = null;
135.1018 +
135.1019 + try
135.1020 + {
135.1021 + stmt = conn.createStatement();
135.1022 + rs = stmt.executeQuery("SELECT * FROM groups ORDER BY name");
135.1023 +
135.1024 + while(rs.next())
135.1025 + {
135.1026 + String name = rs.getString("name");
135.1027 + long id = rs.getLong("group_id");
135.1028 + int flags = rs.getInt("flags");
135.1029 +
135.1030 + Group group = new Group(name, id, flags);
135.1031 + buffer.add(group);
135.1032 + }
135.1033 +
135.1034 + return buffer;
135.1035 + }
135.1036 + catch(SQLException ex)
135.1037 + {
135.1038 + restartConnection(ex);
135.1039 + return getGroups();
135.1040 + }
135.1041 + finally
135.1042 + {
135.1043 + if(stmt != null)
135.1044 + {
135.1045 + try
135.1046 + {
135.1047 + stmt.close(); // Implicitely closes ResultSets
135.1048 + }
135.1049 + catch(SQLException ex)
135.1050 + {
135.1051 + ex.printStackTrace();
135.1052 + }
135.1053 + }
135.1054 + }
135.1055 + }
135.1056 +
135.1057 + @Override
135.1058 + public List<String> getGroupsForList(String listAddress)
135.1059 + throws StorageBackendException
135.1060 + {
135.1061 + ResultSet rs = null;
135.1062 +
135.1063 + try
135.1064 + {
135.1065 + this.pstmtGetGroupForList.setString(1, listAddress);
135.1066 +
135.1067 + rs = this.pstmtGetGroupForList.executeQuery();
135.1068 + List<String> groups = new ArrayList<String>();
135.1069 + while(rs.next())
135.1070 + {
135.1071 + String group = rs.getString(1);
135.1072 + groups.add(group);
135.1073 + }
135.1074 + return groups;
135.1075 + }
135.1076 + catch(SQLException ex)
135.1077 + {
135.1078 + restartConnection(ex);
135.1079 + return getGroupsForList(listAddress);
135.1080 + }
135.1081 + finally
135.1082 + {
135.1083 + if(rs != null)
135.1084 + {
135.1085 + try
135.1086 + {
135.1087 + rs.close();
135.1088 + }
135.1089 + catch(SQLException ex)
135.1090 + {
135.1091 + ex.printStackTrace();
135.1092 + }
135.1093 + }
135.1094 + }
135.1095 + }
135.1096 +
135.1097 + /**
135.1098 + * Returns the Group that is identified by the name.
135.1099 + * @param name
135.1100 + * @return
135.1101 + * @throws StorageBackendException
135.1102 + */
135.1103 + @Override
135.1104 + public Group getGroup(String name)
135.1105 + throws StorageBackendException
135.1106 + {
135.1107 + ResultSet rs = null;
135.1108 +
135.1109 + try
135.1110 + {
135.1111 + this.pstmtGetGroup0.setString(1, name);
135.1112 + rs = this.pstmtGetGroup0.executeQuery();
135.1113 +
135.1114 + if (!rs.next())
135.1115 + {
135.1116 + return null;
135.1117 + }
135.1118 + else
135.1119 + {
135.1120 + long id = rs.getLong("group_id");
135.1121 + int flags = rs.getInt("flags");
135.1122 + return new Group(name, id, flags);
135.1123 + }
135.1124 + }
135.1125 + catch(SQLException ex)
135.1126 + {
135.1127 + restartConnection(ex);
135.1128 + return getGroup(name);
135.1129 + }
135.1130 + finally
135.1131 + {
135.1132 + if(rs != null)
135.1133 + {
135.1134 + try
135.1135 + {
135.1136 + rs.close();
135.1137 + }
135.1138 + catch(SQLException ex)
135.1139 + {
135.1140 + ex.printStackTrace();
135.1141 + }
135.1142 + }
135.1143 + }
135.1144 + }
135.1145 +
135.1146 + @Override
135.1147 + public List<String> getListsForGroup(String group)
135.1148 + throws StorageBackendException
135.1149 + {
135.1150 + ResultSet rs = null;
135.1151 + List<String> lists = new ArrayList<String>();
135.1152 +
135.1153 + try
135.1154 + {
135.1155 + this.pstmtGetListForGroup.setString(1, group);
135.1156 + rs = this.pstmtGetListForGroup.executeQuery();
135.1157 +
135.1158 + while(rs.next())
135.1159 + {
135.1160 + lists.add(rs.getString(1));
135.1161 + }
135.1162 + return lists;
135.1163 + }
135.1164 + catch(SQLException ex)
135.1165 + {
135.1166 + restartConnection(ex);
135.1167 + return getListsForGroup(group);
135.1168 + }
135.1169 + finally
135.1170 + {
135.1171 + if(rs != null)
135.1172 + {
135.1173 + try
135.1174 + {
135.1175 + rs.close();
135.1176 + }
135.1177 + catch(SQLException ex)
135.1178 + {
135.1179 + ex.printStackTrace();
135.1180 + }
135.1181 + }
135.1182 + }
135.1183 + }
135.1184 +
135.1185 + private int getMaxArticleIndex(long groupID)
135.1186 + throws StorageBackendException
135.1187 + {
135.1188 + ResultSet rs = null;
135.1189 +
135.1190 + try
135.1191 + {
135.1192 + this.pstmtGetMaxArticleIndex.setLong(1, groupID);
135.1193 + rs = this.pstmtGetMaxArticleIndex.executeQuery();
135.1194 +
135.1195 + int maxIndex = 0;
135.1196 + if (rs.next())
135.1197 + {
135.1198 + maxIndex = rs.getInt(1);
135.1199 + }
135.1200 +
135.1201 + return maxIndex;
135.1202 + }
135.1203 + catch(SQLException ex)
135.1204 + {
135.1205 + restartConnection(ex);
135.1206 + return getMaxArticleIndex(groupID);
135.1207 + }
135.1208 + finally
135.1209 + {
135.1210 + if(rs != null)
135.1211 + {
135.1212 + try
135.1213 + {
135.1214 + rs.close();
135.1215 + }
135.1216 + catch(SQLException ex)
135.1217 + {
135.1218 + ex.printStackTrace();
135.1219 + }
135.1220 + }
135.1221 + }
135.1222 + }
135.1223 +
135.1224 + private int getMaxArticleID()
135.1225 + throws StorageBackendException
135.1226 + {
135.1227 + ResultSet rs = null;
135.1228 +
135.1229 + try
135.1230 + {
135.1231 + rs = this.pstmtGetMaxArticleID.executeQuery();
135.1232 +
135.1233 + int maxIndex = 0;
135.1234 + if (rs.next())
135.1235 + {
135.1236 + maxIndex = rs.getInt(1);
135.1237 + }
135.1238 +
135.1239 + return maxIndex;
135.1240 + }
135.1241 + catch(SQLException ex)
135.1242 + {
135.1243 + restartConnection(ex);
135.1244 + return getMaxArticleID();
135.1245 + }
135.1246 + finally
135.1247 + {
135.1248 + if(rs != null)
135.1249 + {
135.1250 + try
135.1251 + {
135.1252 + rs.close();
135.1253 + }
135.1254 + catch(SQLException ex)
135.1255 + {
135.1256 + ex.printStackTrace();
135.1257 + }
135.1258 + }
135.1259 + }
135.1260 + }
135.1261 +
135.1262 + @Override
135.1263 + public int getLastArticleNumber(Group group)
135.1264 + throws StorageBackendException
135.1265 + {
135.1266 + ResultSet rs = null;
135.1267 +
135.1268 + try
135.1269 + {
135.1270 + this.pstmtGetLastArticleNumber.setLong(1, group.getInternalID());
135.1271 + rs = this.pstmtGetLastArticleNumber.executeQuery();
135.1272 + if (rs.next())
135.1273 + {
135.1274 + return rs.getInt(1);
135.1275 + }
135.1276 + else
135.1277 + {
135.1278 + return 0;
135.1279 + }
135.1280 + }
135.1281 + catch(SQLException ex)
135.1282 + {
135.1283 + restartConnection(ex);
135.1284 + return getLastArticleNumber(group);
135.1285 + }
135.1286 + finally
135.1287 + {
135.1288 + if(rs != null)
135.1289 + {
135.1290 + try
135.1291 + {
135.1292 + rs.close();
135.1293 + }
135.1294 + catch(SQLException ex)
135.1295 + {
135.1296 + ex.printStackTrace();
135.1297 + }
135.1298 + }
135.1299 + }
135.1300 + }
135.1301 +
135.1302 + @Override
135.1303 + public int getFirstArticleNumber(Group group)
135.1304 + throws StorageBackendException
135.1305 + {
135.1306 + ResultSet rs = null;
135.1307 + try
135.1308 + {
135.1309 + this.pstmtGetFirstArticleNumber.setLong(1, group.getInternalID());
135.1310 + rs = this.pstmtGetFirstArticleNumber.executeQuery();
135.1311 + if(rs.next())
135.1312 + {
135.1313 + return rs.getInt(1);
135.1314 + }
135.1315 + else
135.1316 + {
135.1317 + return 0;
135.1318 + }
135.1319 + }
135.1320 + catch(SQLException ex)
135.1321 + {
135.1322 + restartConnection(ex);
135.1323 + return getFirstArticleNumber(group);
135.1324 + }
135.1325 + finally
135.1326 + {
135.1327 + if(rs != null)
135.1328 + {
135.1329 + try
135.1330 + {
135.1331 + rs.close();
135.1332 + }
135.1333 + catch(SQLException ex)
135.1334 + {
135.1335 + ex.printStackTrace();
135.1336 + }
135.1337 + }
135.1338 + }
135.1339 + }
135.1340 +
135.1341 + /**
135.1342 + * Returns a group name identified by the given id.
135.1343 + * @param id
135.1344 + * @return
135.1345 + * @throws StorageBackendException
135.1346 + */
135.1347 + public String getGroup(int id)
135.1348 + throws StorageBackendException
135.1349 + {
135.1350 + ResultSet rs = null;
135.1351 +
135.1352 + try
135.1353 + {
135.1354 + this.pstmtGetGroup1.setInt(1, id);
135.1355 + rs = this.pstmtGetGroup1.executeQuery();
135.1356 +
135.1357 + if (rs.next())
135.1358 + {
135.1359 + return rs.getString(1);
135.1360 + }
135.1361 + else
135.1362 + {
135.1363 + return null;
135.1364 + }
135.1365 + }
135.1366 + catch(SQLException ex)
135.1367 + {
135.1368 + restartConnection(ex);
135.1369 + return getGroup(id);
135.1370 + }
135.1371 + finally
135.1372 + {
135.1373 + if(rs != null)
135.1374 + {
135.1375 + try
135.1376 + {
135.1377 + rs.close();
135.1378 + }
135.1379 + catch(SQLException ex)
135.1380 + {
135.1381 + ex.printStackTrace();
135.1382 + }
135.1383 + }
135.1384 + }
135.1385 + }
135.1386 +
135.1387 + @Override
135.1388 + public double getEventsPerHour(int key, long gid)
135.1389 + throws StorageBackendException
135.1390 + {
135.1391 + String gidquery = "";
135.1392 + if(gid >= 0)
135.1393 + {
135.1394 + gidquery = " AND group_id = " + gid;
135.1395 + }
135.1396 +
135.1397 + Statement stmt = null;
135.1398 + ResultSet rs = null;
135.1399 +
135.1400 + try
135.1401 + {
135.1402 + stmt = this.conn.createStatement();
135.1403 + rs = stmt.executeQuery("SELECT Count(*) / (Max(event_time) - Min(event_time))" +
135.1404 + " * 1000 * 60 * 60 FROM events WHERE event_key = " + key + gidquery);
135.1405 +
135.1406 + if(rs.next())
135.1407 + {
135.1408 + restarts = 0; // reset error count
135.1409 + return rs.getDouble(1);
135.1410 + }
135.1411 + else
135.1412 + {
135.1413 + return Double.NaN;
135.1414 + }
135.1415 + }
135.1416 + catch(SQLException ex)
135.1417 + {
135.1418 + restartConnection(ex);
135.1419 + return getEventsPerHour(key, gid);
135.1420 + }
135.1421 + finally
135.1422 + {
135.1423 + try
135.1424 + {
135.1425 + if(stmt != null)
135.1426 + {
135.1427 + stmt.close(); // Implicitely closes the result sets
135.1428 + }
135.1429 + }
135.1430 + catch(SQLException ex)
135.1431 + {
135.1432 + ex.printStackTrace();
135.1433 + }
135.1434 + }
135.1435 + }
135.1436 +
135.1437 + @Override
135.1438 + public String getOldestArticle()
135.1439 + throws StorageBackendException
135.1440 + {
135.1441 + ResultSet rs = null;
135.1442 +
135.1443 + try
135.1444 + {
135.1445 + rs = this.pstmtGetOldestArticle.executeQuery();
135.1446 + if(rs.next())
135.1447 + {
135.1448 + return rs.getString(1);
135.1449 + }
135.1450 + else
135.1451 + {
135.1452 + return null;
135.1453 + }
135.1454 + }
135.1455 + catch(SQLException ex)
135.1456 + {
135.1457 + restartConnection(ex);
135.1458 + return getOldestArticle();
135.1459 + }
135.1460 + finally
135.1461 + {
135.1462 + if(rs != null)
135.1463 + {
135.1464 + try
135.1465 + {
135.1466 + rs.close();
135.1467 + }
135.1468 + catch(SQLException ex)
135.1469 + {
135.1470 + ex.printStackTrace();
135.1471 + }
135.1472 + }
135.1473 + }
135.1474 + }
135.1475 +
135.1476 + @Override
135.1477 + public int getPostingsCount(String groupname)
135.1478 + throws StorageBackendException
135.1479 + {
135.1480 + ResultSet rs = null;
135.1481 +
135.1482 + try
135.1483 + {
135.1484 + this.pstmtGetPostingsCount.setString(1, groupname);
135.1485 + rs = this.pstmtGetPostingsCount.executeQuery();
135.1486 + if(rs.next())
135.1487 + {
135.1488 + return rs.getInt(1);
135.1489 + }
135.1490 + else
135.1491 + {
135.1492 + Log.get().warning("Count on postings return nothing!");
135.1493 + return 0;
135.1494 + }
135.1495 + }
135.1496 + catch(SQLException ex)
135.1497 + {
135.1498 + restartConnection(ex);
135.1499 + return getPostingsCount(groupname);
135.1500 + }
135.1501 + finally
135.1502 + {
135.1503 + if(rs != null)
135.1504 + {
135.1505 + try
135.1506 + {
135.1507 + rs.close();
135.1508 + }
135.1509 + catch(SQLException ex)
135.1510 + {
135.1511 + ex.printStackTrace();
135.1512 + }
135.1513 + }
135.1514 + }
135.1515 + }
135.1516 +
135.1517 + @Override
135.1518 + public List<Subscription> getSubscriptions(int feedtype)
135.1519 + throws StorageBackendException
135.1520 + {
135.1521 + ResultSet rs = null;
135.1522 +
135.1523 + try
135.1524 + {
135.1525 + List<Subscription> subs = new ArrayList<Subscription>();
135.1526 + this.pstmtGetSubscriptions.setInt(1, feedtype);
135.1527 + rs = this.pstmtGetSubscriptions.executeQuery();
135.1528 +
135.1529 + while(rs.next())
135.1530 + {
135.1531 + String host = rs.getString("host");
135.1532 + String group = rs.getString("name");
135.1533 + int port = rs.getInt("port");
135.1534 + subs.add(new Subscription(host, port, feedtype, group));
135.1535 + }
135.1536 +
135.1537 + return subs;
135.1538 + }
135.1539 + catch(SQLException ex)
135.1540 + {
135.1541 + restartConnection(ex);
135.1542 + return getSubscriptions(feedtype);
135.1543 + }
135.1544 + finally
135.1545 + {
135.1546 + if(rs != null)
135.1547 + {
135.1548 + try
135.1549 + {
135.1550 + rs.close();
135.1551 + }
135.1552 + catch(SQLException ex)
135.1553 + {
135.1554 + ex.printStackTrace();
135.1555 + }
135.1556 + }
135.1557 + }
135.1558 + }
135.1559 +
135.1560 + /**
135.1561 + * Checks if there is an article with the given messageid in the JDBCDatabase.
135.1562 + * @param name
135.1563 + * @return
135.1564 + * @throws StorageBackendException
135.1565 + */
135.1566 + @Override
135.1567 + public boolean isArticleExisting(String messageID)
135.1568 + throws StorageBackendException
135.1569 + {
135.1570 + ResultSet rs = null;
135.1571 +
135.1572 + try
135.1573 + {
135.1574 + this.pstmtIsArticleExisting.setString(1, messageID);
135.1575 + rs = this.pstmtIsArticleExisting.executeQuery();
135.1576 + return rs.next() && rs.getInt(1) == 1;
135.1577 + }
135.1578 + catch(SQLException ex)
135.1579 + {
135.1580 + restartConnection(ex);
135.1581 + return isArticleExisting(messageID);
135.1582 + }
135.1583 + finally
135.1584 + {
135.1585 + if(rs != null)
135.1586 + {
135.1587 + try
135.1588 + {
135.1589 + rs.close();
135.1590 + }
135.1591 + catch(SQLException ex)
135.1592 + {
135.1593 + ex.printStackTrace();
135.1594 + }
135.1595 + }
135.1596 + }
135.1597 + }
135.1598 +
135.1599 + /**
135.1600 + * Checks if there is a group with the given name in the JDBCDatabase.
135.1601 + * @param name
135.1602 + * @return
135.1603 + * @throws StorageBackendException
135.1604 + */
135.1605 + @Override
135.1606 + public boolean isGroupExisting(String name)
135.1607 + throws StorageBackendException
135.1608 + {
135.1609 + ResultSet rs = null;
135.1610 +
135.1611 + try
135.1612 + {
135.1613 + this.pstmtIsGroupExisting.setString(1, name);
135.1614 + rs = this.pstmtIsGroupExisting.executeQuery();
135.1615 + return rs.next();
135.1616 + }
135.1617 + catch(SQLException ex)
135.1618 + {
135.1619 + restartConnection(ex);
135.1620 + return isGroupExisting(name);
135.1621 + }
135.1622 + finally
135.1623 + {
135.1624 + if(rs != null)
135.1625 + {
135.1626 + try
135.1627 + {
135.1628 + rs.close();
135.1629 + }
135.1630 + catch(SQLException ex)
135.1631 + {
135.1632 + ex.printStackTrace();
135.1633 + }
135.1634 + }
135.1635 + }
135.1636 + }
135.1637 +
135.1638 + @Override
135.1639 + public void setConfigValue(String key, String value)
135.1640 + throws StorageBackendException
135.1641 + {
135.1642 + try
135.1643 + {
135.1644 + conn.setAutoCommit(false);
135.1645 + this.pstmtSetConfigValue0.setString(1, key);
135.1646 + this.pstmtSetConfigValue0.execute();
135.1647 + this.pstmtSetConfigValue1.setString(1, key);
135.1648 + this.pstmtSetConfigValue1.setString(2, value);
135.1649 + this.pstmtSetConfigValue1.execute();
135.1650 + conn.commit();
135.1651 + conn.setAutoCommit(true);
135.1652 + }
135.1653 + catch(SQLException ex)
135.1654 + {
135.1655 + restartConnection(ex);
135.1656 + setConfigValue(key, value);
135.1657 + }
135.1658 + }
135.1659 +
135.1660 + /**
135.1661 + * Closes the JDBCDatabase connection.
135.1662 + */
135.1663 + public void shutdown()
135.1664 + throws StorageBackendException
135.1665 + {
135.1666 + try
135.1667 + {
135.1668 + if(this.conn != null)
135.1669 + {
135.1670 + this.conn.close();
135.1671 + }
135.1672 + }
135.1673 + catch(SQLException ex)
135.1674 + {
135.1675 + throw new StorageBackendException(ex);
135.1676 + }
135.1677 + }
135.1678 +
135.1679 + @Override
135.1680 + public void purgeGroup(Group group)
135.1681 + throws StorageBackendException
135.1682 + {
135.1683 + try
135.1684 + {
135.1685 + this.pstmtPurgeGroup0.setLong(1, group.getInternalID());
135.1686 + this.pstmtPurgeGroup0.executeUpdate();
135.1687 +
135.1688 + this.pstmtPurgeGroup1.setLong(1, group.getInternalID());
135.1689 + this.pstmtPurgeGroup1.executeUpdate();
135.1690 + }
135.1691 + catch(SQLException ex)
135.1692 + {
135.1693 + restartConnection(ex);
135.1694 + purgeGroup(group);
135.1695 + }
135.1696 + }
135.1697 +
135.1698 + private void restartConnection(SQLException cause)
135.1699 + throws StorageBackendException
135.1700 + {
135.1701 + restarts++;
135.1702 + Log.get().severe(Thread.currentThread()
135.1703 + + ": Database connection was closed (restart " + restarts + ").");
135.1704 +
135.1705 + if(restarts >= MAX_RESTARTS)
135.1706 + {
135.1707 + // Delete the current, probably broken JDBCDatabase instance.
135.1708 + // So no one can use the instance any more.
135.1709 + JDBCDatabaseProvider.instances.remove(Thread.currentThread());
135.1710 +
135.1711 + // Throw the exception upwards
135.1712 + throw new StorageBackendException(cause);
135.1713 + }
135.1714 +
135.1715 + try
135.1716 + {
135.1717 + Thread.sleep(1500L * restarts);
135.1718 + }
135.1719 + catch(InterruptedException ex)
135.1720 + {
135.1721 + Log.get().warning("Interrupted: " + ex.getMessage());
135.1722 + }
135.1723 +
135.1724 + // Try to properly close the old database connection
135.1725 + try
135.1726 + {
135.1727 + if(this.conn != null)
135.1728 + {
135.1729 + this.conn.close();
135.1730 + }
135.1731 + }
135.1732 + catch(SQLException ex)
135.1733 + {
135.1734 + Log.get().warning(ex.getMessage());
135.1735 + }
135.1736 +
135.1737 + try
135.1738 + {
135.1739 + // Try to reinitialize database connection
135.1740 + arise();
135.1741 + }
135.1742 + catch(SQLException ex)
135.1743 + {
135.1744 + Log.get().warning(ex.getMessage());
135.1745 + restartConnection(ex);
135.1746 + }
135.1747 + }
135.1748 +
135.1749 + @Override
135.1750 + public boolean update(Article article)
135.1751 + throws StorageBackendException
135.1752 + {
135.1753 + // DELETE FROM headers WHERE article_id = ?
135.1754 +
135.1755 + // INSERT INTO headers ...
135.1756 +
135.1757 + // SELECT * FROM postings WHERE article_id = ? AND group_id = ?
135.1758 + return false;
135.1759 + }
135.1760 +
135.1761 + /**
135.1762 + * Writes the flags and the name of the given group to the database.
135.1763 + * @param group
135.1764 + * @throws StorageBackendException
135.1765 + */
135.1766 + @Override
135.1767 + public boolean update(Group group)
135.1768 + throws StorageBackendException
135.1769 + {
135.1770 + try
135.1771 + {
135.1772 + this.pstmtUpdateGroup.setInt(1, group.getFlags());
135.1773 + this.pstmtUpdateGroup.setString(2, group.getName());
135.1774 + this.pstmtUpdateGroup.setLong(3, group.getInternalID());
135.1775 + int rs = this.pstmtUpdateGroup.executeUpdate();
135.1776 + return rs == 1;
135.1777 + }
135.1778 + catch(SQLException ex)
135.1779 + {
135.1780 + restartConnection(ex);
135.1781 + return update(group);
135.1782 + }
135.1783 + }
135.1784 +
135.1785 +}
136.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
136.2 +++ b/src/org/sonews/storage/impl/JDBCDatabaseProvider.java Sun Aug 29 17:28:58 2010 +0200
136.3 @@ -0,0 +1,69 @@
136.4 +/*
136.5 + * SONEWS News Server
136.6 + * see AUTHORS for the list of contributors
136.7 + *
136.8 + * This program is free software: you can redistribute it and/or modify
136.9 + * it under the terms of the GNU General Public License as published by
136.10 + * the Free Software Foundation, either version 3 of the License, or
136.11 + * (at your option) any later version.
136.12 + *
136.13 + * This program is distributed in the hope that it will be useful,
136.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
136.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
136.16 + * GNU General Public License for more details.
136.17 + *
136.18 + * You should have received a copy of the GNU General Public License
136.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
136.20 + */
136.21 +
136.22 +package org.sonews.storage.impl;
136.23 +
136.24 +import java.sql.SQLException;
136.25 +import java.util.Map;
136.26 +import java.util.concurrent.ConcurrentHashMap;
136.27 +import org.sonews.storage.Storage;
136.28 +import org.sonews.storage.StorageBackendException;
136.29 +import org.sonews.storage.StorageProvider;
136.30 +
136.31 +/**
136.32 + *
136.33 + * @author Christian Lins
136.34 + * @since sonews/1.0
136.35 + */
136.36 +public class JDBCDatabaseProvider implements StorageProvider
136.37 +{
136.38 +
136.39 + protected static final Map<Thread, JDBCDatabase> instances
136.40 + = new ConcurrentHashMap<Thread, JDBCDatabase>();
136.41 +
136.42 + @Override
136.43 + public boolean isSupported(String uri)
136.44 + {
136.45 + throw new UnsupportedOperationException("Not supported yet.");
136.46 + }
136.47 +
136.48 + @Override
136.49 + public Storage storage(Thread thread)
136.50 + throws StorageBackendException
136.51 + {
136.52 + try
136.53 + {
136.54 + if(!instances.containsKey(Thread.currentThread()))
136.55 + {
136.56 + JDBCDatabase db = new JDBCDatabase();
136.57 + db.arise();
136.58 + instances.put(Thread.currentThread(), db);
136.59 + return db;
136.60 + }
136.61 + else
136.62 + {
136.63 + return instances.get(Thread.currentThread());
136.64 + }
136.65 + }
136.66 + catch(SQLException ex)
136.67 + {
136.68 + throw new StorageBackendException(ex);
136.69 + }
136.70 + }
136.71 +
136.72 +}
137.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
137.2 +++ b/src/org/sonews/storage/package.html Sun Aug 29 17:28:58 2010 +0200
137.3 @@ -0,0 +1,2 @@
137.4 +Contains classes of the storage backend and the Group and Article
137.5 +abstraction.
137.6 \ No newline at end of file
138.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
138.2 +++ b/src/org/sonews/util/DatabaseSetup.java Sun Aug 29 17:28:58 2010 +0200
138.3 @@ -0,0 +1,127 @@
138.4 +/*
138.5 + * SONEWS News Server
138.6 + * see AUTHORS for the list of contributors
138.7 + *
138.8 + * This program is free software: you can redistribute it and/or modify
138.9 + * it under the terms of the GNU General Public License as published by
138.10 + * the Free Software Foundation, either version 3 of the License, or
138.11 + * (at your option) any later version.
138.12 + *
138.13 + * This program is distributed in the hope that it will be useful,
138.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
138.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
138.16 + * GNU General Public License for more details.
138.17 + *
138.18 + * You should have received a copy of the GNU General Public License
138.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
138.20 + */
138.21 +
138.22 +package org.sonews.util;
138.23 +
138.24 +import java.io.BufferedReader;
138.25 +import java.io.InputStreamReader;
138.26 +import java.sql.Connection;
138.27 +import java.sql.DriverManager;
138.28 +import java.sql.Statement;
138.29 +import java.util.HashMap;
138.30 +import java.util.Map;
138.31 +import org.sonews.config.Config;
138.32 +import org.sonews.util.io.Resource;
138.33 +
138.34 +/**
138.35 + * Database setup utility class.
138.36 + * @author Christian Lins
138.37 + * @since sonews/0.5.0
138.38 + */
138.39 +public final class DatabaseSetup
138.40 +{
138.41 +
138.42 + private static final Map<String, String> templateMap
138.43 + = new HashMap<String, String>();
138.44 + private static final Map<String, StringTemplate> urlMap
138.45 + = new HashMap<String, StringTemplate>();
138.46 + private static final Map<String, String> driverMap
138.47 + = new HashMap<String, String>();
138.48 +
138.49 + static
138.50 + {
138.51 + templateMap.put("1", "helpers/database_mysql5_tmpl.sql");
138.52 + templateMap.put("2", "helpers/database_postgresql8_tmpl.sql");
138.53 +
138.54 + urlMap.put("1", new StringTemplate("jdbc:mysql://%HOSTNAME/%DB"));
138.55 + urlMap.put("2", new StringTemplate("jdbc:postgresql://%HOSTNAME/%DB"));
138.56 +
138.57 + driverMap.put("1", "com.mysql.jdbc.Driver");
138.58 + driverMap.put("2", "org.postgresql.Driver");
138.59 + }
138.60 +
138.61 + public static void main(String[] args)
138.62 + throws Exception
138.63 + {
138.64 + System.out.println("sonews Database setup helper");
138.65 + System.out.println("This program will create a initial database table structure");
138.66 + System.out.println("for the sonews Newsserver.");
138.67 + System.out.println("You need to create a database and a db user manually before!");
138.68 +
138.69 + System.out.println("Select DBMS type:");
138.70 + System.out.println("[1] MySQL 5.x or higher");
138.71 + System.out.println("[2] PostgreSQL 8.x or higher");
138.72 + System.out.print("Your choice: ");
138.73 +
138.74 + BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
138.75 + String dbmsType = in.readLine();
138.76 + String tmplName = templateMap.get(dbmsType);
138.77 + if(tmplName == null)
138.78 + {
138.79 + System.err.println("Invalid choice. Try again you fool!");
138.80 + main(args);
138.81 + return;
138.82 + }
138.83 +
138.84 + // Load JDBC Driver class
138.85 + Class.forName(driverMap.get(dbmsType));
138.86 +
138.87 + String tmpl = Resource.getAsString(tmplName, true);
138.88 +
138.89 + System.out.print("Database server hostname (e.g. localhost): ");
138.90 + String dbHostname = in.readLine();
138.91 +
138.92 + System.out.print("Database name: ");
138.93 + String dbName = in.readLine();
138.94 +
138.95 + System.out.print("Give name of DB user that can create tables: ");
138.96 + String dbUser = in.readLine();
138.97 +
138.98 + System.out.print("Password: ");
138.99 + String dbPassword = in.readLine();
138.100 +
138.101 + String url = urlMap.get(dbmsType)
138.102 + .set("HOSTNAME", dbHostname)
138.103 + .set("DB", dbName).toString();
138.104 +
138.105 + Connection conn =
138.106 + DriverManager.getConnection(url, dbUser, dbPassword);
138.107 + conn.setAutoCommit(false);
138.108 +
138.109 + String[] tmplChunks = tmpl.split(";");
138.110 +
138.111 + for(String chunk : tmplChunks)
138.112 + {
138.113 + if(chunk.trim().equals(""))
138.114 + {
138.115 + continue;
138.116 + }
138.117 +
138.118 + Statement stmt = conn.createStatement();
138.119 + stmt.execute(chunk);
138.120 + }
138.121 +
138.122 + conn.commit();
138.123 + conn.setAutoCommit(true);
138.124 +
138.125 + // Create config file
138.126 +
138.127 + System.out.println("Ok");
138.128 + }
138.129 +
138.130 +}
139.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
139.2 +++ b/src/org/sonews/util/Log.java Sun Aug 29 17:28:58 2010 +0200
139.3 @@ -0,0 +1,57 @@
139.4 +/*
139.5 + * SONEWS News Server
139.6 + * see AUTHORS for the list of contributors
139.7 + *
139.8 + * This program is free software: you can redistribute it and/or modify
139.9 + * it under the terms of the GNU General Public License as published by
139.10 + * the Free Software Foundation, either version 3 of the License, or
139.11 + * (at your option) any later version.
139.12 + *
139.13 + * This program is distributed in the hope that it will be useful,
139.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
139.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
139.16 + * GNU General Public License for more details.
139.17 + *
139.18 + * You should have received a copy of the GNU General Public License
139.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
139.20 + */
139.21 +
139.22 +package org.sonews.util;
139.23 +
139.24 +import java.util.logging.Level;
139.25 +import java.util.logging.LogManager;
139.26 +import java.util.logging.Logger;
139.27 +import java.util.logging.SimpleFormatter;
139.28 +import java.util.logging.StreamHandler;
139.29 +import org.sonews.config.Config;
139.30 +
139.31 +/**
139.32 + * Provides logging and debugging methods.
139.33 + * @author Christian Lins
139.34 + * @since sonews/0.5.0
139.35 + */
139.36 +public class Log extends Logger
139.37 +{
139.38 +
139.39 + private static Log instance = new Log();
139.40 +
139.41 + private Log()
139.42 + {
139.43 + super("org.sonews", null);
139.44 +
139.45 + StreamHandler handler = new StreamHandler(System.out, new SimpleFormatter());
139.46 + Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
139.47 + handler.setLevel(level);
139.48 + addHandler(handler);
139.49 + setLevel(level);
139.50 + LogManager.getLogManager().addLogger(this);
139.51 + }
139.52 +
139.53 + public static Logger get()
139.54 + {
139.55 + Level level = Level.parse(Config.inst().get(Config.LOGLEVEL, "INFO"));
139.56 + instance.setLevel(level);
139.57 + return instance;
139.58 + }
139.59 +
139.60 +}
140.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
140.2 +++ b/src/org/sonews/util/Pair.java Sun Aug 29 17:28:58 2010 +0200
140.3 @@ -0,0 +1,48 @@
140.4 +/*
140.5 + * SONEWS News Server
140.6 + * see AUTHORS for the list of contributors
140.7 + *
140.8 + * This program is free software: you can redistribute it and/or modify
140.9 + * it under the terms of the GNU General Public License as published by
140.10 + * the Free Software Foundation, either version 3 of the License, or
140.11 + * (at your option) any later version.
140.12 + *
140.13 + * This program is distributed in the hope that it will be useful,
140.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
140.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
140.16 + * GNU General Public License for more details.
140.17 + *
140.18 + * You should have received a copy of the GNU General Public License
140.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
140.20 + */
140.21 +
140.22 +package org.sonews.util;
140.23 +
140.24 +/**
140.25 + * A pair of two objects.
140.26 + * @author Christian Lins
140.27 + * @since sonews/0.5.0
140.28 + */
140.29 +public class Pair<T1, T2>
140.30 +{
140.31 +
140.32 + private T1 a;
140.33 + private T2 b;
140.34 +
140.35 + public Pair(T1 a, T2 b)
140.36 + {
140.37 + this.a = a;
140.38 + this.b = b;
140.39 + }
140.40 +
140.41 + public T1 getA()
140.42 + {
140.43 + return a;
140.44 + }
140.45 +
140.46 + public T2 getB()
140.47 + {
140.48 + return b;
140.49 + }
140.50 +
140.51 +}
141.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
141.2 +++ b/src/org/sonews/util/Purger.java Sun Aug 29 17:28:58 2010 +0200
141.3 @@ -0,0 +1,149 @@
141.4 +/*
141.5 + * SONEWS News Server
141.6 + * see AUTHORS for the list of contributors
141.7 + *
141.8 + * This program is free software: you can redistribute it and/or modify
141.9 + * it under the terms of the GNU General Public License as published by
141.10 + * the Free Software Foundation, either version 3 of the License, or
141.11 + * (at your option) any later version.
141.12 + *
141.13 + * This program is distributed in the hope that it will be useful,
141.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
141.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
141.16 + * GNU General Public License for more details.
141.17 + *
141.18 + * You should have received a copy of the GNU General Public License
141.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
141.20 + */
141.21 +
141.22 +package org.sonews.util;
141.23 +
141.24 +import org.sonews.daemon.AbstractDaemon;
141.25 +import org.sonews.config.Config;
141.26 +import org.sonews.storage.Article;
141.27 +import org.sonews.storage.Headers;
141.28 +import java.util.Date;
141.29 +import java.util.List;
141.30 +import org.sonews.storage.Channel;
141.31 +import org.sonews.storage.Group;
141.32 +import org.sonews.storage.StorageBackendException;
141.33 +import org.sonews.storage.StorageManager;
141.34 +
141.35 +/**
141.36 + * The purger is started in configurable intervals to search
141.37 + * for messages that can be purged. A message must be deleted if its lifetime
141.38 + * has exceeded, if it was marked as deleted or if the maximum number of
141.39 + * articles in the database is reached.
141.40 + * @author Christian Lins
141.41 + * @since sonews/0.5.0
141.42 + */
141.43 +public class Purger extends AbstractDaemon
141.44 +{
141.45 +
141.46 + /**
141.47 + * Loops through all messages and deletes them if their time
141.48 + * has come.
141.49 + */
141.50 + @Override
141.51 + public void run()
141.52 + {
141.53 + try
141.54 + {
141.55 + while(isRunning())
141.56 + {
141.57 + purgeDeleted();
141.58 + purgeOutdated();
141.59 +
141.60 + Thread.sleep(120000); // Sleep for two minutes
141.61 + }
141.62 + }
141.63 + catch(StorageBackendException ex)
141.64 + {
141.65 + ex.printStackTrace();
141.66 + }
141.67 + catch(InterruptedException ex)
141.68 + {
141.69 + Log.get().warning("Purger interrupted: " + ex);
141.70 + }
141.71 + }
141.72 +
141.73 + private void purgeDeleted()
141.74 + throws StorageBackendException
141.75 + {
141.76 + List<Channel> groups = StorageManager.current().getGroups();
141.77 + for(Channel channel : groups)
141.78 + {
141.79 + if(!(channel instanceof Group))
141.80 + continue;
141.81 +
141.82 + Group group = (Group)channel;
141.83 + // Look for groups that are marked as deleted
141.84 + if(group.isDeleted())
141.85 + {
141.86 + List<Long> ids = StorageManager.current().getArticleNumbers(group.getInternalID());
141.87 + if(ids.size() == 0)
141.88 + {
141.89 + StorageManager.current().purgeGroup(group);
141.90 + Log.get().info("Group " + group.getName() + " purged.");
141.91 + }
141.92 +
141.93 + for(int n = 0; n < ids.size() && n < 10; n++)
141.94 + {
141.95 + Article art = StorageManager.current().getArticle(ids.get(n), group.getInternalID());
141.96 + StorageManager.current().delete(art.getMessageID());
141.97 + Log.get().info("Article " + art.getMessageID() + " purged.");
141.98 + }
141.99 + }
141.100 + }
141.101 + }
141.102 +
141.103 + private void purgeOutdated()
141.104 + throws InterruptedException, StorageBackendException
141.105 + {
141.106 + long articleMaximum =
141.107 + Config.inst().get("sonews.article.maxnum", Long.MAX_VALUE);
141.108 + long lifetime =
141.109 + Config.inst().get("sonews.article.lifetime", -1);
141.110 +
141.111 + if(lifetime > 0 || articleMaximum < Stats.getInstance().getNumberOfNews())
141.112 + {
141.113 + Log.get().info("Purging old messages...");
141.114 + String mid = StorageManager.current().getOldestArticle();
141.115 + if (mid == null) // No articles in the database
141.116 + {
141.117 + return;
141.118 + }
141.119 +
141.120 + Article art = StorageManager.current().getArticle(mid);
141.121 + long artDate = 0;
141.122 + String dateStr = art.getHeader(Headers.DATE)[0];
141.123 + try
141.124 + {
141.125 + artDate = Date.parse(dateStr) / 1000 / 60 / 60 / 24;
141.126 + }
141.127 + catch (IllegalArgumentException ex)
141.128 + {
141.129 + Log.get().warning("Could not parse date string: " + dateStr + " " + ex);
141.130 + }
141.131 +
141.132 + // Should we delete the message because of its age or because the
141.133 + // article maximum was reached?
141.134 + if (lifetime < 0 || artDate < (new Date().getTime() + lifetime))
141.135 + {
141.136 + StorageManager.current().delete(mid);
141.137 + System.out.println("Deleted: " + mid);
141.138 + }
141.139 + else
141.140 + {
141.141 + Thread.sleep(1000 * 60); // Wait 60 seconds
141.142 + return;
141.143 + }
141.144 + }
141.145 + else
141.146 + {
141.147 + Log.get().info("Lifetime purger is disabled");
141.148 + Thread.sleep(1000 * 60 * 30); // Wait 30 minutes
141.149 + }
141.150 + }
141.151 +
141.152 +}
142.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
142.2 +++ b/src/org/sonews/util/Stats.java Sun Aug 29 17:28:58 2010 +0200
142.3 @@ -0,0 +1,206 @@
142.4 +/*
142.5 + * SONEWS News Server
142.6 + * see AUTHORS for the list of contributors
142.7 + *
142.8 + * This program is free software: you can redistribute it and/or modify
142.9 + * it under the terms of the GNU General Public License as published by
142.10 + * the Free Software Foundation, either version 3 of the License, or
142.11 + * (at your option) any later version.
142.12 + *
142.13 + * This program is distributed in the hope that it will be useful,
142.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
142.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
142.16 + * GNU General Public License for more details.
142.17 + *
142.18 + * You should have received a copy of the GNU General Public License
142.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
142.20 + */
142.21 +
142.22 +package org.sonews.util;
142.23 +
142.24 +import java.util.Calendar;
142.25 +import org.sonews.config.Config;
142.26 +import org.sonews.storage.Channel;
142.27 +import org.sonews.storage.StorageBackendException;
142.28 +import org.sonews.storage.StorageManager;
142.29 +
142.30 +/**
142.31 + * Class that capsulates statistical data gathering.
142.32 + * @author Christian Lins
142.33 + * @since sonews/0.5.0
142.34 + */
142.35 +public final class Stats
142.36 +{
142.37 +
142.38 + public static final byte CONNECTIONS = 1;
142.39 + public static final byte POSTED_NEWS = 2;
142.40 + public static final byte GATEWAYED_NEWS = 3;
142.41 + public static final byte FEEDED_NEWS = 4;
142.42 + public static final byte MLGW_RUNSTART = 5;
142.43 + public static final byte MLGW_RUNEND = 6;
142.44 +
142.45 + private static Stats instance = new Stats();
142.46 +
142.47 + public static Stats getInstance()
142.48 + {
142.49 + return Stats.instance;
142.50 + }
142.51 +
142.52 + private Stats() {}
142.53 +
142.54 + private volatile int connectedClients = 0;
142.55 +
142.56 + /**
142.57 + * A generic method that writes event data to the storage backend.
142.58 + * If event logging is disabled with sonews.eventlog=false this method
142.59 + * simply does nothing.
142.60 + * @param type
142.61 + * @param groupname
142.62 + */
142.63 + private void addEvent(byte type, String groupname)
142.64 + {
142.65 + try
142.66 + {
142.67 + if (Config.inst().get(Config.EVENTLOG, true))
142.68 + {
142.69 +
142.70 + Channel group = Channel.getByName(groupname);
142.71 + if (group != null)
142.72 + {
142.73 + StorageManager.current().addEvent(
142.74 + System.currentTimeMillis(), type, group.getInternalID());
142.75 + }
142.76 + }
142.77 + else
142.78 + {
142.79 + Log.get().info("Group " + groupname + " does not exist.");
142.80 + }
142.81 + }
142.82 + catch (StorageBackendException ex)
142.83 + {
142.84 + ex.printStackTrace();
142.85 + }
142.86 + }
142.87 +
142.88 + public void clientConnect()
142.89 + {
142.90 + this.connectedClients++;
142.91 + }
142.92 +
142.93 + public void clientDisconnect()
142.94 + {
142.95 + this.connectedClients--;
142.96 + }
142.97 +
142.98 + public int connectedClients()
142.99 + {
142.100 + return this.connectedClients;
142.101 + }
142.102 +
142.103 + public int getNumberOfGroups()
142.104 + {
142.105 + try
142.106 + {
142.107 + return StorageManager.current().countGroups();
142.108 + }
142.109 + catch(StorageBackendException ex)
142.110 + {
142.111 + ex.printStackTrace();
142.112 + return -1;
142.113 + }
142.114 + }
142.115 +
142.116 + public int getNumberOfNews()
142.117 + {
142.118 + try
142.119 + {
142.120 + return StorageManager.current().countArticles();
142.121 + }
142.122 + catch(StorageBackendException ex)
142.123 + {
142.124 + ex.printStackTrace();
142.125 + return -1;
142.126 + }
142.127 + }
142.128 +
142.129 + public int getYesterdaysEvents(final byte eventType, final int hour,
142.130 + final Channel group)
142.131 + {
142.132 + // Determine the timestamp values for yesterday and the given hour
142.133 + Calendar cal = Calendar.getInstance();
142.134 + int year = cal.get(Calendar.YEAR);
142.135 + int month = cal.get(Calendar.MONTH);
142.136 + int dayom = cal.get(Calendar.DAY_OF_MONTH) - 1; // Yesterday
142.137 +
142.138 + cal.set(year, month, dayom, hour, 0, 0);
142.139 + long startTimestamp = cal.getTimeInMillis();
142.140 +
142.141 + cal.set(year, month, dayom, hour + 1, 0, 0);
142.142 + long endTimestamp = cal.getTimeInMillis();
142.143 +
142.144 + try
142.145 + {
142.146 + return StorageManager.current()
142.147 + .getEventsCount(eventType, startTimestamp, endTimestamp, group);
142.148 + }
142.149 + catch(StorageBackendException ex)
142.150 + {
142.151 + ex.printStackTrace();
142.152 + return -1;
142.153 + }
142.154 + }
142.155 +
142.156 + public void mailPosted(String groupname)
142.157 + {
142.158 + addEvent(POSTED_NEWS, groupname);
142.159 + }
142.160 +
142.161 + public void mailGatewayed(String groupname)
142.162 + {
142.163 + addEvent(GATEWAYED_NEWS, groupname);
142.164 + }
142.165 +
142.166 + public void mailFeeded(String groupname)
142.167 + {
142.168 + addEvent(FEEDED_NEWS, groupname);
142.169 + }
142.170 +
142.171 + public void mlgwRunStart()
142.172 + {
142.173 + addEvent(MLGW_RUNSTART, "control");
142.174 + }
142.175 +
142.176 + public void mlgwRunEnd()
142.177 + {
142.178 + addEvent(MLGW_RUNEND, "control");
142.179 + }
142.180 +
142.181 + private double perHour(int key, long gid)
142.182 + {
142.183 + try
142.184 + {
142.185 + return StorageManager.current().getEventsPerHour(key, gid);
142.186 + }
142.187 + catch(StorageBackendException ex)
142.188 + {
142.189 + ex.printStackTrace();
142.190 + return -1;
142.191 + }
142.192 + }
142.193 +
142.194 + public double postedPerHour(long gid)
142.195 + {
142.196 + return perHour(POSTED_NEWS, gid);
142.197 + }
142.198 +
142.199 + public double gatewayedPerHour(long gid)
142.200 + {
142.201 + return perHour(GATEWAYED_NEWS, gid);
142.202 + }
142.203 +
142.204 + public double feededPerHour(long gid)
142.205 + {
142.206 + return perHour(FEEDED_NEWS, gid);
142.207 + }
142.208 +
142.209 +}
143.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
143.2 +++ b/src/org/sonews/util/StringTemplate.java Sun Aug 29 17:28:58 2010 +0200
143.3 @@ -0,0 +1,97 @@
143.4 +/*
143.5 + * SONEWS News Server
143.6 + * see AUTHORS for the list of contributors
143.7 + *
143.8 + * This program is free software: you can redistribute it and/or modify
143.9 + * it under the terms of the GNU General Public License as published by
143.10 + * the Free Software Foundation, either version 3 of the License, or
143.11 + * (at your option) any later version.
143.12 + *
143.13 + * This program is distributed in the hope that it will be useful,
143.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
143.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
143.16 + * GNU General Public License for more details.
143.17 + *
143.18 + * You should have received a copy of the GNU General Public License
143.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
143.20 + */
143.21 +
143.22 +package org.sonews.util;
143.23 +
143.24 +import java.util.HashMap;
143.25 +import java.util.Map;
143.26 +
143.27 +/**
143.28 + * Class that allows simple String template handling.
143.29 + * @author Christian Lins
143.30 + * @since sonews/0.5.0
143.31 + */
143.32 +public class StringTemplate
143.33 +{
143.34 +
143.35 + private String str = null;
143.36 + private String templateDelimiter = "%";
143.37 + private Map<String, String> templateValues = new HashMap<String, String>();
143.38 +
143.39 + public StringTemplate(String str, final String templateDelimiter)
143.40 + {
143.41 + if(str == null || templateDelimiter == null)
143.42 + {
143.43 + throw new IllegalArgumentException("null arguments not allowed");
143.44 + }
143.45 +
143.46 + this.str = str;
143.47 + this.templateDelimiter = templateDelimiter;
143.48 + }
143.49 +
143.50 + public StringTemplate(String str)
143.51 + {
143.52 + this(str, "%");
143.53 + }
143.54 +
143.55 + public StringTemplate set(String template, String value)
143.56 + {
143.57 + if(template == null || value == null)
143.58 + {
143.59 + throw new IllegalArgumentException("null arguments not allowed");
143.60 + }
143.61 +
143.62 + this.templateValues.put(template, value);
143.63 + return this;
143.64 + }
143.65 +
143.66 + public StringTemplate set(String template, long value)
143.67 + {
143.68 + return set(template, Long.toString(value));
143.69 + }
143.70 +
143.71 + public StringTemplate set(String template, double value)
143.72 + {
143.73 + return set(template, Double.toString(value));
143.74 + }
143.75 +
143.76 + public StringTemplate set(String template, Object obj)
143.77 + {
143.78 + if(template == null || obj == null)
143.79 + {
143.80 + throw new IllegalArgumentException("null arguments not allowed");
143.81 + }
143.82 +
143.83 + return set(template, obj.toString());
143.84 + }
143.85 +
143.86 + @Override
143.87 + public String toString()
143.88 + {
143.89 + String ret = str;
143.90 +
143.91 + for(String key : this.templateValues.keySet())
143.92 + {
143.93 + String value = this.templateValues.get(key);
143.94 + ret = ret.replace(templateDelimiter + key, value);
143.95 + }
143.96 +
143.97 + return ret;
143.98 + }
143.99 +
143.100 +}
144.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
144.2 +++ b/src/org/sonews/util/TimeoutMap.java Sun Aug 29 17:28:58 2010 +0200
144.3 @@ -0,0 +1,145 @@
144.4 +/*
144.5 + * SONEWS News Server
144.6 + * see AUTHORS for the list of contributors
144.7 + *
144.8 + * This program is free software: you can redistribute it and/or modify
144.9 + * it under the terms of the GNU General Public License as published by
144.10 + * the Free Software Foundation, either version 3 of the License, or
144.11 + * (at your option) any later version.
144.12 + *
144.13 + * This program is distributed in the hope that it will be useful,
144.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
144.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
144.16 + * GNU General Public License for more details.
144.17 + *
144.18 + * You should have received a copy of the GNU General Public License
144.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
144.20 + */
144.21 +
144.22 +package org.sonews.util;
144.23 +
144.24 +import java.util.HashMap;
144.25 +import java.util.HashSet;
144.26 +import java.util.Map;
144.27 +import java.util.Set;
144.28 +import java.util.concurrent.ConcurrentHashMap;
144.29 +
144.30 +/**
144.31 + * Implementation of a Map that will loose its stored values after a
144.32 + * configurable amount of time.
144.33 + * This class may be used to cache config values for example.
144.34 + * @author Christian Lins
144.35 + * @since sonews/0.5.0
144.36 + */
144.37 +public class TimeoutMap<K,V> extends ConcurrentHashMap<K, V>
144.38 +{
144.39 +
144.40 + private static final long serialVersionUID = 453453467700345L;
144.41 +
144.42 + private int timeout = 60000; // 60 sec
144.43 + private transient Map<K, Long> timeoutMap = new HashMap<K, Long>();
144.44 +
144.45 + /**
144.46 + * Constructor.
144.47 + * @param timeout Timeout in milliseconds
144.48 + */
144.49 + public TimeoutMap(final int timeout)
144.50 + {
144.51 + this.timeout = timeout;
144.52 + }
144.53 +
144.54 + /**
144.55 + * Uses default timeout (60 sec).
144.56 + */
144.57 + public TimeoutMap()
144.58 + {
144.59 + }
144.60 +
144.61 + /**
144.62 + *
144.63 + * @param key
144.64 + * @return true if key is still valid.
144.65 + */
144.66 + protected boolean checkTimeOut(Object key)
144.67 + {
144.68 + synchronized(this.timeoutMap)
144.69 + {
144.70 + if(this.timeoutMap.containsKey(key))
144.71 + {
144.72 + long keytime = this.timeoutMap.get(key);
144.73 + if((System.currentTimeMillis() - keytime) < this.timeout)
144.74 + {
144.75 + return true;
144.76 + }
144.77 + else
144.78 + {
144.79 + remove(key);
144.80 + return false;
144.81 + }
144.82 + }
144.83 + else
144.84 + {
144.85 + return false;
144.86 + }
144.87 + }
144.88 + }
144.89 +
144.90 + @Override
144.91 + public boolean containsKey(Object key)
144.92 + {
144.93 + return checkTimeOut(key);
144.94 + }
144.95 +
144.96 + @Override
144.97 + public synchronized V get(Object key)
144.98 + {
144.99 + if(checkTimeOut(key))
144.100 + {
144.101 + return super.get(key);
144.102 + }
144.103 + else
144.104 + {
144.105 + return null;
144.106 + }
144.107 + }
144.108 +
144.109 + @Override
144.110 + public V put(K key, V value)
144.111 + {
144.112 + synchronized(this.timeoutMap)
144.113 + {
144.114 + removeStaleKeys();
144.115 + this.timeoutMap.put(key, System.currentTimeMillis());
144.116 + return super.put(key, value);
144.117 + }
144.118 + }
144.119 +
144.120 + /**
144.121 + * @param arg0
144.122 + * @return
144.123 + */
144.124 + @Override
144.125 + public V remove(Object arg0)
144.126 + {
144.127 + synchronized(this.timeoutMap)
144.128 + {
144.129 + this.timeoutMap.remove(arg0);
144.130 + V val = super.remove(arg0);
144.131 + return val;
144.132 + }
144.133 + }
144.134 +
144.135 + protected void removeStaleKeys()
144.136 + {
144.137 + synchronized(this.timeoutMap)
144.138 + {
144.139 + Set<Object> keySet = new HashSet<Object>(this.timeoutMap.keySet());
144.140 + for(Object key : keySet)
144.141 + {
144.142 + // The key/value is removed by the checkTimeOut() method if true
144.143 + checkTimeOut(key);
144.144 + }
144.145 + }
144.146 + }
144.147 +
144.148 +}
145.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
145.2 +++ b/src/org/sonews/util/io/ArticleInputStream.java Sun Aug 29 17:28:58 2010 +0200
145.3 @@ -0,0 +1,71 @@
145.4 +/*
145.5 + * SONEWS News Server
145.6 + * see AUTHORS for the list of contributors
145.7 + *
145.8 + * This program is free software: you can redistribute it and/or modify
145.9 + * it under the terms of the GNU General Public License as published by
145.10 + * the Free Software Foundation, either version 3 of the License, or
145.11 + * (at your option) any later version.
145.12 + *
145.13 + * This program is distributed in the hope that it will be useful,
145.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
145.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
145.16 + * GNU General Public License for more details.
145.17 + *
145.18 + * You should have received a copy of the GNU General Public License
145.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
145.20 + */
145.21 +
145.22 +package org.sonews.util.io;
145.23 +
145.24 +import java.io.ByteArrayOutputStream;
145.25 +import java.io.IOException;
145.26 +import java.io.InputStream;
145.27 +import java.io.UnsupportedEncodingException;
145.28 +import org.sonews.storage.Article;
145.29 +
145.30 +/**
145.31 + * Capsulates an Article to provide a raw InputStream.
145.32 + * @author Christian Lins
145.33 + * @since sonews/0.5.0
145.34 + */
145.35 +public class ArticleInputStream extends InputStream
145.36 +{
145.37 +
145.38 + private byte[] buf;
145.39 + private int pos = 0;
145.40 +
145.41 + public ArticleInputStream(final Article art)
145.42 + throws IOException, UnsupportedEncodingException
145.43 + {
145.44 + final ByteArrayOutputStream out = new ByteArrayOutputStream();
145.45 + out.write(art.getHeaderSource().getBytes("UTF-8"));
145.46 + out.write("\r\n\r\n".getBytes());
145.47 + out.write(art.getBody()); // Without CRLF
145.48 + out.flush();
145.49 + this.buf = out.toByteArray();
145.50 + }
145.51 +
145.52 + /**
145.53 + * This method reads one byte from the stream. The <code>pos</code>
145.54 + * counter is advanced to the next byte to be read. The byte read is
145.55 + * returned as an int in the range of 0-255. If the stream position
145.56 + * is already at the end of the buffer, no byte is read and a -1 is
145.57 + * returned in order to indicate the end of the stream.
145.58 + *
145.59 + * @return The byte read, or -1 if end of stream
145.60 + */
145.61 + @Override
145.62 + public synchronized int read()
145.63 + {
145.64 + if(pos < buf.length)
145.65 + {
145.66 + return ((int)buf[pos++]) & 0xFF;
145.67 + }
145.68 + else
145.69 + {
145.70 + return -1;
145.71 + }
145.72 + }
145.73 +
145.74 +}
146.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
146.2 +++ b/src/org/sonews/util/io/ArticleReader.java Sun Aug 29 17:28:58 2010 +0200
146.3 @@ -0,0 +1,135 @@
146.4 +/*
146.5 + * SONEWS News Server
146.6 + * see AUTHORS for the list of contributors
146.7 + *
146.8 + * This program is free software: you can redistribute it and/or modify
146.9 + * it under the terms of the GNU General Public License as published by
146.10 + * the Free Software Foundation, either version 3 of the License, or
146.11 + * (at your option) any later version.
146.12 + *
146.13 + * This program is distributed in the hope that it will be useful,
146.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
146.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
146.16 + * GNU General Public License for more details.
146.17 + *
146.18 + * You should have received a copy of the GNU General Public License
146.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
146.20 + */
146.21 +
146.22 +package org.sonews.util.io;
146.23 +
146.24 +import java.io.BufferedInputStream;
146.25 +import java.io.BufferedOutputStream;
146.26 +import java.io.ByteArrayOutputStream;
146.27 +import java.io.IOException;
146.28 +import java.io.InputStream;
146.29 +import java.io.UnsupportedEncodingException;
146.30 +import java.net.Socket;
146.31 +import java.net.UnknownHostException;
146.32 +import org.sonews.config.Config;
146.33 +import org.sonews.util.Log;
146.34 +
146.35 +/**
146.36 + * Reads an news article from a NNTP server.
146.37 + * @author Christian Lins
146.38 + * @since sonews/0.5.0
146.39 + */
146.40 +public class ArticleReader
146.41 +{
146.42 +
146.43 + private BufferedOutputStream out;
146.44 + private BufferedInputStream in;
146.45 + private String messageID;
146.46 +
146.47 + public ArticleReader(String host, int port, String messageID)
146.48 + throws IOException, UnknownHostException
146.49 + {
146.50 + this.messageID = messageID;
146.51 +
146.52 + // Connect to NNTP server
146.53 + Socket socket = new Socket(host, port);
146.54 + this.out = new BufferedOutputStream(socket.getOutputStream());
146.55 + this.in = new BufferedInputStream(socket.getInputStream());
146.56 + String line = readln(this.in);
146.57 + if(!line.startsWith("200 "))
146.58 + {
146.59 + throw new IOException("Invalid hello from server: " + line);
146.60 + }
146.61 + }
146.62 +
146.63 + private boolean eofArticle(byte[] buf)
146.64 + {
146.65 + if(buf.length < 4)
146.66 + {
146.67 + return false;
146.68 + }
146.69 +
146.70 + int l = buf.length - 1;
146.71 + return buf[l-3] == 10 // '*\n'
146.72 + && buf[l-2] == '.' // '.'
146.73 + && buf[l-1] == 13 && buf[l] == 10; // '\r\n'
146.74 + }
146.75 +
146.76 + public byte[] getArticleData()
146.77 + throws IOException, UnsupportedEncodingException
146.78 + {
146.79 + long maxSize = Config.inst().get(Config.ARTICLE_MAXSIZE, 1024) * 1024L;
146.80 +
146.81 + try
146.82 + {
146.83 + this.out.write(("ARTICLE " + this.messageID + "\r\n").getBytes("UTF-8"));
146.84 + this.out.flush();
146.85 +
146.86 + String line = readln(this.in);
146.87 + if(line.startsWith("220 "))
146.88 + {
146.89 + ByteArrayOutputStream buf = new ByteArrayOutputStream();
146.90 +
146.91 + while(!eofArticle(buf.toByteArray()))
146.92 + {
146.93 + for(int b = in.read(); b != 10; b = in.read())
146.94 + {
146.95 + buf.write(b);
146.96 + }
146.97 +
146.98 + buf.write(10);
146.99 + if(buf.size() > maxSize)
146.100 + {
146.101 + Log.get().warning("Skipping message that is too large: " + buf.size());
146.102 + return null;
146.103 + }
146.104 + }
146.105 +
146.106 + return buf.toByteArray();
146.107 + }
146.108 + else
146.109 + {
146.110 + Log.get().warning("ArticleReader: " + line);
146.111 + return null;
146.112 + }
146.113 + }
146.114 + catch(IOException ex)
146.115 + {
146.116 + throw ex;
146.117 + }
146.118 + finally
146.119 + {
146.120 + this.out.write("QUIT\r\n".getBytes("UTF-8"));
146.121 + this.out.flush();
146.122 + this.out.close();
146.123 + }
146.124 + }
146.125 +
146.126 + private String readln(InputStream in)
146.127 + throws IOException
146.128 + {
146.129 + ByteArrayOutputStream buf = new ByteArrayOutputStream();
146.130 + for(int b = in.read(); b != 10 /* \n */; b = in.read())
146.131 + {
146.132 + buf.write(b);
146.133 + }
146.134 +
146.135 + return new String(buf.toByteArray());
146.136 + }
146.137 +
146.138 +}
147.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
147.2 +++ b/src/org/sonews/util/io/ArticleWriter.java Sun Aug 29 17:28:58 2010 +0200
147.3 @@ -0,0 +1,133 @@
147.4 +/*
147.5 + * SONEWS News Server
147.6 + * see AUTHORS for the list of contributors
147.7 + *
147.8 + * This program is free software: you can redistribute it and/or modify
147.9 + * it under the terms of the GNU General Public License as published by
147.10 + * the Free Software Foundation, either version 3 of the License, or
147.11 + * (at your option) any later version.
147.12 + *
147.13 + * This program is distributed in the hope that it will be useful,
147.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
147.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
147.16 + * GNU General Public License for more details.
147.17 + *
147.18 + * You should have received a copy of the GNU General Public License
147.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
147.20 + */
147.21 +
147.22 +package org.sonews.util.io;
147.23 +
147.24 +import java.io.BufferedOutputStream;
147.25 +import java.io.BufferedReader;
147.26 +import java.io.IOException;
147.27 +import java.io.InputStreamReader;
147.28 +import java.io.UnsupportedEncodingException;
147.29 +import java.net.Socket;
147.30 +import java.net.UnknownHostException;
147.31 +import org.sonews.storage.Article;
147.32 +
147.33 +/**
147.34 + * Posts an Article to a NNTP server using the POST command.
147.35 + * @author Christian Lins
147.36 + * @since sonews/0.5.0
147.37 + */
147.38 +public class ArticleWriter
147.39 +{
147.40 +
147.41 + private BufferedOutputStream out;
147.42 + private BufferedReader inr;
147.43 +
147.44 + public ArticleWriter(String host, int port)
147.45 + throws IOException, UnknownHostException
147.46 + {
147.47 + // Connect to NNTP server
147.48 + Socket socket = new Socket(host, port);
147.49 + this.out = new BufferedOutputStream(socket.getOutputStream());
147.50 + this.inr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
147.51 + String line = inr.readLine();
147.52 + if(line == null || !line.startsWith("200 "))
147.53 + {
147.54 + throw new IOException("Invalid hello from server: " + line);
147.55 + }
147.56 + }
147.57 +
147.58 + public void close()
147.59 + throws IOException, UnsupportedEncodingException
147.60 + {
147.61 + this.out.write("QUIT\r\n".getBytes("UTF-8"));
147.62 + this.out.flush();
147.63 + }
147.64 +
147.65 + protected void finishPOST()
147.66 + throws IOException
147.67 + {
147.68 + this.out.write("\r\n.\r\n".getBytes());
147.69 + this.out.flush();
147.70 + String line = inr.readLine();
147.71 + if(line == null || !line.startsWith("240 ") || !line.startsWith("441 "))
147.72 + {
147.73 + throw new IOException(line);
147.74 + }
147.75 + }
147.76 +
147.77 + protected void preparePOST()
147.78 + throws IOException
147.79 + {
147.80 + this.out.write("POST\r\n".getBytes("UTF-8"));
147.81 + this.out.flush();
147.82 +
147.83 + String line = this.inr.readLine();
147.84 + if(line == null || !line.startsWith("340 "))
147.85 + {
147.86 + throw new IOException(line);
147.87 + }
147.88 + }
147.89 +
147.90 + public void writeArticle(Article article)
147.91 + throws IOException, UnsupportedEncodingException
147.92 + {
147.93 + byte[] buf = new byte[512];
147.94 + ArticleInputStream in = new ArticleInputStream(article);
147.95 +
147.96 + preparePOST();
147.97 +
147.98 + int len = in.read(buf);
147.99 + while(len != -1)
147.100 + {
147.101 + writeLine(buf, len);
147.102 + len = in.read(buf);
147.103 + }
147.104 +
147.105 + finishPOST();
147.106 + }
147.107 +
147.108 + /**
147.109 + * Writes the raw content of an article to the remote server. This method
147.110 + * does no charset conversion/handling of any kind so its the preferred
147.111 + * method for sending an article to remote peers.
147.112 + * @param rawArticle
147.113 + * @throws IOException
147.114 + */
147.115 + public void writeArticle(byte[] rawArticle)
147.116 + throws IOException
147.117 + {
147.118 + preparePOST();
147.119 + writeLine(rawArticle, rawArticle.length);
147.120 + finishPOST();
147.121 + }
147.122 +
147.123 + /**
147.124 + * Writes the given buffer to the connect remote server.
147.125 + * @param buffer
147.126 + * @param len
147.127 + * @throws IOException
147.128 + */
147.129 + protected void writeLine(byte[] buffer, int len)
147.130 + throws IOException
147.131 + {
147.132 + this.out.write(buffer, 0, len);
147.133 + this.out.flush();
147.134 + }
147.135 +
147.136 +}
148.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
148.2 +++ b/src/org/sonews/util/io/Resource.java Sun Aug 29 17:28:58 2010 +0200
148.3 @@ -0,0 +1,132 @@
148.4 +/*
148.5 + * SONEWS News Server
148.6 + * see AUTHORS for the list of contributors
148.7 + *
148.8 + * This program is free software: you can redistribute it and/or modify
148.9 + * it under the terms of the GNU General Public License as published by
148.10 + * the Free Software Foundation, either version 3 of the License, or
148.11 + * (at your option) any later version.
148.12 + *
148.13 + * This program is distributed in the hope that it will be useful,
148.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
148.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
148.16 + * GNU General Public License for more details.
148.17 + *
148.18 + * You should have received a copy of the GNU General Public License
148.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
148.20 + */
148.21 +
148.22 +package org.sonews.util.io;
148.23 +
148.24 +import java.io.BufferedReader;
148.25 +import java.io.IOException;
148.26 +import java.io.InputStream;
148.27 +import java.io.InputStreamReader;
148.28 +import java.net.URL;
148.29 +import java.nio.charset.Charset;
148.30 +
148.31 +/**
148.32 + * Provides method for loading of resources.
148.33 + * @author Christian Lins
148.34 + * @since sonews/0.5.0
148.35 + */
148.36 +public final class Resource
148.37 +{
148.38 +
148.39 + /**
148.40 + * Loads a resource and returns it as URL reference.
148.41 + * The Resource's classloader is used to load the resource, not
148.42 + * the System's ClassLoader so it may be safe to use this method
148.43 + * in a sandboxed environment.
148.44 + * @return
148.45 + */
148.46 + public static URL getAsURL(final String name)
148.47 + {
148.48 + if(name == null)
148.49 + {
148.50 + return null;
148.51 + }
148.52 +
148.53 + return Resource.class.getClassLoader().getResource(name);
148.54 + }
148.55 +
148.56 + /**
148.57 + * Loads a resource and returns an InputStream to it.
148.58 + * @param name
148.59 + * @return
148.60 + */
148.61 + public static InputStream getAsStream(String name)
148.62 + {
148.63 + try
148.64 + {
148.65 + URL url = getAsURL(name);
148.66 + if(url == null)
148.67 + {
148.68 + return null;
148.69 + }
148.70 + else
148.71 + {
148.72 + return url.openStream();
148.73 + }
148.74 + }
148.75 + catch(IOException e)
148.76 + {
148.77 + e.printStackTrace();
148.78 + return null;
148.79 + }
148.80 + }
148.81 +
148.82 + /**
148.83 + * Loads a plain text resource.
148.84 + * @param withNewline If false all newlines are removed from the
148.85 + * return String
148.86 + */
148.87 + public static String getAsString(String name, boolean withNewline)
148.88 + {
148.89 + if(name == null)
148.90 + return null;
148.91 +
148.92 + BufferedReader in = null;
148.93 + try
148.94 + {
148.95 + InputStream ins = getAsStream(name);
148.96 + if(ins == null)
148.97 + return null;
148.98 +
148.99 + in = new BufferedReader(
148.100 + new InputStreamReader(ins, Charset.forName("UTF-8")));
148.101 + StringBuffer buf = new StringBuffer();
148.102 +
148.103 + for(;;)
148.104 + {
148.105 + String line = in.readLine();
148.106 + if(line == null)
148.107 + break;
148.108 +
148.109 + buf.append(line);
148.110 + if(withNewline)
148.111 + buf.append('\n');
148.112 + }
148.113 +
148.114 + return buf.toString();
148.115 + }
148.116 + catch(Exception e)
148.117 + {
148.118 + e.printStackTrace();
148.119 + return null;
148.120 + }
148.121 + finally
148.122 + {
148.123 + try
148.124 + {
148.125 + if(in != null)
148.126 + in.close();
148.127 + }
148.128 + catch(IOException ex)
148.129 + {
148.130 + ex.printStackTrace();
148.131 + }
148.132 + }
148.133 + }
148.134 +
148.135 +}
149.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
149.2 +++ b/src/org/sonews/util/io/package.html Sun Aug 29 17:28:58 2010 +0200
149.3 @@ -0,0 +1,1 @@
149.4 +Contains I/O utilitiy classes.
149.5 \ No newline at end of file
150.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
150.2 +++ b/src/org/sonews/util/package.html Sun Aug 29 17:28:58 2010 +0200
150.3 @@ -0,0 +1,1 @@
150.4 +Contains various utility classes.
150.5 \ No newline at end of file