src/org/sonews/storage/Article.java
author František Kučera <franta-hg@frantovo.cz>
Wed Dec 31 12:07:40 2014 +0100 (2014-12-31)
changeset 120 bb1c8a7b774c
parent 101 d54786065fa3
permissions -rwxr-xr-x
XSLT: licence – GNU GPLv3
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;
franta-hg@117
    34
import org.sonews.acl.User;
chris@3
    35
import org.sonews.config.Config;
cli@51
    36
import org.sonews.util.Log;
chris@3
    37
chris@3
    38
/**
chris@3
    39
 * Represents a newsgroup article.
chris@3
    40
 * @author Christian Lins
chris@3
    41
 * @author Denis Schwerdel
chris@3
    42
 * @since n3tpd/0.1
chris@3
    43
 */
cli@51
    44
public class Article extends ArticleHead {
franta-hg@101
    45
	
franta-hg@117
    46
	private User sender;
chris@3
    47
cli@37
    48
	/**
cli@37
    49
	 * Loads the Article identified by the given ID from the JDBCDatabase.
cli@37
    50
	 * @param messageID
cli@37
    51
	 * @return null if Article is not found or if an error occurred.
cli@37
    52
	 */
cli@51
    53
	public static Article getByMessageID(final String messageID) {
cli@37
    54
		try {
cli@37
    55
			return StorageManager.current().getArticle(messageID);
cli@37
    56
		} catch (StorageBackendException ex) {
cli@51
    57
			Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
cli@37
    58
			return null;
cli@37
    59
		}
cli@37
    60
	}
cli@37
    61
	private byte[] body = new byte[0];
chris@3
    62
cli@37
    63
	/**
cli@37
    64
	 * Default constructor.
cli@37
    65
	 */
cli@51
    66
	public Article() {
cli@37
    67
	}
chris@3
    68
cli@37
    69
	/**
cli@37
    70
	 * Creates a new Article object using the date from the given
cli@37
    71
	 * raw data.
cli@37
    72
	 */
cli@51
    73
	public Article(String headers, byte[] body) {
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@51
    79
					new ByteArrayInputStream(headers.getBytes()));
chris@3
    80
cli@37
    81
			this.headerSrc = headers;
cli@37
    82
		} catch (MessagingException ex) {
cli@51
    83
			Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
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@51
    96
			throws IOException, MessagingException {
cli@37
    97
		this.headers = new InternetHeaders();
chris@3
    98
cli@37
    99
		for (Enumeration e = msg.getAllHeaders(); e.hasMoreElements();) {
cli@37
   100
			final Header header = (Header) e.nextElement();
cli@37
   101
			this.headers.addHeader(header.getName(), header.getValue());
cli@37
   102
		}
chris@3
   103
cli@37
   104
		// Reads the raw byte body using Message.writeTo(OutputStream out)
cli@37
   105
		this.body = readContent(msg);
chris@3
   106
cli@37
   107
		// Validate headers
cli@37
   108
		validateHeaders();
cli@37
   109
	}
chris@3
   110
cli@37
   111
	/**
cli@37
   112
	 * Reads from the given Message into a byte array.
cli@37
   113
	 * @param in
cli@37
   114
	 * @return
cli@37
   115
	 * @throws IOException
cli@37
   116
	 */
cli@37
   117
	private byte[] readContent(Message in)
cli@51
   118
			throws IOException, MessagingException {
cli@37
   119
		ByteArrayOutputStream out = new ByteArrayOutputStream();
cli@37
   120
		in.writeTo(out);
cli@37
   121
		return out.toByteArray();
cli@37
   122
	}
chris@3
   123
cli@37
   124
	/**
cli@37
   125
	 * Removes the header identified by the given key.
cli@37
   126
	 * @param headerKey
cli@37
   127
	 */
cli@51
   128
	public void removeHeader(final String headerKey) {
cli@37
   129
		this.headers.removeHeader(headerKey);
cli@37
   130
		this.headerSrc = null;
cli@37
   131
	}
chris@3
   132
cli@37
   133
	/**
cli@37
   134
	 * Generates a message id for this article and sets it into
cli@37
   135
	 * the header object. You have to update the JDBCDatabase manually to make this
cli@37
   136
	 * change persistent.
cli@37
   137
	 * Note: a Message-ID should never be changed and only generated once.
cli@37
   138
	 */
cli@51
   139
	private String generateMessageID() {
cli@37
   140
		String randomString;
cli@37
   141
		MessageDigest md5;
cli@37
   142
		try {
cli@37
   143
			md5 = MessageDigest.getInstance("MD5");
cli@37
   144
			md5.reset();
cli@37
   145
			md5.update(getBody());
cli@37
   146
			md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
cli@37
   147
			md5.update(getHeader(Headers.FROM)[0].getBytes());
cli@37
   148
			byte[] result = md5.digest();
cli@51
   149
			StringBuilder hexString = new StringBuilder();
cli@37
   150
			for (int i = 0; i < result.length; i++) {
cli@37
   151
				hexString.append(Integer.toHexString(0xFF & result[i]));
cli@37
   152
			}
cli@37
   153
			randomString = hexString.toString();
cli@51
   154
		} catch (NoSuchAlgorithmException ex) {
cli@51
   155
			Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
cli@37
   156
			randomString = UUID.randomUUID().toString();
cli@37
   157
		}
cli@37
   158
		String msgID = "<" + randomString + "@"
cli@51
   159
				+ Config.inst().get(Config.HOSTNAME, "localhost") + ">";
cli@37
   160
cli@37
   161
		this.headers.setHeader(Headers.MESSAGE_ID, msgID);
cli@37
   162
cli@37
   163
		return msgID;
cli@37
   164
	}
cli@37
   165
cli@37
   166
	/**
cli@37
   167
	 * Returns the body string.
cli@37
   168
	 */
cli@51
   169
	public byte[] getBody() {
cli@37
   170
		return body;
cli@37
   171
	}
cli@37
   172
cli@37
   173
	/**
cli@37
   174
	 * @return Numerical IDs of the newsgroups this Article belongs to.
cli@37
   175
	 */
cli@51
   176
	public List<Group> getGroups() {
cli@37
   177
		String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
cli@37
   178
		ArrayList<Group> groups = new ArrayList<Group>();
cli@37
   179
cli@37
   180
		try {
cli@37
   181
			for (String newsgroup : groupnames) {
cli@37
   182
				newsgroup = newsgroup.trim();
cli@37
   183
				Group group = StorageManager.current().getGroup(newsgroup);
cli@37
   184
				if (group != null && // If the server does not provide the group, ignore it
cli@51
   185
						!groups.contains(group)) // Yes, there may be duplicates
cli@37
   186
				{
cli@37
   187
					groups.add(group);
cli@37
   188
				}
cli@37
   189
			}
cli@37
   190
		} catch (StorageBackendException ex) {
cli@51
   191
			Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
cli@37
   192
			return null;
cli@37
   193
		}
cli@37
   194
		return groups;
cli@37
   195
	}
cli@37
   196
cli@51
   197
	public void setBody(byte[] body) {
cli@37
   198
		this.body = body;
cli@37
   199
	}
cli@37
   200
cli@37
   201
	/**
cli@37
   202
	 *
cli@37
   203
	 * @param groupname Name(s) of newsgroups
cli@37
   204
	 */
cli@51
   205
	public void setGroup(String groupname) {
cli@37
   206
		this.headers.setHeader(Headers.NEWSGROUPS, groupname);
cli@37
   207
	}
cli@37
   208
cli@37
   209
	/**
cli@37
   210
	 * Returns the Message-ID of this Article. If the appropriate header
cli@37
   211
	 * is empty, a new Message-ID is created.
cli@37
   212
	 * @return Message-ID of this Article.
cli@37
   213
	 */
cli@51
   214
	public String getMessageID() {
cli@37
   215
		String[] msgID = getHeader(Headers.MESSAGE_ID);
cli@37
   216
		return msgID[0].equals("") ? generateMessageID() : msgID[0];
cli@37
   217
	}
cli@37
   218
cli@37
   219
	/**
cli@37
   220
	 * @return String containing the Message-ID.
cli@37
   221
	 */
cli@37
   222
	@Override
cli@51
   223
	public String toString() {
cli@37
   224
		return getMessageID();
cli@37
   225
	}
franta-hg@101
   226
franta-hg@101
   227
	/**
franta-hg@117
   228
	 * @return sender – currently logged user – or null, if user is not authenticated.
franta-hg@101
   229
	 */
franta-hg@117
   230
	public User getUser() {
franta-hg@117
   231
		return sender;
franta-hg@101
   232
	}
franta-hg@101
   233
	
franta-hg@101
   234
	/**
franta-hg@101
   235
	 * This method is to be called from POST Command implementation.
franta-hg@117
   236
	 * @param sender current username – or null, if user is not authenticated.
franta-hg@101
   237
	 */
franta-hg@117
   238
	public void setUser(User sender) {
franta-hg@117
   239
		this.sender = sender;
franta-hg@101
   240
	}
chris@3
   241
}