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