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