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