001/**
002 * Copyright 2005-2017 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kew.actionlist;
017import java.util.ArrayList;
018import java.util.Collection;
019import java.util.HashSet;
020import java.util.Iterator;
021import java.util.LinkedHashMap;
022import java.util.LinkedHashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026
027import javax.servlet.http.HttpServletRequest;
028import javax.servlet.http.HttpServletResponse;
029
030import org.apache.commons.lang.StringUtils;
031import org.apache.commons.lang.builder.EqualsBuilder;
032import org.apache.commons.lang.builder.HashCodeBuilder;
033import org.apache.struts.action.ActionMessage;
034import org.apache.struts.action.ActionMessages;
035import org.kuali.rice.core.api.config.property.ConfigContext;
036import org.kuali.rice.core.api.delegation.DelegationType;
037import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
038import org.kuali.rice.core.api.exception.RiceRuntimeException;
039import org.kuali.rice.kew.actionitem.ActionItem;
040import org.kuali.rice.kew.actionitem.ActionItemBase;
041import org.kuali.rice.kew.actionitem.OutboxItem;
042import org.kuali.rice.kew.actionlist.service.ActionListService;
043import org.kuali.rice.kew.actionlist.web.ActionListUtil;
044import org.kuali.rice.kew.actionrequest.Recipient;
045import org.kuali.rice.kew.api.KewApiConstants;
046import org.kuali.rice.kew.api.KewApiServiceLocator;
047import org.kuali.rice.kew.api.action.ActionInvocation;
048import org.kuali.rice.kew.api.action.ActionItemCustomization;
049import org.kuali.rice.kew.api.action.ActionSet;
050import org.kuali.rice.kew.api.action.ActionType;
051import org.kuali.rice.kew.api.exception.WorkflowException;
052import org.kuali.rice.kew.api.extension.ExtensionDefinition;
053import org.kuali.rice.kew.api.preferences.Preferences;
054import org.kuali.rice.kew.framework.KewFrameworkServiceLocator;
055import org.kuali.rice.kew.framework.actionlist.ActionListCustomizationMediator;
056import org.kuali.rice.kew.service.KEWServiceLocator;
057import org.kuali.rice.kew.util.PerformanceLogger;
058import org.kuali.rice.kim.api.identity.Person;
059import org.kuali.rice.kim.api.identity.principal.Principal;
060import org.kuali.rice.krad.UserSession;
061import org.kuali.rice.krad.exception.AuthorizationException;
062import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
063import org.kuali.rice.krad.util.GlobalVariables;
064import org.kuali.rice.krad.web.controller.MethodAccessible;
065import org.kuali.rice.krad.web.controller.UifControllerBase;
066import org.kuali.rice.krad.web.form.UifFormBase;
067import org.springframework.stereotype.Controller;
068import org.springframework.validation.BindingResult;
069import org.springframework.web.bind.annotation.ModelAttribute;
070import org.springframework.web.bind.annotation.RequestMapping;
071import org.springframework.web.servlet.ModelAndView;
072
073/**
074 * A controller for the action list view.
075 *
076 * @author Kuali Rice Team (rice.collab@kuali.org)
077 */
078@Controller
079@RequestMapping(value = "/kew/actionList")
080public class ActionListController extends UifControllerBase{
081    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ActionListController.class);
082    protected static final String MAX_ACTION_ITEM_DATE_FORMAT = "yyyy-MM-dd hh:mm:ss.S";
083
084    private static final ActionType [] actionListActionTypes =
085            { ActionType.APPROVE, ActionType.DISAPPROVE, ActionType.CANCEL, ActionType.ACKNOWLEDGE, ActionType.FYI };
086
087    @Override
088    protected ActionListForm createInitialForm() {
089        return new ActionListForm();
090    }
091
092    /**
093    * Refresh request mapping.
094    *
095    * <p>
096    * Handles requests where the methodToCall parameter
097    * is 'refresh'.
098    * </p>
099    *
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