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