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