View Javadoc

1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.actionlist;
17  import org.apache.struts.action.*;
18  import org.apache.commons.collections.ComparatorUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.commons.lang.builder.EqualsBuilder;
21  import org.apache.commons.lang.builder.HashCodeBuilder;
22  import org.displaytag.pagination.PaginatedList;
23  import org.displaytag.properties.SortOrderEnum;
24  import org.displaytag.util.LookupUtil;
25  import org.kuali.rice.core.api.config.property.ConfigContext;
26  import org.kuali.rice.core.api.delegation.DelegationType;
27  import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
28  import org.kuali.rice.core.api.exception.RiceRuntimeException;
29  import org.kuali.rice.core.api.util.ConcreteKeyValue;
30  import org.kuali.rice.core.api.util.KeyValue;
31  import org.kuali.rice.kew.actionitem.ActionItemActionListExtension;
32  import org.kuali.rice.kew.actionitem.OutboxItemActionListExtension;
33  import org.kuali.rice.kew.actionlist.service.ActionListService;
34  import org.kuali.rice.kew.actionlist.web.ActionListUtil;
35  import org.kuali.rice.kew.actionrequest.Recipient;
36  import org.kuali.rice.kew.api.KewApiConstants;
37  import org.kuali.rice.kew.api.action.ActionInvocation;
38  import org.kuali.rice.kew.api.action.ActionItemCustomization;
39  import org.kuali.rice.kew.api.action.ActionSet;
40  import org.kuali.rice.kew.api.action.ActionType;
41  import org.kuali.rice.kew.api.exception.WorkflowException;
42  import org.kuali.rice.kew.api.extension.ExtensionDefinition;
43  import org.kuali.rice.kew.api.preferences.Preferences;
44  import org.kuali.rice.kew.framework.KewFrameworkServiceLocator;
45  import org.kuali.rice.kew.framework.actionlist.ActionListCustomizationMediator;
46  import org.kuali.rice.kew.service.KEWServiceLocator;
47  import org.kuali.rice.kew.util.PerformanceLogger;
48  import org.kuali.rice.kim.api.group.Group;
49  import org.kuali.rice.kim.api.identity.Person;
50  import org.kuali.rice.kim.api.identity.principal.Principal;
51  import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
52  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
53  import org.kuali.rice.kns.web.ui.ExtraButton;
54  import org.kuali.rice.krad.UserSession;
55  import org.kuali.rice.krad.exception.AuthorizationException;
56  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
57  import org.kuali.rice.krad.util.GlobalVariables;
58  import org.kuali.rice.krad.web.controller.UifControllerBase;
59  import org.springframework.stereotype.Controller;
60  import org.springframework.validation.BindingResult;
61  import org.springframework.web.bind.annotation.ModelAttribute;
62  import org.springframework.web.bind.annotation.RequestMapping;
63  import org.springframework.web.servlet.ModelAndView;
64  import org.kuali.rice.krad.web.form.UifFormBase;
65  
66  import javax.servlet.ServletException;
67  import javax.servlet.http.HttpServletRequest;
68  import javax.servlet.http.HttpServletResponse;
69  import java.text.ParseException;
70  import java.text.SimpleDateFormat;
71  import java.util.ArrayList;
72  import java.util.Arrays;
73  import java.util.Collection;
74  import java.util.Collections;
75  import java.util.Comparator;
76  import java.util.Date;
77  import java.util.HashSet;
78  import java.util.Iterator;
79  import java.util.LinkedHashMap;
80  import java.util.LinkedHashSet;
81  import java.util.List;
82  import java.util.Map;
83  import java.util.Set;
84  
85  /**
86   * A controller for the action list view.
87   *
88   * @author Kuali Rice Team (rice.collab@kuali.org)
89   */
90  @Controller
91  @RequestMapping(value = "/new/actionList")
92  public class ActionListController extends UifControllerBase{
93      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ActionListController.class);
94      protected static final String MAX_ACTION_ITEM_DATE_FORMAT = "yyyy-MM-dd hh:mm:ss.S";
95  
96      private static final ActionType [] actionListActionTypes =
97              { ActionType.APPROVE, ActionType.DISAPPROVE, ActionType.CANCEL, ActionType.ACKNOWLEDGE, ActionType.FYI };
98  
99      @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