001package de.deepamehta.plugins.mail; 002 003import java.io.File; 004import java.io.UnsupportedEncodingException; 005import java.util.Collection; 006import java.util.List; 007import java.util.Map; 008import java.util.logging.Level; 009import java.util.logging.Logger; 010import java.util.ArrayList; 011 012import javax.mail.internet.InternetAddress; 013import javax.mail.internet.AddressException; 014import javax.ws.rs.GET; 015import javax.ws.rs.POST; 016import javax.ws.rs.Path; 017import javax.ws.rs.PathParam; 018import javax.ws.rs.Produces; 019import javax.ws.rs.QueryParam; 020import javax.ws.rs.WebApplicationException; 021import javax.ws.rs.core.MediaType; 022 023import org.apache.commons.mail.EmailAttachment; 024import org.apache.commons.mail.EmailException; 025import org.apache.commons.mail.HtmlEmail; 026import org.codehaus.jettison.json.JSONException; 027import org.jsoup.nodes.Document; 028 029import de.deepamehta.core.Association; 030import de.deepamehta.core.ChildTopics; 031import de.deepamehta.core.RelatedTopic; 032import de.deepamehta.core.Topic; 033import de.deepamehta.core.model.AssociationModel; 034import de.deepamehta.core.model.ChildTopicsModel; 035import de.deepamehta.core.model.SimpleValue; 036import de.deepamehta.core.model.TopicModel; 037import de.deepamehta.core.model.TopicRoleModel; 038import de.deepamehta.core.osgi.PluginActivator; 039import de.deepamehta.core.service.Inject; 040import de.deepamehta.core.service.PluginService; 041import de.deepamehta.core.service.ResultList; 042import de.deepamehta.core.service.event.PostCreateTopicListener; 043import de.deepamehta.core.storage.spi.DeepaMehtaTransaction; 044import de.deepamehta.plugins.accesscontrol.model.ACLEntry; 045import de.deepamehta.plugins.accesscontrol.model.AccessControlList; 046import de.deepamehta.plugins.accesscontrol.model.Operation; 047import de.deepamehta.plugins.accesscontrol.model.UserRole; 048import de.deepamehta.plugins.accesscontrol.service.AccessControlService; 049import de.deepamehta.plugins.files.ResourceInfo; 050import de.deepamehta.plugins.files.service.FilesService; 051import de.deepamehta.plugins.mail.service.MailService; 052 053@Path("/mail") 054@Produces(MediaType.APPLICATION_JSON) 055public class MailPlugin extends PluginActivator implements MailService, PostCreateTopicListener { 056 057 private static Logger log = Logger.getLogger(MailPlugin.class.getName()); 058 059 // URI constants 060 061 public static final String AGGREGATION = "dm4.core.aggregation"; 062 public static final String COMPOSITION = "dm4.core.composition"; 063 public static final String CHILD = "dm4.core.child"; 064 public static final String CHILD_TYPE = "dm4.core.child_type"; 065 public static final String TOPIC_TYPE = "dm4.core.topic_type"; 066 public static final String PARENT = "dm4.core.parent"; 067 public static final String PARENT_TYPE = "dm4.core.parent_type"; 068 public static final String FILE = "dm4.files.file"; 069 public static final String ATTACHMENTS = "attachments"; 070 public static final String BODY = "dm4.mail.body"; 071 public static final String EMAIL_ADDRESS = "dm4.contacts.email_address"; 072 public static final String DATE = "dm4.mail.date"; 073 public static final String FROM = "dm4.mail.from"; 074 public static final String MAIL = "dm4.mail"; 075 public static final String MESSAGE_ID = "dm4.mail.id"; 076 public static final String RECIPIENT = "dm4.mail.recipient"; 077 public static final String RECIPIENT_TYPE = "dm4.mail.recipient.type"; 078 public static final String SENDER = "dm4.mail.sender"; 079 public static final String SIGNATURE = "dm4.mail.signature"; 080 public static final String SUBJECT = "dm4.mail.subject"; 081 public static final String USER_ACCOUNT = "dm4.accesscontrol.user_account"; 082 083 // service references 084 @Inject 085 private AccessControlService acService; 086 @Inject 087 private FilesService fileService = null; 088 089 // package internal helpers 090 091 MailConfigurationCache config = null; 092 ImageCidEmbedment cidEmbedment = null; 093 Autocomplete autocomplete = null; 094 095 boolean isInitialized; 096 097 /** 098 * @see #associateRecipient(long, long, RecipientType) 099 */ 100 @POST 101 @Path("{mail}/recipient/{address}") 102 public Association associateRecipient(// 103 @PathParam("mail") long mailId,// 104 @PathParam("address") long addressId) { 105 return associateRecipient(mailId, addressId, config.getDefaultRecipientType()); 106 } 107 108 @Override 109 public Association associateRecipient(long mailId, long addressId, RecipientType type) { 110 log.info("associate " + mailId + " with recipient address " + addressId); 111 // ### create value of the recipient association (#593 ref?) 112 ChildTopicsModel value = new ChildTopicsModel()// 113 .putRef(RECIPIENT_TYPE, type.getUri())// 114 .putRef(EMAIL_ADDRESS, addressId); 115 // get and update or create a new recipient association 116 RelatedTopic recipient = getContactOfEmail(addressId); 117 Association association = getRecipientAssociation(mailId, addressId, recipient.getId()); 118 if (association == null) { // create a recipient association 119 return associateRecipient(mailId, recipient, value); 120 } else { // update address and type references 121 association.setChildTopics(value); 122 return association; 123 } 124 } 125 126 @Override 127 public void associateValidatedRecipients(long mailId, List<Topic> recipients) { 128 for (Topic recipient : recipients) { 129 Topic topic = dms.getTopic(recipient.getId()).loadChildTopics(); 130 if (topic.getChildTopics().has(EMAIL_ADDRESS)) { 131 String personal = recipient.getSimpleValue().toString(); 132 for (Topic email : topic.getChildTopics().getTopics(EMAIL_ADDRESS)) { 133 String address = email.getSimpleValue().toString(); 134 try { 135 new InternetAddress(address, personal).validate(); 136 } catch (Exception e) { 137 log.log(Level.INFO, "email address '" + address + "' of contact '" + // 138 personal + "'" + " is invalid: " + e.getMessage()); 139 continue; // check the next one 140 } 141 // associate validated email address as BCC recipient 142 associateRecipient(mailId, email.getId(), RecipientType.BCC); 143 } 144 } 145 } 146 } 147 148 /** 149 * @see #associateSender(long, long) 150 */ 151 @POST 152 @Path("{mail}/sender/{address}") 153 public Association mailSender(// 154 @PathParam("mail") long mailId,// 155 @PathParam("address") long addressId) { 156 return associateSender(mailId, addressId); 157 } 158 159 @Override 160 public Association associateSender(long mailId, long addressId) { 161 log.info("associate " + mailId + " with sender " + addressId); 162 163 // create value of sender association (#593 ref?) 164 ChildTopicsModel value = new ChildTopicsModel().putRef(EMAIL_ADDRESS, addressId); 165 166 // find existing sender association 167 RelatedTopic sender = getContactOfEmail(addressId); 168 RelatedTopic oldSender = getSender(mailId, false); 169 170 if (oldSender == null) { // create the first sender association 171 return associateSender(mailId, sender, value); 172 } else { // update or delete the old sender 173 Association association = getSenderAssociation(mailId, oldSender.getId()); 174 DeepaMehtaTransaction tx = dms.beginTx(); 175 try { 176 if (sender.getId() != oldSender.getId()) { // delete the old one 177 dms.deleteAssociation(association.getId()); 178 association = associateSender(mailId, sender, value); 179 } else { // update composite 180 association.setChildTopics(value); 181 } 182 tx.success(); 183 } finally { 184 tx.finish(); 185 } 186 return association; 187 } 188 } 189 190 /** 191 * Returns the parent of each search type substring match. 192 * 193 * @param term 194 * String to search. 195 * @return Parent model of each result topic. 196 */ 197 @GET 198 @Path("/autocomplete/{term}") 199 public List<TopicModel> search(@PathParam("term") String term) { 200 String query = "*" + term + "*"; 201 log.info("autocomplete " + query); 202 return autocomplete.search(query); 203 } 204 205 /** 206 * Creates a copy of the mail. 207 * 208 * @param mailId 209 * ID of the mail topic to clone. 210 * @param includeRecipients 211 * Copy recipients of the origin? 212 * @return Cloned mail topic with associated sender and recipients. 213 */ 214 @POST 215 @Path("/{mail}/copy") 216 public Topic copyMail(// 217 @PathParam("mail") long mailId,// 218 @QueryParam("recipients") boolean includeRecipients) { 219 log.info("copy mail " + mailId); 220 DeepaMehtaTransaction tx = dms.beginTx(); 221 try { 222 // 1 clone mail .. 223 Topic mail = dms.getTopic(mailId).loadChildTopics(); 224 ChildTopicsModel oldMail = mail.getModel().getChildTopicsModel(); 225 String subject = oldMail.getString(SUBJECT); 226 String mailBody = oldMail.getString(BODY); 227 boolean fromDummy = oldMail.getBoolean(FROM); 228 String sentDate = "", messageId = ""; // nullify date and ID 229 // 1.1 re-use existing signatures (there is just one but see migration1 ###) 230 List<TopicModel> signatures = oldMail.getTopics(SIGNATURE); 231 long signatureId = -1; 232 for (TopicModel signature : signatures) { 233 signatureId = signature.getId(); 234 } 235 ChildTopicsModel clonedMail = new ChildTopicsModel() 236 .put(SUBJECT, subject).put(BODY, mailBody) 237 .put(FROM, fromDummy).put(DATE, sentDate).put(MESSAGE_ID, messageId) 238 .addRef(SIGNATURE, signatureId); // do not copy signatures.. 239 TopicModel model = new TopicModel(MAIL, clonedMail); 240 Topic clone = dms.createTopic(model); 241 242 // 2 clone sender association .. 243 RelatedTopic sender = getSender(mail, true); 244 ChildTopics senderAssociation = sender.getRelatingAssociation().getChildTopics(); 245 long addressId = senderAssociation.getTopic(EMAIL_ADDRESS).getId(); 246 // 2.1 reference email address topic on new sender association 247 ChildTopicsModel newModel = new ChildTopicsModel() 248 .putRef(EMAIL_ADDRESS, addressId); 249 associateSender(clone.getId(), sender, newModel); 250 251 // 3 clone recipient associations .. 252 ResultList<RelatedTopic> recipientList = mail.getRelatedTopics(RECIPIENT, PARENT, CHILD, null, 0); 253 // migration note: fetchRelatingComposite = true 254 if (includeRecipients) { 255 for (RelatedTopic recipient : recipientList) { 256 if (recipient.getTypeUri().equals(RECIPIENT) == false) { 257 continue; // sender or something else found 258 } 259 // 3.1 re-use existing recipient types 260 Association recipientAssociationModel = dms.getAssociation(recipient.getId()).loadChildTopics(); 261 ChildTopicsModel newValue = new ChildTopicsModel() 262 .putRef(RECIPIENT_TYPE, recipientAssociationModel.getTopic(RECIPIENT_TYPE).getUri()) 263 .putRef(EMAIL_ADDRESS, recipientAssociationModel.getTopic(EMAIL_ADDRESS).getId()); 264 associateRecipient(clone.getId(), recipient, newValue); 265 } 266 } 267 tx.success(); 268 return clone; 269 } finally { 270 tx.finish(); 271 } 272 } 273 274 /** 275 * Creates a new mail to all email addresses of the contact topic. 276 * 277 * @param recipientId 278 * ID of a recipient contact topic. 279 * @return Mail topic with associated recipient. 280 */ 281 @POST 282 @Path("/write/{recipient}") 283 public Topic writeTo(@PathParam("recipient") long recipientId) { 284 log.info("write a mail to recipient " + recipientId); 285 DeepaMehtaTransaction tx = dms.beginTx(); 286 try { 287 Topic mail = dms.createTopic(new TopicModel(MAIL)); 288 Topic recipient = dms.getTopic(recipientId).loadChildTopics(); 289 if (recipient.getChildTopics().has(EMAIL_ADDRESS)) { 290 for (Topic address : recipient.getChildTopics().getTopics(EMAIL_ADDRESS)) { 291 associateRecipient(mail.getId(), address.getId(),// 292 config.getDefaultRecipientType()); 293 } 294 } 295 tx.success(); 296 return mail; 297 } finally { 298 tx.finish(); 299 } 300 } 301 302 /** 303 * @return Default recipient type. 304 */ 305 @GET 306 @Path("/recipient/default") 307 public String getDefaultRecipientType() { 308 return config.getDefaultRecipientType().getUri(); 309 } 310 311 /** 312 * @return Recipient types. 313 */ 314 @GET 315 @Path("/recipient/types") 316 public ResultList<RelatedTopic> getRecipientTypes() { 317 return config.getRecipientTypes(); 318 } 319 320 /** 321 * @see #getSearchParentTypes 322 */ 323 @GET 324 @Path("/search/parents") 325 public ResultList<Topic> listSearchParentTypes() { 326 Collection<Topic> parents = getSearchParentTypes(); 327 return new ResultList<Topic>(parents.size(), new ArrayList<Topic>(parents)); 328 } 329 330 @Override 331 public Collection<Topic> getSearchParentTypes() { 332 return config.getSearchParentTypes(); 333 } 334 335 /** 336 * Load the configuration. 337 * 338 * Useful after type and configuration changes with the web-client. 339 */ 340 @GET 341 @Path("/config/load") 342 public Topic loadConfiguration() { 343 log.info("load mail configuration"); 344 config = new MailConfigurationCache(dms); 345 autocomplete = new Autocomplete(dms, config); 346 return config.getTopic(); 347 } 348 349 /** 350 * Sets the default sender and signature of a mail topic after creation. 351 */ 352 @Override 353 public void postCreateTopic(Topic topic) { 354 if (topic.getTypeUri().equals(MAIL)) { 355 if (topic.getChildTopics().has(FROM) == false) { // new mail 356 associateDefaultSender(topic); 357 } else { // copied mail 358 Topic from = topic.getChildTopics().getTopic(FROM); 359 if (from.getSimpleValue().booleanValue() == false) { // sender? 360 associateDefaultSender(topic); 361 } 362 } 363 } 364 } 365 366 /** 367 * @see #send(Mail) 368 */ 369 @POST 370 @Path("/{mail}/send") 371 public StatusReport send(// 372 @PathParam("mail") long mailId) { 373 log.info("send mail " + mailId); 374 return send(new Mail(mailId, dms)); 375 } 376 377 @Override 378 public StatusReport send(Mail mail) { 379 380 log.info("DEBUG: Current classloader of MailPlugin is: " 381 + Thread.currentThread().getContextClassLoader().toString()); 382 // Thread.currentThread().setContextClassLoader(MailPlugin.class.getClassLoader()); 383 // log.info("DEBUG: Quickfixed classloader of MailPlugin is: " 384 // + Thread.currentThread().getContextClassLoader().toString()); 385 StatusReport statusReport = new StatusReport(mail.getTopic()); 386 387 HtmlEmail email = new HtmlEmail(); 388 email.setDebug(true); // => System.out.println(SMTP communication); 389 email.setHostName(config.getSmtpHost()); 390 391 try { 392 InternetAddress sender = mail.getSender(); 393 // ..) Set Senders of Mail 394 email.setFrom(sender.getAddress(), sender.getPersonal()); 395 } catch (UnsupportedEncodingException e) { 396 reportException(statusReport, Level.INFO, MailError.SENDER, e); 397 } catch (AddressException e) { 398 reportException(statusReport, Level.INFO, MailError.SENDER, e); 399 } catch (EmailException e) { 400 reportException(statusReport, Level.INFO, MailError.SENDER, e); 401 } 402 403 try { 404 String subject = mail.getSubject(); 405 if (subject.isEmpty()) { // caught immediately 406 throw new IllegalArgumentException("Subject of mail is empty"); 407 } 408 // ..) Set Subject of Mail 409 email.setSubject(subject); 410 } catch (Exception e) { 411 reportException(statusReport, Level.INFO, MailError.CONTENT, e); 412 } 413 414 try { 415 Document body = cidEmbedment.embedImages(email, mail.getBody()); 416 String text = body.text(); 417 if (text.isEmpty()) { // caught immediately 418 throw new IllegalArgumentException("Text body of mail is empty"); 419 } 420 // ..) Set Message Body 421 email.setTextMsg(text); 422 email.setHtmlMsg(body.html()); 423 } catch (Exception e) { 424 reportException(statusReport, Level.INFO, MailError.CONTENT, e); 425 } 426 427 for (Long fileId : mail.getAttachmentIds()) { 428 try { 429 String path = fileService.getFile(fileId).getAbsolutePath(); 430 EmailAttachment attachment = new EmailAttachment(); 431 attachment.setPath(path); 432 log.fine("attach " + path); 433 // ..) Attach Attachmentss 434 email.attach(attachment); 435 } catch (Exception e) { 436 reportException(statusReport, Level.INFO, MailError.ATTACHMENTS, e); 437 } 438 } 439 440 RecipientsByType recipients = new RecipientsByType(); 441 try { 442 recipients = mail.getRecipients(); 443 try { 444 // ..) Mapping Recipients 445 mapRecipients(email, recipients); 446 } catch (Exception e) { 447 reportException(statusReport, Level.SEVERE, MailError.RECIPIENT_TYPE, e); 448 } 449 } catch (InvalidRecipients e) { 450 for (String recipient : e.getRecipients()) { 451 log.log(Level.INFO, MailError.RECIPIENTS.getMessage() + ": " + recipient); 452 statusReport.addError(MailError.RECIPIENTS, recipient); 453 } 454 } 455 456 if (statusReport.hasErrors()) { 457 statusReport.setMessage("Mail can NOT be sent"); 458 } else { // send, update message ID and return status with attached mail 459 try { 460 String messageId = email.send(); 461 statusReport.setMessage("Mail was SUCCESSFULLY sent to " + // 462 recipients.getCount() + " mail addresses"); 463 mail.setMessageId(messageId); 464 } catch (EmailException e) { 465 statusReport.setMessage("Sending mail FAILED"); 466 reportException(statusReport, Level.SEVERE, MailError.SEND, e); 467 } catch (Exception e) { // error after send 468 reportException(statusReport, Level.SEVERE, MailError.UPDATE, e); 469 } 470 } 471 return statusReport; 472 } 473 474 /** 475 * Initialize. 476 */ 477 @Override 478 public void init() { 479 isInitialized = true; 480 configureIfReady(); 481 } 482 483 private void configureIfReady() { 484 if (isInitialized && acService != null && fileService != null) { 485 createAttachmentDirectory(); 486 checkACLsOfMigration(); 487 loadConfiguration(); 488 } 489 } 490 491 private void checkACLsOfMigration() { 492 Topic config = dms.getTopic("uri", new SimpleValue("dm4.mail.config")); 493 if (acService.getCreator(config) == null) { 494 DeepaMehtaTransaction tx = dms.beginTx(); 495 log.info("initial ACL update of configuration"); 496 try { 497 Topic admin = acService.getUsername("admin"); 498 String adminName = admin.getSimpleValue().toString(); 499 acService.setCreator(config, adminName); 500 acService.setOwner(config, adminName); 501 acService.setACL(config, new AccessControlList(new ACLEntry(Operation.WRITE, UserRole.OWNER))); 502 tx.success(); 503 } catch (Exception e) { 504 log.warning("could not update ACLs of migration due to a " 505 + e.getClass().toString()); 506 } finally { 507 tx.finish(); 508 } 509 510 } 511 } 512 513 private void createAttachmentDirectory() { 514 // TODO move the initialization to migration "0" 515 try { 516 ResourceInfo resourceInfo = fileService.getResourceInfo(ATTACHMENTS); 517 String kind = resourceInfo.toJSON().getString("kind"); 518 if (kind.equals("directory") == false) { 519 String repoPath = System.getProperty("dm4.filerepo.path"); 520 String message = "attachment storage directory " + repoPath + File.separator + ATTACHMENTS 521 + " can not be used"; 522 throw new IllegalStateException(message); 523 } 524 } catch (WebApplicationException e) { // !exists 525 // catch fileService info request error => create directory 526 if (e.getResponse().getStatus() != 404) { 527 throw e; 528 } else { 529 log.info("create attachment directory"); 530 fileService.createFolder(ATTACHMENTS, "/"); 531 } 532 } catch (JSONException e) { 533 throw new RuntimeException(e); 534 } 535 } 536 537 /** 538 * @param mail 539 * @param clientState 540 * 541 * @see MailPlugin#postCreateTopic(Topic) 542 */ 543 private void associateDefaultSender(Topic mail) { 544 Topic creator = null; 545 RelatedTopic sender = null; 546 547 Topic creatorName = acService.getUsername(acService.getCreator(mail)); 548 if (creatorName != null) { 549 creator = creatorName.getRelatedTopic(null, CHILD, PARENT, USER_ACCOUNT); 550 } 551 552 // get user account specific sender 553 if (creator != null) { 554 sender = getSender(creator, true); 555 } 556 557 // get the configured default sender instead 558 if (sender == null) { 559 sender = config.getDefaultSender(); 560 } 561 562 if (sender != null) { 563 DeepaMehtaTransaction tx = dms.beginTx(); 564 try { 565 ChildTopics value = sender.getRelatingAssociation().getChildTopics(); 566 long addressId = value.getTopic(EMAIL_ADDRESS).getId(); 567 ChildTopicsModel newValue = new ChildTopicsModel() 568 .putRef(EMAIL_ADDRESS, addressId); // re-use existing e-mail address topics 569 associateSender(mail.getId(), sender, newValue); 570 RelatedTopic signature = getContactSignature(sender, addressId); 571 if (signature != null) { 572 mail.getChildTopics().getModel().add(SIGNATURE, signature.getModel()); 573 } 574 tx.success(); 575 } finally { 576 tx.finish(); 577 } 578 } 579 } 580 581 private Association associateRecipient(long topicId, Topic recipient, ChildTopicsModel value) { 582 DeepaMehtaTransaction tx = dms.beginTx(); 583 try { 584 Association assoc = dms.createAssociation(new AssociationModel(RECIPIENT,// 585 new TopicRoleModel(recipient.getId(), CHILD),// 586 new TopicRoleModel(topicId, PARENT), value)); 587 tx.success(); 588 return assoc; 589 } finally { 590 tx.finish(); 591 } 592 } 593 594 private Association associateSender(long topicId, Topic sender, ChildTopicsModel value) { 595 DeepaMehtaTransaction tx = dms.beginTx(); 596 try { 597 Association assoc = dms.createAssociation(new AssociationModel(SENDER,// 598 new TopicRoleModel(sender.getId(), CHILD),// 599 new TopicRoleModel(topicId, PARENT), value)); 600 tx.success(); 601 return assoc; 602 } finally { 603 tx.finish(); 604 } 605 } 606 607 private RelatedTopic getContactOfEmail(long addressId) { 608 return dms.getTopic(addressId).getRelatedTopic(COMPOSITION, CHILD, PARENT, null); 609 } 610 611 /** Comparing contact-signatures by "E-Mail Address"-Topic ID*/ 612 private RelatedTopic getContactSignature(Topic topic, long addressId) { 613 for (RelatedTopic signature : topic.getRelatedTopics(SENDER, CHILD, PARENT, SIGNATURE, 0)) { 614 // signature had fetchComposite = true 615 signature.loadChildTopics(); 616 // and fetchRelatingComposite = true 617 ChildTopics value = signature.getRelatingAssociation().loadChildTopics().getChildTopics(); 618 if (addressId == value.getTopic(EMAIL_ADDRESS).getId()) { 619 return signature; 620 } 621 } 622 return null; 623 } 624 625 private Association getRecipientAssociation(long topicId, long addressId, long recipientId) { 626 for (Association recipient : dms.getAssociations(topicId, recipientId)) { 627 if (recipient.getTypeUri().equals(RECIPIENT)) { 628 Association association = dms.getAssociation(recipient.getId()); 629 association.loadChildTopics(); 630 Topic address = association.getChildTopics().getTopic(EMAIL_ADDRESS); 631 if (address.getId() == addressId) { 632 return association; 633 } 634 } 635 } 636 return null; 637 } 638 639 private RelatedTopic getSender(long topicId, boolean fetchRelatingComposite) { 640 return getSender(dms.getTopic(topicId), fetchRelatingComposite); 641 } 642 643 private RelatedTopic getSender(Topic topic, boolean fetchRelatingComposite) { 644 if (!fetchRelatingComposite) { 645 return topic.getRelatedTopic(SENDER, PARENT, CHILD, null); 646 } else { 647 // do fetch relating composite 648 RelatedTopic sender = topic.getRelatedTopic(SENDER, PARENT, CHILD, null); 649 if (sender != null) { // this may be the case when a new mail is instantiated 650 sender.getAssociation(SENDER, CHILD, PARENT, topic.getId()).loadChildTopics(); 651 } 652 return sender; 653 } 654 } 655 656 private Association getSenderAssociation(long topicId, long senderId) { 657 return dms.getAssociation(SENDER, topicId, senderId, PARENT, CHILD).loadChildTopics(); 658 } 659 660 private void mapRecipients(HtmlEmail email, Map<RecipientType, List<InternetAddress>> recipients) 661 throws EmailException { 662 for (RecipientType type : recipients.keySet()) { 663 switch (type) { 664 case BCC: 665 email.setBcc(recipients.get(type)); 666 break; 667 case CC: 668 email.setCc(recipients.get(type)); 669 break; 670 case TO: 671 email.setTo(recipients.get(type)); 672 break; 673 default: 674 throw new IllegalArgumentException(type.toString()); 675 } 676 } 677 } 678 679 private void reportException(StatusReport report, Level level, MailError error, Exception e) { 680 String message = e.getMessage(); 681 Throwable cause = e.getCause(); 682 if (cause != null) { 683 message += ": " + cause.getMessage(); 684 } 685 String logMessage = error.getMessage() + ": " + message; 686 if (level == Level.WARNING || level == Level.SEVERE) { 687 log.log(level, logMessage, e); // log the exception trace 688 } else { 689 log.log(level, logMessage); // log only the message 690 } 691 report.addError(error, message); 692 } 693 694 @Override 695 public void serviceArrived(PluginService service) { 696 if (service instanceof FilesService) { 697 cidEmbedment = new ImageCidEmbedment(fileService); 698 } 699 } 700 701 @Override 702 public void serviceGone(PluginService service) { 703 if (service instanceof FilesService) { 704 cidEmbedment = null; 705 } 706 } 707 708}