1.1 --- a/org/sonews/daemon/storage/Article.java Wed Jul 01 10:48:22 2009 +0200
1.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1.3 @@ -1,401 +0,0 @@
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.daemon.storage;
1.23 -
1.24 -import org.sonews.daemon.Config;
1.25 -import java.io.BufferedReader;
1.26 -import java.io.ByteArrayInputStream;
1.27 -import java.io.IOException;
1.28 -import java.io.InputStream;
1.29 -import java.io.InputStreamReader;
1.30 -import java.nio.charset.Charset;
1.31 -import java.sql.SQLException;
1.32 -import java.util.UUID;
1.33 -import java.util.ArrayList;
1.34 -import java.util.Enumeration;
1.35 -import java.util.List;
1.36 -import javax.mail.Header;
1.37 -import javax.mail.Message;
1.38 -import javax.mail.MessagingException;
1.39 -import javax.mail.Multipart;
1.40 -import javax.mail.internet.InternetHeaders;
1.41 -import javax.mail.internet.MimeUtility;
1.42 -import org.sonews.util.Log;
1.43 -
1.44 -/**
1.45 - * Represents a newsgroup article.
1.46 - * @author Christian Lins
1.47 - * @author Denis Schwerdel
1.48 - * @since n3tpd/0.1
1.49 - */
1.50 -public class Article extends ArticleHead
1.51 -{
1.52 -
1.53 - /**
1.54 - * Loads the Article identified by the given ID from the Database.
1.55 - * @param messageID
1.56 - * @return null if Article is not found or if an error occurred.
1.57 - */
1.58 - public static Article getByMessageID(final String messageID)
1.59 - {
1.60 - try
1.61 - {
1.62 - return Database.getInstance().getArticle(messageID);
1.63 - }
1.64 - catch(SQLException ex)
1.65 - {
1.66 - ex.printStackTrace();
1.67 - return null;
1.68 - }
1.69 - }
1.70 -
1.71 - public static Article getByArticleNumber(long articleIndex, Group group)
1.72 - throws SQLException
1.73 - {
1.74 - return Database.getInstance().getArticle(articleIndex, group.getID());
1.75 - }
1.76 -
1.77 - private String body = "";
1.78 - private String headerSrc = null;
1.79 -
1.80 - /**
1.81 - * Default constructor.
1.82 - */
1.83 - public Article()
1.84 - {
1.85 - }
1.86 -
1.87 - /**
1.88 - * Creates a new Article object using the date from the given
1.89 - * raw data.
1.90 - * This construction has only package visibility.
1.91 - */
1.92 - Article(String headers, String body)
1.93 - {
1.94 - try
1.95 - {
1.96 - this.body = body;
1.97 -
1.98 - // Parse the header
1.99 - this.headers = new InternetHeaders(
1.100 - new ByteArrayInputStream(headers.getBytes()));
1.101 -
1.102 - this.headerSrc = headers;
1.103 - }
1.104 - catch(MessagingException ex)
1.105 - {
1.106 - ex.printStackTrace();
1.107 - }
1.108 - }
1.109 -
1.110 - /**
1.111 - * Creates an Article instance using the data from the javax.mail.Message
1.112 - * object.
1.113 - * @see javax.mail.Message
1.114 - * @param msg
1.115 - * @throws IOException
1.116 - * @throws MessagingException
1.117 - */
1.118 - public Article(final Message msg)
1.119 - throws IOException, MessagingException
1.120 - {
1.121 - this.headers = new InternetHeaders();
1.122 -
1.123 - for(Enumeration e = msg.getAllHeaders() ; e.hasMoreElements();)
1.124 - {
1.125 - final Header header = (Header)e.nextElement();
1.126 - this.headers.addHeader(header.getName(), header.getValue());
1.127 - }
1.128 -
1.129 - // The "content" of the message can be a String if it's a simple text/plain
1.130 - // message, a Multipart object or an InputStream if the content is unknown.
1.131 - final Object content = msg.getContent();
1.132 - if(content instanceof String)
1.133 - {
1.134 - this.body = (String)content;
1.135 - }
1.136 - else if(content instanceof Multipart) // probably subclass MimeMultipart
1.137 - {
1.138 - // We're are not interested in the different parts of the MultipartMessage,
1.139 - // so we simply read in all data which *can* be huge.
1.140 - InputStream in = msg.getInputStream();
1.141 - this.body = readContent(in);
1.142 - }
1.143 - else if(content instanceof InputStream)
1.144 - {
1.145 - // The message format is unknown to the Message class, but we can
1.146 - // simply read in the whole message data.
1.147 - this.body = readContent((InputStream)content);
1.148 - }
1.149 - else
1.150 - {
1.151 - // Unknown content is probably a malformed mail we should skip.
1.152 - // On the other hand we produce an inconsistent mail mirror, but no
1.153 - // mail system must transport invalid content.
1.154 - Log.msg("Skipping message due to unknown content. Throwing exception...", true);
1.155 - throw new MessagingException("Unknown content: " + content);
1.156 - }
1.157 -
1.158 - // Validate headers
1.159 - validateHeaders();
1.160 - }
1.161 -
1.162 - /**
1.163 - * Reads lines from the given InputString into a String object.
1.164 - * TODO: Move this generalized method to org.sonews.util.io.Resource.
1.165 - * @param in
1.166 - * @return
1.167 - * @throws IOException
1.168 - */
1.169 - private String readContent(InputStream in)
1.170 - throws IOException
1.171 - {
1.172 - StringBuilder buf = new StringBuilder();
1.173 -
1.174 - BufferedReader rin = new BufferedReader(new InputStreamReader(in));
1.175 - String line = rin.readLine();
1.176 - while(line != null)
1.177 - {
1.178 - buf.append('\n');
1.179 - buf.append(line);
1.180 - line = rin.readLine();
1.181 - }
1.182 -
1.183 - return buf.toString();
1.184 - }
1.185 -
1.186 - /**
1.187 - * Removes the header identified by the given key.
1.188 - * @param headerKey
1.189 - */
1.190 - public void removeHeader(final String headerKey)
1.191 - {
1.192 - this.headers.removeHeader(headerKey);
1.193 - this.headerSrc = null;
1.194 - }
1.195 -
1.196 - /**
1.197 - * Generates a message id for this article and sets it into
1.198 - * the header object. You have to update the Database manually to make this
1.199 - * change persistent.
1.200 - * Note: a Message-ID should never be changed and only generated once.
1.201 - */
1.202 - private String generateMessageID()
1.203 - {
1.204 - String msgID = "<" + UUID.randomUUID() + "@"
1.205 - + Config.getInstance().get(Config.HOSTNAME, "localhost") + ">";
1.206 -
1.207 - this.headers.setHeader(Headers.MESSAGE_ID, msgID);
1.208 -
1.209 - return msgID;
1.210 - }
1.211 -
1.212 - /**
1.213 - * Returns the body string.
1.214 - */
1.215 - public String getBody()
1.216 - {
1.217 - return body;
1.218 - }
1.219 -
1.220 - /**
1.221 - * @return Charset of the body text
1.222 - */
1.223 - public Charset getBodyCharset()
1.224 - {
1.225 - // We espect something like
1.226 - // Content-Type: text/plain; charset=ISO-8859-15
1.227 - String contentType = getHeader(Headers.CONTENT_TYPE)[0];
1.228 - int idxCharsetStart = contentType.indexOf("charset=") + "charset=".length();
1.229 - int idxCharsetEnd = contentType.indexOf(";", idxCharsetStart);
1.230 -
1.231 - String charsetName = "UTF-8";
1.232 - if(idxCharsetStart >= 0 && idxCharsetStart < contentType.length())
1.233 - {
1.234 - if(idxCharsetEnd < 0)
1.235 - {
1.236 - charsetName = contentType.substring(idxCharsetStart);
1.237 - }
1.238 - else
1.239 - {
1.240 - charsetName = contentType.substring(idxCharsetStart, idxCharsetEnd);
1.241 - }
1.242 - }
1.243 -
1.244 - // Sometimes there are '"' around the name
1.245 - if(charsetName.length() > 2 &&
1.246 - charsetName.charAt(0) == '"' && charsetName.endsWith("\""))
1.247 - {
1.248 - charsetName = charsetName.substring(1, charsetName.length() - 2);
1.249 - }
1.250 -
1.251 - // Create charset
1.252 - Charset charset = Charset.forName("UTF-8"); // This MUST be supported by JVM
1.253 - try
1.254 - {
1.255 - charset = Charset.forName(charsetName);
1.256 - }
1.257 - catch(Exception ex)
1.258 - {
1.259 - Log.msg(ex.getMessage(), false);
1.260 - Log.msg("Article.getBodyCharset(): Unknown charset: " + charsetName, false);
1.261 - }
1.262 - return charset;
1.263 - }
1.264 -
1.265 - /**
1.266 - * @return Numerical IDs of the newsgroups this Article belongs to.
1.267 - */
1.268 - List<Group> getGroups()
1.269 - {
1.270 - String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
1.271 - ArrayList<Group> groups = new ArrayList<Group>();
1.272 -
1.273 - try
1.274 - {
1.275 - for(String newsgroup : groupnames)
1.276 - {
1.277 - newsgroup = newsgroup.trim();
1.278 - Group group = Database.getInstance().getGroup(newsgroup);
1.279 - if(group != null && // If the server does not provide the group, ignore it
1.280 - !groups.contains(group)) // Yes, there may be duplicates
1.281 - {
1.282 - groups.add(group);
1.283 - }
1.284 - }
1.285 - }
1.286 - catch (SQLException ex)
1.287 - {
1.288 - ex.printStackTrace();
1.289 - return null;
1.290 - }
1.291 - return groups;
1.292 - }
1.293 -
1.294 - public void setBody(String body)
1.295 - {
1.296 - this.body = body;
1.297 - }
1.298 -
1.299 - /**
1.300 - *
1.301 - * @param groupname Name(s) of newsgroups
1.302 - */
1.303 - public void setGroup(String groupname)
1.304 - {
1.305 - this.headers.setHeader(Headers.NEWSGROUPS, groupname);
1.306 - }
1.307 -
1.308 - public String getMessageID()
1.309 - {
1.310 - String[] msgID = getHeader(Headers.MESSAGE_ID);
1.311 - return msgID[0];
1.312 - }
1.313 -
1.314 - public Enumeration getAllHeaders()
1.315 - {
1.316 - return this.headers.getAllHeaders();
1.317 - }
1.318 -
1.319 - /**
1.320 - * @return Header source code of this Article.
1.321 - */
1.322 - public String getHeaderSource()
1.323 - {
1.324 - if(this.headerSrc != null)
1.325 - {
1.326 - return this.headerSrc;
1.327 - }
1.328 -
1.329 - StringBuffer buf = new StringBuffer();
1.330 -
1.331 - for(Enumeration en = this.headers.getAllHeaders(); en.hasMoreElements();)
1.332 - {
1.333 - Header entry = (Header)en.nextElement();
1.334 -
1.335 - buf.append(entry.getName());
1.336 - buf.append(": ");
1.337 - buf.append(
1.338 - MimeUtility.fold(entry.getName().length() + 2, entry.getValue()));
1.339 -
1.340 - if(en.hasMoreElements())
1.341 - {
1.342 - buf.append("\r\n");
1.343 - }
1.344 - }
1.345 -
1.346 - this.headerSrc = buf.toString();
1.347 - return this.headerSrc;
1.348 - }
1.349 -
1.350 - public long getIndexInGroup(Group group)
1.351 - throws SQLException
1.352 - {
1.353 - return Database.getInstance().getArticleIndex(this, group);
1.354 - }
1.355 -
1.356 - /**
1.357 - * Sets the headers of this Article. If headers contain no
1.358 - * Message-Id a new one is created.
1.359 - * @param headers
1.360 - */
1.361 - public void setHeaders(InternetHeaders headers)
1.362 - {
1.363 - this.headers = headers;
1.364 - validateHeaders();
1.365 - }
1.366 -
1.367 - /**
1.368 - * @return String containing the Message-ID.
1.369 - */
1.370 - @Override
1.371 - public String toString()
1.372 - {
1.373 - return getMessageID();
1.374 - }
1.375 -
1.376 - /**
1.377 - * Checks some headers for their validity and generates an
1.378 - * appropriate Path-header for this host if not yet existing.
1.379 - * This method is called by some Article constructors and the
1.380 - * method setHeaders().
1.381 - * @return true if something on the headers was changed.
1.382 - */
1.383 - private void validateHeaders()
1.384 - {
1.385 - // Check for valid Path-header
1.386 - final String path = getHeader(Headers.PATH)[0];
1.387 - final String host = Config.getInstance().get(Config.HOSTNAME, "localhost");
1.388 - if(!path.startsWith(host))
1.389 - {
1.390 - StringBuffer pathBuf = new StringBuffer();
1.391 - pathBuf.append(host);
1.392 - pathBuf.append('!');
1.393 - pathBuf.append(path);
1.394 - this.headers.setHeader(Headers.PATH, pathBuf.toString());
1.395 - }
1.396 -
1.397 - // Generate a messageID if no one is existing
1.398 - if(getMessageID().equals(""))
1.399 - {
1.400 - generateMessageID();
1.401 - }
1.402 - }
1.403 -
1.404 -}