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.web; 017 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.apache.struts.action.*; 023import org.displaytag.pagination.PaginatedList; 024import org.displaytag.properties.SortOrderEnum; 025import org.displaytag.util.LookupUtil; 026import org.kuali.rice.core.api.config.property.ConfigContext; 027import org.kuali.rice.core.api.delegation.DelegationType; 028import org.kuali.rice.core.api.exception.RiceIllegalArgumentException; 029import org.kuali.rice.core.api.exception.RiceRuntimeException; 030import org.kuali.rice.core.web.format.DateFormatter; 031import org.kuali.rice.kew.actionitem.ActionItem; 032import org.kuali.rice.kew.actionitem.ActionItemActionListExtension; 033import org.kuali.rice.kew.actionitem.OutboxItemActionListExtension; 034import org.kuali.rice.kew.actionlist.ActionListFilter; 035import org.kuali.rice.kew.actionlist.ActionToTake; 036import org.kuali.rice.kew.actionlist.PaginatedActionList; 037import org.kuali.rice.kew.actionlist.service.ActionListService; 038import org.kuali.rice.kew.actionrequest.Recipient; 039import org.kuali.rice.kew.api.KewApiConstants; 040import org.kuali.rice.kew.api.action.ActionInvocation; 041import org.kuali.rice.kew.api.action.ActionItemCustomization; 042import org.kuali.rice.kew.api.action.ActionSet; 043import org.kuali.rice.kew.api.action.ActionType; 044import org.kuali.rice.kew.api.document.DocumentStatus; 045import org.kuali.rice.kew.api.exception.WorkflowException; 046import org.kuali.rice.kew.api.extension.ExtensionDefinition; 047import org.kuali.rice.kew.api.preferences.Preferences; 048import org.kuali.rice.kew.framework.KewFrameworkServiceLocator; 049import org.kuali.rice.kew.framework.actionlist.ActionListCustomizationMediator; 050import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValueActionListExtension; 051import org.kuali.rice.kew.service.KEWServiceLocator; 052import org.kuali.rice.kew.util.PerformanceLogger; 053import org.kuali.rice.kim.api.identity.Person; 054import org.kuali.rice.kim.api.identity.principal.Principal; 055import org.kuali.rice.kim.api.identity.principal.PrincipalContract; 056import org.kuali.rice.kns.web.struts.action.KualiAction; 057import org.kuali.rice.kns.web.ui.ExtraButton; 058import org.kuali.rice.krad.UserSession; 059import org.kuali.rice.krad.exception.AuthorizationException; 060import org.kuali.rice.krad.util.GlobalVariables; 061 062import javax.servlet.http.HttpServletRequest; 063import javax.servlet.http.HttpServletResponse; 064import java.text.DateFormat; 065import java.text.ParseException; 066import java.text.SimpleDateFormat; 067import java.util.*; 068 069/** 070 * Action doing Action list stuff 071 * 072 * @author Kuali Rice Team (rice.collab@kuali.org)a 073 * 074 */ 075public class ActionListAction extends KualiAction { 076 077 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ActionListAction.class); 078 protected static final String MAX_ACTION_ITEM_DATE_FORMAT = "yyyy-MM-dd hh:mm:ss.S"; 079 080 private static final String ACTION_LIST_KEY = "actionList"; 081 private static final String ACTION_LIST_PAGE_KEY = "actionListPage"; 082 private static final String ACTION_LIST_USER_KEY = "actionList.user"; 083 /*private static final String REQUERY_ACTION_LIST_KEY = "requeryActionList";*/ 084 private static final String ACTION_ITEM_COUNT_FOR_USER_KEY = "actionList.count"; 085 private static final String MAX_ACTION_ITEM_DATE_ASSIGNED_FOR_USER_KEY = "actionList.maxActionItemDateAssigned"; 086 087 private static final String ACTIONREQUESTCD_PROP = "actionRequestCd"; 088 private static final String CUSTOMACTIONLIST_PROP = "customActionList"; 089 private static final String ACTIONITEM_PROP = "actionitem"; 090 private static final String HELPDESK_ACTIONLIST_USERNAME = "helpDeskActionListUserName"; 091 092 private static final String ACTIONITEM_ACTIONREQUESTCD_INVALID_ERRKEY = "actionitem.actionrequestcd.invalid"; 093 private static final String ACTIONLIST_BAD_CUSTOM_ACTION_LIST_ITEMS_ERRKEY = "actionlist.badCustomActionListItems"; 094 private static final String ACTIONLIST_BAD_ACTION_ITEMS_ERRKEY = "actionlist.badActionItems"; 095 private static final String HELPDESK_LOGIN_EMPTY_ERRKEY = "helpdesk.login.empty"; 096 private static final String HELPDESK_LOGIN_INVALID_ERRKEY = "helpdesk.login.invalid"; 097 098 private static final ActionType [] actionListActionTypes = 099 { ActionType.APPROVE, ActionType.DISAPPROVE, ActionType.CANCEL, ActionType.ACKNOWLEDGE, ActionType.FYI }; 100 101 @Override 102 public ActionForward execute(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception { 103 ActionListForm frm = (ActionListForm)actionForm; 104 request.setAttribute("Constants", getServlet().getServletContext().getAttribute("KewApiConstants")); 105 request.setAttribute("preferences", getUserSession().retrieveObject(KewApiConstants.PREFERENCES)); 106 frm.setHeaderButtons(getHeaderButtons()); 107 return super.execute(mapping, actionForm, request, response); 108 } 109 110 private List<ExtraButton> getHeaderButtons(){ 111 List<ExtraButton> headerButtons = new ArrayList<ExtraButton>(); 112 ExtraButton eb = new ExtraButton(); 113 String krBaseUrl = ConfigContext.getCurrentContextConfig().getKRBaseURL(); 114 eb.setExtraButtonSource( krBaseUrl + "/images/tinybutton-preferences.gif"); 115 eb.setExtraButtonOnclick("Preferences.do?returnMapping=viewActionList"); 116 117 headerButtons.add(eb); 118 eb = new ExtraButton(); 119 eb.setExtraButtonSource(krBaseUrl + "/images/tinybutton-refresh.gif"); 120 eb.setExtraButtonProperty("methodToCall.refresh"); 121 122 headerButtons.add(eb); 123 eb = new ExtraButton(); 124 eb.setExtraButtonSource(krBaseUrl + "/images/tinybutton-filter.gif"); 125 eb.setExtraButtonOnclick("javascript: window.open('ActionListFilter.do?methodToCall=start');"); 126 headerButtons.add(eb); 127 128 129 return headerButtons; 130 } 131 132 @Override 133 public ActionForward refresh(ActionMapping mapping, 134 ActionForm form, 135 HttpServletRequest request, 136 HttpServletResponse response) throws Exception { 137 request.getSession().setAttribute(KewApiConstants.REQUERY_ACTION_LIST_KEY, "true"); 138 return start(mapping, form, request, response); 139 } 140 141 @Override 142 protected ActionForward defaultDispatch(ActionMapping mapping, 143 ActionForm form, HttpServletRequest request, 144 HttpServletResponse response) throws Exception { 145 return start(mapping, form, request, response); 146 } 147 148 public ActionForward start(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception { 149 PerformanceLogger plog = new PerformanceLogger(); 150 plog.log("Starting ActionList fetch"); 151 ActionListForm form = (ActionListForm) actionForm; 152 ActionListService actionListSrv = KEWServiceLocator.getActionListService(); 153 154 155 // process display tag parameters 156 Integer page = form.getPage(); 157 String sortCriterion = form.getSort(); 158 SortOrderEnum sortOrder = SortOrderEnum.ASCENDING; 159 final UserSession uSession = getUserSession(); 160 161 if (form.getDir() != null) { 162 sortOrder = parseSortOrder(form.getDir()); 163 } 164 else if ( !StringUtils.isEmpty((String) uSession.retrieveObject(KewApiConstants.SORT_ORDER_ATTR_NAME))) { 165 sortOrder = parseSortOrder((String) uSession.retrieveObject(KewApiConstants.SORT_ORDER_ATTR_NAME)); 166 } 167 // if both the page and the sort criteria are null, that means its the first entry into the page, use defaults 168 if (page == null && sortCriterion == null) { 169 page = 1; 170 sortCriterion = ActionItemComparator.ACTION_LIST_DEFAULT_SORT; 171 } 172 else if ( !StringUtils.isEmpty((String) uSession.retrieveObject(KewApiConstants.SORT_CRITERIA_ATTR_NAME))) { 173 sortCriterion = (String) uSession.retrieveObject(KewApiConstants.SORT_CRITERIA_ATTR_NAME); 174 } 175 // if the page is still null, that means the user just performed a sort action, pull the currentPage off of the form 176 if (page == null) { 177 page = form.getCurrentPage(); 178 } 179 180 // update the values of the "current" display tag parameters 181 form.setCurrentPage(page); 182 if (!StringUtils.isEmpty(sortCriterion)) { 183 form.setCurrentSort(sortCriterion); 184 form.setCurrentDir(getSortOrderValue(sortOrder)); 185 } 186 187 // reset the default action on the form 188 form.setDefaultActionToTake("NONE"); 189 190 boolean freshActionList = true; 191 // retrieve cached action list 192 List<? extends ActionItemActionListExtension> actionList = (List<? extends ActionItemActionListExtension>)request.getSession().getAttribute(ACTION_LIST_KEY); 193 plog.log("Time to initialize"); 194 try { 195 //UserSession uSession = getUserSession(request); 196 String principalId = null; 197 if (uSession.retrieveObject(KewApiConstants.ACTION_LIST_FILTER_ATTR_NAME) == null) { 198 ActionListFilter filter = new ActionListFilter(); 199 filter.setDelegationType(DelegationType.SECONDARY.getCode()); 200 filter.setExcludeDelegationType(true); 201 uSession.addObject(KewApiConstants.ACTION_LIST_FILTER_ATTR_NAME, filter); 202 } 203 204 final ActionListFilter filter = (ActionListFilter) uSession.retrieveObject(KewApiConstants.ACTION_LIST_FILTER_ATTR_NAME); 205 /* 'forceListRefresh' variable used to signify that the action list filter has changed 206 * any time the filter changes the action list must be refreshed or filter may not take effect on existing 207 * list items... only exception is if action list has not loaded previous and fetching of the list has not 208 * occurred yet 209 */ 210 boolean forceListRefresh = request.getSession().getAttribute(KewApiConstants.REQUERY_ACTION_LIST_KEY) != null; 211 if (uSession.retrieveObject(KewApiConstants.HELP_DESK_ACTION_LIST_PRINCIPAL_ATTR_NAME) != null) { 212 principalId = ((PrincipalContract) uSession.retrieveObject(KewApiConstants.HELP_DESK_ACTION_LIST_PRINCIPAL_ATTR_NAME)).getPrincipalId(); 213 } else { 214 if (!StringUtils.isEmpty(form.getDocType())) { 215 filter.setDocumentType(form.getDocType()); 216 filter.setExcludeDocumentType(false); 217 forceListRefresh = true; 218 } 219 principalId = uSession.getPerson().getPrincipalId(); 220 } 221 222 final Preferences preferences = (Preferences) getUserSession().retrieveObject(KewApiConstants.PREFERENCES); 223 224 if (!StringUtils.isEmpty(form.getDelegationId())) { 225 if (!KewApiConstants.DELEGATION_DEFAULT.equals(form.getDelegationId())) { 226 // If the user can filter by both primary and secondary delegation, and both drop-downs have non-default values assigned, 227 // then reset the primary delegation drop-down's value when the primary delegation drop-down's value has remained unaltered 228 // but the secondary drop-down's value has been altered; but if one of these alteration situations does not apply, reset the 229 // secondary delegation drop-down. 230 if (StringUtils.isNotBlank(form.getPrimaryDelegateId()) && !KewApiConstants.PRIMARY_DELEGATION_DEFAULT.equals(form.getPrimaryDelegateId())){ 231 if (form.getPrimaryDelegateId().equals(request.getParameter("oldPrimaryDelegateId")) && 232 !form.getDelegationId().equals(request.getParameter("oldDelegationId"))) { 233 form.setPrimaryDelegateId(KewApiConstants.PRIMARY_DELEGATION_DEFAULT); 234 } else { 235 form.setDelegationId(KewApiConstants.DELEGATION_DEFAULT); 236 } 237 } else if (StringUtils.isNotBlank(filter.getPrimaryDelegateId()) && 238 !KewApiConstants.PRIMARY_DELEGATION_DEFAULT.equals(filter.getPrimaryDelegateId())) { 239 // If the primary delegation drop-down is invisible but a primary delegation filter is in place, and if the secondary delegation 240 // drop-down has a non-default value selected, then reset the primary delegation filtering. 241 filter.setPrimaryDelegateId(KewApiConstants.PRIMARY_DELEGATION_DEFAULT); 242 } 243 } 244 // Enable the secondary delegation filtering. 245 filter.setDelegatorId(form.getDelegationId()); 246 filter.setExcludeDelegatorId(false); 247 actionList = null; 248 } 249 250 if (!StringUtils.isEmpty(form.getPrimaryDelegateId())) { 251 // If the secondary delegation drop-down is invisible but a secondary delegation filter is in place, and if the primary delegation 252 // drop-down has a non-default value selected, then reset the secondary delegation filtering. 253 if (StringUtils.isBlank(form.getDelegationId()) && !KewApiConstants.PRIMARY_DELEGATION_DEFAULT.equals(form.getPrimaryDelegateId()) && 254 StringUtils.isNotBlank(filter.getDelegatorId()) && 255 !KewApiConstants.DELEGATION_DEFAULT.equals(filter.getDelegatorId())) { 256 filter.setDelegatorId(KewApiConstants.DELEGATION_DEFAULT); 257 } 258 // Enable the primary delegation filtering. 259 filter.setPrimaryDelegateId(form.getPrimaryDelegateId()); 260 filter.setExcludeDelegatorId(false); 261 actionList = null; 262 } 263 264 // if the user has changed, we need to refresh the action list 265 if (!principalId.equals(request.getSession().getAttribute(ACTION_LIST_USER_KEY))) { 266 actionList = null; 267 } 268 269 if (isOutboxMode(form, request, preferences)) { 270 actionList = new ArrayList<OutboxItemActionListExtension>(actionListSrv.getOutbox(principalId, filter)); 271 form.setOutBoxEmpty(actionList.isEmpty()); 272 } else { 273 274 SimpleDateFormat dFormatter = new SimpleDateFormat(MAX_ACTION_ITEM_DATE_FORMAT); 275 if (actionList == null) { 276 List<Object> countAndMaxDate = actionListSrv.getMaxActionItemDateAssignedAndCountForUser(principalId); 277 if (countAndMaxDate.isEmpty() || countAndMaxDate.get(0) == null ) { 278 if (countAndMaxDate.isEmpty()) { 279 countAndMaxDate.add(0, new Date(0)); 280 countAndMaxDate.add(1, 0); 281 } else { 282 countAndMaxDate.set(0, new Date(0)); 283 } 284 } 285 request.getSession().setAttribute(MAX_ACTION_ITEM_DATE_ASSIGNED_FOR_USER_KEY, dFormatter.format(countAndMaxDate.get(0))); 286 request.getSession().setAttribute(ACTION_ITEM_COUNT_FOR_USER_KEY, (Integer)countAndMaxDate.get(1)); 287 // fetch the action list 288 actionList = new ArrayList<ActionItemActionListExtension>(actionListSrv.getActionList(principalId, filter)); 289 290 request.getSession().setAttribute(ACTION_LIST_USER_KEY, principalId); 291 } else if (forceListRefresh) { 292 // force a refresh... usually based on filter change or parameter specifying refresh needed 293 actionList = new ArrayList<ActionItemActionListExtension>(actionListSrv.getActionList(principalId, filter)); 294 request.getSession().setAttribute(ACTION_LIST_USER_KEY, principalId); 295 List<Object> countAndMaxDate = actionListSrv.getMaxActionItemDateAssignedAndCountForUser(principalId); 296 if (countAndMaxDate.isEmpty() || countAndMaxDate.get(0) == null ) { 297 if (countAndMaxDate.isEmpty()) { 298 countAndMaxDate.add(0, new Date(0)); 299 countAndMaxDate.add(1, 0); 300 } else { 301 countAndMaxDate.set(0, new Date(0)); 302 } 303 } 304 request.getSession().setAttribute(MAX_ACTION_ITEM_DATE_ASSIGNED_FOR_USER_KEY, dFormatter.format(countAndMaxDate.get(0))); 305 request.getSession().setAttribute(ACTION_ITEM_COUNT_FOR_USER_KEY, (Integer)countAndMaxDate.get(1)); 306 307 }else if (refreshList(request,principalId)){ 308 actionList = new ArrayList<ActionItemActionListExtension>(actionListSrv.getActionList(principalId, filter)); 309 request.getSession().setAttribute(ACTION_LIST_USER_KEY, principalId); 310 311 } else { 312 Boolean update = (Boolean) uSession.retrieveObject(KewApiConstants.UPDATE_ACTION_LIST_ATTR_NAME); 313 if (update == null || !update) { 314 freshActionList = false; 315 } 316 } 317 request.getSession().setAttribute(ACTION_LIST_KEY, actionList); 318 319 } 320 // reset the requery action list key 321 request.getSession().setAttribute(KewApiConstants.REQUERY_ACTION_LIST_KEY, null); 322 323 // build the drop-down of delegators 324 if (KewApiConstants.DELEGATORS_ON_ACTION_LIST_PAGE.equalsIgnoreCase(preferences.getDelegatorFilter())) { 325 Collection<Recipient> delegators = actionListSrv.findUserSecondaryDelegators(principalId); 326 form.setDelegators(ActionListUtil.getWebFriendlyRecipients(delegators)); 327 form.setDelegationId(filter.getDelegatorId()); 328 } 329 330 // Build the drop-down of primary delegates. 331 if (KewApiConstants.PRIMARY_DELEGATES_ON_ACTION_LIST_PAGE.equalsIgnoreCase(preferences.getPrimaryDelegateFilter())) { 332 Collection<Recipient> pDelegates = actionListSrv.findUserPrimaryDelegations(principalId); 333 form.setPrimaryDelegates(ActionListUtil.getWebFriendlyRecipients(pDelegates)); 334 form.setPrimaryDelegateId(filter.getPrimaryDelegateId()); 335 } 336 337 form.setFilterLegend(filter.getFilterLegend()); 338 plog.log("Setting attributes"); 339 340 int pageSize = getPageSize(preferences); 341 // initialize the action list if necessary 342 if (freshActionList) { 343 plog.log("calling initializeActionList"); 344 initializeActionList(actionList, preferences); 345 plog.log("done w/ initializeActionList"); 346 // put this in to resolve EN-112 (http://beatles.uits.indiana.edu:8081/jira/browse/EN-112) 347 // if the action list gets "refreshed" in between page switches, we need to be sure and re-sort it, even though we don't have sort criteria on the request 348 if (sortCriterion == null) { 349 sortCriterion = form.getCurrentSort(); 350 sortOrder = parseSortOrder(form.getCurrentDir()); 351 } 352 } 353 // sort the action list if necessary 354 if (sortCriterion != null) { 355 sortActionList(actionList, sortCriterion, sortOrder); 356 } 357 358 plog.log("calling buildCurrentPage"); 359 PaginatedList currentPage = buildCurrentPage(actionList, form.getCurrentPage(), form.getCurrentSort(), 360 form.getCurrentDir(), pageSize, preferences, form); 361 plog.log("done w/ buildCurrentPage"); 362 request.setAttribute(ACTION_LIST_PAGE_KEY, currentPage); 363 uSession.addObject(KewApiConstants.UPDATE_ACTION_LIST_ATTR_NAME, Boolean.FALSE); 364 uSession.addObject(KewApiConstants.CURRENT_PAGE_ATTR_NAME, form.getCurrentPage()); 365 uSession.addObject(KewApiConstants.SORT_CRITERIA_ATTR_NAME, form.getSort()); 366 uSession.addObject(KewApiConstants.SORT_ORDER_ATTR_NAME, form.getCurrentDir()); 367 plog.log("finished setting attributes, finishing action list fetch"); 368 } catch (Exception e) { 369 LOG.error("Error loading action list.", e); 370 } 371 372 LOG.debug("end start ActionListAction"); 373 return mapping.findForward("viewActionList"); 374 } 375 376 private boolean refreshList(HttpServletRequest request,String principalId ){ 377 List<Object> maxActionItemDateAssignedAndCount = KEWServiceLocator.getActionListService().getMaxActionItemDateAssignedAndCountForUser( 378 principalId); 379 int count = (Integer) maxActionItemDateAssignedAndCount.get(1); 380 int previousCount = 0; 381 Object actionItemCountFromSession = request.getSession().getAttribute(ACTION_ITEM_COUNT_FOR_USER_KEY); 382 if ( actionItemCountFromSession != null ) { 383 previousCount = Integer.parseInt(actionItemCountFromSession.toString()); 384 } 385 SimpleDateFormat dFormatter = new SimpleDateFormat(MAX_ACTION_ITEM_DATE_FORMAT); 386 Date maxActionItemDateAssigned = (Date) maxActionItemDateAssignedAndCount.get(0); 387 if ( maxActionItemDateAssigned == null ) { 388 maxActionItemDateAssigned = new Date(0); 389 } 390 Date previousMaxActionItemDateAssigned= null; 391 try{ 392 Object dateAttributeFromSession = request.getSession().getAttribute(MAX_ACTION_ITEM_DATE_ASSIGNED_FOR_USER_KEY); 393 if ( dateAttributeFromSession != null ) { 394 previousMaxActionItemDateAssigned = dFormatter.parse(dateAttributeFromSession.toString()); 395 } 396 } catch (ParseException e){ 397 LOG.warn( MAX_ACTION_ITEM_DATE_ASSIGNED_FOR_USER_KEY + " in session did not have expected date format. " 398 + "Was: " + request.getSession().getAttribute(MAX_ACTION_ITEM_DATE_ASSIGNED_FOR_USER_KEY), e ); 399 } 400 if(previousCount!= count){ 401 request.getSession().setAttribute(MAX_ACTION_ITEM_DATE_ASSIGNED_FOR_USER_KEY, dFormatter.format( 402 maxActionItemDateAssigned)); 403 request.getSession().setAttribute(ACTION_ITEM_COUNT_FOR_USER_KEY, count); 404 return true; 405 }else if(previousMaxActionItemDateAssigned == null || previousMaxActionItemDateAssigned.compareTo(maxActionItemDateAssigned)!=0){ 406 request.getSession().setAttribute(MAX_ACTION_ITEM_DATE_ASSIGNED_FOR_USER_KEY, dFormatter.format( 407 maxActionItemDateAssigned)); 408 request.getSession().setAttribute(ACTION_ITEM_COUNT_FOR_USER_KEY, count); 409 return true; 410 } else{ 411 return false; 412 } 413 414 } 415 416 private SortOrderEnum parseSortOrder(String dir) throws WorkflowException { 417 if ("asc".equals(dir)) { 418 return SortOrderEnum.ASCENDING; 419 } else if ("desc".equals(dir)) { 420 return SortOrderEnum.DESCENDING; 421 } 422 throw new WorkflowException("Invalid sort direction: " + dir); 423 } 424 425 private String getSortOrderValue(SortOrderEnum sortOrder) { 426 if (SortOrderEnum.ASCENDING.equals(sortOrder)) { 427 return "asc"; 428 } else if (SortOrderEnum.DESCENDING.equals(sortOrder)) { 429 return "desc"; 430 } 431 return null; 432 } 433 434 private static final String OUT_BOX_MODE = "_OUT_BOX_MODE"; 435 436 /** 437 * this method is setting 2 props on the {@link ActionListForm} that controls outbox behavior. 438 * alForm.setViewOutbox("false"); -> this is set by user preferences and the actionlist.outbox.off config prop 439 * alForm.setShowOutbox(false); -> this is set by user action clicking the ActionList vs. Outbox links. 440 * 441 * @param alForm 442 * @param request 443 * @return boolean indication whether the outbox should be fetched 444 */ 445 private boolean isOutboxMode(ActionListForm alForm, HttpServletRequest request, Preferences preferences) { 446 447 boolean outBoxView = false; 448 449 if (! preferences.isUsingOutbox() || ! ConfigContext.getCurrentContextConfig().getOutBoxOn()) { 450 request.getSession().setAttribute(OUT_BOX_MODE, Boolean.FALSE); 451 alForm.setViewOutbox("false"); 452 alForm.setShowOutbox(false); 453 return false; 454 } 455 456 alForm.setShowOutbox(true); 457 if (StringUtils.isNotEmpty(alForm.getViewOutbox())) { 458 if (!Boolean.valueOf(alForm.getViewOutbox())) { 459 request.getSession().setAttribute(OUT_BOX_MODE, Boolean.FALSE); 460 outBoxView = false; 461 } else { 462 request.getSession().setAttribute(OUT_BOX_MODE, Boolean.TRUE); 463 outBoxView = true; 464 } 465 } else { 466 467 if (request.getSession().getAttribute(OUT_BOX_MODE) == null) { 468 outBoxView = false; 469 } else { 470 outBoxView = (Boolean) request.getSession().getAttribute(OUT_BOX_MODE); 471 } 472 } 473 if (outBoxView) { 474 alForm.setViewOutbox("true"); 475 } else { 476 alForm.setViewOutbox("false"); 477 } 478 return outBoxView; 479 } 480 481 private void sortActionList(List<? extends ActionItemActionListExtension> actionList, String sortName, SortOrderEnum sortOrder) { 482 if (StringUtils.isEmpty(sortName)) { 483 return; 484 } 485 Comparator<ActionItemActionListExtension> comparator = new ActionItemComparator(sortName); 486 if (SortOrderEnum.DESCENDING.equals(sortOrder)) { 487 comparator = ComparatorUtils.reversedComparator(comparator); 488 } 489 Collections.sort(actionList, comparator); 490 // re-index the action items 491 int index = 0; 492 for (ActionItemActionListExtension actionItem : actionList) { 493 actionItem.setActionListIndex(index++); 494 } 495 } 496 497 private void initializeActionList(List<? extends ActionItemActionListExtension> actionList, Preferences preferences) { 498 List<String> actionItemProblemIds = new ArrayList<String>(); 499 int index = 0; 500 generateActionItemErrors(actionList); 501 for (Iterator<? extends ActionItemActionListExtension> iterator = actionList.iterator(); iterator.hasNext();) { 502 ActionItemActionListExtension actionItem = iterator.next(); 503 if (actionItem.getDocumentId() == null) { 504 LOG.error("Somehow there exists an ActionItem with a null document id! actionItemId=" + actionItem.getId()); 505 iterator.remove(); 506 continue; 507 } 508 try { 509 actionItem.initialize(preferences); 510 actionItem.setActionListIndex(index); 511 index++; 512 } catch (Exception e) { 513 // 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 514 // and display an appropriate error message to the user 515 LOG.error("Error loading action list for action item " + actionItem.getId(), e); 516 iterator.remove(); 517 actionItemProblemIds.add(actionItem.getDocumentId()); 518 } 519 } 520 generateActionItemErrors(ACTIONITEM_PROP, ACTIONLIST_BAD_ACTION_ITEMS_ERRKEY, actionItemProblemIds); 521 } 522 523 /** 524 * Gets the page size of the Action List. Uses the user's preferences for page size unless the action list 525 * has been throttled by an application constant, in which case it uses the smaller of the two values. 526 */ 527 protected int getPageSize(Preferences preferences) { 528 return Integer.parseInt(preferences.getPageSize()); 529 } 530 531 protected PaginatedList buildCurrentPage(List<? extends ActionItemActionListExtension> actionList, Integer page, String sortCriterion, String sortDirection, 532 int pageSize, Preferences preferences, ActionListForm form) throws WorkflowException { 533 List<ActionItemActionListExtension> currentPage = new ArrayList<ActionItemActionListExtension>(pageSize); 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 on this page 541 Set<ActionType> pageActions = new HashSet<ActionType>(); 542 543 List<String> customActionListProblemIds = new ArrayList<String>(); 544 SortOrderEnum sortOrder = parseSortOrder(sortDirection); 545 int startIndex = (page - 1) * pageSize; 546 int endIndex = startIndex + pageSize; 547 generateActionItemErrors(actionList); 548 549 LOG.info("Beginning processing of Action List Customizations (total: " + actionList.size() + " Action Items)"); 550 long start = System.currentTimeMillis(); 551 552 Map<String, ActionItemCustomization> customizationMap = 553 getActionListCustomizationMediator().getActionListCustomizations( 554 getUserSession().getPrincipalId(), convertToApiActionItems(actionList) 555 ); 556 557 long end = System.currentTimeMillis(); 558 LOG.info("Finished processing of Action List Customizations (total time: " + (end - start) + " ms)"); 559 560 for (int index = startIndex; index < endIndex && index < actionList.size(); index++) { 561 ActionItemActionListExtension actionItem = actionList.get(index); 562 // evaluate custom action list component for mass actions 563 try { 564 ActionItemCustomization customization = customizationMap.get(actionItem.getId()); 565 if (customization != null) { 566 ActionSet actionSet = customization.getActionSet(); 567 568 // If only it were this easy: actionItem.setCustomActions(customization.getActionSet()); 569 570 Map<String, String> customActions = new LinkedHashMap<String, String>(); 571 customActions.put("NONE", "NONE"); 572 573 for (ActionType actionType : actionListActionTypes) { 574 if (actionSet.hasAction(actionType.getCode()) && 575 isActionCompatibleRequest(actionItem, actionType.getCode())) { 576 577 final boolean isFyi = ActionType.FYI == actionType; // make the conditional easier to read 578 579 if (!isFyi || (isFyi && showClearFyi)) { // deal with special FYI preference 580 customActions.put(actionType.getCode(), actionType.getLabel()); 581 pageActions.add(actionType); 582 } 583 } 584 } 585 586 if (customActions.size() > 1) { 587 actionItem.setCustomActions(customActions); 588 haveCustomActions = true; 589 } 590 591 actionItem.setDisplayParameters(customization.getDisplayParameters()); 592 593 haveDisplayParameters = haveDisplayParameters || (actionItem.getDisplayParameters() != null); 594 } 595 596 } catch (Exception e) { 597 // if there's a problem loading the custom action list attribute, let's go ahead and display the vanilla action item 598 LOG.error("Problem loading custom action list attribute", e); 599 customActionListProblemIds.add(actionItem.getDocumentId()); 600 } 601 currentPage.add(actionItem); 602 } 603 604 // configure custom actions on form 605 form.setHasCustomActions(haveCustomActions); 606 607 Map<String, String> defaultActions = new LinkedHashMap<String, String>(); 608 defaultActions.put("NONE", "NONE"); 609 610 for (ActionType actionType : actionListActionTypes) { 611 if (pageActions.contains(actionType)) { 612 613 final boolean isFyi = ActionType.FYI == actionType; 614 // special logic for FYIs: 615 if (isFyi) { 616 // clearing FYIs can be done in any action list not just a customized one 617 if(showClearFyi) { 618 defaultActions.put(actionType.getCode(), actionType.getLabel()); 619 } 620 } else { // all the other actions 621 defaultActions.put(actionType.getCode(), actionType.getLabel()); 622 form.setCustomActionList(Boolean.TRUE); 623 } 624 } 625 } 626 627 if (defaultActions.size() > 1) { 628 form.setDefaultActions(defaultActions); 629 } 630 631 form.setHasDisplayParameters(haveDisplayParameters); 632 633 generateActionItemErrors(CUSTOMACTIONLIST_PROP, ACTIONLIST_BAD_CUSTOM_ACTION_LIST_ITEMS_ERRKEY, customActionListProblemIds); 634 return new PaginatedActionList(currentPage, actionList.size(), page, pageSize, "actionList", sortCriterion, sortOrder); 635 } 636 637 // convert a List of org.kuali.rice.kew.actionitem.ActionItemS to org.kuali.rice.kew.api.action.ActionItemS 638 private List<org.kuali.rice.kew.api.action.ActionItem> convertToApiActionItems(List<? extends ActionItemActionListExtension> actionList) { 639 List<org.kuali.rice.kew.api.action.ActionItem> apiActionItems = new ArrayList<org.kuali.rice.kew.api.action.ActionItem>(actionList.size()); 640 641 for (ActionItemActionListExtension actionItemObj : actionList) { 642 apiActionItems.add( 643 org.kuali.rice.kew.api.action.ActionItem.Builder.create(actionItemObj).build()); 644 } 645 return apiActionItems; 646 } 647 648 private void generateActionItemErrors(String propertyName, String errorKey, List<String> documentIds) { 649 if (!documentIds.isEmpty()) { 650 String documentIdsString = StringUtils.join(documentIds.iterator(), ", "); 651 GlobalVariables.getMessageMap().putError(propertyName, errorKey, documentIdsString); 652 } 653 } 654 655 private void generateActionItemErrors(List<? extends ActionItemActionListExtension> actionList) { 656 for (ActionItemActionListExtension actionItem : actionList) { 657 if(!KewApiConstants.ACTION_REQUEST_CODES.containsKey(actionItem.getActionRequestCd())) { 658 GlobalVariables.getMessageMap().putError(ACTIONREQUESTCD_PROP,ACTIONITEM_ACTIONREQUESTCD_INVALID_ERRKEY,actionItem.getId()+""); 659 } 660 } 661 } 662 663 664 public ActionForward takeMassActions(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 665 ActionListForm actionListForm = (ActionListForm) form; 666 List<? extends ActionItemActionListExtension> actionList = (List<? extends ActionItemActionListExtension>) request.getSession().getAttribute(ACTION_LIST_KEY); 667 if (actionList == null) { 668 return start(mapping, new ActionListForm(), request, response); 669 } 670 ActionMessages messages = new ActionMessages(); 671 List<ActionInvocation> invocations = new ArrayList<ActionInvocation>(); 672 int index = 0; 673 for (Object element : actionListForm.getActionsToTake()) { 674 ActionToTake actionToTake = (ActionToTake) element; 675 if (actionToTake != null && actionToTake.getActionTakenCd() != null && 676 !"".equals(actionToTake.getActionTakenCd()) && 677 !"NONE".equalsIgnoreCase(actionToTake.getActionTakenCd()) && 678 actionToTake.getActionItemId() != null) { 679 ActionItemActionListExtension actionItem = getActionItemFromActionList(actionList, actionToTake.getActionItemId()); 680 if (actionItem == null) { 681 LOG.warn("Could not locate the ActionItem to take mass action against in the action list: " + actionToTake.getActionItemId()); 682 continue; 683 } 684 invocations.add(ActionInvocation.create(ActionType.fromCode(actionToTake.getActionTakenCd()), actionItem.getId())); 685 } 686 index++; 687 } 688 KEWServiceLocator.getWorkflowDocumentService().takeMassActions(getUserSession().getPrincipalId(), invocations); 689 messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("general.routing.processed")); 690 saveMessages(request, messages); 691 ActionListForm cleanForm = new ActionListForm(); 692 request.setAttribute(mapping.getName(), cleanForm); 693 request.getSession().setAttribute(KewApiConstants.REQUERY_ACTION_LIST_KEY, "true"); 694 return start(mapping, cleanForm, request, response); 695 } 696 697 protected ActionItemActionListExtension getActionItemFromActionList(List<? extends ActionItemActionListExtension> actionList, String actionItemId) { 698 for (ActionItemActionListExtension actionItem : actionList) { 699 if (actionItem.getId().equals(actionItemId)) { 700 return actionItem; 701 } 702 } 703 return null; 704 } 705 706 public ActionForward helpDeskActionListLogin(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 707 ActionListForm actionListForm = (ActionListForm) form; 708 String name = actionListForm.getHelpDeskActionListUserName(); 709 if (!"true".equals(request.getAttribute("helpDeskActionList"))) { 710 throw new AuthorizationException(getUserSession().getPrincipalId(), "helpDeskActionListLogin", getClass().getSimpleName()); 711 } 712 try 713 { 714 final Principal helpDeskActionListPrincipal = KEWServiceLocator.getIdentityHelperService().getPrincipalByPrincipalName(name); 715 final Person helpDeskActionListPerson = KEWServiceLocator.getIdentityHelperService().getPersonByPrincipalName(name); 716 717 GlobalVariables.getUserSession().addObject(KewApiConstants.HELP_DESK_ACTION_LIST_PRINCIPAL_ATTR_NAME, helpDeskActionListPrincipal); 718 GlobalVariables.getUserSession().addObject(KewApiConstants.HELP_DESK_ACTION_LIST_PERSON_ATTR_NAME, helpDeskActionListPerson); 719 } 720 catch (RiceRuntimeException rre) 721 { 722 GlobalVariables.getMessageMap().putError(HELPDESK_ACTIONLIST_USERNAME, HELPDESK_LOGIN_INVALID_ERRKEY, name); 723 } 724 catch (RiceIllegalArgumentException e) { 725 GlobalVariables.getMessageMap().putError(HELPDESK_ACTIONLIST_USERNAME, HELPDESK_LOGIN_INVALID_ERRKEY, name); 726 } 727 catch (NullPointerException npe) 728 { 729 GlobalVariables.getMessageMap().putError("null", HELPDESK_LOGIN_EMPTY_ERRKEY, name); 730 } 731 actionListForm.setDelegator(null); 732 request.getSession().setAttribute(KewApiConstants.REQUERY_ACTION_LIST_KEY, "true"); 733 return start(mapping, form, request, response); 734 } 735 736 public ActionForward clearFilter(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 737 LOG.debug("clearFilter ActionListAction"); 738 final org.kuali.rice.krad.UserSession commonUserSession = getUserSession(); 739 commonUserSession.removeObject(KewApiConstants.ACTION_LIST_FILTER_ATTR_NAME); 740 request.getSession().setAttribute(KewApiConstants.REQUERY_ACTION_LIST_KEY, "true"); 741 LOG.debug("end clearFilter ActionListAction"); 742 return start(mapping, form, request, response); 743 } 744 745 public ActionForward clearHelpDeskActionListUser(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 746 LOG.debug("clearHelpDeskActionListUser ActionListAction"); 747 GlobalVariables.getUserSession().removeObject(KewApiConstants.HELP_DESK_ACTION_LIST_PRINCIPAL_ATTR_NAME); 748 GlobalVariables.getUserSession().removeObject(KewApiConstants.HELP_DESK_ACTION_LIST_PERSON_ATTR_NAME); 749 LOG.debug("end clearHelpDeskActionListUser ActionListAction"); 750 return start(mapping, form, request, response); 751 } 752 753 /** 754 * Generates an Action List count. 755 */ 756 public ActionForward count(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 757 ActionListForm alForm = (ActionListForm)form; 758 Person user = getUserSession().getPerson(); 759 alForm.setCount(KEWServiceLocator.getActionListService().getCount(user.getPrincipalId())); 760 LOG.info("Fetched Action List count of " + alForm.getCount() + " for user " + user.getPrincipalName()); 761 return mapping.findForward("count"); 762 } 763 764 public ActionForward removeOutboxItems(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 765 ActionListForm alForm = (ActionListForm)form; 766 if (alForm.getOutboxItems() != null) { 767 KEWServiceLocator.getActionListService().removeOutboxItems(getUserSession().getPrincipalId(), Arrays.asList(alForm.getOutboxItems())); 768 } 769 770 alForm.setViewOutbox("true"); 771 return start(mapping, form, request, response); 772 } 773 774 /** 775 * Navigate to the Action List Filter page, preserving any newly-modified primary/secondary delegation filters as necessary. 776 */ 777 public ActionForward viewFilter(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception { 778 start(mapping, actionForm, request, response); 779 return mapping.findForward("viewFilter"); 780 } 781 782 /** 783 * Navigate to the user's Preferences page, preserving any newly-modified primary/secondary delegation filters as necessary. 784 */ 785 public ActionForward viewPreferences(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception { 786 start(mapping, actionForm, request, response); 787 return mapping.findForward("viewPreferences"); 788 } 789 790 private boolean isActionCompatibleRequest(ActionItemActionListExtension actionItem, String actionTakenCode) { 791 boolean actionCompatible = false; 792 String requestCd = actionItem.getActionRequestCd(); 793 794 //FYI request matches FYI 795 if (KewApiConstants.ACTION_REQUEST_FYI_REQ.equals(requestCd) && KewApiConstants.ACTION_TAKEN_FYI_CD.equals(actionTakenCode)) { 796 actionCompatible = true || actionCompatible; 797 } 798 799 // ACK request matches ACK 800 if (KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ.equals(requestCd) && KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD.equals(actionTakenCode)) { 801 actionCompatible = true || actionCompatible; 802 } 803 804 // APPROVE request matches all but FYI and ACK 805 if (KewApiConstants.ACTION_REQUEST_APPROVE_REQ.equals(requestCd) && !(KewApiConstants.ACTION_TAKEN_FYI_CD.equals(actionTakenCode) || KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD.equals(actionTakenCode))) { 806 actionCompatible = true || actionCompatible; 807 } 808 809 // COMPLETE request matches all but FYI and ACK 810 if (KewApiConstants.ACTION_REQUEST_COMPLETE_REQ.equals(requestCd) && !(KewApiConstants.ACTION_TAKEN_FYI_CD.equals(actionTakenCode) || KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD.equals(actionTakenCode))) { 811 actionCompatible = true || actionCompatible; 812 } 813 814 return actionCompatible; 815 } 816 817 private UserSession getUserSession(){ 818 return GlobalVariables.getUserSession(); 819 } 820 821 private static class ActionItemComparator implements Comparator<ActionItemActionListExtension> { 822 823 private static final String ACTION_LIST_DEFAULT_SORT = "routeHeader.createDate"; 824 825 private final String sortName; 826 827 public ActionItemComparator(String sortName) { 828 if (StringUtils.isEmpty(sortName)) { 829 sortName = ACTION_LIST_DEFAULT_SORT; 830 } 831 this.sortName = sortName; 832 } 833 834 @Override 835 public int compare(ActionItemActionListExtension actionItem1, ActionItemActionListExtension actionItem2) { 836 try { 837 // invoke the power of the lookup functionality provided by the display tag library, this LookupUtil method allows for us 838 // to evaulate nested bean properties (like workgroup.groupNameId.nameId) in a null-safe manner. For example, in the 839 // example if workgroup evaluated to NULL then LookupUtil.getProperty would return null rather than blowing an exception 840 Object property1 = LookupUtil.getProperty(actionItem1, sortName); 841 Object property2 = LookupUtil.getProperty(actionItem2, sortName); 842 if (property1 == null && property2 == null) { 843 return 0; 844 } else if (property1 == null) { 845 return -1; 846 } else if (property2 == null) { 847 return 1; 848 } 849 if (property1 instanceof Comparable) { 850 return ((Comparable)property1).compareTo(property2); 851 } 852 return property1.toString().compareTo(property2.toString()); 853 } catch (Exception e) { 854 if (e instanceof RuntimeException) { 855 throw (RuntimeException) e; 856 } 857 throw new RuntimeException("Could not sort for the given sort name: " + sortName, e); 858 } 859 } 860 } 861 862 // Lazy initialization holder class (see Effective Java Item #71) 863 private static class ActionListCustomizationMediatorHolder { 864 static final ActionListCustomizationMediator actionListCustomizationMediator = 865 KewFrameworkServiceLocator.getActionListCustomizationMediator(); 866 } 867 868 private ActionListCustomizationMediator getActionListCustomizationMediator() { 869 return ActionListCustomizationMediatorHolder.actionListCustomizationMediator; 870 } 871 872 /** 873 * Simple class which defines the key of a partition of Action Items associated with an Application ID. 874 * 875 * <p>This class allows direct field access since it is intended for internal use only.</p> 876 */ 877 private static final class PartitionKey { 878 String applicationId; 879 Set<String> customActionListAttributeNames; 880 881 PartitionKey(String applicationId, Collection<ExtensionDefinition> extensionDefinitions) { 882 this.applicationId = applicationId; 883 this.customActionListAttributeNames = new HashSet<String>(); 884 for (ExtensionDefinition extensionDefinition : extensionDefinitions) { 885 this.customActionListAttributeNames.add(extensionDefinition.getName()); 886 } 887 } 888 889 List<String> getCustomActionListAttributeNameList() { 890 return new ArrayList<String>(customActionListAttributeNames); 891 } 892 893 @Override 894 public boolean equals(Object o) { 895 if (!(o instanceof PartitionKey)) { 896 return false; 897 } 898 PartitionKey key = (PartitionKey) o; 899 EqualsBuilder builder = new EqualsBuilder(); 900 builder.append(applicationId, key.applicationId); 901 builder.append(customActionListAttributeNames, key.customActionListAttributeNames); 902 return builder.isEquals(); 903 } 904 905 @Override 906 public int hashCode() { 907 HashCodeBuilder builder = new HashCodeBuilder(); 908 builder.append(applicationId); 909 builder.append(customActionListAttributeNames); 910 return builder.hashCode(); 911 } 912 } 913}