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