001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.geronimo.javamail.store.pop3;
021    
022    import java.io.ByteArrayInputStream;
023    import java.io.ByteArrayOutputStream;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.OutputStream;
027    import java.util.Enumeration;
028    
029    import javax.mail.Flags;
030    import javax.mail.Folder;
031    import javax.mail.IllegalWriteException;
032    import javax.mail.MessagingException;
033    import javax.mail.event.MessageChangedEvent;
034    import javax.mail.internet.InternetHeaders;
035    import javax.mail.internet.MimeMessage;
036    
037    import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection;
038    
039    /**
040     * POP3 implementation of javax.mail.internet.MimeMessage
041     * 
042     * Only the most basic information is given and Message objects created here is
043     * a light-weight reference to the actual Message As per the JavaMail spec items
044     * from the actual message will get filled up on demand
045     * 
046     * If some other items are obtained from the server as a result of one call,
047     * then the other details are also processed and filled in. For ex if RETR is
048     * called then header information will also be processed in addition to the
049     * content
050     * 
051     * @version $Rev: 597135 $ $Date: 2007-11-21 11:26:57 -0500 (Wed, 21 Nov 2007) $
052     */
053    public class POP3Message extends MimeMessage {
054        // the size of the message, in bytes
055        protected int msgSize = -1;
056        // the size of the headers.  We keep this around, as it's needed to 
057        // properly calculate the size of the message 
058        protected int headerSize = -1;
059        // the UID value retrieved from the server 
060        protected String uid; 
061        // the raw message data from loading the message
062        protected byte[] messageData; 
063    
064        /**
065         * Create a new POP3 message associated with a folder.
066         * 
067         * @param folder The owning folder.
068         * @param msgnum The message sequence number in the folder.
069         */
070        protected POP3Message(Folder folder, int msgnum) {
071            super(folder, msgnum);
072            this.session = session;
073            // force the headers to empty so we'll load them the first time they're referenced. 
074            this.headers = null; 
075        }
076    
077        /**
078         * Get an InputStream for reading the message content. 
079         * 
080         * @return An InputStream instance initialized to read the message 
081         *         content.
082         * @exception MessagingException
083         */
084        protected InputStream getContentStream() throws MessagingException {
085            // make sure the content is loaded first 
086            loadContent(); 
087            // allow the super class to handle creating it from the loaded content.
088            return super.getContentStream();
089        }
090    
091    
092        /**
093         * Write out the byte data to the provided output stream.
094         *
095         * @param out    The target stream.
096         *
097         * @exception IOException
098         * @exception MessagingException
099         */
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    }