001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.javamail.store.imap; 019 020 import java.io.BufferedReader; 021 import java.io.ByteArrayInputStream; 022 import java.io.ByteArrayOutputStream; 023 import java.io.IOException; 024 import java.io.InputStream; 025 import java.io.InputStreamReader; 026 import java.io.OutputStream; 027 import java.io.UnsupportedEncodingException; 028 import java.util.Date; 029 import java.util.Enumeration; 030 import java.util.List; 031 032 import javax.activation.DataHandler; 033 034 import javax.mail.Address; 035 import javax.mail.FetchProfile; 036 import javax.mail.Flags; 037 import javax.mail.Folder; 038 import javax.mail.Header; 039 import javax.mail.IllegalWriteException; 040 import javax.mail.Message; 041 import javax.mail.MessagingException; 042 import javax.mail.MessageRemovedException; 043 import javax.mail.Session; 044 import javax.mail.Store; 045 import javax.mail.UIDFolder; 046 import javax.mail.event.MessageChangedEvent; 047 048 import javax.mail.internet.InternetAddress; 049 import javax.mail.internet.InternetHeaders; 050 import javax.mail.internet.MailDateFormat; 051 import javax.mail.internet.MimeMessage; 052 import javax.mail.internet.MimeUtility; 053 054 import org.apache.geronimo.javamail.store.imap.connection.IMAPBody; 055 import org.apache.geronimo.javamail.store.imap.connection.IMAPBodyStructure; 056 import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection; 057 import org.apache.geronimo.javamail.store.imap.connection.IMAPEnvelope; 058 import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchDataItem; 059 import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchResponse; 060 import org.apache.geronimo.javamail.store.imap.connection.IMAPInternalDate; 061 import org.apache.geronimo.javamail.store.imap.connection.IMAPInternetHeader; 062 import org.apache.geronimo.javamail.store.imap.connection.IMAPMessageSize; 063 import org.apache.geronimo.javamail.store.imap.connection.IMAPUid; 064 import org.apache.geronimo.javamail.util.MailConnection; 065 066 /** 067 * IMAP implementation of javax.mail.internet.MimeMessage 068 * 069 * Only the most basic information is given and 070 * Message objects created here is a light-weight reference to the actual Message 071 * As per the JavaMail spec items from the actual message will get filled up on demand 072 * 073 * If some other items are obtained from the server as a result of one call, then the other 074 * details are also processed and filled in. For ex if RETR is called then header information 075 * will also be processed in addition to the content 076 * 077 * @version $Rev: 739377 $ $Date: 2009-01-30 13:57:39 -0500 (Fri, 30 Jan 2009) $ 078 */ 079 public class IMAPMessage extends MimeMessage { 080 081 private static final byte[] CRLF = "\r\n".getBytes(); 082 083 // the Store we're stored in (which manages the connection and other stuff). 084 protected IMAPStore store; 085 086 // the IMAP server sequence number (potentially updated during the life of this message object). 087 protected int sequenceNumber; 088 // the IMAP uid value; 089 protected long uid = -1; 090 // the section identifier. This is only really used for nested messages. The toplevel version 091 // will be null, and each nested message will set the appropriate part identifier 092 protected String section; 093 // the loaded message envelope (delayed until needed) 094 protected IMAPEnvelope envelope; 095 // the body structure information (also lazy loaded). 096 protected IMAPBodyStructure bodyStructure; 097 // the IMAP INTERNALDATE value. 098 protected Date receivedDate; 099 // the size item, which is maintained separately from the body structure 100 // as it can be retrieved without getting the body structure 101 protected int size; 102 // turned on once we've requested the entire header set. 103 protected boolean allHeadersRetrieved = false; 104 // singleton date formatter for this class. 105 static protected MailDateFormat dateFormat = new MailDateFormat(); 106 107 108 /** 109 * Contruct an IMAPMessage instance. 110 * 111 * @param folder The hosting folder for the message. 112 * @param store The Store owning the article (and folder). 113 * @param msgnum The article message number. This is assigned by the Folder, and is unique 114 * for each message in the folder. The message numbers are only valid 115 * as long as the Folder is open. 116 * @param sequenceNumber The IMAP server manages messages by sequence number, which is subject to 117 * change whenever messages are expunged. This is the server retrieval number 118 * of the message, which needs to be synchronized with status updates 119 * sent from the server. 120 * 121 * @exception MessagingException 122 */ 123 IMAPMessage(IMAPFolder folder, IMAPStore store, int msgnum, int sequenceNumber) { 124 super(folder, msgnum); 125 this.sequenceNumber = sequenceNumber; 126 this.store = store; 127 // The default constructor creates an empty Flags item. We need to clear this out so we 128 // know if the flags need to be fetched from the server when requested. 129 flags = null; 130 // make sure this is a totally fresh set of headers. We'll fill things in as we retrieve them. 131 headers = new InternetHeaders(); 132 } 133 134 135 /** 136 * Override for the Message class setExpunged() method to allow 137 * us to do additional cleanup for expunged messages. 138 * 139 * @param value The new expunge setting. 140 */ 141 public void setExpunged(boolean value) { 142 // super class handles most of the details 143 super.setExpunged(value); 144 // if we're now expunged, this removes us from the server message sequencing scheme, so 145 // we need to invalidate the sequence number. 146 if (isExpunged()) { 147 sequenceNumber = -1; 148 } 149 } 150 151 152 /** 153 * Return a copy the flags associated with this message. 154 * 155 * @return a copy of the flags for this message 156 * @throws MessagingException if there was a problem accessing the Store 157 */ 158 public synchronized Flags getFlags() throws MessagingException { 159 // load the flags, if needed 160 loadFlags(); 161 return super.getFlags(); 162 } 163 164 165 /** 166 * Check whether the supplied flag is set. 167 * The default implementation checks the flags returned by {@link #getFlags()}. 168 * 169 * @param flag the flags to check for 170 * @return true if the flags is set 171 * @throws MessagingException if there was a problem accessing the Store 172 */ 173 public synchronized boolean isSet(Flags.Flag flag) throws MessagingException { 174 // load the flags, if needed 175 loadFlags(); 176 return super.isSet(flag); 177 } 178 179 /** 180 * Set or clear a flag value. 181 * 182 * @param flags The set of flags to effect. 183 * @param set The value to set the flag to (true or false). 184 * 185 * @exception MessagingException 186 */ 187 public synchronized void setFlags(Flags flag, boolean set) throws MessagingException { 188 // make sure this is in a valid state. 189 checkValidity(); 190 191 // we need to ensure that we're the only ones with access to the folder's 192 // message cache any time we need to talk to the server. This needs to be 193 // held until after we release the connection so that any pending EXPUNGE 194 // untagged responses are processed before the next time the folder connection is 195 // used. 196 synchronized (folder) { 197 IMAPConnection connection = getConnection(); 198 199 try { 200 // set the flags for this item and update the 201 // internal state with the new values returned from the 202 // server. 203 flags = connection.setFlags(sequenceNumber, flag, set); 204 } finally { 205 releaseConnection(connection); 206 } 207 } 208 } 209 210 211 /** 212 * Return an InputStream instance for accessing the 213 * message content. 214 * 215 * @return An InputStream instance for accessing the content 216 * (body) of the message. 217 * @exception MessagingException 218 * @see javax.mail.internet.MimeMessage#getContentStream() 219 */ 220 protected InputStream getContentStream() throws MessagingException { 221 222 // no content loaded yet? 223 if (content == null) { 224 // make sure we're still valid 225 checkValidity(); 226 // make sure the content is fully loaded 227 loadContent(); 228 } 229 230 // allow the super class to handle creating it from the loaded content. 231 return super.getContentStream(); 232 } 233 234 235 /** 236 * Write out the byte data to the provided output stream. 237 * 238 * @param out The target stream. 239 * 240 * @exception IOException 241 * @exception MessagingException 242 */ 243 public void writeTo(OutputStream out) throws IOException, MessagingException { 244 // no content loaded yet? 245 if (content == null) { 246 // make sure we're still valid 247 checkValidity(); 248 // make sure the content is fully loaded 249 loadContent(); 250 } 251 252 loadHeaders(); 253 254 Enumeration e = headers.getAllHeaderLines(); 255 while(e.hasMoreElements()) { 256 String line = (String)e.nextElement(); 257 out.write(line.getBytes()); 258 out.write(CRLF); 259 } 260 out.write(CRLF); 261 out.write(CRLF); 262 out.write(content); 263 } 264 265 /****************************************************************** 266 * Following is a set of methods that deal with information in the 267 * envelope. These methods ensure the enveloper is loaded and 268 * retrieve the information. 269 ********************************************************************/ 270 271 272 /** 273 * Get the message "From" addresses. This looks first at the 274 * "From" headers, and no "From" header is found, the "Sender" 275 * header is checked. Returns null if not found. 276 * 277 * @return An array of addresses identifying the message from target. Returns 278 * null if this is not resolveable from the headers. 279 * @exception MessagingException 280 */ 281 public Address[] getFrom() throws MessagingException { 282 // make sure we've retrieved the envelope information. 283 loadEnvelope(); 284 // make sure we return a copy of the array so this can't be changed. 285 Address[] addresses = envelope.from; 286 if (addresses == null) { 287 return null; 288 } 289 return (Address[])addresses.clone(); 290 } 291 292 293 /** 294 * Return the "Sender" header as an address. 295 * 296 * @return the "Sender" header as an address, or null if not present 297 * @throws MessagingException if there was a problem parsing the header 298 */ 299 public Address getSender() throws MessagingException { 300 // make sure we've retrieved the envelope information. 301 loadEnvelope(); 302 // make sure we return a copy of the array so this can't be changed. 303 Address[] addresses = envelope.sender; 304 if (addresses == null) { 305 return null; 306 } 307 // There's only a single sender, despite IMAP potentially returning a list 308 return addresses[0]; 309 } 310 311 /** 312 * Gets the recipients by type. Returns null if there are no 313 * headers of the specified type. Acceptable RecipientTypes are: 314 * 315 * javax.mail.Message.RecipientType.TO 316 * javax.mail.Message.RecipientType.CC 317 * javax.mail.Message.RecipientType.BCC 318 * javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS 319 * 320 * @param type The message RecipientType identifier. 321 * 322 * @return The array of addresses for the specified recipient types. 323 * @exception MessagingException 324 */ 325 public Address[] getRecipients(Message.RecipientType type) throws MessagingException { 326 // make sure we've retrieved the envelope information. 327 loadEnvelope(); 328 Address[] addresses = null; 329 330 if (type == Message.RecipientType.TO) { 331 addresses = envelope.to; 332 } 333 else if (type == Message.RecipientType.CC) { 334 addresses = envelope.cc; 335 } 336 else if (type == Message.RecipientType.BCC) { 337 addresses = envelope.bcc; 338 } 339 else { 340 // this could be a newsgroup type, which will tickle the message headers. 341 return super.getRecipients(type); 342 } 343 // make sure we return a copy of the array so this can't be changed. 344 if (addresses == null) { 345 return null; 346 } 347 return (Address[])addresses.clone(); 348 } 349 350 /** 351 * Get the ReplyTo address information. The headers are parsed 352 * using the "mail.mime.address.strict" setting. If the "Reply-To" header does 353 * not have any addresses, then the value of the "From" field is used. 354 * 355 * @return An array of addresses obtained from parsing the header. 356 * @exception MessagingException 357 */ 358 public Address[] getReplyTo() throws MessagingException { 359 // make sure we've retrieved the envelope information. 360 loadEnvelope(); 361 // make sure we return a copy of the array so this can't be changed. 362 Address[] addresses = envelope.replyTo; 363 if (addresses == null) { 364 return null; 365 } 366 return (Address[])addresses.clone(); 367 } 368 369 /** 370 * Returns the value of the "Subject" header. If the subject 371 * is encoded as an RFC 2047 value, the value is decoded before 372 * return. If decoding fails, the raw string value is 373 * returned. 374 * 375 * @return The String value of the subject field. 376 * @exception MessagingException 377 */ 378 public String getSubject() throws MessagingException { 379 // make sure we've retrieved the envelope information. 380 loadEnvelope(); 381 382 if (envelope.subject == null) { 383 return null; 384 } 385 // the subject could be encoded. If there is a decoding error, 386 // return the raw subject string. 387 try { 388 return MimeUtility.decodeText(envelope.subject); 389 } catch (UnsupportedEncodingException e) { 390 return envelope.subject; 391 } 392 } 393 394 /** 395 * Get the value of the "Date" header field. Returns null if 396 * if the field is absent or the date is not in a parseable format. 397 * 398 * @return A Date object parsed according to RFC 822. 399 * @exception MessagingException 400 */ 401 public Date getSentDate() throws MessagingException { 402 // make sure we've retrieved the envelope information. 403 loadEnvelope(); 404 // just return that directly 405 return envelope.date; 406 } 407 408 409 /** 410 * Get the message received date. 411 * 412 * @return Always returns the formatted INTERNALDATE, if available. 413 * @exception MessagingException 414 */ 415 public Date getReceivedDate() throws MessagingException { 416 loadEnvelope(); 417 return receivedDate; 418 } 419 420 421 /** 422 * Retrieve the size of the message content. The content will 423 * be retrieved from the server, if necessary. 424 * 425 * @return The size of the content. 426 * @exception MessagingException 427 */ 428 public int getSize() throws MessagingException { 429 // make sure we've retrieved the envelope information. We load the 430 // size when we retrieve that. 431 loadEnvelope(); 432 return size; 433 } 434 435 436 /** 437 * Get a line count for the IMAP message. This is potentially 438 * stored in the Lines article header. If not there, we return 439 * a default of -1. 440 * 441 * @return The header line count estimate, or -1 if not retrieveable. 442 * @exception MessagingException 443 */ 444 public int getLineCount() throws MessagingException { 445 loadBodyStructure(); 446 return bodyStructure.lines; 447 } 448 449 /** 450 * Return the IMAP in reply to information (retrieved with the 451 * ENVELOPE). 452 * 453 * @return The in reply to String value, if available. 454 * @exception MessagingException 455 */ 456 public String getInReplyTo() throws MessagingException { 457 loadEnvelope(); 458 return envelope.inReplyTo; 459 } 460 461 /** 462 * Returns the current content type (defined in the "Content-Type" 463 * header. If not available, "text/plain" is the default. 464 * 465 * @return The String name of the message content type. 466 * @exception MessagingException 467 */ 468 public String getContentType() throws MessagingException { 469 loadBodyStructure(); 470 return bodyStructure.mimeType.toString(); 471 } 472 473 474 /** 475 * Tests to see if this message has a mime-type match with the 476 * given type name. 477 * 478 * @param type The tested type name. 479 * 480 * @return If this is a type match on the primary and secondare portion of the types. 481 * @exception MessagingException 482 */ 483 public boolean isMimeType(String type) throws MessagingException { 484 loadBodyStructure(); 485 return bodyStructure.mimeType.match(type); 486 } 487 488 /** 489 * Retrieve the message "Content-Disposition" header field. 490 * This value represents how the part should be represented to 491 * the user. 492 * 493 * @return The string value of the Content-Disposition field. 494 * @exception MessagingException 495 */ 496 public String getDisposition() throws MessagingException { 497 loadBodyStructure(); 498 if (bodyStructure.disposition != null) { 499 return bodyStructure.disposition.getDisposition(); 500 } 501 return null; 502 } 503 504 /** 505 * Decode the Content-Transfer-Encoding header to determine 506 * the transfer encoding type. 507 * 508 * @return The string name of the required encoding. 509 * @exception MessagingException 510 */ 511 public String getEncoding() throws MessagingException { 512 loadBodyStructure(); 513 return bodyStructure.transferEncoding; 514 } 515 516 /** 517 * Retrieve the value of the "Content-ID" header. Returns null 518 * if the header does not exist. 519 * 520 * @return The current header value or null. 521 * @exception MessagingException 522 */ 523 public String getContentID() throws MessagingException { 524 loadBodyStructure(); 525 return bodyStructure.contentID; 526 } 527 528 public String getContentMD5() throws MessagingException { 529 loadBodyStructure(); 530 return bodyStructure.md5Hash; 531 } 532 533 534 public String getDescription() throws MessagingException { 535 loadBodyStructure(); 536 537 if (bodyStructure.contentDescription == null) { 538 return null; 539 } 540 // the subject could be encoded. If there is a decoding error, 541 // return the raw subject string. 542 try { 543 return MimeUtility.decodeText(bodyStructure.contentDescription); 544 } catch (UnsupportedEncodingException e) { 545 return bodyStructure.contentDescription; 546 } 547 } 548 549 /** 550 * Return the content languages associated with this 551 * message. 552 * 553 * @return 554 * @exception MessagingException 555 */ 556 public String[] getContentLanguage() throws MessagingException { 557 loadBodyStructure(); 558 559 if (!bodyStructure.languages.isEmpty()) { 560 return (String[])bodyStructure.languages.toArray(new String[bodyStructure.languages.size()]); 561 } 562 return null; 563 } 564 565 public String getMessageID() throws MessagingException { 566 loadEnvelope(); 567 return envelope.messageID; 568 } 569 570 public void setFrom(Address address) throws MessagingException { 571 throw new IllegalWriteException("IMAP messages are read-only"); 572 } 573 574 public void addFrom(Address[] address) throws MessagingException { 575 throw new IllegalWriteException("IMAP messages are read-only"); 576 } 577 578 public void setSender(Address address) throws MessagingException { 579 throw new IllegalWriteException("IMAP messages are read-only"); 580 } 581 582 public void setRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException { 583 throw new IllegalWriteException("IMAP messages are read-only"); 584 } 585 586 public void setRecipients(Message.RecipientType type, String address) throws MessagingException { 587 throw new IllegalWriteException("IMAP messages are read-only"); 588 } 589 590 public void addRecipients(Message.RecipientType type, Address[] address) throws MessagingException { 591 throw new IllegalWriteException("IMAP messages are read-only"); 592 } 593 594 public void setReplyTo(Address[] address) throws MessagingException { 595 throw new IllegalWriteException("IMAP messages are read-only"); 596 } 597 598 public void setSubject(String subject) throws MessagingException { 599 throw new IllegalWriteException("IMAP messages are read-only"); 600 } 601 602 public void setSubject(String subject, String charset) throws MessagingException { 603 throw new IllegalWriteException("IMAP messages are read-only"); 604 } 605 606 public void setSentDate(Date sent) throws MessagingException { 607 throw new IllegalWriteException("IMAP messages are read-only"); 608 } 609 610 public void setDisposition(String disposition) throws MessagingException { 611 throw new IllegalWriteException("IMAP messages are read-only"); 612 } 613 614 public void setContentID(String cid) throws MessagingException { 615 throw new IllegalWriteException("IMAP messages are read-only"); 616 } 617 618 public void setContentMD5(String md5) throws MessagingException { 619 throw new IllegalWriteException("IMAP messages are read-only"); 620 } 621 622 public void setDescription(String description) throws MessagingException { 623 throw new IllegalWriteException("IMAP messages are read-only"); 624 } 625 626 public void setDescription(String description, String charset) throws MessagingException { 627 throw new IllegalWriteException("IMAP messages are read-only"); 628 } 629 630 public void setContentLanguage(String[] languages) throws MessagingException { 631 throw new IllegalWriteException("IMAP messages are read-only"); 632 } 633 634 635 /****************************************************************** 636 * Following is a set of methods that deal with headers 637 * These methods are just overrides on the superclass methods to 638 * allow lazy loading of the header information. 639 ********************************************************************/ 640 641 public String[] getHeader(String name) throws MessagingException { 642 loadHeaders(); 643 return headers.getHeader(name); 644 } 645 646 public String getHeader(String name, String delimiter) throws MessagingException { 647 loadHeaders(); 648 return headers.getHeader(name, delimiter); 649 } 650 651 public Enumeration getAllHeaders() throws MessagingException { 652 loadHeaders(); 653 return headers.getAllHeaders(); 654 } 655 656 public Enumeration getMatchingHeaders(String[] names) throws MessagingException { 657 loadHeaders(); 658 return headers.getMatchingHeaders(names); 659 } 660 661 public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException { 662 loadHeaders(); 663 return headers.getNonMatchingHeaders(names); 664 } 665 666 public Enumeration getAllHeaderLines() throws MessagingException { 667 loadHeaders(); 668 return headers.getAllHeaderLines(); 669 } 670 671 public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException { 672 loadHeaders(); 673 return headers.getMatchingHeaderLines(names); 674 } 675 676 public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException { 677 loadHeaders(); 678 return headers.getNonMatchingHeaderLines(names); 679 } 680 681 // the following are overrides for header modification methods. These messages are read only, 682 // so the headers cannot be modified. 683 public void addHeader(String name, String value) throws MessagingException { 684 throw new IllegalWriteException("IMAP messages are read-only"); 685 } 686 687 public void setHeader(String name, String value) throws MessagingException { 688 throw new IllegalWriteException("IMAP messages are read-only"); 689 } 690 691 692 public void removeHeader(String name) throws MessagingException { 693 throw new IllegalWriteException("IMAP messages are read-only"); 694 } 695 696 public void addHeaderLine(String line) throws MessagingException { 697 throw new IllegalWriteException("IMAP messages are read-only"); 698 } 699 700 /** 701 * We cannot modify these messages 702 */ 703 public void saveChanges() throws MessagingException { 704 throw new IllegalWriteException("IMAP messages are read-only"); 705 } 706 707 708 /** 709 * Utility method for synchronizing IMAP envelope information and 710 * the message headers. 711 * 712 * @param header The target header name. 713 * @param addresses The update addresses. 714 */ 715 protected void updateHeader(String header, InternetAddress[] addresses) throws MessagingException { 716 if (addresses != null) { 717 headers.addHeader(header, InternetAddress.toString(addresses)); 718 } 719 } 720 721 /** 722 * Utility method for synchronizing IMAP envelope information and 723 * the message headers. 724 * 725 * @param header The target header name. 726 * @param address The update address. 727 */ 728 protected void updateHeader(String header, Address address) throws MessagingException { 729 if (address != null) { 730 headers.setHeader(header, address.toString()); 731 } 732 } 733 734 /** 735 * Utility method for synchronizing IMAP envelope information and 736 * the message headers. 737 * 738 * @param header The target header name. 739 * @param value The update value. 740 */ 741 protected void updateHeader(String header, String value) throws MessagingException { 742 if (value != null) { 743 headers.setHeader(header, value); 744 } 745 } 746 747 748 /** 749 * Create the DataHandler object for this message. 750 * 751 * @return The DataHandler object that processes the content set for this 752 * message. 753 * @exception MessagingException 754 */ 755 public synchronized DataHandler getDataHandler() throws MessagingException { 756 // check the validity and make sure we have the body structure information. 757 checkValidity(); 758 loadBodyStructure(); 759 if (dh == null) { 760 // are we working with a multipart message here? 761 if (bodyStructure.isMultipart()) { 762 dh = new DataHandler(new IMAPMultipartDataSource(this, this, section, bodyStructure)); 763 return dh; 764 } 765 else if (bodyStructure.isAttachedMessage()) { 766 dh = new DataHandler(new IMAPAttachedMessage(this, section, bodyStructure.nestedEnvelope, bodyStructure.nestedBody), 767 bodyStructure.mimeType.toString()); 768 return dh; 769 } 770 } 771 772 // single part messages get handled the normal way. 773 return super.getDataHandler(); 774 } 775 776 public void setDataHandler(DataHandler content) throws MessagingException { 777 throw new IllegalWriteException("IMAP body parts are read-only"); 778 } 779 780 /** 781 * Update the message headers from an input stream. 782 * 783 * @param in The InputStream source for the header information. 784 * 785 * @exception MessagingException 786 */ 787 public void updateHeaders(InputStream in) throws MessagingException { 788 // wrap a stream around the reply data and read as headers. 789 headers = new InternetHeaders(in); 790 allHeadersRetrieved = true; 791 } 792 793 /** 794 * Load the flag set for this message from the server. 795 * 796 * @exception MessagingeException 797 */ 798 public void loadFlags() throws MessagingException { 799 // make sure this is in a valid state. 800 checkValidity(); 801 // if the flags are already loaded, nothing to do 802 if (flags != null) { 803 return; 804 } 805 // we need to ensure that we're the only ones with access to the folder's 806 // message cache any time we need to talk to the server. This needs to be 807 // held until after we release the connection so that any pending EXPUNGE 808 // untagged responses are processed before the next time the folder connection is 809 // used. 810 synchronized (folder) { 811 IMAPConnection connection = getConnection(); 812 813 try { 814 // fetch the flags for this item. 815 flags = connection.fetchFlags(sequenceNumber); 816 } finally { 817 releaseConnection(connection); 818 } 819 } 820 } 821 822 823 /** 824 * Retrieve the message raw message headers from the IMAP server, synchronizing with the existing header set. 825 * 826 * @exception MessagingException 827 */ 828 protected synchronized void loadHeaders() throws MessagingException { 829 // don't retrieve if already loaded. 830 if (allHeadersRetrieved) { 831 return; 832 } 833 834 // make sure this is in a valid state. 835 checkValidity(); 836 // we need to ensure that we're the only ones with access to the folder's 837 // message cache any time we need to talk to the server. This needs to be 838 // held until after we release the connection so that any pending EXPUNGE 839 // untagged responses are processed before the next time the folder connection is 840 // used. 841 synchronized (folder) { 842 IMAPConnection connection = getConnection(); 843 844 try { 845 // get the headers and set 846 headers = connection.fetchHeaders(sequenceNumber, section); 847 // we have the entire header set, not just a subset. 848 allHeadersRetrieved = true; 849 } finally { 850 releaseConnection(connection); 851 } 852 } 853 } 854 855 856 /** 857 * Retrieve the message envelope from the IMAP server, synchronizing the headers with the 858 * information. 859 * 860 * @exception MessagingException 861 */ 862 protected synchronized void loadEnvelope() throws MessagingException { 863 // don't retrieve if already loaded. 864 if (envelope != null) { 865 return; 866 } 867 868 // make sure this is in a valid state. 869 checkValidity(); 870 // we need to ensure that we're the only ones with access to the folder's 871 // message cache any time we need to talk to the server. This needs to be 872 // held until after we release the connection so that any pending EXPUNGE 873 // untagged responses are processed before the next time the folder connection is 874 // used. 875 synchronized (folder) { 876 IMAPConnection connection = getConnection(); 877 try { 878 // fetch the envelope information for this 879 List fetches = connection.fetchEnvelope(sequenceNumber); 880 // now process all of the fetch responses before releasing the folder lock. 881 // it's possible that an unsolicited update on another thread might try to 882 // make an update, causing a potential deadlock. 883 for (int i = 0; i < fetches.size(); i++) { 884 // get the returned data items from each of the fetch responses 885 // and process. 886 IMAPFetchResponse fetch = (IMAPFetchResponse)fetches.get(i); 887 // update the internal info 888 updateMessageInformation(fetch); 889 } 890 } finally { 891 releaseConnection(connection); 892 } 893 } 894 } 895 896 897 /** 898 * Retrieve the message envelope from the IMAP server, synchronizing the headers with the 899 * information. 900 * 901 * @exception MessagingException 902 */ 903 protected synchronized void updateEnvelope(IMAPEnvelope envelope) throws MessagingException { 904 // set the envelope item 905 this.envelope = envelope; 906 907 // copy header type information from the envelope into the headers. 908 updateHeader("From", envelope.from); 909 if (envelope.sender != null) { 910 // we can only have a single sender, even though the envelope theoretically supports more. 911 updateHeader("Sender", envelope.sender[0]); 912 } 913 updateHeader("To", envelope.to); 914 updateHeader("Cc", envelope.cc); 915 updateHeader("Bcc", envelope.bcc); 916 updateHeader("Reply-To", envelope.replyTo); 917 // NB: This is already in encoded form, if needed. 918 updateHeader("Subject", envelope.subject); 919 updateHeader("Message-ID", envelope.messageID); 920 } 921 922 923 /** 924 * Retrieve the BODYSTRUCTURE information from the IMAP server. 925 * 926 * @exception MessagingException 927 */ 928 protected synchronized void loadBodyStructure() throws MessagingException { 929 // don't retrieve if already loaded. 930 if (bodyStructure != null) { 931 return; 932 } 933 934 // make sure this is in a valid state. 935 checkValidity(); 936 // we need to ensure that we're the only ones with access to the folder's 937 // message cache any time we need to talk to the server. This needs to be 938 // held until after we release the connection so that any pending EXPUNGE 939 // untagged responses are processed before the next time the folder connection is 940 // used. 941 synchronized (folder) { 942 IMAPConnection connection = getConnection(); 943 try { 944 // fetch the envelope information for this 945 bodyStructure = connection.fetchBodyStructure(sequenceNumber); 946 // go update all of the information 947 } finally { 948 releaseConnection(connection); 949 } 950 951 // update this before we release the folder lock so we can avoid 952 // deadlock. 953 updateBodyStructure(bodyStructure); 954 } 955 } 956 957 958 /** 959 * Update the BODYSTRUCTURE information from the IMAP server. 960 * 961 * @exception MessagingException 962 */ 963 protected synchronized void updateBodyStructure(IMAPBodyStructure structure) throws MessagingException { 964 // save the reference. 965 bodyStructure = structure; 966 // now update various headers with the information from the body structure 967 968 // now update header information with the body structure data. 969 if (bodyStructure.lines != -1) { 970 updateHeader("Lines", Integer.toString(bodyStructure.lines)); 971 } 972 973 // languages are a little more complicated 974 if (bodyStructure.languages != null) { 975 // this is a duplicate of what happens in the super class, but 976 // the superclass methods call setHeader(), which we override and 977 // throw an exception for. We need to set the headers ourselves. 978 if (bodyStructure.languages.size() == 1) { 979 updateHeader("Content-Language", (String)bodyStructure.languages.get(0)); 980 } 981 else { 982 StringBuffer buf = new StringBuffer(bodyStructure.languages.size() * 20); 983 buf.append(bodyStructure.languages.get(0)); 984 for (int i = 1; i < bodyStructure.languages.size(); i++) { 985 buf.append(',').append(bodyStructure.languages.get(i)); 986 } 987 updateHeader("Content-Language", buf.toString()); 988 } 989 } 990 991 updateHeader("Content-Type", bodyStructure.mimeType.toString()); 992 if (bodyStructure.disposition != null) { 993 updateHeader("Content-Disposition", bodyStructure.disposition.toString()); 994 } 995 996 updateHeader("Content-Transfer-Encoding", bodyStructure.transferEncoding); 997 updateHeader("Content-ID", bodyStructure.contentID); 998 // NB: This is already in encoded form, if needed. 999 updateHeader("Content-Description", bodyStructure.contentDescription); 1000 } 1001 1002 1003 /** 1004 * Load the message content into the Message object. 1005 * 1006 * @exception MessagingException 1007 */ 1008 protected void loadContent() throws MessagingException { 1009 // if we've loaded this already, just return 1010 if (content != null) { 1011 return; 1012 } 1013 1014 // we need to ensure that we're the only ones with access to the folder's 1015 // message cache any time we need to talk to the server. This needs to be 1016 // held until after we release the connection so that any pending EXPUNGE 1017 // untagged responses are processed before the next time the folder connection is 1018 // used. 1019 synchronized (folder) { 1020 IMAPConnection connection = getConnection(); 1021 try { 1022 // load the content from the server. 1023 content = connection.fetchContent(getSequenceNumber(), section); 1024 } finally { 1025 releaseConnection(connection); 1026 } 1027 } 1028 } 1029 1030 1031 /** 1032 * Retrieve the sequence number assigned to this message. 1033 * 1034 * @return The messages assigned sequence number. This maps back to the server's assigned number for 1035 * this message. 1036 */ 1037 int getSequenceNumber() { 1038 return sequenceNumber; 1039 } 1040 1041 /** 1042 * Set the sequence number for the message. This 1043 * is updated whenever messages get expunged from 1044 * the folder. 1045 * 1046 * @param s The new sequence number. 1047 */ 1048 void setSequenceNumber(int s) { 1049 sequenceNumber = s; 1050 } 1051 1052 1053 /** 1054 * Retrieve the message UID value. 1055 * 1056 * @return The assigned UID value, if we have the information. 1057 */ 1058 long getUID() { 1059 return uid; 1060 } 1061 1062 /** 1063 * Set the message UID value. 1064 * 1065 * @param uid The new UID value. 1066 */ 1067 void setUID(long uid) { 1068 this.uid = uid; 1069 } 1070 1071 1072 /** 1073 * get the current connection pool attached to the folder. We need 1074 * to do this dynamically, to A) ensure we're only accessing an 1075 * currently open folder, and B) to make sure we're using the 1076 * correct connection attached to the folder. 1077 * 1078 * @return A connection attached to the hosting folder. 1079 */ 1080 protected IMAPConnection getConnection() throws MessagingException { 1081 // the folder owns everything. 1082 return ((IMAPFolder)folder).getMessageConnection(); 1083 } 1084 1085 /** 1086 * Release the connection back to the Folder after performing an operation 1087 * that requires a connection. 1088 * 1089 * @param connection The previously acquired connection. 1090 */ 1091 protected void releaseConnection(IMAPConnection connection) throws MessagingException { 1092 // the folder owns everything. 1093 ((IMAPFolder)folder).releaseMessageConnection(connection); 1094 } 1095 1096 1097 /** 1098 * Check the validity of the current message. This ensures that 1099 * A) the folder is currently open, B) that the message has not 1100 * been expunged (after getting the latest status from the server). 1101 * 1102 * @exception MessagingException 1103 */ 1104 protected void checkValidity() throws MessagingException { 1105 checkValidity(false); 1106 } 1107 1108 1109 /** 1110 * Check the validity of the current message. This ensures that 1111 * A) the folder is currently open, B) that the message has not 1112 * been expunged (after getting the latest status from the server). 1113 * 1114 * @exception MessagingException 1115 */ 1116 protected void checkValidity(boolean update) throws MessagingException { 1117 // we need to ensure that we're the only ones with access to the folder's 1118 // message cache any time we need to talk to the server. This needs to be 1119 // held until after we release the connection so that any pending EXPUNGE 1120 // untagged responses are processed before the next time the folder connection is 1121 // used. 1122 if (update) { 1123 synchronized (folder) { 1124 // have the connection update the folder status. This might result in this message 1125 // changing its state to expunged. It might also result in an exception if the 1126 // folder has been closed. 1127 IMAPConnection connection = getConnection(); 1128 1129 try { 1130 connection.updateMailboxStatus(); 1131 } finally { 1132 // this will force any expunged messages to be processed before we release 1133 // the lock. 1134 releaseConnection(connection); 1135 } 1136 } 1137 } 1138 1139 // now see if we've been expunged, this is a bad op on the message. 1140 if (isExpunged()) { 1141 throw new MessageRemovedException("Illegal opertion on a deleted message"); 1142 } 1143 } 1144 1145 1146 /** 1147 * Evaluate whether this message requires any of the information 1148 * in a FetchProfile to be fetched from the server. If the messages 1149 * already contains the information in the profile, it returns false. 1150 * This allows IMAPFolder to optimize fetch() requests to just the 1151 * messages that are missing any of the requested information. 1152 * 1153 * NOTE: If any of the items in the profile are missing, then this 1154 * message will be updated with ALL of the items. 1155 * 1156 * @param profile The FetchProfile indicating the information that should be prefetched. 1157 * 1158 * @return true if any of the profile information requires fetching. false if this 1159 * message already contains the given information. 1160 */ 1161 protected boolean evaluateFetch(FetchProfile profile) { 1162 // the fetch profile can contain a number of different item types. Validate 1163 // whether we need any of these and return true on the first mismatch. 1164 1165 // the UID is a common fetch request, put it first. 1166 if (profile.contains(UIDFolder.FetchProfileItem.UID) && uid == -1) { 1167 return true; 1168 } 1169 if (profile.contains(FetchProfile.Item.ENVELOPE) && envelope == null) { 1170 return true; 1171 } 1172 if (profile.contains(FetchProfile.Item.FLAGS) && flags == null) { 1173 return true; 1174 } 1175 if (profile.contains(FetchProfile.Item.CONTENT_INFO) && bodyStructure == null) { 1176 return true; 1177 } 1178 // The following profile items are our implementation of items that the 1179 // Sun IMAPFolder implementation supports. 1180 if (profile.contains(IMAPFolder.FetchProfileItem.HEADERS) && !allHeadersRetrieved) { 1181 return true; 1182 } 1183 if (profile.contains(IMAPFolder.FetchProfileItem.SIZE) && bodyStructure.bodySize < 0) { 1184 return true; 1185 } 1186 // last bit after checking each of the information types is to see if 1187 // particular headers have been requested and whether those are on the 1188 // set we do have loaded. 1189 String [] requestedHeaders = profile.getHeaderNames(); 1190 1191 // ok, any missing header in the list is enough to force us to request the 1192 // information. 1193 for (int i = 0; i < requestedHeaders.length; i++) { 1194 if (headers.getHeader(requestedHeaders[i]) == null) { 1195 return true; 1196 } 1197 } 1198 // this message, at least, does not need anything fetched. 1199 return false; 1200 } 1201 1202 /** 1203 * Update a message instance with information retrieved via an IMAP FETCH 1204 * command. The command response for this message may contain multiple pieces 1205 * that we need to process. 1206 * 1207 * @param response The response line, which may contain multiple data items. 1208 * 1209 * @exception MessagingException 1210 */ 1211 void updateMessageInformation(IMAPFetchResponse response) throws MessagingException { 1212 // get the list of data items associated with this response. We can have 1213 // a large number of items returned in a single update. 1214 List items = response.getDataItems(); 1215 1216 for (int i = 0; i < items.size(); i++) { 1217 IMAPFetchDataItem item = (IMAPFetchDataItem)items.get(i); 1218 1219 switch (item.getType()) { 1220 // if the envelope has been requested, we'll end up with all of these items. 1221 case IMAPFetchDataItem.ENVELOPE: 1222 // update the envelope and map the envelope items into the headers. 1223 updateEnvelope((IMAPEnvelope)item); 1224 break; 1225 case IMAPFetchDataItem.INTERNALDATE: 1226 receivedDate = ((IMAPInternalDate)item).getDate();; 1227 break; 1228 case IMAPFetchDataItem.SIZE: 1229 size = ((IMAPMessageSize)item).size; 1230 break; 1231 case IMAPFetchDataItem.UID: 1232 uid = ((IMAPUid)item).uid; 1233 // make sure the folder knows about the UID update. 1234 ((IMAPFolder)folder).addToUidCache(new Long(uid), this); 1235 break; 1236 case IMAPFetchDataItem.BODYSTRUCTURE: 1237 updateBodyStructure((IMAPBodyStructure)item); 1238 break; 1239 // a partial or full header update 1240 case IMAPFetchDataItem.HEADER: 1241 { 1242 // if we've fetched the complete set, then replace what we have 1243 IMAPInternetHeader h = (IMAPInternetHeader)item; 1244 if (h.isComplete()) { 1245 // we've got a complete header set now. 1246 this.headers = h.headers; 1247 allHeadersRetrieved = true; 1248 } 1249 else { 1250 // need to merge the requested headers in with 1251 // our existing set. We need to be careful, since we 1252 // don't want to add duplicates. 1253 mergeHeaders(h.headers); 1254 } 1255 } 1256 default: 1257 } 1258 } 1259 } 1260 1261 1262 /** 1263 * Merge a subset of the requested headers with our existing partial set. 1264 * The new set will contain all headers requested from the server, plus 1265 * any of our existing headers that were not included in the retrieved set. 1266 * 1267 * @param newHeaders The retrieved set of headers. 1268 */ 1269 protected synchronized void mergeHeaders(InternetHeaders newHeaders) { 1270 // This is sort of tricky to manage. The input headers object is a fresh set 1271 // retrieved from the server, but it's a subset of the headers. Our existing set 1272 // might not be complete, but it may contain duplicates of information in the 1273 // retrieved set, plus headers that are not in the retrieved set. To keep from 1274 // adding duplicates, we'll only add headers that are not in the retrieved set to 1275 // that set. 1276 1277 // start by running through the list of headers 1278 Enumeration e = headers.getAllHeaders(); 1279 1280 while (e.hasMoreElements()) { 1281 Header header = (Header)e.nextElement(); 1282 // if there are no headers with this name in the new set, then 1283 // we can add this. Note that to add the header, we need to 1284 // retrieve all instances by this name and add them as a unit. 1285 // When we hit one of the duplicates again with the enumeration, 1286 // we'll skip it then because the merge target will have everything. 1287 if (newHeaders.getHeader(header.getName()) == null) { 1288 // get all occurrences of this name and stuff them into the 1289 // new list 1290 String name = header.getName(); 1291 String[] a = headers.getHeader(name); 1292 for (int i = 0; i < a.length; i++) { 1293 newHeaders.addHeader(name, a[i]); 1294 } 1295 } 1296 } 1297 // and replace the current header set 1298 headers = newHeaders; 1299 } 1300 }