| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.geronimo.javamail.store.nntp; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.StringTokenizer; |
| |
| import javax.mail.FetchProfile; |
| import javax.mail.FolderNotFoundException; |
| import javax.mail.Message; |
| import javax.mail.MessagingException; |
| |
| import org.apache.geronimo.javamail.store.nntp.newsrc.NNTPNewsrcGroup; |
| import org.apache.geronimo.javamail.transport.nntp.NNTPReply; |
| |
| /** |
| * The NNTP implementation of the javax.mail.Folder Note that only INBOX is |
| * supported in NNTP |
| * <p> |
| * <url>http://www.faqs.org/rfcs/rfc1939.html</url> |
| * </p> |
| * |
| * @see javax.mail.Folder |
| * |
| * @version $Rev: 686231 $ $Date: 2008-08-15 10:24:20 -0400 (Fri, 15 Aug 2008) $ |
| */ |
| public class NNTPGroupFolder extends NNTPFolder { |
| |
| // holders for status information returned by the GROUP command. |
| protected int firstArticle = -1; |
| |
| protected int lastArticle = -1; |
| |
| // retrieved articles, mapped by article number. |
| Map articles; |
| |
| // information stored in the newsrc group. |
| NNTPNewsrcGroup groupInfo; |
| |
| /** |
| * Construct a "real" folder representing an NNTP news group. |
| * |
| * @param parent |
| * The parent root folder. |
| * @param store |
| * The Store this folder is attached to. |
| * @param name |
| * The folder name. |
| * @param groupInfo |
| * The newsrc group information attached to the newsrc database. |
| * This contains subscription and article "SEEN" information. |
| */ |
| protected NNTPGroupFolder(NNTPRootFolder parent, NNTPStore store, String name, NNTPNewsrcGroup groupInfo) { |
| super(store); |
| // the name and the full name are the same. |
| this.name = name; |
| this.fullName = name; |
| // set the parent appropriately. |
| this.parent = parent = parent; |
| this.groupInfo = groupInfo; |
| } |
| |
| /** |
| * Ping the server and update the group count, first, and last information. |
| * |
| * @exception MessagingException |
| */ |
| private void updateGroupStats() throws MessagingException { |
| // ask the server for information about the group. This is a one-line |
| // reponse with status on |
| // the group, if it exists. |
| NNTPReply reply = connection.sendCommand("GROUP " + name); |
| |
| // explicitly not there? |
| if (reply.getCode() == NNTPReply.NO_SUCH_NEWSGROUP) { |
| throw new FolderNotFoundException(this, "Folder does not exist on server: " + reply); |
| } else if (reply.getCode() != NNTPReply.GROUP_SELECTED) { |
| throw new MessagingException("Error requesting group information: " + reply); |
| } |
| |
| // we've gotten back a good response, now parse out the group specifics |
| // from the |
| // status response. |
| |
| StringTokenizer tokenizer = new StringTokenizer(reply.getMessage()); |
| |
| // we should have a least 3 tokens here, in the order "count first |
| // last". |
| |
| // article count |
| if (tokenizer.hasMoreTokens()) { |
| String count = tokenizer.nextToken(); |
| try { |
| messageCount = Integer.parseInt(count); |
| } catch (NumberFormatException e) { |
| // ignore |
| } |
| } |
| |
| // first article number |
| if (tokenizer.hasMoreTokens()) { |
| String first = tokenizer.nextToken(); |
| try { |
| firstArticle = Integer.parseInt(first); |
| } catch (NumberFormatException e) { |
| // ignore |
| } |
| } |
| |
| // last article number. |
| if (tokenizer.hasMoreTokens()) { |
| String last = tokenizer.nextToken(); |
| try { |
| lastArticle = Integer.parseInt(last); |
| } catch (NumberFormatException e) { |
| // ignore |
| } |
| } |
| } |
| |
| /** |
| * Test to see if this folder actually exists. This pings the server for |
| * information about the GROUP and updates the article count and index |
| * information. |
| * |
| * @return true if the newsgroup exists on the server, false otherwise. |
| * @exception MessagingException |
| */ |
| public boolean exists() throws MessagingException { |
| |
| try { |
| // update the group statistics. If the folder doesn't exist, we'll |
| // get an exception that we |
| // can turn into a false reply. |
| updateGroupStats(); |
| // updated ok, so it must be there. |
| return true; |
| } catch (FolderNotFoundException e) { |
| return false; |
| } |
| } |
| |
| /** |
| * Ping the NNTP server to check if a newsgroup has any new messages. |
| * |
| * @return True if the server has new articles from the last time we |
| * checked. Also returns true if this is the first time we've |
| * checked. |
| * @exception MessagingException |
| */ |
| public boolean hasNewMessages() throws MessagingException { |
| int oldLast = lastArticle; |
| updateGroupStats(); |
| |
| return lastArticle > oldLast; |
| } |
| |
| /** |
| * Open the folder for use. This retrieves article count information from |
| * the server. |
| * |
| * @exception MessagingException |
| */ |
| protected void openFolder() throws MessagingException { |
| // update the group specifics, especially the message count. |
| updateGroupStats(); |
| |
| // get a cache for retrieved articles |
| articles = new HashMap(); |
| } |
| |
| /** |
| * Close the folder, which also clears out the article caches. |
| * |
| * @exception MessagingException |
| */ |
| public void closeFolder() throws MessagingException { |
| // get ride of any retrieve articles, and flip over the open for |
| // business sign. |
| articles = null; |
| } |
| |
| /** |
| * Checks wether the message is in cache, if not will create a new message |
| * object and return it. |
| * |
| * @see javax.mail.Folder#getMessage(int) |
| */ |
| public Message getMessage(int msgNum) throws MessagingException { |
| // Can only be performed on an Open folder |
| checkOpen(); |
| |
| // get an object form to look up in the retrieve messages list (oh how I |
| // wish there was |
| // something like Map that could use integer keys directly!). |
| Integer key = new Integer(msgNum); |
| NNTPMessage message = (NNTPMessage) articles.get(key); |
| if (message != null) { |
| // piece of cake! |
| return message; |
| } |
| |
| // we need to suck a message down from the server. |
| // but first, make sure the group is still valid. |
| updateGroupStats(); |
| |
| // just send a STAT command to this message. Right now, all we want is |
| // existance proof. We'll |
| // retrieve the other bits when requested. |
| NNTPReply reply = connection.sendCommand("STAT " + Integer.toString(msgNum)); |
| if (reply.getCode() != NNTPReply.REQUEST_TEXT_SEPARATELY) { |
| throw new MessagingException("Error retrieving article from NNTP server: " + reply); |
| } |
| |
| // we need to parse out the message id. |
| String response = reply.getMessage(); |
| |
| int idStart = response.indexOf('<'); |
| int idEnd = response.indexOf('>'); |
| |
| // NB: The "<" and ">" delimiters are required elements of the message id, not just |
| // delimiters for the sake of the command. We need to keep these around |
| message = new NNTPMessage(this, (NNTPStore) store, msgNum, response.substring(idStart, idEnd + 1)); |
| |
| // add this to the article cache. |
| articles.put(key, message); |
| |
| return message; |
| } |
| |
| /** |
| * Retrieve all articles in the group. |
| * |
| * @return An array of all messages in the group. |
| */ |
| public Message[] getMessages() throws MessagingException { |
| // Can only be performed on an Open folder |
| checkOpen(); |
| |
| // we're going to try first with XHDR, which will allow us to retrieve |
| // everything in one shot. If that |
| // fails, we'll fall back on issing STAT commands for the entire article |
| // range. |
| NNTPReply reply = connection.sendCommand("XHDR Message-ID " + Integer.toString(firstArticle) + "-" |
| + Integer.toString(lastArticle), NNTPReply.HEAD_FOLLOWS); |
| |
| List messages = new ArrayList(); |
| |
| if (reply.getCode() == NNTPReply.HEAD_FOLLOWS) { |
| List lines = reply.getData(); |
| |
| for (int i = 0; i < lines.size(); i++) { |
| String line = (String) lines.get(i); |
| |
| try { |
| int pos = line.indexOf(' '); |
| int articleID = Integer.parseInt(line.substring(0, pos)); |
| String messageID = line.substring(pos + 1); |
| Integer key = new Integer(articleID); |
| // see if we have this message cached, If not, create it. |
| Message message = (Message)articles.get(key); |
| if (message == null) { |
| message = new NNTPMessage(this, (NNTPStore) store, key.intValue(), messageID); |
| articles.put(key, message); |
| } |
| |
| messages.add(message); |
| |
| } catch (NumberFormatException e) { |
| // should never happen, but just skip this entry if it does. |
| } |
| } |
| } else { |
| // grumble, we need to stat each article id to see if it |
| // exists....lots of round trips. |
| for (int i = firstArticle; i <= lastArticle; i++) { |
| try { |
| messages.add(getMessage(i)); |
| } catch (MessagingException e) { |
| // just assume if there is an error, it's because the |
| // message id doesn't exist. |
| } |
| } |
| } |
| |
| return (Message[]) messages.toArray(new Message[0]); |
| } |
| |
| /** |
| * @see javax.mail.Folder#fetch(javax.mail.Message[], |
| * javax.mail.FetchProfile) |
| * |
| * The JavaMail API recommends that this method be overrident to provide a |
| * meaningfull implementation. |
| */ |
| public void fetch(Message[] msgs, FetchProfile fp) throws MessagingException { |
| // Can only be performed on an Open folder |
| checkOpen(); |
| |
| for (int i = 0; i < msgs.length; i++) { |
| Message msg = msgs[i]; |
| // we can only perform this operation for NNTPMessages. |
| if (msg == null || !(msg instanceof NNTPMessage)) { |
| // we can't fetch if it's the wrong message type |
| continue; |
| } |
| |
| // fetching both the headers and body? |
| if (fp.contains(FetchProfile.Item.ENVELOPE) && fp.contains(FetchProfile.Item.CONTENT_INFO)) { |
| |
| // retrive everything |
| ((NNTPMessage) msg).loadArticle(); |
| } |
| // headers only? |
| else if (fp.contains(FetchProfile.Item.ENVELOPE)) { |
| ((NNTPMessage) msg).loadHeaders(); |
| } else if (fp.contains(FetchProfile.Item.CONTENT_INFO)) { |
| ((NNTPMessage) msg).loadContent(); |
| } |
| } |
| } |
| |
| /** |
| * Return the subscription status of this folder. |
| * |
| * @return true if the folder is marked as subscribed, false for |
| * unsubscribed. |
| */ |
| public boolean isSubscribed() { |
| return groupInfo.isSubscribed(); |
| } |
| |
| /** |
| * Set or clear the subscription status of a file. |
| * |
| * @param flag |
| * The new subscription state. |
| */ |
| public void setSubscribed(boolean flag) { |
| groupInfo.setSubscribed(flag); |
| } |
| |
| /** |
| * Return the "seen" state for an article in a folder. |
| * |
| * @param article |
| * The article number. |
| * |
| * @return true if the article is marked as seen in the newsrc file, false |
| * for unseen files. |
| */ |
| public boolean isSeen(int article) { |
| return groupInfo.isArticleSeen(article); |
| } |
| |
| /** |
| * Set the seen state for an article in a folder. |
| * |
| * @param article |
| * The article number. |
| * @param flag |
| * The new seen state. |
| */ |
| public void setSeen(int article, boolean flag) { |
| if (flag) { |
| groupInfo.markArticleSeen(article); |
| } else { |
| groupInfo.markArticleUnseen(article); |
| } |
| } |
| } |