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 */ 017package org.apache.commons.mail; 018 019import java.io.File; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.UnsupportedEncodingException; 023import java.net.URL; 024 025import javax.activation.DataHandler; 026import javax.activation.DataSource; 027import javax.activation.FileDataSource; 028import javax.activation.URLDataSource; 029import javax.mail.BodyPart; 030import javax.mail.MessagingException; 031import javax.mail.internet.MimeBodyPart; 032import javax.mail.internet.MimeMultipart; 033import javax.mail.internet.MimePart; 034import javax.mail.internet.MimeUtility; 035 036/** 037 * A multipart email. 038 * 039 * <p>This class is used to send multi-part internet email like 040 * messages with attachments. 041 * 042 * <p>To create a multi-part email, call the default constructor and 043 * then you can call setMsg() to set the message and call the 044 * different attach() methods. 045 * 046 * @since 1.0 047 */ 048public class MultiPartEmail extends Email 049{ 050 /** Body portion of the email. */ 051 private MimeMultipart container; 052 053 /** The message container. */ 054 private BodyPart primaryBodyPart; 055 056 /** The MIME subtype. */ 057 private String subType; 058 059 /** Indicates if the message has been initialized. */ 060 private boolean initialized; 061 062 /** Indicates if attachments have been added to the message. */ 063 private boolean boolHasAttachments; 064 065 /** 066 * Set the MIME subtype of the email. 067 * 068 * @param aSubType MIME subtype of the email 069 * @since 1.0 070 */ 071 public void setSubType(final String aSubType) 072 { 073 this.subType = aSubType; 074 } 075 076 /** 077 * Get the MIME subtype of the email. 078 * 079 * @return MIME subtype of the email 080 * @since 1.0 081 */ 082 public String getSubType() 083 { 084 return subType; 085 } 086 087 /** 088 * Add a new part to the email. 089 * 090 * @param partContent The content. 091 * @param partContentType The content type. 092 * @return An Email. 093 * @throws EmailException see javax.mail.internet.MimeBodyPart 094 * for definitions 095 * @since 1.0 096 */ 097 public Email addPart(final String partContent, final String partContentType) 098 throws EmailException 099 { 100 final BodyPart bodyPart = createBodyPart(); 101 try 102 { 103 bodyPart.setContent(partContent, partContentType); 104 getContainer().addBodyPart(bodyPart); 105 } 106 catch (final MessagingException me) 107 { 108 throw new EmailException(me); 109 } 110 111 return this; 112 } 113 114 /** 115 * Add a new part to the email. 116 * 117 * @param multipart The MimeMultipart. 118 * @return An Email. 119 * @throws EmailException see javax.mail.internet.MimeBodyPart 120 * for definitions 121 * @since 1.0 122 */ 123 public Email addPart(final MimeMultipart multipart) throws EmailException 124 { 125 try 126 { 127 return addPart(multipart, getContainer().getCount()); 128 } 129 catch (final MessagingException me) 130 { 131 throw new EmailException(me); 132 } 133 } 134 135 /** 136 * Add a new part to the email. 137 * 138 * @param multipart The part to add. 139 * @param index The index to add at. 140 * @return The email. 141 * @throws EmailException An error occurred while adding the part. 142 * @since 1.0 143 */ 144 public Email addPart(final MimeMultipart multipart, final int index) throws EmailException 145 { 146 final BodyPart bodyPart = createBodyPart(); 147 try 148 { 149 bodyPart.setContent(multipart); 150 getContainer().addBodyPart(bodyPart, index); 151 } 152 catch (final MessagingException me) 153 { 154 throw new EmailException(me); 155 } 156 157 return this; 158 } 159 160 /** 161 * Initialize the multipart email. 162 * @since 1.0 163 */ 164 protected void init() 165 { 166 if (initialized) 167 { 168 throw new IllegalStateException("Already initialized"); 169 } 170 171 container = createMimeMultipart(); 172 super.setContent(container); 173 174 initialized = true; 175 } 176 177 /** 178 * Set the message of the email. 179 * 180 * @param msg A String. 181 * @return An Email. 182 * @throws EmailException see javax.mail.internet.MimeBodyPart 183 * for definitions 184 * @since 1.0 185 */ 186 @Override 187 public Email setMsg(final String msg) throws EmailException 188 { 189 // throw exception on null message 190 if (EmailUtils.isEmpty(msg)) 191 { 192 throw new EmailException("Invalid message supplied"); 193 } 194 try 195 { 196 final BodyPart primary = getPrimaryBodyPart(); 197 198 if (primary instanceof MimePart && EmailUtils.isNotEmpty(charset)) 199 { 200 ((MimePart) primary).setText(msg, charset); 201 } 202 else 203 { 204 primary.setText(msg); 205 } 206 } 207 catch (final MessagingException me) 208 { 209 throw new EmailException(me); 210 } 211 return this; 212 } 213 214 /** 215 * Does the work of actually building the MimeMessage. Please note that 216 * a user rarely calls this method directly and only if he/she is 217 * interested in the sending the underlying MimeMessage without 218 * commons-email. 219 * 220 * @throws EmailException if there was an error. 221 * @since 1.0 222 */ 223 @Override 224 public void buildMimeMessage() throws EmailException 225 { 226 try 227 { 228 if (primaryBodyPart != null) 229 { 230 // before a multipart message can be sent, we must make sure that 231 // the content for the main body part was actually set. If not, 232 // an IOException will be thrown during super.send(). 233 234 final BodyPart body = this.getPrimaryBodyPart(); 235 try 236 { 237 body.getContent(); 238 } 239 catch (final IOException e) // NOPMD 240 { 241 // do nothing here. 242 // content will be set to an empty string as a result. 243 // (Should this really be rethrown as an email exception?) 244 // throw new EmailException(e); 245 } 246 } 247 248 if (subType != null) 249 { 250 getContainer().setSubType(subType); 251 } 252 253 super.buildMimeMessage(); 254 } 255 catch (final MessagingException me) 256 { 257 throw new EmailException(me); 258 } 259 } 260 261 /** 262 * Attach a file. 263 * 264 * @param file A file attachment 265 * @return A MultiPartEmail. 266 * @throws EmailException see javax.mail.internet.MimeBodyPart 267 * for definitions 268 * @since 1.3 269 */ 270 public MultiPartEmail attach(final File file) 271 throws EmailException 272 { 273 final String fileName = file.getAbsolutePath(); 274 275 try 276 { 277 if (!file.exists()) 278 { 279 throw new IOException("\"" + fileName + "\" does not exist"); 280 } 281 282 final FileDataSource fds = new FileDataSource(file); 283 284 return attach(fds, file.getName(), null, EmailAttachment.ATTACHMENT); 285 } 286 catch (final IOException e) 287 { 288 throw new EmailException("Cannot attach file \"" + fileName + "\"", e); 289 } 290 } 291 292 /** 293 * Attach an EmailAttachment. 294 * 295 * @param attachment An EmailAttachment. 296 * @return A MultiPartEmail. 297 * @throws EmailException see javax.mail.internet.MimeBodyPart 298 * for definitions 299 * @since 1.0 300 */ 301 public MultiPartEmail attach(final EmailAttachment attachment) 302 throws EmailException 303 { 304 MultiPartEmail result = null; 305 306 if (attachment == null) 307 { 308 throw new EmailException("Invalid attachment supplied"); 309 } 310 311 final URL url = attachment.getURL(); 312 313 if (url == null) 314 { 315 String fileName = null; 316 try 317 { 318 fileName = attachment.getPath(); 319 final File file = new File(fileName); 320 if (!file.exists()) 321 { 322 throw new IOException("\"" + fileName + "\" does not exist"); 323 } 324 result = 325 attach( 326 new FileDataSource(file), 327 attachment.getName(), 328 attachment.getDescription(), 329 attachment.getDisposition()); 330 } 331 catch (final IOException e) 332 { 333 throw new EmailException("Cannot attach file \"" + fileName + "\"", e); 334 } 335 } 336 else 337 { 338 result = 339 attach( 340 url, 341 attachment.getName(), 342 attachment.getDescription(), 343 attachment.getDisposition()); 344 } 345 346 return result; 347 } 348 349 /** 350 * Attach a file located by its URL. The disposition of the file 351 * is set to mixed. 352 * 353 * @param url The URL of the file (may be any valid URL). 354 * @param name The name field for the attachment. 355 * @param description A description for the attachment. 356 * @return A MultiPartEmail. 357 * @throws EmailException see javax.mail.internet.MimeBodyPart 358 * for definitions 359 * @since 1.0 360 */ 361 public MultiPartEmail attach(final URL url, final String name, final String description) 362 throws EmailException 363 { 364 return attach(url, name, description, EmailAttachment.ATTACHMENT); 365 } 366 367 /** 368 * Attach a file located by its URL. 369 * 370 * @param url The URL of the file (may be any valid URL). 371 * @param name The name field for the attachment. 372 * @param description A description for the attachment. 373 * @param disposition Either mixed or inline. 374 * @return A MultiPartEmail. 375 * @throws EmailException see javax.mail.internet.MimeBodyPart 376 * for definitions 377 * @since 1.0 378 */ 379 public MultiPartEmail attach( 380 final URL url, 381 final String name, 382 final String description, 383 final String disposition) 384 throws EmailException 385 { 386 // verify that the URL is valid 387 try 388 { 389 final InputStream is = url.openStream(); 390 is.close(); 391 } 392 catch (final IOException e) 393 { 394 throw new EmailException("Invalid URL set:" + url, e); 395 } 396 397 return attach(new URLDataSource(url), name, description, disposition); 398 } 399 400 /** 401 * Attach a file specified as a DataSource interface. 402 * 403 * @param ds A DataSource interface for the file. 404 * @param name The name field for the attachment. 405 * @param description A description for the attachment. 406 * @return A MultiPartEmail. 407 * @throws EmailException see javax.mail.internet.MimeBodyPart 408 * for definitions 409 * @since 1.0 410 */ 411 public MultiPartEmail attach( 412 final DataSource ds, 413 final String name, 414 final String description) 415 throws EmailException 416 { 417 // verify that the DataSource is valid 418 try 419 { 420 final InputStream is = ds != null ? ds.getInputStream() : null; 421 if (is != null) 422 { 423 // close the input stream to prevent file locking on windows 424 is.close(); 425 } 426 427 if (is == null) 428 { 429 throw new EmailException("Invalid Datasource"); 430 } 431 } 432 catch (final IOException e) 433 { 434 throw new EmailException("Invalid Datasource", e); 435 } 436 437 return attach(ds, name, description, EmailAttachment.ATTACHMENT); 438 } 439 440 /** 441 * Attach a file specified as a DataSource interface. 442 * 443 * @param ds A DataSource interface for the file. 444 * @param name The name field for the attachment. 445 * @param description A description for the attachment. 446 * @param disposition Either mixed or inline. 447 * @return A MultiPartEmail. 448 * @throws EmailException see javax.mail.internet.MimeBodyPart 449 * for definitions 450 * @since 1.0 451 */ 452 public MultiPartEmail attach( 453 final DataSource ds, 454 String name, 455 final String description, 456 final String disposition) 457 throws EmailException 458 { 459 if (EmailUtils.isEmpty(name)) 460 { 461 name = ds.getName(); 462 } 463 final BodyPart bodyPart = createBodyPart(); 464 try 465 { 466 bodyPart.setDisposition(disposition); 467 bodyPart.setFileName(MimeUtility.encodeText(name)); 468 bodyPart.setDescription(description); 469 bodyPart.setDataHandler(new DataHandler(ds)); 470 471 getContainer().addBodyPart(bodyPart); 472 } 473 catch (final UnsupportedEncodingException uee) 474 { 475 // in case the filename could not be encoded 476 throw new EmailException(uee); 477 } 478 catch (final MessagingException me) 479 { 480 throw new EmailException(me); 481 } 482 setBoolHasAttachments(true); 483 484 return this; 485 } 486 487 /** 488 * Gets first body part of the message. 489 * 490 * @return The primary body part. 491 * @throws MessagingException An error occurred while getting the primary body part. 492 * @since 1.0 493 */ 494 protected BodyPart getPrimaryBodyPart() throws MessagingException 495 { 496 if (!initialized) 497 { 498 init(); 499 } 500 501 // Add the first body part to the message. The fist body part must be 502 if (this.primaryBodyPart == null) 503 { 504 primaryBodyPart = createBodyPart(); 505 getContainer().addBodyPart(primaryBodyPart, 0); 506 } 507 508 return primaryBodyPart; 509 } 510 511 /** 512 * Gets the message container. 513 * 514 * @return The message container. 515 * @since 1.0 516 */ 517 protected MimeMultipart getContainer() 518 { 519 if (!initialized) 520 { 521 init(); 522 } 523 return container; 524 } 525 526 /** 527 * Creates a body part object. 528 * Can be overridden if you don't want to create a BodyPart. 529 * 530 * @return the created body part 531 */ 532 protected BodyPart createBodyPart() 533 { 534 return new MimeBodyPart(); 535 } 536 537 /** 538 * Creates a mime multipart object. 539 * 540 * @return the created mime part 541 */ 542 protected MimeMultipart createMimeMultipart() 543 { 544 return new MimeMultipart(); 545 } 546 547 /** 548 * Checks whether there are attachments. 549 * 550 * @return true if there are attachments 551 * @since 1.0 552 */ 553 public boolean isBoolHasAttachments() 554 { 555 return boolHasAttachments; 556 } 557 558 /** 559 * Sets whether there are attachments. 560 * 561 * @param b the attachments flag 562 * @since 1.0 563 */ 564 public void setBoolHasAttachments(final boolean b) 565 { 566 boolHasAttachments = b; 567 } 568 569 /** 570 * Checks if this object is initialized. 571 * 572 * @return true if initialized 573 */ 574 protected boolean isInitialized() 575 { 576 return initialized; 577 } 578 579 /** 580 * Sets the initialized status of this object. 581 * 582 * @param b the initialized status flag 583 */ 584 protected void setInitialized(final boolean b) 585 { 586 initialized = b; 587 } 588 589}