src/org/sonews/storage/Article.java
author cli
Sun Sep 11 15:05:04 2011 +0200 (2011-09-11)
changeset 48 b78e77619152
parent 35 ed84c8bdd87b
child 51 be419cf170d6
permissions -rwxr-xr-x
Merge Channel and Group classes.
     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 			return StorageManager.current().getArticle(messageID);
    54 		} catch (StorageBackendException ex) {
    55 			ex.printStackTrace();
    56 			return null;
    57 		}
    58 	}
    59 	private byte[] body = new byte[0];
    60 
    61 	/**
    62 	 * Default constructor.
    63 	 */
    64 	public Article()
    65 	{
    66 	}
    67 
    68 	/**
    69 	 * Creates a new Article object using the date from the given
    70 	 * raw data.
    71 	 */
    72 	public Article(String headers, byte[] body)
    73 	{
    74 		try {
    75 			this.body = body;
    76 
    77 			// Parse the header
    78 			this.headers = new InternetHeaders(
    79 				new ByteArrayInputStream(headers.getBytes()));
    80 
    81 			this.headerSrc = headers;
    82 		} catch (MessagingException ex) {
    83 			ex.printStackTrace();
    84 		}
    85 	}
    86 
    87 	/**
    88 	 * Creates an Article instance using the data from the javax.mail.Message
    89 	 * object. This constructor is called by the Mailinglist gateway.
    90 	 * @see javax.mail.Message
    91 	 * @param msg
    92 	 * @throws IOException
    93 	 * @throws MessagingException
    94 	 */
    95 	public Article(final Message msg)
    96 		throws IOException, MessagingException
    97 	{
    98 		this.headers = new InternetHeaders();
    99 
   100 		for (Enumeration e = msg.getAllHeaders(); e.hasMoreElements();) {
   101 			final Header header = (Header) e.nextElement();
   102 			this.headers.addHeader(header.getName(), header.getValue());
   103 		}
   104 
   105 		// Reads the raw byte body using Message.writeTo(OutputStream out)
   106 		this.body = readContent(msg);
   107 
   108 		// Validate headers
   109 		validateHeaders();
   110 	}
   111 
   112 	/**
   113 	 * Reads from the given Message into a byte array.
   114 	 * @param in
   115 	 * @return
   116 	 * @throws IOException
   117 	 */
   118 	private byte[] readContent(Message in)
   119 		throws IOException, MessagingException
   120 	{
   121 		ByteArrayOutputStream out = new ByteArrayOutputStream();
   122 		in.writeTo(out);
   123 		return out.toByteArray();
   124 	}
   125 
   126 	/**
   127 	 * Removes the header identified by the given key.
   128 	 * @param headerKey
   129 	 */
   130 	public void removeHeader(final String headerKey)
   131 	{
   132 		this.headers.removeHeader(headerKey);
   133 		this.headerSrc = null;
   134 	}
   135 
   136 	/**
   137 	 * Generates a message id for this article and sets it into
   138 	 * the header object. You have to update the JDBCDatabase manually to make this
   139 	 * change persistent.
   140 	 * Note: a Message-ID should never be changed and only generated once.
   141 	 */
   142 	private String generateMessageID()
   143 	{
   144 		String randomString;
   145 		MessageDigest md5;
   146 		try {
   147 			md5 = MessageDigest.getInstance("MD5");
   148 			md5.reset();
   149 			md5.update(getBody());
   150 			md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
   151 			md5.update(getHeader(Headers.FROM)[0].getBytes());
   152 			byte[] result = md5.digest();
   153 			StringBuffer hexString = new StringBuffer();
   154 			for (int i = 0; i < result.length; i++) {
   155 				hexString.append(Integer.toHexString(0xFF & result[i]));
   156 			}
   157 			randomString = hexString.toString();
   158 		} catch (NoSuchAlgorithmException e) {
   159 			e.printStackTrace();
   160 			randomString = UUID.randomUUID().toString();
   161 		}
   162 		String msgID = "<" + randomString + "@"
   163 			+ Config.inst().get(Config.HOSTNAME, "localhost") + ">";
   164 
   165 		this.headers.setHeader(Headers.MESSAGE_ID, msgID);
   166 
   167 		return msgID;
   168 	}
   169 
   170 	/**
   171 	 * Returns the body string.
   172 	 */
   173 	public byte[] getBody()
   174 	{
   175 		return body;
   176 	}
   177 
   178 	/**
   179 	 * @return Numerical IDs of the newsgroups this Article belongs to.
   180 	 */
   181 	public List<Group> getGroups()
   182 	{
   183 		String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
   184 		ArrayList<Group> groups = new ArrayList<Group>();
   185 
   186 		try {
   187 			for (String newsgroup : groupnames) {
   188 				newsgroup = newsgroup.trim();
   189 				Group group = StorageManager.current().getGroup(newsgroup);
   190 				if (group != null && // If the server does not provide the group, ignore it
   191 					!groups.contains(group)) // Yes, there may be duplicates
   192 				{
   193 					groups.add(group);
   194 				}
   195 			}
   196 		} catch (StorageBackendException ex) {
   197 			ex.printStackTrace();
   198 			return null;
   199 		}
   200 		return groups;
   201 	}
   202 
   203 	public void setBody(byte[] body)
   204 	{
   205 		this.body = body;
   206 	}
   207 
   208 	/**
   209 	 *
   210 	 * @param groupname Name(s) of newsgroups
   211 	 */
   212 	public void setGroup(String groupname)
   213 	{
   214 		this.headers.setHeader(Headers.NEWSGROUPS, groupname);
   215 	}
   216 
   217 	/**
   218 	 * Returns the Message-ID of this Article. If the appropriate header
   219 	 * is empty, a new Message-ID is created.
   220 	 * @return Message-ID of this Article.
   221 	 */
   222 	public String getMessageID()
   223 	{
   224 		String[] msgID = getHeader(Headers.MESSAGE_ID);
   225 		return msgID[0].equals("") ? generateMessageID() : msgID[0];
   226 	}
   227 
   228 	/**
   229 	 * @return String containing the Message-ID.
   230 	 */
   231 	@Override
   232 	public String toString()
   233 	{
   234 		return getMessageID();
   235 	}
   236 }