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}