3 * see AUTHORS for the list of contributors
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.
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.
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/>.
18 package org.sonews.storage;
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.acl.User;
35 import org.sonews.config.Config;
36 import org.sonews.util.Log;
39 * Represents a newsgroup article.
40 * @author Christian Lins
41 * @author Denis Schwerdel
44 public class Article extends ArticleHead {
49 * Loads the Article identified by the given ID from the JDBCDatabase.
51 * @return null if Article is not found or if an error occurred.
53 public static Article getByMessageID(final String messageID) {
55 return StorageManager.current().getArticle(messageID);
56 } catch (StorageBackendException ex) {
57 Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
61 private byte[] body = new byte[0];
64 * Default constructor.
70 * Creates a new Article object using the date from the given
73 public Article(String headers, byte[] body) {
78 this.headers = new InternetHeaders(
79 new ByteArrayInputStream(headers.getBytes()));
81 this.headerSrc = headers;
82 } catch (MessagingException ex) {
83 Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
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
93 * @throws MessagingException
95 public Article(final Message msg)
96 throws IOException, MessagingException {
97 this.headers = new InternetHeaders();
99 for (Enumeration e = msg.getAllHeaders(); e.hasMoreElements();) {
100 final Header header = (Header) e.nextElement();
101 this.headers.addHeader(header.getName(), header.getValue());
104 // Reads the raw byte body using Message.writeTo(OutputStream out)
105 this.body = readContent(msg);
112 * Reads from the given Message into a byte array.
115 * @throws IOException
117 private byte[] readContent(Message in)
118 throws IOException, MessagingException {
119 ByteArrayOutputStream out = new ByteArrayOutputStream();
121 return out.toByteArray();
125 * Removes the header identified by the given key.
128 public void removeHeader(final String headerKey) {
129 this.headers.removeHeader(headerKey);
130 this.headerSrc = null;
134 * Generates a message id for this article and sets it into
135 * the header object. You have to update the JDBCDatabase manually to make this
137 * Note: a Message-ID should never be changed and only generated once.
139 private String generateMessageID() {
143 md5 = MessageDigest.getInstance("MD5");
145 md5.update(getBody());
146 md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
147 md5.update(getHeader(Headers.FROM)[0].getBytes());
148 byte[] result = md5.digest();
149 StringBuilder hexString = new StringBuilder();
150 for (int i = 0; i < result.length; i++) {
151 hexString.append(Integer.toHexString(0xFF & result[i]));
153 randomString = hexString.toString();
154 } catch (NoSuchAlgorithmException ex) {
155 Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
156 randomString = UUID.randomUUID().toString();
158 String msgID = "<" + randomString + "@"
159 + Config.inst().get(Config.HOSTNAME, "localhost") + ">";
161 this.headers.setHeader(Headers.MESSAGE_ID, msgID);
167 * Returns the body string.
169 public byte[] getBody() {
174 * @return Numerical IDs of the newsgroups this Article belongs to.
176 public List<Group> getGroups() {
177 String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
178 ArrayList<Group> groups = new ArrayList<Group>();
181 for (String newsgroup : groupnames) {
182 newsgroup = newsgroup.trim();
183 Group group = StorageManager.current().getGroup(newsgroup);
184 if (group != null && // If the server does not provide the group, ignore it
185 !groups.contains(group)) // Yes, there may be duplicates
190 } catch (StorageBackendException ex) {
191 Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
197 public void setBody(byte[] body) {
203 * @param groupname Name(s) of newsgroups
205 public void setGroup(String groupname) {
206 this.headers.setHeader(Headers.NEWSGROUPS, groupname);
210 * Returns the Message-ID of this Article. If the appropriate header
211 * is empty, a new Message-ID is created.
212 * @return Message-ID of this Article.
214 public String getMessageID() {
215 String[] msgID = getHeader(Headers.MESSAGE_ID);
216 return msgID[0].equals("") ? generateMessageID() : msgID[0];
220 * @return String containing the Message-ID.
223 public String toString() {
224 return getMessageID();
228 * @return sender – currently logged user – or null, if user is not authenticated.
230 public User getUser() {
235 * This method is to be called from POST Command implementation.
236 * @param sender current username – or null, if user is not authenticated.
238 public void setUser(User sender) {
239 this.sender = sender;