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