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