blob: bb24800270a038a7051b118b0f85f8912f1a10d3 [file] [log] [blame]
/*
* 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);
}
}
}