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