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