org/sonews/storage/Article.java
author cli
Sun Aug 29 17:03:21 2010 +0200 (2010-08-29)
changeset 33 f9bf183447d1
parent 16 5a4a41cfc0a3
permissions -rw-r--r--
Article(javax.mail.Message) now has a safe method to read the byte body from the given message object (fixes #16).
     1 /*
     2  *   SONEWS News Server
     3  *   see AUTHORS for the list of contributors
     4  *
     5  *   This program is free software: you can redistribute it and/or modify
     6  *   it under the terms of the GNU General Public License as published by
     7  *   the Free Software Foundation, either version 3 of the License, or
     8  *   (at your option) any later version.
     9  *
    10  *   This program is distributed in the hope that it will be useful,
    11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  *   GNU General Public License for more details.
    14  *
    15  *   You should have received a copy of the GNU General Public License
    16  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  */
    18 
    19 package org.sonews.storage;
    20 
    21 import java.io.ByteArrayInputStream;
    22 import java.io.ByteArrayOutputStream;
    23 import java.io.IOException;
    24 import java.security.MessageDigest;
    25 import java.security.NoSuchAlgorithmException;
    26 import java.util.UUID;
    27 import java.util.ArrayList;
    28 import java.util.Enumeration;
    29 import java.util.List;
    30 import javax.mail.Header;
    31 import javax.mail.Message;
    32 import javax.mail.MessagingException;
    33 import javax.mail.internet.InternetHeaders;
    34 import org.sonews.config.Config;
    35 
    36 /**
    37  * Represents a newsgroup article.
    38  * @author Christian Lins
    39  * @author Denis Schwerdel
    40  * @since n3tpd/0.1
    41  */
    42 public class Article extends ArticleHead
    43 {
    44   
    45   /**
    46    * Loads the Article identified by the given ID from the JDBCDatabase.
    47    * @param messageID
    48    * @return null if Article is not found or if an error occurred.
    49    */
    50   public static Article getByMessageID(final String messageID)
    51   {
    52     try
    53     {
    54       return StorageManager.current().getArticle(messageID);
    55     }
    56     catch(StorageBackendException ex)
    57     {
    58       ex.printStackTrace();
    59       return null;
    60     }
    61   }
    62   
    63   private byte[] body       = new byte[0];
    64   
    65   /**
    66    * Default constructor.
    67    */
    68   public Article()
    69   {
    70   }
    71   
    72   /**
    73    * Creates a new Article object using the date from the given
    74    * raw data.
    75    */
    76   public Article(String headers, byte[] body)
    77   {
    78     try
    79     {
    80       this.body  = body;
    81 
    82       // Parse the header
    83       this.headers = new InternetHeaders(
    84         new ByteArrayInputStream(headers.getBytes()));
    85       
    86       this.headerSrc = headers;
    87     }
    88     catch(MessagingException ex)
    89     {
    90       ex.printStackTrace();
    91     }
    92   }
    93 
    94   /**
    95    * Creates an Article instance using the data from the javax.mail.Message
    96    * object. This constructor is called by the Mailinglist gateway.
    97    * @see javax.mail.Message
    98    * @param msg
    99    * @throws IOException
   100    * @throws MessagingException
   101    */
   102   public Article(final Message msg)
   103     throws IOException, MessagingException
   104   {
   105     this.headers = new InternetHeaders();
   106 
   107     for(Enumeration e = msg.getAllHeaders() ; e.hasMoreElements();) 
   108     {
   109       final Header header = (Header)e.nextElement();
   110       this.headers.addHeader(header.getName(), header.getValue());
   111     }
   112 
   113 	// Reads the raw byte body using Message.writeTo(OutputStream out)
   114 	this.body = readContent(msg);
   115     
   116     // Validate headers
   117     validateHeaders();
   118   }
   119 
   120   /**
   121    * Reads from the given Message into a byte array.
   122    * @param in
   123    * @return
   124    * @throws IOException
   125    */
   126   private byte[] readContent(Message in)
   127     throws IOException, MessagingException
   128   {
   129     ByteArrayOutputStream out = new ByteArrayOutputStream();
   130     in.writeTo(out);
   131     return out.toByteArray();
   132   }
   133 
   134   /**
   135    * Removes the header identified by the given key.
   136    * @param headerKey
   137    */
   138   public void removeHeader(final String headerKey)
   139   {
   140     this.headers.removeHeader(headerKey);
   141     this.headerSrc = null;
   142   }
   143 
   144   /**
   145    * Generates a message id for this article and sets it into
   146    * the header object. You have to update the JDBCDatabase manually to make this
   147    * change persistent.
   148    * Note: a Message-ID should never be changed and only generated once.
   149    */
   150   private String generateMessageID()
   151   {
   152     String randomString;
   153     MessageDigest md5;
   154     try
   155     {
   156       md5 = MessageDigest.getInstance("MD5");
   157       md5.reset();
   158       md5.update(getBody());
   159       md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
   160       md5.update(getHeader(Headers.FROM)[0].getBytes());
   161       byte[] result = md5.digest();
   162       StringBuffer hexString = new StringBuffer();
   163       for (int i = 0; i < result.length; i++)
   164       {
   165         hexString.append(Integer.toHexString(0xFF & result[i]));
   166       }
   167       randomString = hexString.toString();
   168     }
   169     catch (NoSuchAlgorithmException e)
   170     {
   171       e.printStackTrace();
   172       randomString = UUID.randomUUID().toString();
   173     }
   174     String msgID = "<" + randomString + "@"
   175         + Config.inst().get(Config.HOSTNAME, "localhost") + ">";
   176     
   177     this.headers.setHeader(Headers.MESSAGE_ID, msgID);
   178     
   179     return msgID;
   180   }
   181 
   182   /**
   183    * Returns the body string.
   184    */
   185   public byte[] getBody()
   186   {
   187     return body;
   188   }
   189   
   190   /**
   191    * @return Numerical IDs of the newsgroups this Article belongs to.
   192    */
   193   public List<Group> getGroups()
   194   {
   195     String[]         groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
   196     ArrayList<Group> groups     = new ArrayList<Group>();
   197 
   198     try
   199     {
   200       for(String newsgroup : groupnames)
   201       {
   202         newsgroup = newsgroup.trim();
   203         Group group = StorageManager.current().getGroup(newsgroup);
   204         if(group != null &&         // If the server does not provide the group, ignore it
   205           !groups.contains(group))  // Yes, there may be duplicates
   206         {
   207           groups.add(group);
   208         }
   209       }
   210     }
   211     catch(StorageBackendException ex)
   212     {
   213       ex.printStackTrace();
   214       return null;
   215     }
   216     return groups;
   217   }
   218 
   219   public void setBody(byte[] body)
   220   {
   221     this.body = body;
   222   }
   223   
   224   /**
   225    * 
   226    * @param groupname Name(s) of newsgroups
   227    */
   228   public void setGroup(String groupname)
   229   {
   230     this.headers.setHeader(Headers.NEWSGROUPS, groupname);
   231   }
   232 
   233   /**
   234    * Returns the Message-ID of this Article. If the appropriate header
   235    * is empty, a new Message-ID is created.
   236    * @return Message-ID of this Article.
   237    */
   238   public String getMessageID()
   239   {
   240     String[] msgID = getHeader(Headers.MESSAGE_ID);
   241     return msgID[0].equals("") ? generateMessageID() : msgID[0];
   242   }
   243   
   244   /**
   245    * @return String containing the Message-ID.
   246    */
   247   @Override
   248   public String toString()
   249   {
   250     return getMessageID();
   251   }
   252 
   253 }