View Javadoc

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