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 }