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