001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.kew.mail.service.impl; 017 018import java.text.FieldPosition; 019import java.text.MessageFormat; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.Comparator; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.Iterator; 027import java.util.LinkedHashMap; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031 032import org.apache.commons.lang.StringUtils; 033import org.kuali.rice.core.api.config.property.ConfigContext; 034import org.kuali.rice.core.api.mail.EmailBody; 035import org.kuali.rice.core.api.mail.EmailFrom; 036import org.kuali.rice.core.api.mail.EmailSubject; 037import org.kuali.rice.core.api.mail.EmailTo; 038import org.kuali.rice.core.api.mail.Mailer; 039import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator; 040import org.kuali.rice.kew.actionitem.ActionItem; 041import org.kuali.rice.kew.actionitem.ActionItemActionListExtension; 042import org.kuali.rice.kew.actionlist.service.ActionListService; 043import org.kuali.rice.kew.actionrequest.ActionRequestValue; 044import org.kuali.rice.kew.actiontaken.ActionTakenValue; 045import org.kuali.rice.kew.api.KewApiConstants; 046import org.kuali.rice.kew.api.KewApiServiceLocator; 047import org.kuali.rice.kew.api.action.ActionItemContract; 048import org.kuali.rice.kew.api.action.ActionRequest; 049import org.kuali.rice.kew.api.document.Document; 050import org.kuali.rice.kew.api.preferences.Preferences; 051import org.kuali.rice.kew.doctype.bo.DocumentType; 052import org.kuali.rice.kew.mail.CustomEmailAttribute; 053import org.kuali.rice.kew.mail.DailyEmailJob; 054import org.kuali.rice.kew.mail.WeeklyEmailJob; 055import org.kuali.rice.kew.mail.service.ActionListEmailService; 056import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue; 057import org.kuali.rice.kew.service.KEWServiceLocator; 058import org.kuali.rice.kew.useroptions.UserOptions; 059import org.kuali.rice.kew.useroptions.UserOptionsService; 060import org.kuali.rice.kim.api.KimConstants; 061import org.kuali.rice.kim.api.identity.Person; 062import org.kuali.rice.kim.api.services.KimApiServiceLocator; 063import org.kuali.rice.krad.util.KRADConstants; 064import org.kuali.rice.krad.util.ObjectUtils; 065import org.kuali.rice.ksb.service.KSBServiceLocator; 066import org.quartz.CronTrigger; 067import org.quartz.JobDetail; 068import org.quartz.ObjectAlreadyExistsException; 069import org.quartz.Scheduler; 070import org.quartz.SchedulerException; 071import org.quartz.Trigger; 072 073/** 074 * ActionListeEmailService which generates messages whose body and subject can be customized via KEW 075 * configuration parameters, 'immediate.reminder.email.message' and 076 * 'immediate.reminder.email.subject'. The immediate reminder email message key should specify a 077 * MessageFormat string. See code for the parameters to this MessageFormat. 078 * @author Kuali Rice Team (rice.collab@kuali.org) 079 */ 080public class ActionListEmailServiceImpl implements ActionListEmailService { 081 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger 082 .getLogger(ActionListEmailServiceImpl.class); 083 084 private static final String DEFAULT_EMAIL_FROM_ADDRESS = "admin@localhost"; 085 086 private static final String ACTION_LIST_REMINDER = "Action List Reminder"; 087 088 private static final String IMMEDIATE_REMINDER_EMAIL_MESSAGE_KEY = "immediate.reminder.email.message"; 089 090 private static final String IMMEDIATE_REMINDER_EMAIL_SUBJECT_KEY = "immediate.reminder.email.subject"; 091 092 private static final String DAILY_TRIGGER_NAME = "Daily Email Trigger"; 093 private static final String DAILY_JOB_NAME = "Daily Email"; 094 private static final String WEEKLY_TRIGGER_NAME = "Weekly Email Trigger"; 095 private static final String WEEKLY_JOB_NAME = "Weekly Email"; 096 097 private String deploymentEnvironment; 098 099 private Mailer mailer; 100 101 public void setMailer(Mailer mailer) { 102 this.mailer = mailer; 103 } 104 105 public String getDocumentTypeEmailAddress(DocumentType documentType) { 106 String fromAddress = (documentType == null ? null : documentType 107 .getNotificationFromAddress()); 108 if (org.apache.commons.lang.StringUtils.isEmpty(fromAddress)) { 109 fromAddress = getApplicationEmailAddress(); 110 } 111 return fromAddress; 112 } 113 114 public String getApplicationEmailAddress() { 115 // first check the configured value 116 String fromAddress = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString( 117 KewApiConstants.KEW_NAMESPACE, 118 KRADConstants.DetailTypes.MAILER_DETAIL_TYPE, 119 KewApiConstants.EMAIL_REMINDER_FROM_ADDRESS); 120 // if there's no value configured, use the default 121 if (org.apache.commons.lang.StringUtils.isEmpty(fromAddress)) { 122 fromAddress = DEFAULT_EMAIL_FROM_ADDRESS; 123 } 124 return fromAddress; 125 } 126 127 protected String getHelpLink() { 128 return getHelpLink(null); 129 } 130 131 protected String getHelpLink(DocumentType documentType) { 132 return "For additional help, email " + "<mailto:" 133 + getDocumentTypeEmailAddress(documentType) + ">"; 134 } 135 136 public EmailSubject getEmailSubject() { 137 String subject = ConfigContext.getCurrentContextConfig().getProperty(IMMEDIATE_REMINDER_EMAIL_SUBJECT_KEY); 138 if (subject == null) { 139 subject = ACTION_LIST_REMINDER; 140 } 141 return new EmailSubject(subject); 142 } 143 144 public EmailSubject getEmailSubject(String customSubject) { 145 String subject = ConfigContext.getCurrentContextConfig().getProperty(IMMEDIATE_REMINDER_EMAIL_SUBJECT_KEY); 146 if (subject == null) { 147 subject = ACTION_LIST_REMINDER; 148 } 149 return new EmailSubject(subject + " " + customSubject); 150 } 151 152 protected EmailFrom getEmailFrom(DocumentType documentType) { 153 return new EmailFrom(getDocumentTypeEmailAddress(documentType)); 154 } 155 156 protected EmailTo getEmailTo(Person user) { 157 String address = user.getEmailAddressUnmasked(); 158 if (!isProduction()) { 159 LOG.info("If this were production, email would be sent to "+ user.getEmailAddressUnmasked()); 160 address = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString( 161 KewApiConstants.KEW_NAMESPACE, 162 KRADConstants.DetailTypes.ACTION_LIST_DETAIL_TYPE, 163 KewApiConstants.ACTIONLIST_EMAIL_TEST_ADDRESS); 164 } 165 return new EmailTo(address); 166 } 167 168 protected void sendEmail(Person user, EmailSubject subject, 169 EmailBody body) { 170 sendEmail(user, subject, body, null); 171 } 172 173 protected void sendEmail(Person user, EmailSubject subject, 174 EmailBody body, DocumentType documentType) { 175 try { 176 if (StringUtils.isNotBlank(user.getEmailAddressUnmasked())) { 177 mailer.sendEmail(getEmailFrom(documentType), 178 getEmailTo(user), 179 subject, 180 body, 181 false); 182 } 183 } catch (Exception e) { 184 LOG.error("Error sending Action List email to " + user.getEmailAddressUnmasked(), e); 185 } 186 } 187 188 /** 189 * 190 * This method takes in a type of email which is being sent, an action item 191 * which is being checked and a user's preferences and it checks to see if 192 * the action item should be included in the given kind of email based on 193 * the user's preferences. 194 * 195 * @param actionItem 196 * @param preferences 197 * @param emailSetting 198 * @return 199 */ 200 protected boolean checkEmailNotificationPreferences(ActionItemContract actionItem, Preferences preferences, String emailSetting) { 201 boolean shouldSend = true; 202 // Check the user's primary and secondary delegation email preferences 203 if (actionItem.getDelegationType() != null) { 204 if (KimConstants.KimUIConstants.DELEGATION_PRIMARY.equalsIgnoreCase(actionItem.getDelegationType().getCode())) { 205 shouldSend = KewApiConstants.PREFERENCES_YES_VAL.equals(preferences.getNotifyPrimaryDelegation()); 206 } else if (KimConstants.KimUIConstants.DELEGATION_SECONDARY.equalsIgnoreCase(actionItem.getDelegationType().getCode())) { 207 shouldSend = KewApiConstants.PREFERENCES_YES_VAL.equals(preferences.getNotifySecondaryDelegation()); 208 } 209 } 210 if(!shouldSend) { 211 // If the action item has a delegation type and the user's 212 // preference for the delegation type is false, the item should not 213 // be included 214 return false; 215 } 216 217 // Check the request code of the action item and the complete, approve, 218 // acknowledge, or FYI notification preference accordingly 219 boolean checkRequestCodePreferences = ((StringUtils.equals(actionItem.getActionRequestCd(), KewApiConstants.ACTION_REQUEST_COMPLETE_REQ) && 220 StringUtils.equals(preferences.getNotifyComplete(), KewApiConstants.PREFERENCES_YES_VAL)) || 221 (StringUtils.equals(actionItem.getActionRequestCd(), KewApiConstants.ACTION_REQUEST_APPROVE_REQ) && 222 StringUtils.equals(preferences.getNotifyApprove(), KewApiConstants.PREFERENCES_YES_VAL)) || 223 (StringUtils.equals(actionItem.getActionRequestCd(), KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ) && 224 StringUtils.equals(preferences.getNotifyAcknowledge(), KewApiConstants.PREFERENCES_YES_VAL)) || 225 (StringUtils.equals(actionItem.getActionRequestCd(), KewApiConstants.ACTION_REQUEST_FYI_REQ) && 226 StringUtils.equals(preferences.getNotifyFYI(), KewApiConstants.PREFERENCES_YES_VAL))); 227 228 DocumentRouteHeaderValue document = KEWServiceLocator.getRouteHeaderService().getRouteHeader(actionItem.getDocumentId()); 229 DocumentType documentType = null; 230 Boolean suppressImmediateEmailsOnSuActionPolicy = false; 231 if (document != null) { 232 documentType = document.getDocumentType(); 233 suppressImmediateEmailsOnSuActionPolicy = documentType.getSuppressImmediateEmailsOnSuActionPolicy().getPolicyValue(); 234 } 235 236 if (suppressImmediateEmailsOnSuActionPolicy) { 237 String docId = actionItem.getDocumentId(); 238 LOG.warn("checkEmailNotificationPreferences processing document: " + docId + " of type: " + documentType.getName() + " and getSuppressImmediateEmailsOnSuActionPolicy set to true."); 239 240 List<ActionTakenValue> actionsTaken = document.getActionsTaken(); 241 if (actionsTaken != null && actionsTaken.size() > 0) { 242 Collections.sort(actionsTaken, new Comparator<ActionTakenValue>() { 243 244 @Override 245 // Sort by date in descending order 246 public int compare(ActionTakenValue o1, ActionTakenValue o2) { 247 if (o1 == null && o2 == null) 248 return 0; 249 if (o1 == null) 250 return -1; 251 if (o2 == null) 252 return 1; 253 254 if (o1.getActionDate() == null && o2.getActionDate() == null) 255 return 0; 256 if (o1.getActionDate() == null) 257 return -1; 258 if (o2.getActionDate() == null) 259 return 1; 260 261 return o2.getActionDate().compareTo(o1.getActionDate()); 262 } 263 }); 264 } 265 266 ActionTakenValue mostRecentActionTaken = (ActionTakenValue) actionsTaken.get(0); 267 if (mostRecentActionTaken !=null && mostRecentActionTaken.isSuperUserAction()) { 268 return false; 269 } 270 } 271 272 // If the user has document type notification preferences check them to 273 // see if the action item should be included in the email. 274 if(preferences.getDocumentTypeNotificationPreferences().size() > 0) { 275 while(ObjectUtils.isNotNull(documentType)) { 276 // Check to see if there is a notification preference for the 277 // given document type in the user's preferences 278 String documentTypePreference = preferences.getDocumentTypeNotificationPreference(documentType.getName()); 279 if(StringUtils.isNotBlank(documentTypePreference)) { 280 // If a document type notification preference is found, 281 // check the type of email being sent 282 if(StringUtils.equals(emailSetting, KewApiConstants.EMAIL_RMNDR_DAY_VAL) || StringUtils.equals(emailSetting, KewApiConstants.EMAIL_RMNDR_WEEK_VAL)) { 283 // If a daily or weekly email is being sent, include 284 // the action item unless the notification preference 285 // is 'None' 286 return !StringUtils.equals(documentTypePreference, KewApiConstants.EMAIL_RMNDR_NO_VAL); 287 } else if(StringUtils.equals(emailSetting, KewApiConstants.EMAIL_RMNDR_IMMEDIATE)) { 288 // Otherwise if this is an immediate notification check 289 // the action item request code preferences 290 return StringUtils.equals(emailSetting, documentTypePreference) && checkRequestCodePreferences; 291 } else { 292 // Otherwise the emailSetting is "None" so no email 293 // should be sent 294 return false; 295 } 296 } 297 // If no matches were found, continue checking the document 298 // type hierarchy 299 documentType = documentType.getParentDocType(); 300 } 301 } 302 303 // If no document type notification preference is found, include the 304 // item if the email setting matches the user's default email 305 // notification preference 306 if(StringUtils.equals(emailSetting, preferences.getEmailNotification())) { 307 if(StringUtils.equals(emailSetting, KewApiConstants.EMAIL_RMNDR_IMMEDIATE)) { 308 // If this is an immediate notification check 309 // the request code of the action item with the user's preferences 310 return checkRequestCodePreferences; 311 } else { 312 // Otherwise just return true if the email setting isn't "None" 313 return !StringUtils.equals(emailSetting, KewApiConstants.EMAIL_RMNDR_NO_VAL); 314 } 315 } 316 return false; 317 } 318 319 public void sendImmediateReminder(org.kuali.rice.kew.api.action.ActionItem actionItem, Boolean skipOnApprovals) { 320 if (actionItem == null) { 321 LOG.warn("Request to send immediate reminder to recipient of a null action item... aborting."); 322 return; 323 } 324 325 if (actionItem.getPrincipalId() == null) { 326 LOG.warn("Request to send immediate reminder to null recipient of an action item... aborting."); 327 return; 328 } 329 330 if (skipOnApprovals != null && skipOnApprovals.booleanValue() 331 && actionItem.getActionRequestCd().equals(KewApiConstants.ACTION_REQUEST_APPROVE_REQ)) { 332 LOG.debug("As requested, skipping immediate reminder notification on action item approval for " + actionItem.getPrincipalId()); 333 return; 334 } 335 336 Preferences preferences = KewApiServiceLocator.getPreferencesService().getPreferences(actionItem.getPrincipalId()); 337 if(!checkEmailNotificationPreferences(actionItem, preferences, KewApiConstants.EMAIL_RMNDR_IMMEDIATE)) { 338 LOG.debug("Email suppressed due to the user's preferences"); 339 return; 340 } 341 342 boolean shouldSendActionListEmailNotification = sendActionListEmailNotification(); 343 if (shouldSendActionListEmailNotification) { 344 LOG.debug("sending immediate reminder"); 345 346 Person person = KimApiServiceLocator.getPersonService().getPerson(actionItem.getPrincipalId()); 347 348 DocumentRouteHeaderValue document = KEWServiceLocator.getRouteHeaderService().getRouteHeader( 349 actionItem.getDocumentId()); 350 StringBuffer emailBody = new StringBuffer(buildImmediateReminderBody(person, actionItem, 351 document.getDocumentType())); 352 StringBuffer emailSubject = new StringBuffer(); 353 try { 354 CustomEmailAttribute customEmailAttribute = document.getCustomEmailAttribute(); 355 if (customEmailAttribute != null) { 356 Document routeHeaderVO = DocumentRouteHeaderValue.to(document); 357 ActionRequestValue actionRequest = KEWServiceLocator 358 .getActionRequestService().findByActionRequestId(actionItem.getActionRequestId()); 359 ActionRequest actionRequestVO = ActionRequestValue.to(actionRequest); 360 customEmailAttribute.setRouteHeaderVO(routeHeaderVO); 361 customEmailAttribute.setActionRequestVO(actionRequestVO); 362 String customBody = customEmailAttribute 363 .getCustomEmailBody(); 364 if (!org.apache.commons.lang.StringUtils.isEmpty(customBody)) { 365 emailBody.append(customBody); 366 } 367 String customEmailSubject = customEmailAttribute 368 .getCustomEmailSubject(); 369 if (!org.apache.commons.lang.StringUtils.isEmpty(customEmailSubject)) { 370 emailSubject.append(customEmailSubject); 371 } 372 } 373 } catch (Exception e) { 374 LOG 375 .error( 376 "Error when checking for custom email body and subject.", 377 e); 378 } 379 LOG.debug("Sending email to " + person); 380 sendEmail(person, getEmailSubject(emailSubject.toString()), 381 new EmailBody(emailBody.toString()), document 382 .getDocumentType()); 383 } 384 385 } 386 387 protected boolean isProduction() { 388 return ConfigContext.getCurrentContextConfig().isProductionEnvironment(); 389 } 390 391 public void sendDailyReminder() { 392 LOG.info("Starting SendDailyReminder"); 393 if (sendActionListEmailNotification()) { 394 Collection<String> users = getUsersWithEmailSetting(KewApiConstants.EMAIL_RMNDR_DAY_VAL); 395 for (Iterator<String> userIter = users.iterator(); userIter.hasNext();) { 396 String principalId = userIter.next(); 397 try { 398 Collection<ActionItemActionListExtension> actionItems = getActionListService().getActionList(principalId, null); 399 if (actionItems != null && actionItems.size() > 0) { 400 sendPeriodicReminder(principalId, actionItems, 401 KewApiConstants.EMAIL_RMNDR_DAY_VAL); 402 } 403 } catch (Exception e) { 404 LOG.error("Error sending daily action list reminder to user: " + principalId, e); 405 } 406 } 407 } 408 LOG.info("Daily action list emails successfully sent"); 409 } 410 411 public void sendWeeklyReminder() { 412 LOG.info("Starting sendWeeklyReminder"); 413 if (sendActionListEmailNotification()) { 414 Collection<String> users = getUsersWithEmailSetting(KewApiConstants.EMAIL_RMNDR_WEEK_VAL); 415 for (Iterator<String> userIter = users.iterator(); userIter.hasNext();) { 416 String principalId = userIter.next(); 417 try { 418 Collection<ActionItemActionListExtension> actionItems = getActionListService().getActionList(principalId, null); 419 if (actionItems != null && actionItems.size() > 0) { 420 sendPeriodicReminder(principalId, actionItems, 421 KewApiConstants.EMAIL_RMNDR_WEEK_VAL); 422 } 423 } catch (Exception e) { 424 LOG.error("Error sending weekly action list reminder to user: " + principalId, e); 425 } 426 } 427 } 428 LOG.info("Weekly action list emails successfully sent"); 429 } 430 431 protected void sendPeriodicReminder(String principalId, Collection<ActionItemActionListExtension> actionItems, String emailSetting) { 432 String emailBody = null; 433 actionItems = filterActionItemsToNotify(principalId, actionItems, emailSetting); 434 // if there are no action items after being filtered, there's no 435 // reason to send the email 436 if (actionItems.isEmpty()) { 437 return; 438 } 439 if (KewApiConstants.EMAIL_RMNDR_DAY_VAL.equals(emailSetting)) { 440 emailBody = buildDailyReminderBody(actionItems); 441 } else if (KewApiConstants.EMAIL_RMNDR_WEEK_VAL.equals(emailSetting)) { 442 emailBody = buildWeeklyReminderBody(actionItems); 443 } 444 Person person = KimApiServiceLocator.getPersonService().getPerson(principalId); 445 sendEmail(person, getEmailSubject(), new EmailBody(emailBody)); 446 } 447 448 /** 449 * Returns a filtered Collection of {@link ActionItem}s which are filtered according to the 450 * user's preferences. If they have opted not to recieve secondary or primary delegation emails 451 * then they will not be included. 452 */ 453 protected Collection<ActionItemActionListExtension> filterActionItemsToNotify(String principalId, Collection<ActionItemActionListExtension> actionItems, String emailSetting) { 454 List<ActionItemActionListExtension> filteredItems = new ArrayList<ActionItemActionListExtension>(); 455 Preferences preferences = KewApiServiceLocator.getPreferencesService().getPreferences(principalId); 456 for (ActionItemActionListExtension actionItem : actionItems) { 457 if (!actionItem.getPrincipalId().equals(principalId)) { 458 LOG.warn("Encountered an ActionItem with an incorrect workflow ID. Was " + actionItem.getPrincipalId() 459 + 460 " but expected " + principalId); 461 continue; 462 } 463 if (checkEmailNotificationPreferences(actionItem, preferences, emailSetting)) { 464 filteredItems.add(actionItem); 465 } 466 } 467 return filteredItems; 468 } 469 470 protected Collection<String> getUsersWithEmailSetting(String setting) { 471 Set<String> users = new HashSet<String>(); 472 Collection<UserOptions> userOptions = getUserOptionsService().retrieveEmailPreferenceUserOptions(setting); 473 for(UserOptions userOption : userOptions) { 474 String principalId = userOption.getWorkflowId(); 475 try { 476 if (!users.contains(principalId)) { 477 users.add(principalId); 478 } else { 479 LOG.info("User " + principalId + " was already added to the list, so not adding again."); 480 } 481 } catch (Exception e) { 482 LOG.error("error retrieving workflow user with ID: " 483 + principalId); 484 } 485 } 486 return users; 487 } 488 489 /** 490 * 0 = actionItem.getDocumentId() 1 = 491 * actionItem.getRouteHeader().getInitiatorUser().getDisplayName() 2 = 492 * actionItem.getRouteHeader().getDocumentType().getName() 3 = actionItem.getDocTitle() 4 = 493 * docHandlerUrl 5 = getActionListUrl() 6 = getPreferencesUrl() 7 = getHelpLink(documentType) 494 */ 495 private static final MessageFormat DEFAULT_IMMEDIATE_REMINDER = new MessageFormat( 496 "Your Action List has an eDoc(electronic document) that needs your attention: \n\n" 497 + 498 "Document ID:\t{0,number,#}\n" 499 + 500 "Initiator:\t\t{1}\n" 501 + 502 "Type:\t\tAdd/Modify {2}\n" 503 + 504 "Title:\t\t{3}\n" 505 + 506 "\n\n" 507 + 508 "To respond to this eDoc: \n" 509 + 510 "\tGo to {4}\n\n" 511 + 512 "\tOr you may access the eDoc from your Action List: \n" 513 + 514 "\tGo to {5}, and then click on the numeric Document ID: {0,number,#} in the first column of the List. \n" 515 + 516 "\n\n\n" + 517 "To change how these email notifications are sent(daily, weekly or none): \n" + 518 "\tGo to {6}\n" + 519 "\n\n\n" + 520 "{7}\n\n\n" 521 ); 522 523 /** 524 * 0 = actionItem.getDocumentId() 1 = 525 * actionItem.getRouteHeader().getInitiatorUser().getDisplayName() 2 = 526 * actionItem.getRouteHeader().getDocumentType().getName() 3 = actionItem.getDocTitle() 4 = 527 * getActionListUrl() 5 = getPreferencesUrl() 6 = getHelpLink(documentType) 528 */ 529 private static final MessageFormat DEFAULT_IMMEDIATE_REMINDER_NO_DOC_HANDLER = new MessageFormat( 530 "Your Action List has an eDoc(electronic document) that needs your attention: \n\n" + 531 "Document ID:\t{0,number,#}\n" + 532 "Initiator:\t\t{1}\n" + 533 "Type:\t\tAdd/Modify {2}\n" + 534 "Title:\t\t{3}\n" + 535 "\n\n" + 536 "To respond to this eDoc you may use your Action List: \n" + 537 "\tGo to {4}, and then take actions related to Document ID: {0,number,#}. \n" + 538 "\n\n\n" + 539 "To change how these email notifications are sent(daily, weekly or none): \n" + 540 "\tGo to {5}\n" + 541 "\n\n\n" + 542 "{6}\n\n\n" 543 ); 544 545 public String buildImmediateReminderBody(Person person, org.kuali.rice.kew.api.action.ActionItem actionItem, DocumentType documentType) { 546 String docHandlerUrl = documentType.getResolvedDocumentHandlerUrl(); 547 if (StringUtils.isNotBlank(docHandlerUrl)) { 548 if (!docHandlerUrl.contains("?")) { 549 docHandlerUrl += "?"; 550 } else { 551 docHandlerUrl += "&"; 552 } 553 docHandlerUrl += KewApiConstants.DOCUMENT_ID_PARAMETER + "=" 554 + actionItem.getDocumentId(); 555 docHandlerUrl += "&" + KewApiConstants.COMMAND_PARAMETER + "=" 556 + KewApiConstants.ACTIONLIST_COMMAND; 557 } 558 StringBuffer sf = new StringBuffer(); 559 560 /*sf 561 .append("Your Action List has an eDoc(electronic document) that needs your attention: \n\n"); 562 sf.append("Document ID:\t" + actionItem.getDocumentId() + "\n"); 563 sf.append("Initiator:\t\t"); 564 try { 565 sf.append(actionItem.getRouteHeader().getInitiatorUser() 566 .getDisplayName() 567 + "\n"); 568 } catch (Exception e) { 569 LOG.error("Error retrieving initiator for action item " 570 + actionItem.getDocumentId()); 571 sf.append("\n"); 572 } 573 sf.append("Type:\t\t" + "Add/Modify " 574 + actionItem.getRouteHeader().getDocumentType().getName() 575 + "\n"); 576 sf.append("Title:\t\t" + actionItem.getDocTitle() + "\n"); 577 sf.append("\n\n"); 578 sf.append("To respond to this eDoc: \n"); 579 sf.append("\tGo to " + docHandlerUrl + "\n\n"); 580 sf.append("\tOr you may access the eDoc from your Action List: \n"); 581 sf.append("\tGo to " + getActionListUrl() 582 + ", and then click on the numeric Document ID: " 583 + actionItem.getDocumentId() 584 + " in the first column of the List. \n"); 585 sf.append("\n\n\n"); 586 sf 587 .append("To change how these email notifications are sent(daily, weekly or none): \n"); 588 sf.append("\tGo to " + getPreferencesUrl() + "\n"); 589 sf.append("\n\n\n"); 590 sf.append(getHelpLink(documentType) + "\n\n\n");*/ 591 592 MessageFormat messageFormat = null; 593 String stringMessageFormat = ConfigContext.getCurrentContextConfig().getProperty( 594 IMMEDIATE_REMINDER_EMAIL_MESSAGE_KEY); 595 LOG.debug("Immediate reminder email message from configuration (" + IMMEDIATE_REMINDER_EMAIL_MESSAGE_KEY 596 + "): " + stringMessageFormat); 597 if (stringMessageFormat == null) { 598 messageFormat = DEFAULT_IMMEDIATE_REMINDER; 599 } else { 600 messageFormat = new MessageFormat(stringMessageFormat); 601 } 602 String initiatorUser = (person == null ? "" : person.getName()); 603 604 if (StringUtils.isNotBlank(docHandlerUrl)) { 605 Object[] args = {actionItem.getDocumentId(), 606 initiatorUser, 607 documentType.getName(), 608 actionItem.getDocTitle(), 609 docHandlerUrl, 610 getActionListUrl(), 611 getPreferencesUrl(), 612 getHelpLink(documentType) 613 }; 614 615 messageFormat.format(args, sf, new FieldPosition(0)); 616 617 LOG.debug("default immediate reminder: " + DEFAULT_IMMEDIATE_REMINDER.format(args)); 618 } else { 619 Object[] args = {actionItem.getDocumentId(), 620 initiatorUser, 621 documentType.getName(), 622 actionItem.getDocTitle(), 623 getActionListUrl(), 624 getPreferencesUrl(), 625 getHelpLink(documentType) 626 }; 627 628 messageFormat.format(args, sf, new FieldPosition(0)); 629 630 LOG.debug("default immediate reminder: " + DEFAULT_IMMEDIATE_REMINDER_NO_DOC_HANDLER.format(args)); 631 } 632 LOG.debug("immediate reminder: " + sf); 633 634 // for debugging purposes on the immediate reminder only 635 if (!isProduction()) { 636 try { 637 sf.append("Action Item sent to " + actionItem.getPrincipalId()); 638 if (actionItem.getDelegationType() != null) { 639 sf.append(" for delegation type " 640 + actionItem.getDelegationType()); 641 } 642 } catch (Exception e) { 643 throw new RuntimeException(e); 644 } 645 } 646 647 return sf.toString(); 648 } 649 650 public String buildDailyReminderBody(Collection<ActionItemActionListExtension> actionItems) { 651 StringBuffer sf = new StringBuffer(); 652 sf.append(getDailyWeeklyMessageBody(actionItems)); 653 sf 654 .append("To change how these email notifications are sent (immediately, weekly or none): \n"); 655 sf.append("\tGo to " + getPreferencesUrl() + "\n"); 656 // sf.append("\tSend as soon as you get an eDoc\n\t" + 657 // getPreferencesUrl() + "\n\n"); 658 // sf.append("\tSend weekly\n\t" + getPreferencesUrl() + "\n\n"); 659 // sf.append("\tDo not send\n\t" + getPreferencesUrl() + "\n"); 660 sf.append("\n\n\n"); 661 sf.append(getHelpLink() + "\n\n\n"); 662 return sf.toString(); 663 } 664 665 public String buildWeeklyReminderBody(Collection<ActionItemActionListExtension> actionItems) { 666 StringBuffer sf = new StringBuffer(); 667 sf.append(getDailyWeeklyMessageBody(actionItems)); 668 sf 669 .append("To change how these email notifications are sent (immediately, daily or none): \n"); 670 sf.append("\tGo to " + getPreferencesUrl() + "\n"); 671 // sf.append("\tSend as soon as you get an eDoc\n\t" + 672 // getPreferencesUrl() + "\n\n"); 673 // sf.append("\tSend daily\n\t" + getPreferencesUrl() + "\n\n"); 674 // sf.append("\tDo not send\n\t" + getPreferencesUrl() + "\n"); 675 sf.append("\n\n\n"); 676 sf.append(getHelpLink() + "\n\n\n"); 677 return sf.toString(); 678 } 679 680 String getDailyWeeklyMessageBody(Collection<ActionItemActionListExtension> actionItems) { 681 StringBuffer sf = new StringBuffer(); 682 HashMap<String, Integer> docTypes = getActionListItemsStat(actionItems); 683 684 sf 685 .append("Your Action List has " 686 + actionItems.size() 687 + " eDocs(electronic documents) that need your attention: \n\n"); 688 for(String docTypeName : docTypes.keySet()) { 689 sf.append("\t" + ((Integer) docTypes.get(docTypeName)).toString() 690 + "\t" + docTypeName + "\n"); 691 } 692 sf.append("\n\n"); 693 sf.append("To respond to each of these eDocs: \n"); 694 sf 695 .append("\tGo to " 696 + getActionListUrl() 697 + ", and then click on its numeric Document ID in the first column of the List.\n"); 698 sf.append("\n\n\n"); 699 return sf.toString(); 700 } 701 702 private HashMap<String, Integer> getActionListItemsStat(Collection<ActionItemActionListExtension> actionItems) { 703 HashMap<String, Integer> docTypes = new LinkedHashMap<String, Integer>(); 704 Collection<org.kuali.rice.kew.api.action.ActionItem> apiActionItems = new ArrayList<org.kuali.rice.kew.api.action.ActionItem>(); 705 for(ActionItem actionItem : actionItems) { 706 apiActionItems.add(ActionItem.to(actionItem)); 707 } 708 Map<String, DocumentRouteHeaderValue> routeHeaders = KEWServiceLocator.getRouteHeaderService().getRouteHeadersForActionItems(apiActionItems); 709 Iterator<ActionItemActionListExtension> iter = actionItems.iterator(); 710 711 while (iter.hasNext()) { 712 String docTypeName = routeHeaders.get(iter.next().getDocumentId()).getDocumentType().getName(); 713 if (docTypes.containsKey(docTypeName)) { 714 docTypes.put(docTypeName, new Integer(docTypes.get(docTypeName).intValue() + 1)); 715 } else { 716 docTypes.put(docTypeName, new Integer(1)); 717 } 718 } 719 return docTypes; 720 } 721 722 protected boolean sendActionListEmailNotification() { 723 if (LOG.isDebugEnabled()) 724 LOG.debug("actionlistsendconstant: " 725 + CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString( 726 KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.ACTION_LIST_DETAIL_TYPE, 727 KewApiConstants.ACTION_LIST_SEND_EMAIL_NOTIFICATION_IND)); 728 729 return KewApiConstants.ACTION_LIST_SEND_EMAIL_NOTIFICATION_VALUE 730 .equals(CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString( 731 KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.ACTION_LIST_DETAIL_TYPE, 732 KewApiConstants.ACTION_LIST_SEND_EMAIL_NOTIFICATION_IND)); 733 } 734 735 public void scheduleBatchEmailReminders() throws Exception { 736 String emailBatchGroup = "Email Batch"; 737 String dailyCron = ConfigContext.getCurrentContextConfig() 738 .getProperty(KewApiConstants.DAILY_EMAIL_CRON_EXPRESSION); 739 if (!StringUtils.isBlank(dailyCron)) { 740 LOG.info("Scheduling Daily Email batch with cron expression: " + dailyCron); 741 CronTrigger dailyTrigger = new CronTrigger(DAILY_TRIGGER_NAME, emailBatchGroup, dailyCron); 742 JobDetail dailyJobDetail = new JobDetail(DAILY_JOB_NAME, emailBatchGroup, DailyEmailJob.class); 743 dailyTrigger.setJobName(dailyJobDetail.getName()); 744 dailyTrigger.setJobGroup(dailyJobDetail.getGroup()); 745 addJobToScheduler(dailyJobDetail); 746 addTriggerToScheduler(dailyTrigger); 747 } else { 748 LOG.warn("No " + KewApiConstants.DAILY_EMAIL_CRON_EXPRESSION 749 + " parameter was configured. Daily Email batch was not scheduled!"); 750 } 751 752 String weeklyCron = ConfigContext.getCurrentContextConfig().getProperty( 753 KewApiConstants.WEEKLY_EMAIL_CRON_EXPRESSION); 754 if (!StringUtils.isBlank(weeklyCron)) { 755 LOG.info("Scheduling Weekly Email batch with cron expression: " + weeklyCron); 756 CronTrigger weeklyTrigger = new CronTrigger(WEEKLY_TRIGGER_NAME, emailBatchGroup, weeklyCron); 757 JobDetail weeklyJobDetail = new JobDetail(WEEKLY_JOB_NAME, emailBatchGroup, WeeklyEmailJob.class); 758 weeklyTrigger.setJobName(weeklyJobDetail.getName()); 759 weeklyTrigger.setJobGroup(weeklyJobDetail.getGroup()); 760 addJobToScheduler(weeklyJobDetail); 761 addTriggerToScheduler(weeklyTrigger); 762 } else { 763 LOG.warn("No " + KewApiConstants.WEEKLY_EMAIL_CRON_EXPRESSION 764 + " parameter was configured. Weekly Email batch was not scheduled!"); 765 } 766 } 767 768 private void addJobToScheduler(JobDetail jobDetail) throws SchedulerException { 769 getScheduler().addJob(jobDetail, true); 770 } 771 772 private void addTriggerToScheduler(Trigger trigger) throws SchedulerException { 773 boolean triggerExists = (getScheduler().getTrigger(trigger.getName(), trigger.getGroup()) != null); 774 if (!triggerExists) { 775 try { 776 getScheduler().scheduleJob(trigger); 777 } catch (ObjectAlreadyExistsException ex) { 778 getScheduler().rescheduleJob(trigger.getName(), trigger.getGroup(), trigger); 779 } 780 } else { 781 getScheduler().rescheduleJob(trigger.getName(), trigger.getGroup(), trigger); 782 } 783 } 784 785 private Scheduler getScheduler() { 786 return KSBServiceLocator.getScheduler(); 787 } 788 789 private UserOptionsService getUserOptionsService() { 790 return (UserOptionsService) KEWServiceLocator 791 .getUserOptionsService(); 792 } 793 794 protected ActionListService getActionListService() { 795 return (ActionListService) KEWServiceLocator.getActionListService(); 796 } 797 798 public String getDeploymentEnvironment() { 799 return deploymentEnvironment; 800 } 801 802 public void setDeploymentEnvironment(String deploymentEnvironment) { 803 this.deploymentEnvironment = deploymentEnvironment; 804 } 805 806 protected String getActionListUrl() { 807 return ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.WORKFLOW_URL_KEY) 808 + "/" + "ActionList.do"; 809 } 810 811 protected String getPreferencesUrl() { 812 return ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.WORKFLOW_URL_KEY) 813 + "/" + "Preferences.do"; 814 } 815}