Merging fixes from sonews/1.0.3
authorcli
Thu, 20 Aug 2009 14:31:19 +0200
changeset 12bb6990c0dd1a
parent 11 961a8a3acb9a
child 13 de98fd5b35f5
Merging fixes from sonews/1.0.3
org/sonews/config/BackendConfig.java
org/sonews/config/Config.java
org/sonews/daemon/command/ListCommand.java
org/sonews/daemon/command/PostCommand.java
org/sonews/mlgw/Dispatcher.java
org/sonews/mlgw/SMTPTransport.java
org/sonews/storage/Article.java
org/sonews/storage/Headers.java
org/sonews/storage/Storage.java
org/sonews/storage/StorageBackendException.java
org/sonews/storage/impl/JDBCDatabase.java
test/unit/MLGWTests.java
test/unit/util/ResourceTest.java
test/util/mlgw/DispatcherTest.java
     1.1 --- a/org/sonews/config/BackendConfig.java	Mon Aug 17 11:00:51 2009 +0200
     1.2 +++ b/org/sonews/config/BackendConfig.java	Thu Aug 20 14:31:19 2009 +0200
     1.3 @@ -62,6 +62,12 @@
     1.4        String configValue = values.get(key);
     1.5        if(configValue == null)
     1.6        {
     1.7 +        if(StorageManager.current() == null)
     1.8 +        {
     1.9 +          Log.msg("Warning: BackendConfig not available, using default.", false);
    1.10 +          return defaultValue;
    1.11 +        }
    1.12 +
    1.13          configValue = StorageManager.current().getConfigValue(key);
    1.14          if(configValue == null)
    1.15          {
     2.1 --- a/org/sonews/config/Config.java	Mon Aug 17 11:00:51 2009 +0200
     2.2 +++ b/org/sonews/config/Config.java	Thu Aug 20 14:31:19 2009 +0200
     2.3 @@ -78,7 +78,7 @@
     2.4    /** The config key for the filename of the logfile */
     2.5    public static final String LOGFILE = "sonews.log";
     2.6  
     2.7 -    public static final String[] AVAILABLE_KEYS = {
     2.8 +  public static final String[] AVAILABLE_KEYS = {
     2.9        ARTICLE_MAXSIZE,
    2.10        EVENTLOG,
    2.11        FEED_NEWSPERRUN,
     3.1 --- a/org/sonews/daemon/command/ListCommand.java	Mon Aug 17 11:00:51 2009 +0200
     3.2 +++ b/org/sonews/daemon/command/ListCommand.java	Thu Aug 20 14:31:19 2009 +0200
     3.3 @@ -57,15 +57,15 @@
     3.4    {
     3.5      final String[] command = line.split(" ");
     3.6      
     3.7 -    if (command.length >= 2)
     3.8 +    if(command.length >= 2)
     3.9      {
    3.10 -      if (command[1].equalsIgnoreCase("OVERVIEW.FMT"))
    3.11 +      if(command[1].equalsIgnoreCase("OVERVIEW.FMT"))
    3.12        {
    3.13          conn.println("215 information follows");
    3.14          conn.println("Subject:\nFrom:\nDate:\nMessage-ID:\nReferences:\nBytes:\nLines:\nXref");
    3.15          conn.println(".");
    3.16        }
    3.17 -      else if (command[1].equalsIgnoreCase("NEWSGROUPS"))
    3.18 +      else if(command[1].equalsIgnoreCase("NEWSGROUPS"))
    3.19        {
    3.20          conn.println("215 information follows");
    3.21          final List<Channel> list = Channel.getAll();
    3.22 @@ -75,12 +75,12 @@
    3.23          }
    3.24          conn.println(".");
    3.25        }
    3.26 -      else if (command[1].equalsIgnoreCase("SUBSCRIPTIONS"))
    3.27 +      else if(command[1].equalsIgnoreCase("SUBSCRIPTIONS"))
    3.28        {
    3.29          conn.println("215 information follows");
    3.30          conn.println(".");
    3.31        }
    3.32 -      else if (command[1].equalsIgnoreCase("EXTENSIONS"))
    3.33 +      else if(command[1].equalsIgnoreCase("EXTENSIONS"))
    3.34        {
    3.35          conn.println("202 Supported NNTP extensions.");
    3.36          conn.println("LISTGROUP");
    3.37 @@ -88,6 +88,11 @@
    3.38          conn.println("XPAT");
    3.39          conn.println(".");
    3.40        }
    3.41 +      else if(command[1].equalsIgnoreCase("ACTIVE"))
    3.42 +      {
    3.43 +        // TODO: Implement wildcards for LIST ACTIVE
    3.44 +        printGroupInfo(conn);
    3.45 +      }
    3.46        else
    3.47        {
    3.48          conn.println("500 unknown argument to LIST command");
    3.49 @@ -95,26 +100,31 @@
    3.50      }
    3.51      else
    3.52      {
    3.53 -      final List<Channel> groups = Channel.getAll();
    3.54 -      if(groups != null)
    3.55 +      printGroupInfo(conn);
    3.56 +    }
    3.57 +  }
    3.58 +
    3.59 +  private void printGroupInfo(NNTPConnection conn)
    3.60 +    throws IOException, StorageBackendException
    3.61 +  {
    3.62 +    final List<Channel> groups = Channel.getAll();
    3.63 +    if (groups != null)
    3.64 +    {
    3.65 +      conn.println("215 list of newsgroups follows");
    3.66 +      for (Channel g : groups)
    3.67        {
    3.68 -        conn.println("215 list of newsgroups follows");
    3.69 -        for (Channel g : groups)
    3.70 +        if (!g.isDeleted())
    3.71          {
    3.72 -          if(!g.isDeleted())
    3.73 -          {
    3.74 -            String writeable = g.isWriteable() ? " y" : " n";
    3.75 -            // Indeed first the higher article number then the lower
    3.76 -            conn.println(g.getName() + " " + g.getLastArticleNumber() + " "
    3.77 -                + g.getFirstArticleNumber() + writeable);
    3.78 -          }
    3.79 +          String writeable = g.isWriteable() ? " y" : " n";
    3.80 +          // Indeed first the higher article number then the lower
    3.81 +          conn.println(g.getName() + " " + g.getLastArticleNumber() + " " + g.getFirstArticleNumber() + writeable);
    3.82          }
    3.83 -        conn.println(".");
    3.84        }
    3.85 -      else
    3.86 -      {
    3.87 -        conn.println("500 server database malfunction");
    3.88 -      }
    3.89 +      conn.println(".");
    3.90 +    }
    3.91 +    else
    3.92 +    {
    3.93 +      conn.println("500 server database malfunction");
    3.94      }
    3.95    }
    3.96  
     4.1 --- a/org/sonews/daemon/command/PostCommand.java	Mon Aug 17 11:00:51 2009 +0200
     4.2 +++ b/org/sonews/daemon/command/PostCommand.java	Thu Aug 20 14:31:19 2009 +0200
     4.3 @@ -21,12 +21,8 @@
     4.4  import java.io.IOException;
     4.5  import java.io.ByteArrayInputStream;
     4.6  import java.io.ByteArrayOutputStream;
     4.7 -import java.nio.charset.Charset;
     4.8 -import java.nio.charset.IllegalCharsetNameException;
     4.9 -import java.nio.charset.UnsupportedCharsetException;
    4.10  import java.sql.SQLException;
    4.11  import java.util.Arrays;
    4.12 -import java.util.Locale;
    4.13  import javax.mail.MessagingException;
    4.14  import javax.mail.internet.AddressException;
    4.15  import javax.mail.internet.InternetHeaders;
    4.16 @@ -303,7 +299,7 @@
    4.17          boolean success = false;
    4.18          String[] groupnames = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
    4.19          for(String groupname : groupnames)
    4.20 -        {
    4.21 +        {          
    4.22            Group group = StorageManager.current().getGroup(groupname);
    4.23            if(group != null && !group.isDeleted())
    4.24            {
    4.25 @@ -311,7 +307,7 @@
    4.26              {
    4.27                // Send to mailing list; the Dispatcher writes 
    4.28                // statistics to database
    4.29 -              Dispatcher.toList(article);
    4.30 +              Dispatcher.toList(article, group.getName());
    4.31                success = true;
    4.32              }
    4.33              else
     5.1 --- a/org/sonews/mlgw/Dispatcher.java	Mon Aug 17 11:00:51 2009 +0200
     5.2 +++ b/org/sonews/mlgw/Dispatcher.java	Thu Aug 20 14:31:19 2009 +0200
     5.3 @@ -21,6 +21,8 @@
     5.4  import java.io.IOException;
     5.5  import java.util.ArrayList;
     5.6  import java.util.List;
     5.7 +import java.util.regex.Matcher;
     5.8 +import java.util.regex.Pattern;
     5.9  import javax.mail.Address;
    5.10  import javax.mail.Authenticator;
    5.11  import javax.mail.Message;
    5.12 @@ -29,6 +31,7 @@
    5.13  import javax.mail.internet.InternetAddress;
    5.14  import org.sonews.config.Config;
    5.15  import org.sonews.storage.Article;
    5.16 +import org.sonews.storage.Group;
    5.17  import org.sonews.storage.Headers;
    5.18  import org.sonews.storage.StorageBackendException;
    5.19  import org.sonews.storage.StorageManager;
    5.20 @@ -36,7 +39,7 @@
    5.21  import org.sonews.util.Stats;
    5.22  
    5.23  /**
    5.24 - * Dispatches messages from mailing list or newsserver or vice versa.
    5.25 + * Dispatches messages from mailing list to newsserver or vice versa.
    5.26   * @author Christian Lins
    5.27   * @since sonews/0.5.0
    5.28   */
    5.29 @@ -58,86 +61,160 @@
    5.30      }
    5.31      
    5.32    }
    5.33 +
    5.34 +  /**
    5.35 +   * Chunks out the email address of the full List-Post header field.
    5.36 +   * @param listPostValue
    5.37 +   * @return The matching email address or null
    5.38 +   */
    5.39 +  private static String chunkListPost(String listPostValue)
    5.40 +  {
    5.41 +    // listPostValue is of form "<mailto:dev@openoffice.org>"
    5.42 +    Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
    5.43 +    Matcher mailMatcher = mailPattern.matcher(listPostValue);
    5.44 +    if(mailMatcher.find())
    5.45 +    {
    5.46 +      return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
    5.47 +    }
    5.48 +    else
    5.49 +    {
    5.50 +      return null;
    5.51 +    }
    5.52 +  }
    5.53 +
    5.54 +  /**
    5.55 +   * This method inspects the header of the given message, trying
    5.56 +   * to find the most appropriate recipient.
    5.57 +   * @param msg
    5.58 +   * @param fallback If this is false only List-Post and X-List-Post headers
    5.59 +   *                 are examined.
    5.60 +   * @return null or fitting group name for the given message.
    5.61 +   */
    5.62 +  private static List<String> getGroupFor(final Message msg, final boolean fallback)
    5.63 +    throws MessagingException, StorageBackendException
    5.64 +  {
    5.65 +    List<String> groups = null;
    5.66 +
    5.67 +    // Is there a List-Post header?
    5.68 +    String[]        listPost = msg.getHeader(Headers.LIST_POST);
    5.69 +    InternetAddress listPostAddr;
    5.70 +
    5.71 +    if(listPost == null || listPost.length == 0 || "".equals(listPost[0]))
    5.72 +    {
    5.73 +      // Is there a X-List-Post header?
    5.74 +      listPost = msg.getHeader(Headers.X_LIST_POST);
    5.75 +    }
    5.76 +
    5.77 +    if(listPost != null && listPost.length > 0 
    5.78 +      && !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null)
    5.79 +    {
    5.80 +      // listPost[0] is of form "<mailto:dev@openoffice.org>"
    5.81 +      listPost[0]  = chunkListPost(listPost[0]);
    5.82 +      listPostAddr = new InternetAddress(listPost[0], false);
    5.83 +      groups = StorageManager.current().getGroupsForList(listPostAddr);
    5.84 +    }
    5.85 +    else if(fallback)
    5.86 +    {
    5.87 +      Log.msg("Using fallback recipient discovery for: " + msg.getSubject(), true);
    5.88 +      groups = new ArrayList<String>();
    5.89 +      // Fallback to TO/CC/BCC addresses
    5.90 +      Address[] to = msg.getAllRecipients();
    5.91 +      for(Address toa : to) // Address can have '<' '>' around
    5.92 +      {
    5.93 +        if(toa instanceof InternetAddress)
    5.94 +        {
    5.95 +          List<String> g = StorageManager.current().getGroupsForList((InternetAddress) toa);
    5.96 +          groups.addAll(g);
    5.97 +        }
    5.98 +      }
    5.99 +    }
   5.100 +    
   5.101 +    return groups;
   5.102 +  }
   5.103    
   5.104    /**
   5.105     * Posts a message that was received from a mailing list to the 
   5.106     * appropriate newsgroup.
   5.107 +   * If the message already exists in the storage, this message checks
   5.108 +   * if it must be posted in an additional group. This can happen for
   5.109 +   * crosspostings in different mailing lists.
   5.110     * @param msg
   5.111     */
   5.112    public static boolean toGroup(final Message msg)
   5.113    {
   5.114      try
   5.115      {
   5.116 -      Address[] to = msg.getAllRecipients(); // includes TO/CC/BCC
   5.117 -      if(to == null || to.length <= 0)
   5.118 +      // Create new Article object
   5.119 +      Article article = new Article(msg);
   5.120 +      boolean posted  = false;
   5.121 +
   5.122 +      // Check if this mail is already existing the storage
   5.123 +      boolean updateReq = 
   5.124 +        StorageManager.current().isArticleExisting(article.getMessageID());
   5.125 +
   5.126 +      List<String> newsgroups = getGroupFor(msg, !updateReq);
   5.127 +      List<String> oldgroups  = new ArrayList<String>();
   5.128 +      if(updateReq)
   5.129        {
   5.130 -        to = msg.getReplyTo();
   5.131 +        // Check for duplicate entries of the same group
   5.132 +        Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
   5.133 +        List<Group> oldGroups = oldArticle.getGroups();
   5.134 +        for(Group oldGroup : oldGroups)
   5.135 +        {
   5.136 +          if(!newsgroups.contains(oldGroup.getName()))
   5.137 +          {
   5.138 +            oldgroups.add(oldGroup.getName());
   5.139 +          }
   5.140 +        }
   5.141        }
   5.142  
   5.143 -      if(to == null || to.length <= 0)
   5.144 +      if(newsgroups.size() > 0)
   5.145        {
   5.146 -        Log.msg("Skipping message because no recipient!", false);
   5.147 -        return false;
   5.148 +        newsgroups.addAll(oldgroups);
   5.149 +        StringBuilder groups = new StringBuilder();
   5.150 +        for(int n = 0; n < newsgroups.size(); n++)
   5.151 +        {
   5.152 +          groups.append(newsgroups.get(n));
   5.153 +          if (n + 1 != newsgroups.size())
   5.154 +          {
   5.155 +            groups.append(',');
   5.156 +          }
   5.157 +        }
   5.158 +        Log.msg("Posting to group " + groups.toString(), true);
   5.159 +
   5.160 +        article.setGroup(groups.toString());
   5.161 +        //article.removeHeader(Headers.REPLY_TO);
   5.162 +        //article.removeHeader(Headers.TO);
   5.163 +
   5.164 +        // Write article to database
   5.165 +        if(updateReq)
   5.166 +        {
   5.167 +          Log.msg("Updating " + article.getMessageID() + " with additional groups", true);
   5.168 +          StorageManager.current().delete(article.getMessageID());
   5.169 +          StorageManager.current().addArticle(article);
   5.170 +        }
   5.171 +        else
   5.172 +        {
   5.173 +          Log.msg("Gatewaying " + article.getMessageID() + " to "
   5.174 +            + article.getHeader(Headers.NEWSGROUPS)[0], true);
   5.175 +          StorageManager.current().addArticle(article);
   5.176 +          Stats.getInstance().mailGatewayed(
   5.177 +            article.getHeader(Headers.NEWSGROUPS)[0]);
   5.178 +        }
   5.179 +        posted = true;
   5.180        }
   5.181        else
   5.182        {
   5.183 -        boolean      posted     = false;
   5.184 -        List<String> newsgroups = new ArrayList<String>();
   5.185 -
   5.186 -        for (Address toa : to) // Address can have '<' '>' around
   5.187 +        StringBuilder buf = new StringBuilder();
   5.188 +        for (Address toa : msg.getAllRecipients())
   5.189          {
   5.190 -          if (toa instanceof InternetAddress)
   5.191 -          {
   5.192 -            List<String> groups = StorageManager.current()
   5.193 -              .getGroupsForList((InternetAddress)toa);
   5.194 -            newsgroups.addAll(groups);
   5.195 -          }
   5.196 +          buf.append(' ');
   5.197 +          buf.append(toa.toString());
   5.198          }
   5.199 -
   5.200 -        if (newsgroups.size() > 0)
   5.201 -        {
   5.202 -          StringBuilder groups = new StringBuilder();
   5.203 -          for(int n = 0; n < newsgroups.size(); n++)
   5.204 -          {
   5.205 -            groups.append(newsgroups.get(n));
   5.206 -            if(n + 1 != newsgroups.size())
   5.207 -            {
   5.208 -              groups.append(',');
   5.209 -            }
   5.210 -          }
   5.211 -          Log.msg("Posting to group " + groups.toString(), true);
   5.212 -
   5.213 -          // Create new Article object
   5.214 -          Article article = new Article(msg);
   5.215 -          article.setGroup(groups.toString());
   5.216 -          article.removeHeader(Headers.REPLY_TO);
   5.217 -          article.removeHeader(Headers.TO);
   5.218 -
   5.219 -          // Write article to database
   5.220 -          if(!StorageManager.current().isArticleExisting(article.getMessageID()))
   5.221 -          {
   5.222 -            StorageManager.current().addArticle(article);
   5.223 -            Stats.getInstance().mailGatewayed(
   5.224 -              article.getHeader(Headers.NEWSGROUPS)[0]);
   5.225 -          }
   5.226 -          else
   5.227 -          {
   5.228 -            Log.msg("Article " + article.getMessageID() + " already existing.", true);
   5.229 -          }
   5.230 -          posted = true;
   5.231 -        }
   5.232 -        else
   5.233 -        {
   5.234 -          StringBuilder buf = new StringBuilder();
   5.235 -          for(Address toa : to)
   5.236 -          {
   5.237 -            buf.append(' ');
   5.238 -            buf.append(toa.toString());
   5.239 -          }
   5.240 -          Log.msg("No group for" + buf.toString(), false);
   5.241 -        }
   5.242 -        return posted;
   5.243 +        buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
   5.244 +        Log.msg("No group for" + buf.toString(), false);
   5.245        }
   5.246 +      return posted;
   5.247      }
   5.248      catch(Exception ex)
   5.249      {
   5.250 @@ -148,56 +225,53 @@
   5.251    
   5.252    /**
   5.253     * Mails a message received through NNTP to the appropriate mailing list.
   5.254 +   * This method MAY be called several times by PostCommand for the same
   5.255 +   * article.
   5.256     */
   5.257 -  public static void toList(Article article)
   5.258 +  public static void toList(Article article, String group)
   5.259      throws IOException, MessagingException, StorageBackendException
   5.260    {
   5.261      // Get mailing lists for the group of this article
   5.262 -    List<String> listAddresses = new ArrayList<String>();
   5.263 -    String[]     groupnames    = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
   5.264 -    
   5.265 -    for(String groupname : groupnames)
   5.266 +    List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
   5.267 +
   5.268 +    if(rcptAddresses == null || rcptAddresses.size() == 0)
   5.269      {
   5.270 -      String listAddress = StorageManager.current().getListForGroup(groupname);
   5.271 -      if(listAddress != null)
   5.272 -      {
   5.273 -        listAddresses.add(listAddress);
   5.274 -      }
   5.275 +      Log.msg("No ML-address for " + group + " found.", false);
   5.276 +      return;
   5.277      }
   5.278  
   5.279 -    for(String listAddress : listAddresses)
   5.280 +    for(String rcptAddress : rcptAddresses)
   5.281      {
   5.282        // Compose message and send it via given SMTP-Host
   5.283        String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
   5.284 -      int    smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
   5.285 +      int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
   5.286        String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
   5.287 -      String smtpPw   = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
   5.288 +      String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
   5.289        String smtpFrom = Config.inst().get(
   5.290 -          Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
   5.291 +        Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
   5.292  
   5.293        // TODO: Make Article cloneable()
   5.294 -      String group = article.getHeader(Headers.NEWSGROUPS)[0];
   5.295        article.getMessageID(); // Make sure an ID is existing
   5.296        article.removeHeader(Headers.NEWSGROUPS);
   5.297        article.removeHeader(Headers.PATH);
   5.298        article.removeHeader(Headers.LINES);
   5.299        article.removeHeader(Headers.BYTES);
   5.300  
   5.301 -      article.setHeader("To", listAddress);
   5.302 -      article.setHeader("Reply-To", listAddress);
   5.303 +      article.setHeader("To", rcptAddress);
   5.304 +      //article.setHeader("Reply-To", listAddress);
   5.305  
   5.306 -      if(Config.inst().get(Config.MLSEND_RW_SENDER, false))
   5.307 +      if (Config.inst().get(Config.MLSEND_RW_SENDER, false))
   5.308        {
   5.309          rewriteSenderAddress(article); // Set the SENDER address
   5.310        }
   5.311  
   5.312        SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
   5.313 -      smtpTransport.send(article, smtpFrom, listAddress);
   5.314 +      smtpTransport.send(article, smtpFrom, rcptAddress);
   5.315        smtpTransport.close();
   5.316  
   5.317        Stats.getInstance().mailGatewayed(group);
   5.318 -      Log.msg("MLGateway: Mail " + article.getHeader("Subject")[0] 
   5.319 -        + " was delivered to " + listAddress + ".", true);
   5.320 +      Log.msg("MLGateway: Mail " + article.getHeader("Subject")[0]
   5.321 +        + " was delivered to " + rcptAddress + ".", true);
   5.322      }
   5.323    }
   5.324    
     6.1 --- a/org/sonews/mlgw/SMTPTransport.java	Mon Aug 17 11:00:51 2009 +0200
     6.2 +++ b/org/sonews/mlgw/SMTPTransport.java	Thu Aug 20 14:31:19 2009 +0200
     6.3 @@ -84,6 +84,10 @@
     6.4    public void send(Article article, String mailFrom, String rcptTo)
     6.5      throws IOException
     6.6    {
     6.7 +    assert(article != null);
     6.8 +    assert(mailFrom != null);
     6.9 +    assert(rcptTo != null);
    6.10 +
    6.11      this.out.println("MAIL FROM: " + mailFrom);
    6.12      this.out.flush();
    6.13      String line = this.in.readLine();
     7.1 --- a/org/sonews/storage/Article.java	Mon Aug 17 11:00:51 2009 +0200
     7.2 +++ b/org/sonews/storage/Article.java	Thu Aug 20 14:31:19 2009 +0200
     7.3 @@ -119,7 +119,7 @@
     7.4      final Object content = msg.getContent();
     7.5      if(content instanceof String)
     7.6      {
     7.7 -      this.body = ((String)content).getBytes();
     7.8 +      this.body = ((String)content).getBytes(getBodyCharset());
     7.9      }
    7.10      else if(content instanceof Multipart) // probably subclass MimeMultipart
    7.11      {
     8.1 --- a/org/sonews/storage/Headers.java	Mon Aug 17 11:00:51 2009 +0200
     8.2 +++ b/org/sonews/storage/Headers.java	Thu Aug 20 14:31:19 2009 +0200
     8.3 @@ -33,6 +33,7 @@
     8.4    public static final String DATE              = "date";
     8.5    public static final String FROM              = "from";
     8.6    public static final String LINES             = "lines";
     8.7 +  public static final String LIST_POST         = "list-post";
     8.8    public static final String MESSAGE_ID        = "message-id";
     8.9    public static final String NEWSGROUPS        = "newsgroups";
    8.10    public static final String NNTP_POSTING_DATE = "nntp-posting-date";
    8.11 @@ -45,6 +46,7 @@
    8.12    public static final String SUPERSEDES        = "subersedes";
    8.13    public static final String TO                = "to";
    8.14    public static final String X_COMPLAINTS_TO   = "x-complaints-to";
    8.15 +  public static final String X_LIST_POST       = "x-list-post";
    8.16    public static final String X_TRACE           = "x-trace";
    8.17    public static final String XREF              = "xref";
    8.18  
     9.1 --- a/org/sonews/storage/Storage.java	Mon Aug 17 11:00:51 2009 +0200
     9.2 +++ b/org/sonews/storage/Storage.java	Thu Aug 20 14:31:19 2009 +0200
     9.3 @@ -31,6 +31,11 @@
     9.4  public interface Storage
     9.5  {
     9.6  
     9.7 +  /**
     9.8 +   * Stores the given Article in the storage.
     9.9 +   * @param art
    9.10 +   * @throws StorageBackendException
    9.11 +   */
    9.12    void addArticle(Article art)
    9.13      throws StorageBackendException;
    9.14  
    9.15 @@ -93,7 +98,14 @@
    9.16    int getLastArticleNumber(Group group)
    9.17      throws StorageBackendException;
    9.18  
    9.19 -  String getListForGroup(String groupname)
    9.20 +  /**
    9.21 +   * Returns a list of email addresses that are related to the given
    9.22 +   * groupname. In most cases the list may contain only one entry.
    9.23 +   * @param groupname
    9.24 +   * @return
    9.25 +   * @throws StorageBackendException
    9.26 +   */
    9.27 +  List<String> getListsForGroup(String groupname)
    9.28      throws StorageBackendException;
    9.29  
    9.30    String getOldestArticle()
    10.1 --- a/org/sonews/storage/StorageBackendException.java	Mon Aug 17 11:00:51 2009 +0200
    10.2 +++ b/org/sonews/storage/StorageBackendException.java	Thu Aug 20 14:31:19 2009 +0200
    10.3 @@ -31,4 +31,9 @@
    10.4      super(cause);
    10.5    }
    10.6  
    10.7 +  public StorageBackendException(String msg)
    10.8 +  {
    10.9 +    super(msg);
   10.10 +  }
   10.11 +
   10.12  }
    11.1 --- a/org/sonews/storage/impl/JDBCDatabase.java	Mon Aug 17 11:00:51 2009 +0200
    11.2 +++ b/org/sonews/storage/impl/JDBCDatabase.java	Thu Aug 20 14:31:19 2009 +0200
    11.3 @@ -1142,28 +1142,27 @@
    11.4    }
    11.5  
    11.6    @Override
    11.7 -  public String getListForGroup(String group)
    11.8 +  public List<String> getListsForGroup(String group)
    11.9      throws StorageBackendException
   11.10    {
   11.11 -    ResultSet rs = null;
   11.12 +    ResultSet     rs    = null;
   11.13 +    List<String>  lists = new ArrayList<String>();
   11.14  
   11.15      try
   11.16      {
   11.17        this.pstmtGetListForGroup.setString(1, group);
   11.18        rs = this.pstmtGetListForGroup.executeQuery();
   11.19 -      if (rs.next())
   11.20 +
   11.21 +      while(rs.next())
   11.22        {
   11.23 -        return rs.getString(1);
   11.24 +        lists.add(rs.getString(1));
   11.25        }
   11.26 -      else
   11.27 -      {
   11.28 -        return null;
   11.29 -      }
   11.30 +      return lists;
   11.31      }
   11.32      catch(SQLException ex)
   11.33      {
   11.34        restartConnection(ex);
   11.35 -      return getListForGroup(group);
   11.36 +      return getListsForGroup(group);
   11.37      }
   11.38      finally
   11.39      {
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/test/unit/MLGWTests.java	Thu Aug 20 14:31:19 2009 +0200
    12.3 @@ -0,0 +1,38 @@
    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 test.unit;
   12.23 +
   12.24 +import junit.textui.TestRunner;
   12.25 +import test.util.mlgw.DispatcherTest;
   12.26 +
   12.27 +/**
   12.28 + * Unit tests for package org.sonews.mlgw.
   12.29 + * @author Christian Lins
   12.30 + * @since sonews/1.0.3
   12.31 + */
   12.32 +public class MLGWTests
   12.33 +{
   12.34 +
   12.35 +  public static void main(String[] args)
   12.36 +  {
   12.37 +    System.out.println("DispatcherTest");
   12.38 +    TestRunner.run(DispatcherTest.class);
   12.39 +  }
   12.40 +
   12.41 +}
    13.1 --- a/test/unit/util/ResourceTest.java	Mon Aug 17 11:00:51 2009 +0200
    13.2 +++ b/test/unit/util/ResourceTest.java	Thu Aug 20 14:31:19 2009 +0200
    13.3 @@ -41,7 +41,7 @@
    13.4      assertNull(url);
    13.5      
    13.6      // This file should exist
    13.7 -    url = Resource.getAsURL("org/sonews/daemon/Main.class");
    13.8 +    url = Resource.getAsURL("org/sonews/Main.class");
    13.9      assertNotNull(url);
   13.10    }
   13.11    
   13.12 @@ -55,7 +55,7 @@
   13.13      stream = Resource.getAsStream("this is bullshit");
   13.14      assertNull(stream);
   13.15      
   13.16 -    stream = Resource.getAsStream("org/sonews/daemon/Main.class");
   13.17 +    stream = Resource.getAsStream("org/sonews/Main.class");
   13.18      assertNotNull(stream);
   13.19    }
   13.20    
   13.21 @@ -69,10 +69,10 @@
   13.22      str = Resource.getAsString("this is bullshit", true);
   13.23      assertNull(str);
   13.24      
   13.25 -    str = Resource.getAsString("org/sonews/daemon/Main.class", true);
   13.26 +    str = Resource.getAsString("org/sonews/Main.class", true);
   13.27      assertNotNull(str);
   13.28      
   13.29 -    str = Resource.getAsString("org/sonews/daemon/Main.class", false);
   13.30 +    str = Resource.getAsString("org/sonews/Main.class", false);
   13.31      assertNotNull(str);
   13.32      assertEquals(str.indexOf("\n"), -1);
   13.33    }
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/test/util/mlgw/DispatcherTest.java	Thu Aug 20 14:31:19 2009 +0200
    14.3 @@ -0,0 +1,71 @@
    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 test.util.mlgw;
   14.23 +
   14.24 +import java.lang.reflect.InvocationTargetException;
   14.25 +import java.lang.reflect.Method;
   14.26 +import junit.framework.TestCase;
   14.27 +import org.sonews.mlgw.Dispatcher;
   14.28 +
   14.29 +/**
   14.30 + * Tests the methods of class org.sonews.mlgw.Dispatcher.
   14.31 + * @author Christian Lins
   14.32 + * @since sonews/1.0.3
   14.33 + */
   14.34 +public class DispatcherTest extends TestCase
   14.35 +{
   14.36 +
   14.37 +  public DispatcherTest()
   14.38 +  {
   14.39 +    super("DispatcherTest");
   14.40 +  }
   14.41 +
   14.42 +  public void testChunkListPost()
   14.43 +    throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
   14.44 +  {
   14.45 +    Dispatcher disp = new Dispatcher();
   14.46 +
   14.47 +    Class  clazz             = disp.getClass();
   14.48 +    Method methChunkListPost = clazz.getDeclaredMethod("chunkListPost", String.class);
   14.49 +    methChunkListPost.setAccessible(true);
   14.50 +
   14.51 +    try
   14.52 +    {
   14.53 +      // disp.chunkListPost(null)
   14.54 +      methChunkListPost.invoke(disp, null);
   14.55 +      fail("Should have raised an IllegalArgumentException");
   14.56 +    }
   14.57 +    catch(IllegalArgumentException ex){}
   14.58 +
   14.59 +    // disp.chunkListPost("")
   14.60 +    Object obj = methChunkListPost.invoke(disp, "");
   14.61 +    assertNull(obj);
   14.62 +
   14.63 +    // disp.chunkListPost("listPostValue is of form <mailto:dev@openoffice.org>")
   14.64 +    obj = methChunkListPost.invoke(disp, "listPostValue is of form <mailto:dev@openoffice.org>");
   14.65 +    assertNotNull(obj);
   14.66 +    assertEquals("dev@openoffice.org", (String)obj);
   14.67 +
   14.68 +    // disp.chunkListPost("<mailto:frisbee-users@fun.rec.uk.sun.com")
   14.69 +    obj = methChunkListPost.invoke(disp, "<mailto:frisbee-users@fun.rec.uk.sun.com");
   14.70 +    assertNotNull(obj);
   14.71 +    assertEquals("frisbee-users@fun.rec.uk.sun.com", (String)obj);
   14.72 +  }
   14.73 +
   14.74 +}