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.pop3;
21  
22  import java.io.ByteArrayInputStream;
23  import java.io.ByteArrayOutputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.util.Enumeration;
28  
29  import javax.mail.Flags;
30  import javax.mail.Folder;
31  import javax.mail.IllegalWriteException;
32  import javax.mail.MessagingException;
33  import javax.mail.event.MessageChangedEvent;
34  import javax.mail.internet.InternetHeaders;
35  import javax.mail.internet.MimeMessage;
36  
37  import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection;
38  
39  /**
40   * POP3 implementation of javax.mail.internet.MimeMessage
41   * 
42   * Only the most basic information is given and Message objects created here is
43   * a light-weight reference to the actual Message As per the JavaMail spec items
44   * from the actual message will get filled up on demand
45   * 
46   * If some other items are obtained from the server as a result of one call,
47   * then the other details are also processed and filled in. For ex if RETR is
48   * called then header information will also be processed in addition to the
49   * content
50   * 
51   * @version $Rev: 597135 $ $Date: 2007-11-21 11:26:57 -0500 (Wed, 21 Nov 2007) $
52   */
53  public class POP3Message extends MimeMessage {
54      // the size of the message, in bytes
55      protected int msgSize = -1;
56      // the size of the headers.  We keep this around, as it's needed to 
57      // properly calculate the size of the message 
58      protected int headerSize = -1;
59      // the UID value retrieved from the server 
60      protected String uid; 
61      // the raw message data from loading the message
62      protected byte[] messageData; 
63  
64      /**
65       * Create a new POP3 message associated with a folder.
66       * 
67       * @param folder The owning folder.
68       * @param msgnum The message sequence number in the folder.
69       */
70      protected POP3Message(Folder folder, int msgnum) {
71          super(folder, msgnum);
72          this.session = session;
73          // force the headers to empty so we'll load them the first time they're referenced. 
74          this.headers = null; 
75      }
76  
77      /**
78       * Get an InputStream for reading the message content. 
79       * 
80       * @return An InputStream instance initialized to read the message 
81       *         content.
82       * @exception MessagingException
83       */
84      protected InputStream getContentStream() throws MessagingException {
85          // make sure the content is loaded first 
86          loadContent(); 
87          // allow the super class to handle creating it from the loaded content.
88          return super.getContentStream();
89      }
90  
91  
92      /**
93       * Write out the byte data to the provided output stream.
94       *
95       * @param out    The target stream.
96       *
97       * @exception IOException
98       * @exception MessagingException
99       */
100     public void writeTo(OutputStream out) throws IOException, MessagingException {
101         // make sure we have everything loaded 
102         loadContent(); 
103         // just write out the raw message data 
104         out.write(messageData); 
105     }
106     
107 
108     /**
109      * Set a flag value for this Message.  The flags are 
110      * only set locally, not the server.  When the folder 
111      * is closed, any messages with the Deleted flag set 
112      * will be removed from the server. 
113      * 
114      * @param newFlags The new flag values.
115      * @param set      Indicates whether this is a set or an unset operation.
116      * 
117      * @exception MessagingException
118      */
119     public void setFlags(Flags newFlags, boolean set) throws MessagingException {
120         Flags oldFlags = (Flags) flags.clone();
121         super.setFlags(newFlags, set);
122 
123         if (!flags.equals(oldFlags)) {
124             ((POP3Folder) folder).notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, this);
125         }
126     }
127 
128     /**
129      * Unconditionally load the headers from an inputstream. 
130      * When retrieving content, we get back the entire message, 
131      * including the headers.  This allows us to skip over 
132      * them to reach the content, even if we already have 
133      * headers loaded. 
134      * 
135      * @param in     The InputStream with the header data.
136      * 
137      * @exception MessagingException
138      */
139     protected void loadHeaders(InputStream in) throws MessagingException {
140         try {
141             headerSize = in.available(); 
142             // just load and replace the haders 
143             headers = new InternetHeaders(in);
144             headerSize -= in.available(); 
145         } catch (IOException e) {
146             // reading from a ByteArrayInputStream...this should never happen. 
147         }
148     }
149     
150     /**
151      * Lazy loading of the message content. 
152      * 
153      * @exception MessagingException
154      */
155     protected void loadContent() throws MessagingException {
156         if (content == null) {
157             POP3Connection connection = getConnection(); 
158             try {
159                 // retrieve (and save the raw message data 
160                 messageData = connection.retrieveMessageData(msgnum);
161             } finally {
162                 // done with the connection
163                 releaseConnection(connection); 
164             }
165             // now create a input stream for splitting this into headers and 
166             // content 
167             ByteArrayInputStream in = new ByteArrayInputStream(messageData); 
168             
169             // the Sun implementation has an option that forces headers loaded using TOP 
170             // should be forgotten when retrieving the message content.  This is because 
171             // some POP3 servers return different results for TOP and RETR.  Since we need to 
172             // retrieve the headers anyway, and this set should be the most complete, we'll 
173             // just replace the headers unconditionally. 
174             loadHeaders(in);
175             // load headers stops loading at the header terminator.  Everything 
176             // after that is content. 
177             loadContent(in);
178         }
179     }
180 
181     /**
182      * Load the message content from the server.
183      * 
184      * @param stream A ByteArrayInputStream containing the message content.
185      *               We explicitly use ByteArrayInputStream because
186      *               there are some optimizations that can take advantage
187      *               of the fact it is such a stream.
188      * 
189      * @exception MessagingException
190      */
191     protected void loadContent(ByteArrayInputStream stream) throws MessagingException {
192         // since this is a byte array input stream, available() returns reliable value. 
193         content = new byte[stream.available()];
194         try {
195             // just read everything in to the array 
196             stream.read(content); 
197         } catch (IOException e) {
198             // should never happen 
199             throw new MessagingException("Error loading content info", e);
200         }
201     }
202 
203     /**
204      * Get the size of the message.
205      * 
206      * @return The calculated message size, in bytes. 
207      * @exception MessagingException
208      */
209     public int getSize() throws MessagingException {
210         if (msgSize < 0) {
211             // we need to get the headers loaded, since we need that information to calculate the total 
212             // content size without retrieving the content. 
213             loadHeaders();  
214             
215             POP3Connection connection = getConnection(); 
216             try {
217 
218                 // get the total message size, and adjust by size of the headers to get the content size. 
219                 msgSize = connection.retrieveMessageSize(msgnum) - headerSize; 
220             } finally {
221                 // done with the connection
222                 releaseConnection(connection); 
223             }
224         }
225         return msgSize;
226     }
227 
228     /**
229      * notice that we pass zero as the no of lines from the message,as it
230      * doesn't serv any purpose to get only a certain number of lines.
231      * 
232      * However this maybe important if a mail client only shows 3 or 4 lines of
233      * the message in the list and then when the user clicks they would load the
234      * message on demand.
235      * 
236      */
237     protected void loadHeaders() throws MessagingException {
238         if (headers == null) {
239             POP3Connection connection = getConnection(); 
240             try {
241                 loadHeaders(connection.retrieveMessageHeaders(msgnum)); 
242             } finally {
243                 // done with the connection
244                 releaseConnection(connection); 
245             }
246         }
247     }
248     
249     /**
250      * Retrieve the message UID from the server.
251      * 
252      * @return The string UID value. 
253      * @exception MessagingException
254      */
255     protected String getUID() throws MessagingException {
256         if (uid == null) {
257             POP3Connection connection = getConnection(); 
258             try {
259                 uid = connection.retrieveMessageUid(msgnum); 
260             } finally {
261                 // done with the connection
262                 releaseConnection(connection); 
263             }
264         }
265         return uid; 
266     }
267     
268     // The following are methods that deal with all header accesses.  Most of the 
269     // methods that retrieve information from the headers funnel through these, so we 
270     // can lazy-retrieve the header information. 
271 
272     public String[] getHeader(String name) throws MessagingException {
273         // make sure the headers are loaded 
274         loadHeaders(); 
275         // allow the super class to handle everything from here 
276         return super.getHeader(name); 
277     }
278 
279     public String getHeader(String name, String delimiter) throws MessagingException {
280         // make sure the headers are loaded 
281         loadHeaders(); 
282         // allow the super class to handle everything from here 
283         return super.getHeader(name, delimiter); 
284     }
285 
286     public Enumeration getAllHeaders() throws MessagingException {
287         // make sure the headers are loaded 
288         loadHeaders(); 
289         // allow the super class to handle everything from here 
290         return super.getAllHeaders(); 
291     }
292 
293     public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
294         // make sure the headers are loaded 
295         loadHeaders(); 
296         // allow the super class to handle everything from here 
297         return super.getMatchingHeaders(names); 
298     }
299 
300     public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
301         // make sure the headers are loaded 
302         loadHeaders(); 
303         // allow the super class to handle everything from here 
304         return super.getNonMatchingHeaders(names); 
305     }
306 
307     public Enumeration getAllHeaderLines() throws MessagingException {
308         // make sure the headers are loaded 
309         loadHeaders(); 
310         // allow the super class to handle everything from here 
311         return super.getAllHeaderLines();       
312     }
313 
314     public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
315         // make sure the headers are loaded 
316         loadHeaders(); 
317         // allow the super class to handle everything from here 
318         return super.getMatchingHeaderLines(names); 
319     }
320 
321     public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
322         // make sure the headers are loaded 
323         loadHeaders(); 
324         // allow the super class to handle everything from here 
325         return super.getNonMatchingHeaderLines(names); 
326     }
327 
328     // the following are overrides for header modification methods. These
329     // messages are read only,
330     // so the headers cannot be modified.
331     public void addHeader(String name, String value) throws MessagingException {
332         throw new IllegalWriteException("POP3 messages are read-only");
333     }
334 
335     public void setHeader(String name, String value) throws MessagingException {
336         throw new IllegalWriteException("POP3 messages are read-only");
337     }
338 
339     public void removeHeader(String name) throws MessagingException {
340         throw new IllegalWriteException("POP3 messages are read-only");
341     }
342 
343     public void addHeaderLine(String line) throws MessagingException {
344         throw new IllegalWriteException("POP3 messages are read-only");
345     }
346 
347     /**
348      * We cannot modify these messages
349      */
350     public void saveChanges() throws MessagingException {
351         throw new IllegalWriteException("POP3 messages are read-only");
352     }
353 
354     
355     /**
356      * get the current connection pool attached to the folder.  We need
357      * to do this dynamically, to A) ensure we're only accessing an
358      * currently open folder, and B) to make sure we're using the
359      * correct connection attached to the folder.
360      *
361      * @return A connection attached to the hosting folder.
362      */
363     protected POP3Connection getConnection() throws MessagingException {
364         // the folder owns everything.
365         return ((POP3Folder)folder).getMessageConnection();
366     }
367     
368     /**
369      * Release the connection back to the Folder after performing an operation 
370      * that requires a connection.
371      * 
372      * @param connection The previously acquired connection.
373      */
374     protected void releaseConnection(POP3Connection connection) throws MessagingException {
375         // the folder owns everything.
376         ((POP3Folder)folder).releaseMessageConnection(connection);
377     }
378 }