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