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}