View Javadoc

1   /**
2    *
3    * Copyright 2006 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.io.ByteArrayOutputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.util.Enumeration;
24  
25  import javax.mail.Flags;
26  import javax.mail.IllegalWriteException;
27  import javax.mail.MessagingException;
28  import javax.mail.Session;
29  import javax.mail.internet.InternetHeaders;
30  import javax.mail.internet.MimeMessage;
31  
32  import org.apache.geronimo.javamail.transport.nntp.NNTPConnection;
33  import org.apache.geronimo.javamail.transport.nntp.NNTPReply;
34  import org.apache.geronimo.javamail.transport.nntp.StringListInputStream;
35  
36  /**
37   * NNTP implementation of javax.mail.internet.MimeMessage
38   * 
39   * Only the most basic information is given and Message objects created here is
40   * a light-weight reference to the actual Message As per the JavaMail spec items
41   * from the actual message will get filled up on demand
42   * 
43   * If some other items are obtained from the server as a result of one call,
44   * then the other details are also processed and filled in. For ex if RETR is
45   * called then header information will also be processed in addition to the
46   * content
47   * 
48   * @version $Rev: 432884 $ $Date: 2006-08-19 14:53:20 -0700 (Sat, 19 Aug 2006) $
49   */
50  public class NNTPMessage extends MimeMessage {
51      // the server message identifer
52      String messageID = null;
53  
54      // our attached session
55      protected Session session;
56  
57      // the Store we're stored in (which manages the connection and other stuff).
58      protected NNTPStore store;
59  
60      // our active connection.
61      protected NNTPConnection connection;
62  
63      // used to force loading of headers
64      protected boolean headersLoaded = false;
65  
66      // use to force content loading
67      protected boolean contentLoaded = false;
68  
69      /**
70       * Contruct an NNTPMessage instance.
71       * 
72       * @param folder
73       *            The hosting folder for the message.
74       * @param store
75       *            The Store owning the article (and folder).
76       * @param msgnum
77       *            The article message number.
78       * @param messageID
79       *            The article messageID (as assigned by the server).
80       * 
81       * @exception MessagingException
82       */
83      NNTPMessage(NNTPFolder folder, NNTPStore store, int msgnum, String messageID) throws MessagingException {
84          super(folder, msgnum);
85          this.messageID = messageID;
86          this.store = store;
87          this.session = ((NNTPStore) store).getSession();
88          // get the active connection from the store...all commands are sent
89          // there
90          this.connection = ((NNTPStore) store).getConnection();
91  
92          // get our flag set from the folder.
93          flags = folder.getPermanentFlags();
94          // now check our initial SEEN state and set the flags appropriately
95          if (folder.isSeen(msgnum)) {
96              flags.add(Flags.Flag.SEEN);
97          } else {
98              flags.remove(Flags.Flag.SEEN);
99          }
100     }
101 
102     /**
103      * Retrieve the size of the message content. The content will be retrieved
104      * from the server, if necessary.
105      * 
106      * @return The size of the content.
107      * @exception MessagingException
108      */
109     public int getSize() throws MessagingException {
110         // make sure we've retrieved the message content and continue with the
111         // superclass version.
112         loadContent();
113         return super.getSize();
114     }
115 
116     /**
117      * Get a line count for the NNTP message. This is potentially stored in the
118      * Lines article header. If not there, we return a default of -1.
119      * 
120      * @return The header line count estimate, or -1 if not retrieveable.
121      * @exception MessagingException
122      */
123     public int getLineCount() throws MessagingException {
124         String[] headers = getHeader("Lines");
125 
126         // hopefully, there's only a single one of these. No sensible way of
127         // interpreting
128         // multiples.
129         if (headers.length == 1) {
130             try {
131                 return Integer.parseInt(headers[0].trim());
132 
133             } catch (NumberFormatException e) {
134                 // ignore
135             }
136         }
137         // dunno...and let them know I don't know.
138         return -1;
139     }
140 
141     /**
142      * @see javax.mail.internet.MimeMessage#getContentStream()
143      */
144     protected InputStream getContentStream() throws MessagingException {
145         // get the article information.
146         loadArticle();
147         return super.getContentStream();
148     }
149 
150     /***************************************************************************
151      * Following is a set of methods that deal with headers These methods are
152      * just overrides on the superclass methods to allow lazy loading of the
153      * header information.
154      **************************************************************************/
155 
156     public String[] getHeader(String name) throws MessagingException {
157         loadHeaders();
158         return headers.getHeader(name);
159     }
160 
161     public String getHeader(String name, String delimiter) throws MessagingException {
162         loadHeaders();
163         return headers.getHeader(name, delimiter);
164     }
165 
166     public Enumeration getAllHeaders() throws MessagingException {
167         loadHeaders();
168         return headers.getAllHeaders();
169     }
170 
171     public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
172         loadHeaders();
173         return headers.getMatchingHeaders(names);
174     }
175 
176     public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
177         loadHeaders();
178         return headers.getNonMatchingHeaders(names);
179     }
180 
181     public Enumeration getAllHeaderLines() throws MessagingException {
182         loadHeaders();
183         return headers.getAllHeaderLines();
184     }
185 
186     public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
187         loadHeaders();
188         return headers.getMatchingHeaderLines(names);
189     }
190 
191     public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
192         loadHeaders();
193         return headers.getNonMatchingHeaderLines(names);
194     }
195 
196     // the following are overrides for header modification methods. These
197     // messages are read only,
198     // so the headers cannot be modified.
199     public void addHeader(String name, String value) throws MessagingException {
200         throw new IllegalWriteException("NNTP messages are read-only");
201     }
202 
203     public void setHeader(String name, String value) throws MessagingException {
204         throw new IllegalWriteException("NNTP messages are read-only");
205     }
206 
207     public void removeHeader(String name) throws MessagingException {
208         throw new IllegalWriteException("NNTP messages are read-only");
209     }
210 
211     public void addHeaderLine(String line) throws MessagingException {
212         throw new IllegalWriteException("IMAP messages are read-only");
213     }
214 
215     /**
216      * We cannot modify these messages
217      */
218     public void saveChanges() throws MessagingException {
219         throw new IllegalWriteException("NNTP messages are read-only");
220     }
221 
222     /**
223      * Retrieve the message headers from the NNTP server.
224      * 
225      * @exception MessagingException
226      */
227     public void loadHeaders() throws MessagingException {
228         // don't retrieve if already loaded.
229         if (headersLoaded) {
230             return;
231         }
232 
233         NNTPReply reply = connection.sendCommand("HEAD " + messageID, NNTPReply.HEAD_FOLLOWS);
234 
235         if (reply.getCode() == NNTPReply.HEAD_FOLLOWS) {
236             try {
237                 // wrap a stream around the reply data and read as headers.
238                 updateHeaders(new StringListInputStream(reply.getData()));
239             } catch (IOException e) {
240                 throw new MessagingException("Error retrieving article headers from server", e);
241             }
242         } else {
243             throw new MessagingException("Error retrieving article headers from server: " + reply);
244         }
245     }
246 
247     /**
248      * Update the message headers from an input stream.
249      * 
250      * @param in
251      *            The InputStream source for the header information.
252      * 
253      * @exception MessagingException
254      */
255     public void updateHeaders(InputStream in) throws MessagingException {
256         // wrap a stream around the reply data and read as headers.
257         headers = new InternetHeaders(in);
258         headersLoaded = true;
259     }
260 
261     /**
262      * Load just the message content from the NNTP server.
263      * 
264      * @exception MessagingException
265      */
266     public void loadContent() throws MessagingException {
267         if (contentLoaded) {
268             return;
269         }
270 
271         NNTPReply reply = connection.sendCommand("BODY " + messageID, NNTPReply.BODY_FOLLOWS);
272 
273         if (reply.getCode() == NNTPReply.BODY_FOLLOWS) {
274             try {
275                 InputStream in = new StringListInputStream(reply.getData());
276                 updateContent(in);
277             } catch (IOException e) {
278                 throw new MessagingException("Error retrieving article body from server", e);
279             }
280         } else {
281             throw new MessagingException("Error retrieving article body from server: " + reply);
282         }
283     }
284 
285     /**
286      * Load the entire article from the NNTP server. This updates both the
287      * headers and the content.
288      * 
289      * @exception MessagingException
290      */
291     public void loadArticle() throws MessagingException {
292         // if the headers are already loaded, retrieve the content portion.
293         if (headersLoaded) {
294             loadContent();
295             return;
296         }
297 
298         // we need to retrieve everything.
299         NNTPReply reply = connection.sendCommand("ARTICLE " + messageID, NNTPReply.ARTICLE_FOLLOWS);
300 
301         if (reply.getCode() == NNTPReply.ARTICLE_FOLLOWS) {
302             try {
303                 InputStream in = new StringListInputStream(reply.getData());
304                 // update both the headers and the content.
305                 updateHeaders(in);
306                 updateContent(in);
307             } catch (IOException e) {
308                 throw new MessagingException("Error retrieving article from server", e);
309             }
310         } else {
311             throw new MessagingException("Error retrieving article from server: " + reply);
312         }
313     }
314 
315     /**
316      * Update the article content from an input stream.
317      * 
318      * @param in
319      *            The content data source.
320      * 
321      * @exception MessagingException
322      */
323     public void updateContent(InputStream in) throws MessagingException {
324         try {
325             ByteArrayOutputStream out = new ByteArrayOutputStream();
326 
327             byte[] buffer = new byte[4096];
328 
329             // copy the content data from the stream into a byte buffer for the
330             // content.
331             while (true) {
332                 int read = in.read(buffer);
333                 if (read == -1) {
334                     break;
335                 }
336                 out.write(buffer, 0, read);
337             }
338 
339             content = out.toByteArray();
340             contentLoaded = true;
341         } catch (IOException e) {
342             throw new MessagingException("Error retrieving message body from server", e);
343         }
344     }
345 
346     /**
347      * Get the server assigned messageid for the article.
348      * 
349      * @return The server assigned message id.
350      */
351     public String getMessageId() {
352         return messageID;
353     }
354 
355     /**
356      * Override of setFlags(). We need to ensure that if the SEEN flag is set or
357      * cleared, that the newsrc file correctly reflects the current state.
358      * 
359      * @param flag
360      *            The flag being set.
361      * @param newvalue
362      *            The new flag value.
363      * 
364      * @exception MessagingException
365      */
366     public void setFlags(Flags flag, boolean newvalue) throws MessagingException {
367         // if this is the SEEN flag, make sure we shadow this in the newsrc
368         // file.
369         if (flag.contains(Flags.Flag.SEEN)) {
370             ((NNTPFolder) folder).setSeen(msgnum, newvalue);
371         }
372         // have the superclass do the real flag setting.
373         super.setFlags(flag, newvalue);
374     }
375 }