View Javadoc

1   /**
2    *
3    * Copyright 2003-2005 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.geronimo.javamail.store.nntp;
19  
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.StringTokenizer;
25  
26  import javax.mail.FetchProfile;
27  import javax.mail.FolderNotFoundException;
28  import javax.mail.Message;
29  import javax.mail.MessagingException;
30  
31  import org.apache.geronimo.javamail.store.nntp.newsrc.NNTPNewsrcGroup;
32  import org.apache.geronimo.javamail.transport.nntp.NNTPReply;
33  
34  /**
35   * The NNTP implementation of the javax.mail.Folder Note that only INBOX is
36   * supported in NNTP
37   * <p>
38   * <url>http://www.faqs.org/rfcs/rfc1939.html</url>
39   * </p>
40   * 
41   * @see javax.mail.Folder
42   * 
43   * @version $Rev: 432884 $ $Date: 2006-08-19 14:53:20 -0700 (Sat, 19 Aug 2006) $
44   */
45  public class NNTPGroupFolder extends NNTPFolder {
46  
47      // holders for status information returned by the GROUP command.
48      protected int firstArticle = -1;
49  
50      protected int lastArticle = -1;
51  
52      // retrieved articles, mapped by article number.
53      Map articles;
54  
55      // information stored in the newsrc group.
56      NNTPNewsrcGroup groupInfo;
57  
58      /**
59       * Construct a "real" folder representing an NNTP news group.
60       * 
61       * @param parent
62       *            The parent root folder.
63       * @param store
64       *            The Store this folder is attached to.
65       * @param name
66       *            The folder name.
67       * @param groupInfo
68       *            The newsrc group information attached to the newsrc database.
69       *            This contains subscription and article "SEEN" information.
70       */
71      protected NNTPGroupFolder(NNTPRootFolder parent, NNTPStore store, String name, NNTPNewsrcGroup groupInfo) {
72          super(store);
73          // the name and the full name are the same.
74          this.name = name;
75          this.fullName = name;
76          // set the parent appropriately.
77          this.parent = parent = parent;
78          this.groupInfo = groupInfo;
79      }
80  
81      /**
82       * Ping the server and update the group count, first, and last information.
83       * 
84       * @exception MessagingException
85       */
86      private void updateGroupStats() throws MessagingException {
87          // ask the server for information about the group. This is a one-line
88          // reponse with status on
89          // the group, if it exists.
90          NNTPReply reply = connection.sendCommand("GROUP " + name);
91  
92          // explicitly not there?
93          if (reply.getCode() == NNTPReply.NO_SUCH_NEWSGROUP) {
94              throw new FolderNotFoundException(this, "Folder does not exist on server: " + reply);
95          } else if (reply.getCode() != NNTPReply.GROUP_SELECTED) {
96              throw new MessagingException("Error requesting group information: " + reply);
97          }
98  
99          // we've gotten back a good response, now parse out the group specifics
100         // from the
101         // status response.
102 
103         StringTokenizer tokenizer = new StringTokenizer(reply.getMessage());
104 
105         // we should have a least 3 tokens here, in the order "count first
106         // last".
107 
108         // article count
109         if (tokenizer.hasMoreTokens()) {
110             String count = tokenizer.nextToken();
111             try {
112                 messageCount = Integer.parseInt(count);
113             } catch (NumberFormatException e) {
114                 // ignore
115             }
116         }
117 
118         // first article number
119         if (tokenizer.hasMoreTokens()) {
120             String first = tokenizer.nextToken();
121             try {
122                 firstArticle = Integer.parseInt(first);
123             } catch (NumberFormatException e) {
124                 // ignore
125             }
126         }
127 
128         // last article number.
129         if (tokenizer.hasMoreTokens()) {
130             String last = tokenizer.nextToken();
131             try {
132                 lastArticle = Integer.parseInt(last);
133             } catch (NumberFormatException e) {
134                 // ignore
135             }
136         }
137     }
138 
139     /**
140      * Test to see if this folder actually exists. This pings the server for
141      * information about the GROUP and updates the article count and index
142      * information.
143      * 
144      * @return true if the newsgroup exists on the server, false otherwise.
145      * @exception MessagingException
146      */
147     public boolean exists() throws MessagingException {
148 
149         try {
150             // update the group statistics. If the folder doesn't exist, we'll
151             // get an exception that we
152             // can turn into a false reply.
153             updateGroupStats();
154             // updated ok, so it must be there.
155             return true;
156         } catch (FolderNotFoundException e) {
157             return false;
158         }
159     }
160 
161     /**
162      * Ping the NNTP server to check if a newsgroup has any new messages.
163      * 
164      * @return True if the server has new articles from the last time we
165      *         checked. Also returns true if this is the first time we've
166      *         checked.
167      * @exception MessagingException
168      */
169     public boolean hasNewMessages() throws MessagingException {
170         int oldLast = lastArticle;
171         updateGroupStats();
172 
173         return lastArticle > oldLast;
174     }
175 
176     /**
177      * Open the folder for use. This retrieves article count information from
178      * the server.
179      * 
180      * @exception MessagingException
181      */
182     public void openFolder() throws MessagingException {
183         // update the group specifics, especially the message count.
184         updateGroupStats();
185 
186         // get a cache for retrieve articles
187         articles = new HashMap();
188     }
189 
190     /**
191      * Close the folder, which also clears out the article caches.
192      * 
193      * @exception MessagingException
194      */
195     public void closeFolder() throws MessagingException {
196         // get ride of any retrieve articles, and flip over the open for
197         // business sign.
198         articles = null;
199     }
200 
201     /**
202      * Checks wether the message is in cache, if not will create a new message
203      * object and return it.
204      * 
205      * @see javax.mail.Folder#getMessage(int)
206      */
207     public Message getMessage(int msgNum) throws MessagingException {
208         // Can only be performed on an Open folder
209         checkOpen();
210 
211         // get an object form to look up in the retrieve messages list (oh how I
212         // wish there was
213         // something like Map that could use integer keys directly!).
214         Integer key = new Integer(msgNum);
215         NNTPMessage message = (NNTPMessage) articles.get(key);
216         if (message != null) {
217             // piece of cake!
218             return message;
219         }
220 
221         // we need to suck a message down from the server.
222         // but first, make sure the group is still valid.
223         updateGroupStats();
224 
225         // just send a STAT command to this message. Right now, all we want is
226         // existance proof. We'll
227         // retrieve the other bits when requested.
228         NNTPReply reply = connection.sendCommand("STAT " + Integer.toString(msgNum));
229         if (reply.getCode() != NNTPReply.REQUEST_TEXT_SEPARATELY) {
230             throw new MessagingException("Error retrieving article from NNTP server: " + reply);
231         }
232 
233         // we need to parse out the message id.
234         String response = reply.getMessage();
235 
236         int idStart = response.indexOf('<');
237         int idEnd = response.indexOf('>');
238 
239         message = new NNTPMessage(this, (NNTPStore) store, msgNum, response.substring(idStart + 1, idEnd));
240 
241         // add this to the article cache.
242         articles.put(key, message);
243 
244         return message;
245     }
246 
247     /**
248      * Retrieve all articles in the group.
249      * 
250      * @return An array of all messages in the group.
251      */
252     public Message[] getMessages() throws MessagingException {
253         // we're going to try first with XHDR, which will allow us to retrieve
254         // everything in one shot. If that
255         // fails, we'll fall back on issing STAT commands for the entire article
256         // range.
257         NNTPReply reply = connection.sendCommand("XHDR Message-ID " + Integer.toString(firstArticle) + "-"
258                 + Integer.toString(lastArticle), NNTPReply.HEAD_FOLLOWS);
259 
260         List messages = new ArrayList();
261 
262         if (reply.getCode() == NNTPReply.HEAD_FOLLOWS) {
263             List lines = reply.getData();
264 
265             for (int i = 0; i < lines.size(); i++) {
266                 String line = (String) lines.get(i);
267 
268                 try {
269                     int pos = line.indexOf(' ');
270                     int articleID = Integer.parseInt(line.substring(0, pos));
271                     String messageID = line.substring(pos + 1);
272                     Integer key = new Integer(articleID);
273                     // see if we have this message cached, If not, create it.
274                     Message message = (Message) articles.get(key);
275                     if (message == null) {
276                         message = new NNTPMessage(this, (NNTPStore) store, key.intValue(), messageID);
277                         articles.put(key, message);
278                     }
279 
280                     messages.add(message);
281 
282                 } catch (NumberFormatException e) {
283                     // should never happen, but just skip this entry if it does.
284                 }
285             }
286         } else {
287             // grumble, we need to stat each article id to see if it
288             // exists....lots of round trips.
289             for (int i = firstArticle; i <= lastArticle; i++) {
290                 try {
291                     messages.add(getMessage(i));
292                 } catch (MessagingException e) {
293                     // just assume if there is an error, it's because the
294                     // message id doesn't exist.
295                 }
296             }
297         }
298 
299         return (Message[]) messages.toArray(new Message[0]);
300     }
301 
302     /**
303      * @see javax.mail.Folder#fetch(javax.mail.Message[],
304      *      javax.mail.FetchProfile)
305      * 
306      * The JavaMail API recommends that this method be overrident to provide a
307      * meaningfull implementation.
308      */
309     public void fetch(Message[] msgs, FetchProfile fp) throws MessagingException {
310         // Can only be performed on an Open folder
311         checkOpen();
312 
313         for (int i = 0; i < msgs.length; i++) {
314             Message msg = msgs[i];
315             // we can only perform this operation for NNTPMessages.
316             if (msg == null || !(msg instanceof NNTPMessage)) {
317                 // we can't fetch if it's the wrong message type
318                 continue;
319             }
320 
321             // fetching both the headers and body?
322             if (fp.contains(FetchProfile.Item.ENVELOPE) && fp.contains(FetchProfile.Item.CONTENT_INFO)) {
323 
324                 // retrive everything
325                 ((NNTPMessage) msg).loadArticle();
326             }
327             // headers only?
328             else if (fp.contains(FetchProfile.Item.ENVELOPE)) {
329                 ((NNTPMessage) msg).loadHeaders();
330             } else if (fp.contains(FetchProfile.Item.CONTENT_INFO)) {
331                 ((NNTPMessage) msg).loadContent();
332             }
333         }
334     }
335 
336     /**
337      * Return the subscription status of this folder.
338      * 
339      * @return true if the folder is marked as subscribed, false for
340      *         unsubscribed.
341      */
342     public boolean isSubscribed() {
343         return groupInfo.isSubscribed();
344     }
345 
346     /**
347      * Set or clear the subscription status of a file.
348      * 
349      * @param flag
350      *            The new subscription state.
351      */
352     public void setSubscribed(boolean flag) {
353         groupInfo.setSubscribed(flag);
354     }
355 
356     /**
357      * Return the "seen" state for an article in a folder.
358      * 
359      * @param article
360      *            The article number.
361      * 
362      * @return true if the article is marked as seen in the newsrc file, false
363      *         for unseen files.
364      */
365     public boolean isSeen(int article) {
366         return groupInfo.isArticleSeen(article);
367     }
368 
369     /**
370      * Set the seen state for an article in a folder.
371      * 
372      * @param article
373      *            The article number.
374      * @param flag
375      *            The new seen state.
376      */
377     public void setSeen(int article, boolean flag) {
378         if (flag) {
379             groupInfo.markArticleSeen(article);
380         } else {
381             groupInfo.markArticleUnseen(article);
382         }
383     }
384 }