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