1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/src/org/sonews/storage/Article.java Sun Aug 29 17:28:58 2010 +0200
1.3 @@ -0,0 +1,253 @@
1.4 +/*
1.5 + * SONEWS News Server
1.6 + * see AUTHORS for the list of contributors
1.7 + *
1.8 + * This program is free software: you can redistribute it and/or modify
1.9 + * it under the terms of the GNU General Public License as published by
1.10 + * the Free Software Foundation, either version 3 of the License, or
1.11 + * (at your option) any later version.
1.12 + *
1.13 + * This program is distributed in the hope that it will be useful,
1.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.16 + * GNU General Public License for more details.
1.17 + *
1.18 + * You should have received a copy of the GNU General Public License
1.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
1.20 + */
1.21 +
1.22 +package org.sonews.storage;
1.23 +
1.24 +import java.io.ByteArrayInputStream;
1.25 +import java.io.ByteArrayOutputStream;
1.26 +import java.io.IOException;
1.27 +import java.security.MessageDigest;
1.28 +import java.security.NoSuchAlgorithmException;
1.29 +import java.util.UUID;
1.30 +import java.util.ArrayList;
1.31 +import java.util.Enumeration;
1.32 +import java.util.List;
1.33 +import javax.mail.Header;
1.34 +import javax.mail.Message;
1.35 +import javax.mail.MessagingException;
1.36 +import javax.mail.internet.InternetHeaders;
1.37 +import org.sonews.config.Config;
1.38 +
1.39 +/**
1.40 + * Represents a newsgroup article.
1.41 + * @author Christian Lins
1.42 + * @author Denis Schwerdel
1.43 + * @since n3tpd/0.1
1.44 + */
1.45 +public class Article extends ArticleHead
1.46 +{
1.47 +
1.48 + /**
1.49 + * Loads the Article identified by the given ID from the JDBCDatabase.
1.50 + * @param messageID
1.51 + * @return null if Article is not found or if an error occurred.
1.52 + */
1.53 + public static Article getByMessageID(final String messageID)
1.54 + {
1.55 + try
1.56 + {
1.57 + return StorageManager.current().getArticle(messageID);
1.58 + }
1.59 + catch(StorageBackendException ex)
1.60 + {
1.61 + ex.printStackTrace();
1.62 + return null;
1.63 + }
1.64 + }
1.65 +
1.66 + private byte[] body = new byte[0];
1.67 +
1.68 + /**
1.69 + * Default constructor.
1.70 + */
1.71 + public Article()
1.72 + {
1.73 + }
1.74 +
1.75 + /**
1.76 + * Creates a new Article object using the date from the given
1.77 + * raw data.
1.78 + */
1.79 + public Article(String headers, byte[] body)
1.80 + {
1.81 + try
1.82 + {
1.83 + this.body = body;
1.84 +
1.85 + // Parse the header
1.86 + this.headers = new InternetHeaders(
1.87 + new ByteArrayInputStream(headers.getBytes()));
1.88 +
1.89 + this.headerSrc = headers;
1.90 + }
1.91 + catch(MessagingException ex)
1.92 + {
1.93 + ex.printStackTrace();
1.94 + }
1.95 + }
1.96 +
1.97 + /**
1.98 + * Creates an Article instance using the data from the javax.mail.Message
1.99 + * object. This constructor is called by the Mailinglist gateway.
1.100 + * @see javax.mail.Message
1.101 + * @param msg
1.102 + * @throws IOException
1.103 + * @throws MessagingException
1.104 + */
1.105 + public Article(final Message msg)
1.106 + throws IOException, MessagingException
1.107 + {
1.108 + this.headers = new InternetHeaders();
1.109 +
1.110 + for(Enumeration e = msg.getAllHeaders() ; e.hasMoreElements();)
1.111 + {
1.112 + final Header header = (Header)e.nextElement();
1.113 + this.headers.addHeader(header.getName(), header.getValue());
1.114 + }
1.115 +
1.116 + // Reads the raw byte body using Message.writeTo(OutputStream out)
1.117 + this.body = readContent(msg);
1.118 +
1.119 + // Validate headers
1.120 + validateHeaders();
1.121 + }
1.122 +
1.123 + /**
1.124 + * Reads from the given Message into a byte array.
1.125 + * @param in
1.126 + * @return
1.127 + * @throws IOException
1.128 + */
1.129 + private byte[] readContent(Message in)
1.130 + throws IOException, MessagingException
1.131 + {
1.132 + ByteArrayOutputStream out = new ByteArrayOutputStream();
1.133 + in.writeTo(out);
1.134 + return out.toByteArray();
1.135 + }
1.136 +
1.137 + /**
1.138 + * Removes the header identified by the given key.
1.139 + * @param headerKey
1.140 + */
1.141 + public void removeHeader(final String headerKey)
1.142 + {
1.143 + this.headers.removeHeader(headerKey);
1.144 + this.headerSrc = null;
1.145 + }
1.146 +
1.147 + /**
1.148 + * Generates a message id for this article and sets it into
1.149 + * the header object. You have to update the JDBCDatabase manually to make this
1.150 + * change persistent.
1.151 + * Note: a Message-ID should never be changed and only generated once.
1.152 + */
1.153 + private String generateMessageID()
1.154 + {
1.155 + String randomString;
1.156 + MessageDigest md5;
1.157 + try
1.158 + {
1.159 + md5 = MessageDigest.getInstance("MD5");
1.160 + md5.reset();
1.161 + md5.update(getBody());
1.162 + md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
1.163 + md5.update(getHeader(Headers.FROM)[0].getBytes());
1.164 + byte[] result = md5.digest();
1.165 + StringBuffer hexString = new StringBuffer();
1.166 + for (int i = 0; i < result.length; i++)
1.167 + {
1.168 + hexString.append(Integer.toHexString(0xFF & result[i]));
1.169 + }
1.170 + randomString = hexString.toString();
1.171 + }
1.172 + catch (NoSuchAlgorithmException e)
1.173 + {
1.174 + e.printStackTrace();
1.175 + randomString = UUID.randomUUID().toString();
1.176 + }
1.177 + String msgID = "<" + randomString + "@"
1.178 + + Config.inst().get(Config.HOSTNAME, "localhost") + ">";
1.179 +
1.180 + this.headers.setHeader(Headers.MESSAGE_ID, msgID);
1.181 +
1.182 + return msgID;
1.183 + }
1.184 +
1.185 + /**
1.186 + * Returns the body string.
1.187 + */
1.188 + public byte[] getBody()
1.189 + {
1.190 + return body;
1.191 + }
1.192 +
1.193 + /**
1.194 + * @return Numerical IDs of the newsgroups this Article belongs to.
1.195 + */
1.196 + public List<Group> getGroups()
1.197 + {
1.198 + String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
1.199 + ArrayList<Group> groups = new ArrayList<Group>();
1.200 +
1.201 + try
1.202 + {
1.203 + for(String newsgroup : groupnames)
1.204 + {
1.205 + newsgroup = newsgroup.trim();
1.206 + Group group = StorageManager.current().getGroup(newsgroup);
1.207 + if(group != null && // If the server does not provide the group, ignore it
1.208 + !groups.contains(group)) // Yes, there may be duplicates
1.209 + {
1.210 + groups.add(group);
1.211 + }
1.212 + }
1.213 + }
1.214 + catch(StorageBackendException ex)
1.215 + {
1.216 + ex.printStackTrace();
1.217 + return null;
1.218 + }
1.219 + return groups;
1.220 + }
1.221 +
1.222 + public void setBody(byte[] body)
1.223 + {
1.224 + this.body = body;
1.225 + }
1.226 +
1.227 + /**
1.228 + *
1.229 + * @param groupname Name(s) of newsgroups
1.230 + */
1.231 + public void setGroup(String groupname)
1.232 + {
1.233 + this.headers.setHeader(Headers.NEWSGROUPS, groupname);
1.234 + }
1.235 +
1.236 + /**
1.237 + * Returns the Message-ID of this Article. If the appropriate header
1.238 + * is empty, a new Message-ID is created.
1.239 + * @return Message-ID of this Article.
1.240 + */
1.241 + public String getMessageID()
1.242 + {
1.243 + String[] msgID = getHeader(Headers.MESSAGE_ID);
1.244 + return msgID[0].equals("") ? generateMessageID() : msgID[0];
1.245 + }
1.246 +
1.247 + /**
1.248 + * @return String containing the Message-ID.
1.249 + */
1.250 + @Override
1.251 + public String toString()
1.252 + {
1.253 + return getMessageID();
1.254 + }
1.255 +
1.256 +}