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.actionlist;
017import org.apache.struts.action.*;
018import org.apache.commons.collections.ComparatorUtils;
019import org.apache.commons.lang.StringUtils;
020import org.apache.commons.lang.builder.EqualsBuilder;
021import org.apache.commons.lang.builder.HashCodeBuilder;
022import org.displaytag.pagination.PaginatedList;
023import org.displaytag.properties.SortOrderEnum;
024import org.displaytag.util.LookupUtil;
025import org.kuali.rice.core.api.config.property.ConfigContext;
026import org.kuali.rice.core.api.delegation.DelegationType;
027import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
028import org.kuali.rice.core.api.exception.RiceRuntimeException;
029import org.kuali.rice.core.api.util.ConcreteKeyValue;
030import org.kuali.rice.core.api.util.KeyValue;
031import org.kuali.rice.kew.actionitem.ActionItemActionListExtension;
032import org.kuali.rice.kew.actionitem.OutboxItemActionListExtension;
033import org.kuali.rice.kew.actionlist.service.ActionListService;
034import org.kuali.rice.kew.actionlist.web.ActionListUtil;
035import org.kuali.rice.kew.actionrequest.Recipient;
036import org.kuali.rice.kew.api.KewApiConstants;
037import org.kuali.rice.kew.api.action.ActionInvocation;
038import org.kuali.rice.kew.api.action.ActionItemCustomization;
039import org.kuali.rice.kew.api.action.ActionSet;
040import org.kuali.rice.kew.api.action.ActionType;
041import org.kuali.rice.kew.api.exception.WorkflowException;
042import org.kuali.rice.kew.api.extension.ExtensionDefinition;
043import org.kuali.rice.kew.api.preferences.Preferences;
044import org.kuali.rice.kew.framework.KewFrameworkServiceLocator;
045import org.kuali.rice.kew.framework.actionlist.ActionListCustomizationMediator;
046import org.kuali.rice.kew.service.KEWServiceLocator;
047import org.kuali.rice.kew.util.PerformanceLogger;
048import org.kuali.rice.kim.api.group.Group;
049import org.kuali.rice.kim.api.identity.Person;
050import org.kuali.rice.kim.api.identity.principal.Principal;
051import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
052import org.kuali.rice.kim.api.services.KimApiServiceLocator;
053import org.kuali.rice.kns.web.ui.ExtraButton;
054import org.kuali.rice.krad.UserSession;
055import org.kuali.rice.krad.exception.AuthorizationException;
056import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
057import org.kuali.rice.krad.util.GlobalVariables;
058import org.kuali.rice.krad.web.controller.UifControllerBase;
059import org.springframework.stereotype.Controller;
060import org.springframework.validation.BindingResult;
061import org.springframework.web.bind.annotation.ModelAttribute;
062import org.springframework.web.bind.annotation.RequestMapping;
063import org.springframework.web.servlet.ModelAndView;
064import org.kuali.rice.krad.web.form.UifFormBase;
065
066import javax.servlet.ServletException;
067import javax.servlet.http.HttpServletRequest;
068import javax.servlet.http.HttpServletResponse;
069import java.text.ParseException;
070import java.text.SimpleDateFormat;
071import java.util.ArrayList;
072import java.util.Arrays;
073import java.util.Collection;
074import java.util.Collections;
075import java.util.Comparator;
076import java.util.Date;
077import java.util.HashSet;
078import java.util.Iterator;
079import java.util.LinkedHashMap;
080import java.util.LinkedHashSet;
081import java.util.List;
082import java.util.Map;
083import java.util.Set;
084
085/**
086 * A controller for the action list view.
087 *
088 * @author Kuali Rice Team (rice.collab@kuali.org)
089 */
090@Controller
091@RequestMapping(value = "/new/actionList")
092public class ActionListController extends UifControllerBase{
093    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ActionListController.class);
094    protected static final String MAX_ACTION_ITEM_DATE_FORMAT = "yyyy-MM-dd hh:mm:ss.S";
095
096    private static final ActionType [] actionListActionTypes =
097            { ActionType.APPROVE, ActionType.DISAPPROVE, ActionType.CANCEL, ActionType.ACKNOWLEDGE, ActionType.FYI };
098
099    @Override
100    protected ActionListForm createInitialForm(HttpServletRequest request) {
101        return new ActionListForm();
102    }
103
104    /**
105    * Refresh request mapping.
106    *
107    * <p>
108    * Handles requests where the methodToCall parameter
109    * is 'refresh'.
110    * </p>
111    *
112    * @param form - ActionListForm form
113    * @param result - Spring form binding result
114    * @param request - http request
115    * @param response - http response
116    * @return start - forwards to start method
117    */
118    @RequestMapping(params = "methodToCall=refresh")
119    public ModelAndView refresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
120            HttpServletRequest request, HttpServletResponse response){
121        ActionListForm actionListForm = (ActionListForm)form;
122        actionListForm.setRequeryActionList(true);
123
124        return start(form,result,request,response);
125    }
126
127    /**
128    * Initializes filter.
129    *
130    * <p>
131    * Sets up the action list filter
132    * </p>
133    *
134    * @param form - ActionListForm form
135    */
136    protected void initializeFilter(ActionListForm form) {
137        if (form.getFilter() == null) {
138            ActionListFilter filter = new ActionListFilter();
139            filter.setDelegationType(DelegationType.SECONDARY.getCode());
140            filter.setExcludeDelegationType(true);
141            form.setFilter(filter);
142        }
143    }
144
145    /**
146    * Initializes principal id.
147    *
148    * <p>
149    * Sets up the principal id in the form.
150    * </p>
151    *
152    * @param actionListForm - ActionListForm form
153    * @param filter - action list filter
154    * @return String
155    */
156    protected String initializePrinicpalId(ActionListForm actionListForm,ActionListFilter filter) {
157        String principalId = null;
158        Principal principal = actionListForm.getHelpDeskActionListPrincipal();
159        if (principal != null) {
160            principalId = principal.getPrincipalId();
161        } else {
162            if (!StringUtils.isEmpty(actionListForm.getDocType())) {
163                initializeDocType(actionListForm,filter);
164            }
165            final UserSession uSession = getUserSession();
166            principalId = uSession.getPerson().getPrincipalId();
167        }
168
169        return principalId;
170    }
171
172    /**
173    * Initializes Document Type.
174    *
175    * <p>
176    * Sets up the document type in the form.
177    * </p>
178    *
179    * @param actionListForm - ActionListForm form
180    * @param filter - action list filter
181    * @return void
182    */
183    protected void initializeDocType(ActionListForm actionListForm,ActionListFilter filter) {
184        filter.setDocumentType(actionListForm.getDocType());
185        filter.setExcludeDocumentType(false);
186        actionListForm.setRequeryActionList(true);
187    }
188
189    /**
190    * Initializes Delegators
191    *
192    * <p>
193    * Sets up the delegators for the form and filter
194    * </p>
195    *
196    * @param actionListForm - ActionListForm form
197    * @param filter - action list filter
198    * @param actionList - list of action items
199    * @param request - http request
200    * @return void
201    */
202    protected void initializeDelegators(ActionListForm actionListForm,ActionListFilter filter,List<? extends ActionItemActionListExtension> actionList,HttpServletRequest request)   {
203        if (!KewApiConstants.DELEGATION_DEFAULT.equals(actionListForm.getDelegationId())) {
204            // If the user can filter by both primary and secondary delegation, and both drop-downs have non-default values assigned,
205            // then reset the primary delegation drop-down's value when the primary delegation drop-down's value has remained unaltered
206            // but the secondary drop-down's value has been altered; but if one of these alteration situations does not apply, reset the
207            // secondary delegation drop-down.
208
209            if (StringUtils.isNotBlank(actionListForm.getPrimaryDelegateId()) && !KewApiConstants.PRIMARY_DELEGATION_DEFAULT.equals(actionListForm.getPrimaryDelegateId())){
210                setDelegationId(actionListForm,request);
211            } else if (StringUtils.isNotBlank(filter.getPrimaryDelegateId()) &&
212                    !KewApiConstants.PRIMARY_DELEGATION_DEFAULT.equals(filter.getPrimaryDelegateId())) {
213                // If the primary delegation drop-down is invisible but a primary delegation filter is in place, and if the secondary delegation
214                // drop-down has a non-default value selected, then reset the primary delegation filtering.
215                filter.setPrimaryDelegateId(KewApiConstants.PRIMARY_DELEGATION_DEFAULT);
216            }
217        }
218        // Enable the secondary delegation filtering.
219        filter.setDelegatorId(actionListForm.getDelegationId());
220        filter.setExcludeDelegatorId(false);
221        actionList = null;
222    }
223
224    /**
225     * Sets the delegation id
226     *
227     * <p>
228     * Sets the delegation id on the form
229     * </p>
230     *
231     * @param actionListForm - ActionListForm form
232     * @param request - http request
233     * @return void
234     */
235    protected void setDelegationId(ActionListForm actionListForm,HttpServletRequest request)   {
236        if (actionListForm.getPrimaryDelegateId().equals(request.getParameter("oldPrimaryDelegateId")) &&
237                !actionListForm.getDelegationId().equals(request.getParameter("oldDelegationId"))) {
238            actionListForm.setPrimaryDelegateId(KewApiConstants.PRIMARY_DELEGATION_DEFAULT);
239        } else {
240            actionListForm.setDelegationId(KewApiConstants.DELEGATION_DEFAULT);
241        }
242    }
243
244    /**
245     * Initializes primary delegate.
246     *
247     * <p>
248     * Sets up the primary delegate in the form.
249     * </p>
250     *
251     * @param actionListForm - ActionListForm form
252     * @param filter - action list filter
253     * @param actionList - list of action items
254     * @param request - http request
255     * @return void
256     */
257    protected void initializePrimaryDelegate(ActionListForm actionListForm,ActionListFilter filter,List<? extends ActionItemActionListExtension> actionList,HttpServletRequest request)   {
258        if (!StringUtils.isEmpty(actionListForm.getPrimaryDelegateId())) {
259
260            // If the secondary delegation drop-down is invisible but a secondary delegation filter is in place, and if the primary delegation
261            // drop-down has a non-default value selected, then reset the secondary delegation filtering.
262            if (StringUtils.isBlank(actionListForm.getDelegationId()) && !KewApiConstants.PRIMARY_DELEGATION_DEFAULT.equals(actionListForm.getPrimaryDelegateId()) &&
263                    StringUtils.isNotBlank(filter.getDelegatorId()) &&
264                    !KewApiConstants.DELEGATION_DEFAULT.equals(filter.getDelegatorId())) {
265                filter.setDelegatorId(KewApiConstants.DELEGATION_DEFAULT);
266            }
267
268            // Enable the primary delegation filtering.
269            filter.setPrimaryDelegateId(actionListForm.getPrimaryDelegateId());
270            filter.setExcludeDelegatorId(false);
271            actionList = null;
272        }
273    }
274
275    /**
276    * Start request mapping.
277    *
278    * <p>
279    * Handles requests where the methodToCall parameter
280    * is 'start'.  Runs on most requests and sets up the
281    * basic variables.
282    * </p>
283    *
284    * @param form - ActionListForm form
285    * @param result - Spring form binding result
286    * @param request - http request
287    * @param response - http response
288    * @return ModelAndView - uses standard KRAD getUIFModelAndView()
289    */
290    @Override
291    @RequestMapping(params = "methodToCall=start")
292    public ModelAndView start(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
293            HttpServletRequest request, HttpServletResponse response) {
294        ActionListForm actionListForm = (ActionListForm)form;
295        request.setAttribute("preferences", actionListForm.getPreferences());
296
297        PerformanceLogger plog = new PerformanceLogger();
298        plog.log("Starting ActionList fetch");
299        ActionListService actionListSrv = KEWServiceLocator.getActionListService();
300
301        // reset the default action on the form
302        actionListForm.setDefaultActionToTake("NONE");
303        boolean freshActionList = true;
304
305        // retrieve cached action list
306        List<? extends ActionItemActionListExtension> actionList = (List<? extends ActionItemActionListExtension>)actionListForm.getActionList();
307        plog.log("Time to initialize");
308
309
310        try {
311
312            initializeFilter(actionListForm);
313            final ActionListFilter filter = actionListForm.getFilter();
314
315            String principalId = initializePrinicpalId(actionListForm,filter);
316
317            /* 'forceListRefresh' variable used to signify that the action list filter has changed
318             * any time the filter changes the action list must be refreshed or filter may not take effect on existing
319             * list items... only exception is if action list has not loaded previous and fetching of the list has not
320             * occurred yet
321             */
322            boolean forceListRefresh = actionListForm.isRequeryActionList();
323
324            final Preferences preferences = (Preferences)actionListForm.getPreferences();
325
326            //set primary delegation id
327            if (!StringUtils.isEmpty(actionListForm.getDelegationId())) {
328                initializeDelegators(actionListForm,filter,actionList,request);
329            }
330
331            //set primary delegate
332            if (!StringUtils.isEmpty(actionListForm.getPrimaryDelegateId())) {
333                initializePrimaryDelegate(actionListForm,filter,actionList,request);
334            }
335
336            // if the user has changed, we need to refresh the action list
337            if (!principalId.equals(actionListForm.getUser())) {
338                actionList = null;
339            }
340
341            if (isOutboxMode(actionListForm, request, preferences)) {
342                actionList = new ArrayList<OutboxItemActionListExtension>(actionListSrv.getOutbox(principalId, filter));
343                actionListForm.setOutBoxEmpty(actionList.isEmpty());
344                //added because we now use the actionList rather than the actionListPage
345                actionListForm.setActionList((ArrayList) actionList);
346            } else {
347
348                if (actionList == null) {
349                    // fetch the action list
350                    actionList = new ArrayList<ActionItemActionListExtension>(actionListSrv.getActionList(principalId, filter));
351                    actionListForm.setUser(principalId);
352                } else if (forceListRefresh) {
353                    // force a refresh... usually based on filter change or parameter specifying refresh needed
354                    actionList = new ArrayList<ActionItemActionListExtension>(actionListSrv.getActionList(principalId, filter));
355                    actionListForm.setUser(principalId);
356                } else {
357                    Boolean update = actionListForm.isUpdateActionList();
358                }
359
360                actionListForm.setActionList((ArrayList) actionList);
361            }
362
363            // reset the requery action list key
364            actionListForm.setRequeryActionList(false);
365
366            // build the drop-down of delegators
367            if (KewApiConstants.DELEGATORS_ON_ACTION_LIST_PAGE.equalsIgnoreCase(preferences.getDelegatorFilter())) {
368                Collection<Recipient> delegators = actionListSrv.findUserSecondaryDelegators(principalId);
369                actionListForm.setDelegators(ActionListUtil.getWebFriendlyRecipients(delegators));
370                actionListForm.setDelegationId(filter.getDelegatorId());
371            }
372
373            // Build the drop-down of primary delegates.
374            if (KewApiConstants.PRIMARY_DELEGATES_ON_ACTION_LIST_PAGE.equalsIgnoreCase(preferences.getPrimaryDelegateFilter())) {
375                Collection<Recipient> pDelegates = actionListSrv.findUserPrimaryDelegations(principalId);
376                actionListForm.setPrimaryDelegates(ActionListUtil.getWebFriendlyRecipients(pDelegates));
377                actionListForm.setPrimaryDelegateId(filter.getPrimaryDelegateId());
378            }
379
380            actionListForm.setFilterLegend(filter.getFilterLegend());
381            plog.log("Setting attributes");
382
383            int pageSize = getPageSize(preferences);
384
385            // initialize the action list if necessary
386            if (freshActionList) {
387                plog.log("calling initializeActionList");
388                initializeActionList(actionList, preferences);
389                plog.log("done w/ initializeActionList");
390            }
391
392            plog.log("start addActions");
393            addCustomActions(actionList,preferences,actionListForm);
394            plog.log("done w/ addCustomActions");
395            actionListForm.setUpdateActionList(false);
396            plog.log("finished setting attributes, finishing action list fetch");
397        } catch (Exception e) {
398            LOG.error("Error loading action list.", e);
399        }
400
401        LOG.debug("end start ActionListAction");
402
403        String returnPage = "ActionListPage1";
404        String methodToCall = actionListForm.getMethodToCall();
405        if(methodToCall.equals("clear")) {
406           returnPage = "ActionListPage2";
407        }
408
409        return getUIFModelAndView(actionListForm,returnPage);
410    }
411
412    private static final String OUT_BOX_MODE = "_OUT_BOX_MODE";
413
414    /**
415    *  Determines whether the page is in outbox mode.
416    *
417    *  <p>
418    *  This method is setting 2 props on the {@link org.kuali.rice.kew.actionlist.web.ActionListForm} that controls outbox behavior.
419    *  alForm.setViewOutbox("false"); -> this is set by user preferences and the actionlist.outbox.off config prop
420    *  alForm.setShowOutbox(false); -> this is set by user action clicking the ActionList vs. Outbox links.
421    *  </p>
422    *
423    * @param alForm - action list form
424    * @param request - http request
425    * @return boolean indication whether the outbox should be fetched
426    */
427    private boolean isOutboxMode(ActionListForm alForm, HttpServletRequest request, Preferences preferences) {
428
429        boolean outBoxView = false;
430
431        if (! preferences.isUsingOutbox() || ! ConfigContext.getCurrentContextConfig().getOutBoxOn()) {
432            alForm.setOutBoxMode(Boolean.FALSE);
433            alForm.setViewOutbox("false");
434            alForm.setShowOutbox(false);
435
436            return false;
437        }
438
439        alForm.setShowOutbox(true);
440
441        if (StringUtils.isNotEmpty(alForm.getViewOutbox())) {
442            if (!Boolean.valueOf(alForm.getViewOutbox())) {
443                //request.getSession().setAttribute(OUT_BOX_MODE, Boolean.FALSE);
444                alForm.setOutBoxMode(Boolean.FALSE);
445                outBoxView = false;
446            } else {
447                //request.getSession().setAttribute(OUT_BOX_MODE, Boolean.TRUE);
448                alForm.setOutBoxMode(Boolean.FALSE);
449                outBoxView = true;
450            }
451        } else {
452            outBoxView = alForm.isOutBoxMode();
453        }
454
455        if (outBoxView) {
456            alForm.setViewOutbox("true");
457        } else {
458            alForm.setViewOutbox("false");
459        }
460
461        return outBoxView;
462    }
463
464    /**
465     *  Initializes the action list.
466     *
467     *  <p>
468     *  Checks for errors in the action list upon initial load.
469     *  </p>
470     *
471     * @param actionList list of action items
472     * @param preferences KEW user preferences
473     * @return void
474     */
475    private void initializeActionList(List<? extends ActionItemActionListExtension> actionList, Preferences preferences) {
476        List<String> actionItemProblemIds = new ArrayList<String>();
477        int index = 0;
478        generateActionItemErrors(actionList);
479
480        for (Iterator<? extends ActionItemActionListExtension> iterator = actionList.iterator(); iterator.hasNext();) {
481            ActionItemActionListExtension actionItem = iterator.next();
482            if (actionItem.getDocumentId() == null) {
483                LOG.error("Somehow there exists an ActionItem with a null document id!  actionItemId=" + actionItem.getId());
484                iterator.remove();
485                continue;
486            }
487
488            try {
489                actionItem.initialize(preferences);
490                actionItem.setActionListIndex(index);
491                index++;
492            } catch (Exception e) {
493                // if there's a problem loading the action item, we don't want to blow out the whole screen but we will remove it from the list
494                // and display an appropriate error message to the user
495                LOG.error("Error loading action list for action item " + actionItem.getId(), e);
496                iterator.remove();
497                actionItemProblemIds.add(actionItem.getDocumentId());
498            }
499        }
500
501        generateActionItemErrors("actionitem", "actionlist.badActionItems", actionItemProblemIds);
502    }
503
504    /**
505    *  Get the action list page size.
506    *
507    *  <p>
508    *  Gets the page size of the Action List.  Uses the user's preferences for page size unless the action list
509    *  has been throttled by an application constant, in which case it uses the smaller of the two values.
510    *  </p>
511    *
512    * @param preferences KEW user preferences
513    * @return int
514    */
515    protected int getPageSize(Preferences preferences) {
516        return Integer.parseInt(preferences.getPageSize());
517    }
518
519    /**
520    *  Adds custom actions to action items.
521    *
522    *  <p>
523    *  Goes through each item in the action list and adds the custom actions.  It also adds flags for whether each
524    *  item has actions.  Finally, creates list of actions and flag for the entire action list.
525    *  </p>
526    *
527    * @param actionList list of action items
528    * @param preferences KEW preferences
529    * @form action list form
530    * @return void
531    */
532    protected void addCustomActions(List<? extends ActionItemActionListExtension> actionList,
533            Preferences preferences, ActionListForm form) throws WorkflowException {
534
535        boolean haveCustomActions = false;
536        boolean haveDisplayParameters = false;
537
538        final boolean showClearFyi = KewApiConstants.PREFERENCES_YES_VAL.equalsIgnoreCase(preferences.getShowClearFyi());
539
540        // collects all the actions for items
541        Set<ActionType> pageActions = new HashSet<ActionType>();
542
543        List<String> customActionListProblemIds = new ArrayList<String>();
544        generateActionItemErrors(actionList);
545
546        LOG.info("Beginning processing of Action List Customizations (total: " + actionList.size() + " Action Items)");
547        long start = System.currentTimeMillis();
548        Map<String, ActionItemCustomization> customizationMap =
549                getActionListCustomizationMediator().getActionListCustomizations(
550                        getUserSession().getPrincipalId(), convertToApiActionItems(actionList)
551                );
552        long end = System.currentTimeMillis();
553        LOG.info("Finished processing of Action List Customizations (total time: " + (end - start) + " ms)");
554
555        for(ActionItemActionListExtension actionItem : actionList ){
556            // evaluate custom action list component for mass actions
557            try {
558                ActionItemCustomization customization = customizationMap.get(actionItem.getId());
559
560                if (customization != null) {
561                    ActionSet actionSet = customization.getActionSet();
562
563                    // If only it were this easy: actionItem.setCustomActions(customization.getActionSet());
564                    Map<String, String> customActions = new LinkedHashMap<String, String>();
565                    customActions.put("NONE", "NONE");
566
567                    for (ActionType actionType : actionListActionTypes) {
568                        if (actionSet.hasAction(actionType.getCode()) &&
569                                isActionCompatibleRequest(actionItem, actionType.getCode())) {
570                            final boolean isFyi = ActionType.FYI == actionType; // make the conditional easier to read
571                            if (!isFyi || (isFyi && showClearFyi)) { // deal with special FYI preference
572                                customActions.put(actionType.getCode(), actionType.getLabel());
573                                pageActions.add(actionType);
574                            }
575                        }
576                    }
577
578                    if (customActions.size() > 1) {
579                        actionItem.setCustomActions(customActions);
580                        haveCustomActions = true;
581                    }
582
583                    actionItem.setDisplayParameters(customization.getDisplayParameters());
584                    haveDisplayParameters = haveDisplayParameters || (actionItem.getDisplayParameters() != null);
585                }
586
587            } catch (Exception e) {
588                // if there's a problem loading the custom action list attribute, let's go ahead and display the vanilla action item
589                LOG.error("Problem loading custom action list attribute", e);
590                customActionListProblemIds.add(actionItem.getDocumentId());
591            }
592        }
593
594        // configure custom actions on form
595        form.setHasCustomActions(haveCustomActions);
596
597        Map<String, String> defaultActions = new LinkedHashMap<String, String>();
598        defaultActions.put("NONE", "NONE");
599
600        for (ActionType actionType : actionListActionTypes) {
601            if (pageActions.contains(actionType)) {
602                // special logic for FYIs:
603                final boolean isFyi = ActionType.FYI == actionType;
604                if (isFyi) {
605                    // clearing FYIs can be done in any action list not just a customized one
606                    if(showClearFyi) {
607                        defaultActions.put(actionType.getCode(), actionType.getLabel());
608                    }
609                } else { // all the other actions
610                    defaultActions.put(actionType.getCode(), actionType.getLabel());
611                    form.setCustomActionList(Boolean.TRUE);
612                }
613            }
614        }
615
616        if (defaultActions.size() > 1) {
617            form.setDefaultActions(defaultActions);
618        }
619
620        form.setHasDisplayParameters(haveDisplayParameters);
621        generateActionItemErrors("customActionList", "actionlist.badCustomActionListItems", customActionListProblemIds);
622
623    }
624
625    /**
626    *  Converts actionItems to list.
627    *
628    *  <p>
629    *  Convert a List of org.kuali.rice.kew.actionitem.ActionItemS to org.kuali.rice.kew.api.action.ActionItemS.
630    *  </p>
631    *
632    * @param actionList list of action items
633    * @return List<org.kuali.rice.kew.api.action.ActionItem>
634    */
635    private List<org.kuali.rice.kew.api.action.ActionItem> convertToApiActionItems(List<? extends ActionItemActionListExtension> actionList) {
636        List<org.kuali.rice.kew.api.action.ActionItem> apiActionItems = new ArrayList<org.kuali.rice.kew.api.action.ActionItem>(actionList.size());
637        for (ActionItemActionListExtension actionItemObj : actionList) {
638            apiActionItems.add(
639                    org.kuali.rice.kew.api.action.ActionItem.Builder.create(actionItemObj).build());
640        }
641
642        return apiActionItems;
643    }
644
645    /**
646    *  Creates action item errors.
647    *
648    *  <p>
649    *  Creates an error for each action item that has an empty ID.
650    *  </p>
651    *
652    * @param propertyName the property name
653    * @param errorKey  string of the error key
654    * @param documentIds list of document IDs
655    * @return void
656    */
657    private void generateActionItemErrors(String propertyName, String errorKey, List<String> documentIds) {
658        if (!documentIds.isEmpty()) {
659            String documentIdsString = StringUtils.join(documentIds.iterator(), ", ");
660            GlobalVariables.getMessageMap().putError(propertyName, errorKey, documentIdsString);
661        }
662    }
663
664    /**
665    *  Creates action item errors.
666    *
667    *  <p>
668    *  Creates an error for each action item that has an empty ID.
669    *  </p>
670    *
671    * @param actionList list of action items.
672    * @return void
673    */
674    private void generateActionItemErrors(List<? extends ActionItemActionListExtension> actionList) {
675        for (ActionItemActionListExtension actionItem : actionList) {
676            if(!KewApiConstants.ACTION_REQUEST_CODES.containsKey(actionItem.getActionRequestCd())) {
677                GlobalVariables.getMessageMap().putError("actionRequestCd","actionitem.actionrequestcd.invalid",actionItem.getId()+"");
678            }
679        }
680    }
681
682    /**
683     * Process taking mass action on action items
684     *
685     * <p>
686     * Handles requests where the methodToCall parameter
687     * is 'takeMassActions'.  Iterates through action items that have custom actions and process each selected action.
688     * </p>
689     *
690     * @param form - ActionListForm form
691     * @param result - Spring form binding result
692     * @param request - http request
693     * @param response - http response
694     * @return start - forwards to the start method
695     */
696    @RequestMapping(params = "methodToCall=takeMassActions")
697    protected ModelAndView takeMassActions(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
698            HttpServletRequest request, HttpServletResponse response){
699        ActionListForm actionListForm = (ActionListForm) form;
700
701        Object obj = ObjectPropertyUtils.getPropertyValue(form, "extensionData['actionInputField_actionSelect_line2']");
702
703        List<? extends ActionItemActionListExtension> actionList = (List<? extends ActionItemActionListExtension>) actionListForm.getActionList();
704        if (actionList == null) {
705            return getUIFModelAndView(form);
706        }
707
708        ActionMessages messages = new ActionMessages();
709        List<ActionInvocation> invocations = new ArrayList<ActionInvocation>();
710
711        int index = 0;
712        for (Object element : actionListForm.getActionsToTake()) {
713            ActionToTake actionToTake = (ActionToTake) element;
714            if (actionToTake != null && actionToTake.getActionTakenCd() != null &&
715                    !"".equals(actionToTake.getActionTakenCd()) &&
716                    !"NONE".equalsIgnoreCase(actionToTake.getActionTakenCd()) &&
717                    actionToTake.getActionItemId() != null) {
718                ActionItemActionListExtension actionItem = getActionItemFromActionList(actionList, actionToTake.getActionItemId());
719                if (actionItem == null) {
720                    LOG.warn("Could not locate the ActionItem to take mass action against in the action list: " + actionToTake.getActionItemId());
721                    continue;
722                }
723                invocations.add(ActionInvocation.create(ActionType.fromCode(actionToTake.getActionTakenCd()), actionItem.getId()));
724            }
725            index++;
726        }
727
728        KEWServiceLocator.getWorkflowDocumentService().takeMassActions(getUserSession().getPrincipalId(), invocations);
729        messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("general.routing.processed"));
730
731        org.kuali.rice.kew.actionlist.web.ActionListForm
732                cleanForm = new org.kuali.rice.kew.actionlist.web.ActionListForm();
733        actionListForm.setRequeryActionList(true);
734
735        return start(actionListForm,result,request,response);
736    }
737
738    /**
739    * Gets action item from list.
740    *
741    * <p>
742    * Gets the action item from the action item list based on the ID.
743    * </p>
744    *
745    * @param actionList - list of action items
746    * @param actionItemId - primary key for action item
747    * @return ActionItemActionListExtension or null
748    */
749    protected ActionItemActionListExtension getActionItemFromActionList(List<? extends ActionItemActionListExtension> actionList, String actionItemId) {
750        for (ActionItemActionListExtension actionItem : actionList) {
751            if (actionItem.getId().equals(actionItemId)) {
752                return actionItem;
753            }
754        }
755
756        return null;
757    }
758
759    /**
760     * Sets up view for help desk login.
761     *
762     * <p>
763     * Setups the view for the help desk login.  User can see other's action items but can't take action on them.
764     * </p>
765     *
766     * @param form - ActionListForm form
767     * @param result - Spring form binding result
768     * @param request - http request
769     * @param response - http response
770     * @return start() - forwards to start method to refresh action list
771     */
772    @RequestMapping(params = "methodToCall=helpDeskActionListLogin")
773    public ModelAndView helpDeskActionListLogin(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
774            HttpServletRequest request, HttpServletResponse response){
775        ActionListForm actionListForm = (ActionListForm) form;
776
777        String name = actionListForm.getHelpDeskActionListUserName();
778        if (!actionListForm.isHelpDeskActionList()) {
779            throw new AuthorizationException(getUserSession().getPrincipalId(), "helpDeskActionListLogin", getClass().getSimpleName());
780        }
781
782        try
783        {
784            final Principal helpDeskActionListPrincipal = KEWServiceLocator.getIdentityHelperService().getPrincipalByPrincipalName(name);
785            final Person helpDeskActionListPerson = KEWServiceLocator.getIdentityHelperService().getPersonByPrincipalName(name);
786            actionListForm.setHelpDeskActionListPrincipal(helpDeskActionListPrincipal);
787            actionListForm.setHelpDeskActionListPerson(helpDeskActionListPerson);
788        }
789        catch (RiceRuntimeException rre)
790        {
791            GlobalVariables.getMessageMap().putError("helpDeskActionListUserName", "helpdesk.login.invalid", name);
792        }
793        catch (RiceIllegalArgumentException e) {
794            GlobalVariables.getMessageMap().putError("helpDeskActionListUserName", "helpdesk.login.invalid", name);
795        }
796        catch (NullPointerException npe)
797        {
798            GlobalVariables.getMessageMap().putError("null", "helpdesk.login.empty", name);
799        }
800
801        actionListForm.setDelegator(null);
802        actionListForm.setRequeryActionList(true);
803
804        return start(actionListForm,result,request,response);
805    }
806
807    /**
808    * Clears the action list filter.
809    *
810    * <p>
811    * Clears the action list filter so all action items are shown.
812    * </p>
813    *
814    * @param form - ActionListForm form
815    * @param result - Spring form binding result
816    * @param request - http request
817    * @param response - http response
818    * @return start() - forwards to start to refresh action list
819    */
820    @RequestMapping(params = "methodToCall=clearFilter")
821    public ModelAndView clearFilter(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
822            HttpServletRequest request, HttpServletResponse response){
823        ActionListForm actionListForm = (ActionListForm) form;
824
825        LOG.debug("clearFilter ActionListController");
826        final org.kuali.rice.krad.UserSession commonUserSession = getUserSession();
827        actionListForm.setFilter(new ActionListFilter());
828        ActionListFilter filter = new ActionListFilter();
829        filter.setDelegationType(DelegationType.SECONDARY.getCode());
830        filter.setExcludeDelegationType(true);
831        actionListForm.setFilter(filter);
832        LOG.debug("end clearFilter ActionListController");
833
834        return start(actionListForm,result,request,response);
835    }
836
837    /**
838    * Clears the action list filter.
839    *
840    * <p>
841    * Clears the action list filter so all action items are shown.  Clears filter from secondary page and then
842    * forwards to the correct page after the start method runs.
843    * </p>
844    *
845    * @param form - ActionListForm form
846    * @param result - Spring form binding result
847    * @param request - http request
848    * @param response - http response
849    * @return clearFilter() - forwards to clearFilter method
850    */
851    @RequestMapping(params = "methodToCall=clear")
852    public ModelAndView clear(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
853            HttpServletRequest request, HttpServletResponse response){
854        return clearFilter(form,result,request,response);
855    }
856
857    /**
858    * Sets the filter.
859    *
860    * <p>
861    * Sets the action list filter in the form.
862    * </p>
863    *
864    * @param form - ActionListForm form
865    * @param result - Spring form binding result
866    * @param request - http request
867    * @param response - http response
868    * @return start() forwards to start method to refresh action list
869    */
870    @RequestMapping(params = "methodToCall=setFilter")
871    public ModelAndView setFilter(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
872            HttpServletRequest request, HttpServletResponse response){
873        ActionListForm actionListForm = (ActionListForm) form;
874
875        //validate the filter through the actionitem/actionlist service (I'm thinking actionlistservice)
876        final UserSession uSession = getUserSession();
877
878        ActionListFilter alFilter = actionListForm.getLoadedFilter();
879        if (StringUtils.isNotBlank(alFilter.getDelegatorId()) && !KewApiConstants.DELEGATION_DEFAULT.equals(alFilter.getDelegatorId()) &&
880                StringUtils.isNotBlank(alFilter.getPrimaryDelegateId()) && !KewApiConstants.PRIMARY_DELEGATION_DEFAULT.equals(alFilter.getPrimaryDelegateId())){
881            // If the primary and secondary delegation drop-downs are both visible and are both set to non-default values,
882            // then reset the secondary delegation drop-down to its default value.
883            alFilter.setDelegatorId(KewApiConstants.DELEGATION_DEFAULT);
884        }
885
886        actionListForm.setFilter(alFilter);
887        if (GlobalVariables.getMessageMap().hasNoErrors()) {
888            actionListForm.setRequeryActionList(true);
889            return start(actionListForm,result,request,response);
890        }
891
892        return start(actionListForm,result,request,response);
893    }
894
895    /**
896    * Clears help desk login.
897    *
898    * <p>
899    * Set the form back to display the logged in user's action list.
900    * </p>
901    *
902    * @param form - ActionListForm form
903    * @param result - Spring form binding result
904    * @param request - http request
905    * @param response - http response
906    * @return start() - forwards to start method to refresh the action list
907    */
908    @RequestMapping(params = "methodToCall=clearHelpDeskActionListUser")
909    public ModelAndView clearHelpDeskActionListUser(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
910            HttpServletRequest request, HttpServletResponse response){
911        ActionListForm actionListForm = (ActionListForm) form;
912
913        LOG.debug("clearHelpDeskActionListUser ActionListAction");
914        actionListForm.setHelpDeskActionListPrincipal(null);
915        actionListForm.setHelpDeskActionListPerson(null);
916        LOG.debug("end clearHelpDeskActionListUser ActionListAction");
917
918        actionListForm.setRequeryActionList(true);
919
920        return start((UifFormBase)actionListForm,result,request,response);
921    }
922
923    /**
924    * Removes outbox items.
925    *
926    * <p>
927    * Removes any outbox items that are selected.
928    * </p>
929    *
930    * @param form - ActionListForm form
931    * @param result - Spring form binding result
932    * @param request - http request
933    * @param response - http response
934    * @return start() forwards to start to refresh the outbox.
935    */
936    @RequestMapping(params = "methodToCall=removeOutboxItems")
937    public ModelAndView removeOutboxItems(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
938            HttpServletRequest request, HttpServletResponse response){
939        ActionListForm actionListForm = (ActionListForm)form;
940        Map selectedCollectionLines = actionListForm.getSelectedCollectionLines();
941        Object selectedItems = selectedCollectionLines.get("ActionList");
942
943        if (selectedItems != null) {
944            List<String> outboxItemsForDeletion = new ArrayList<String>((LinkedHashSet)selectedItems);
945            KEWServiceLocator.getActionListService().removeOutboxItems(getUserSession().getPrincipalId(), outboxItemsForDeletion);
946            selectedCollectionLines.remove("ActionList");
947            actionListForm.setSelectedCollectionLines(selectedCollectionLines);
948        }
949
950        actionListForm.setViewOutbox("true");
951        actionListForm.setRequeryActionList(true);
952
953        return start(actionListForm,result,request,response);
954    }
955
956    /**
957    * Navigates to filter view.
958    *
959    * <p>
960    * Navigate to the Action List Filter page, preserving any newly-modified primary/secondary delegation filters as necessary.
961    * </p>
962    *
963    * @param form - ActionListForm form
964    * @param result - Spring form binding result
965    * @param request - http request
966    * @param response - http response
967    * @return ModelAndView - forwards to the standard KRAD getUIFModelAndView method
968    */
969    @RequestMapping(params = "methodToCall=viewFilter")
970    public ModelAndView viewFilter(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
971            HttpServletRequest request, HttpServletResponse response){
972        ActionListForm actionListForm = (ActionListForm)form;
973        actionListForm.setOldFilter(new ActionListFilter(actionListForm.getFilter()));
974
975        return getUIFModelAndView(actionListForm,"ActionListPage2");
976    }
977
978    /**
979    * Revert to previous filter.
980    *
981    * <p>
982    * When user changes the filter but presses cancel, the filter goes back to the old filter.
983    * </p>
984    *
985    * @param form - ActionListForm form
986    * @param result - Spring form binding result
987    * @param request - http request
988    * @param response - http response
989    * @return start() forwards to start method to refresh teh action list
990    */
991    @RequestMapping(params = "methodToCall=cancelFilter")
992    public ModelAndView cancelFilter(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
993            HttpServletRequest request, HttpServletResponse response){
994        ActionListForm actionListForm = (ActionListForm)form;
995        actionListForm.setFilter(new ActionListFilter(actionListForm.getOldFilter()));
996
997        return start(actionListForm,result,request,response);
998    }
999
1000    /**
1001    * Navigate to preferences page.
1002    *
1003    * <p>
1004    * Navigate to the user's Preferences page, preserving any newly-modified primary/secondary delegation filters as
1005    * necessary.
1006    * </p>
1007    *
1008    * @param form - ActionListForm form
1009    * @param result - Spring form binding result
1010    * @param request - http request
1011    * @param response - http response
1012    * @return ModelAndView - forwards to KRAD standard getUIFModelAndView method
1013    */
1014    @RequestMapping(params = "methodToCall=viewPreferences")
1015    public ModelAndView viewPreferences(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1016            HttpServletRequest request, HttpServletResponse response){
1017        return getUIFModelAndView(form,"ActionListPage3");
1018    }
1019
1020    /**
1021    * Is the action item a compatible request.
1022    *
1023    * <p>
1024    * Checks whether the action taken is valid for the action item.
1025    * </p>
1026    *
1027    * @param actionItem - an action item
1028    * @param actionTakenCode - code of action taken on the action item
1029    * @return boolean
1030    */
1031    private boolean isActionCompatibleRequest(ActionItemActionListExtension actionItem, String actionTakenCode) {
1032        boolean actionCompatible = false;
1033        String requestCd = actionItem.getActionRequestCd();
1034
1035        //FYI request matches FYI
1036        if (KewApiConstants.ACTION_REQUEST_FYI_REQ.equals(requestCd) && KewApiConstants.ACTION_TAKEN_FYI_CD.equals(actionTakenCode)) {
1037            actionCompatible = true || actionCompatible;
1038        }
1039
1040        // ACK request matches ACK
1041        if (KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ.equals(requestCd) && KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD.equals(actionTakenCode)) {
1042            actionCompatible = true || actionCompatible;
1043        }
1044
1045        // APPROVE request matches all but FYI and ACK
1046        if (KewApiConstants.ACTION_REQUEST_APPROVE_REQ.equals(requestCd) && !(KewApiConstants.ACTION_TAKEN_FYI_CD.equals(actionTakenCode) || KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD.equals(actionTakenCode))) {
1047            actionCompatible = true || actionCompatible;
1048        }
1049
1050        // COMPLETE request matches all but FYI and ACK
1051        if (KewApiConstants.ACTION_REQUEST_COMPLETE_REQ.equals(requestCd) && !(KewApiConstants.ACTION_TAKEN_FYI_CD.equals(actionTakenCode) || KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD.equals(actionTakenCode))) {
1052            actionCompatible = true || actionCompatible;
1053        }
1054
1055        return actionCompatible;
1056    }
1057
1058    /**
1059    * Gets session.
1060    *
1061    * <p>
1062    * Gets the user session object.
1063    * </p>
1064    *
1065    * @return UserSession
1066    */
1067    private UserSession getUserSession(){
1068        return GlobalVariables.getUserSession();
1069    }
1070
1071    /**
1072    * Lazy initialization holder class
1073    *
1074    * <p>
1075    * Lazy initialization holder static class (see Effective Java Item #71)
1076    * </p>
1077    *
1078    */
1079    private static class ActionListCustomizationMediatorHolder {
1080        static final ActionListCustomizationMediator actionListCustomizationMediator =
1081                KewFrameworkServiceLocator.getActionListCustomizationMediator();
1082    }
1083
1084    /**
1085    * Action list customization mediator.
1086    *
1087    * <p>
1088    * Action list customization mediator.
1089    * </p>
1090    *
1091    * @return ActionListCustomizationMediatorHolder.actionListCustomizationMediator
1092    */
1093    private ActionListCustomizationMediator getActionListCustomizationMediator() {
1094        return ActionListCustomizationMediatorHolder.actionListCustomizationMediator;
1095    }
1096
1097    /**
1098    * Simple class which defines the key of a partition of Action Items associated with an Application ID.
1099    *
1100    * <p>
1101    * This class allows direct field access since it is intended for internal use only.
1102    * </p>
1103    *
1104    */
1105    private static final class PartitionKey {
1106        String applicationId;
1107        Set<String> customActionListAttributeNames;
1108
1109        PartitionKey(String applicationId, Collection<ExtensionDefinition> extensionDefinitions) {
1110            this.applicationId = applicationId;
1111            this.customActionListAttributeNames = new HashSet<String>();
1112            for (ExtensionDefinition extensionDefinition : extensionDefinitions) {
1113                this.customActionListAttributeNames.add(extensionDefinition.getName());
1114            }
1115        }
1116
1117        List<String> getCustomActionListAttributeNameList() {
1118            return new ArrayList<String>(customActionListAttributeNames);
1119        }
1120
1121        @Override
1122        public boolean equals(Object o) {
1123            if (!(o instanceof PartitionKey)) {
1124                return false;
1125            }
1126            PartitionKey key = (PartitionKey) o;
1127            EqualsBuilder builder = new EqualsBuilder();
1128            builder.append(applicationId, key.applicationId);
1129            builder.append(customActionListAttributeNames, key.customActionListAttributeNames);
1130
1131            return builder.isEquals();
1132        }
1133
1134        @Override
1135        public int hashCode() {
1136            HashCodeBuilder builder = new HashCodeBuilder();
1137            builder.append(applicationId);
1138            builder.append(customActionListAttributeNames);
1139
1140            return builder.hashCode();
1141        }
1142    }
1143
1144
1145}
1146