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.config.Config;
35 import org.sonews.util.Log;
38 * Represents a newsgroup article.
39 * @author Christian Lins
40 * @author Denis Schwerdel
43 public class Article extends ArticleHead {
45 private String authenticatedUser;
48 * Loads the Article identified by the given ID from the JDBCDatabase.
50 * @return null if Article is not found or if an error occurred.
52 public static Article getByMessageID(final String messageID) {
54 return StorageManager.current().getArticle(messageID);
55 } catch (StorageBackendException ex) {
56 Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
60 private byte[] body = new byte[0];
63 * Default constructor.
69 * Creates a new Article object using the date from the given
72 public Article(String headers, byte[] body) {
77 this.headers = new InternetHeaders(
78 new ByteArrayInputStream(headers.getBytes()));
80 this.headerSrc = headers;
81 } catch (MessagingException ex) {
82 Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
87 * Creates an Article instance using the data from the javax.mail.Message
88 * object. This constructor is called by the Mailinglist gateway.
89 * @see javax.mail.Message
92 * @throws MessagingException
94 public Article(final Message msg)
95 throws IOException, MessagingException {
96 this.headers = new InternetHeaders();
98 for (Enumeration e = msg.getAllHeaders(); e.hasMoreElements();) {
99 final Header header = (Header) e.nextElement();
100 this.headers.addHeader(header.getName(), header.getValue());
103 // Reads the raw byte body using Message.writeTo(OutputStream out)
104 this.body = readContent(msg);
111 * Reads from the given Message into a byte array.
114 * @throws IOException
116 private byte[] readContent(Message in)
117 throws IOException, MessagingException {
118 ByteArrayOutputStream out = new ByteArrayOutputStream();
120 return out.toByteArray();
124 * Removes the header identified by the given key.
127 public void removeHeader(final String headerKey) {
128 this.headers.removeHeader(headerKey);
129 this.headerSrc = null;
133 * Generates a message id for this article and sets it into
134 * the header object. You have to update the JDBCDatabase manually to make this
136 * Note: a Message-ID should never be changed and only generated once.
138 private String generateMessageID() {
142 md5 = MessageDigest.getInstance("MD5");
144 md5.update(getBody());
145 md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
146 md5.update(getHeader(Headers.FROM)[0].getBytes());
147 byte[] result = md5.digest();
148 StringBuilder hexString = new StringBuilder();
149 for (int i = 0; i < result.length; i++) {
150 hexString.append(Integer.toHexString(0xFF & result[i]));
152 randomString = hexString.toString();
153 } catch (NoSuchAlgorithmException ex) {
154 Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
155 randomString = UUID.randomUUID().toString();
157 String msgID = "<" + randomString + "@"
158 + Config.inst().get(Config.HOSTNAME, "localhost") + ">";
160 this.headers.setHeader(Headers.MESSAGE_ID, msgID);
166 * Returns the body string.
168 public byte[] getBody() {
173 * @return Numerical IDs of the newsgroups this Article belongs to.
175 public List<Group> getGroups() {
176 String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
177 ArrayList<Group> groups = new ArrayList<Group>();
180 for (String newsgroup : groupnames) {
181 newsgroup = newsgroup.trim();
182 Group group = StorageManager.current().getGroup(newsgroup);
183 if (group != null && // If the server does not provide the group, ignore it
184 !groups.contains(group)) // Yes, there may be duplicates
189 } catch (StorageBackendException ex) {
190 Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
196 public void setBody(byte[] body) {
202 * @param groupname Name(s) of newsgroups
204 public void setGroup(String groupname) {
205 this.headers.setHeader(Headers.NEWSGROUPS, groupname);
209 * Returns the Message-ID of this Article. If the appropriate header
210 * is empty, a new Message-ID is created.
211 * @return Message-ID of this Article.
213 public String getMessageID() {
214 String[] msgID = getHeader(Headers.MESSAGE_ID);
215 return msgID[0].equals("") ? generateMessageID() : msgID[0];
219 * @return String containing the Message-ID.
222 public String toString() {
223 return getMessageID();
227 * @return username of currently logged user – or null, if user is not authenticated.
229 public String getAuthenticatedUser() {
230 return authenticatedUser;
234 * This method is to be called from POST Command implementation.
235 * @param authenticatedUser current username – or null, if user is not authenticated.
237 public void setAuthenticatedUser(String authenticatedUser) {
238 this.authenticatedUser = authenticatedUser;