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 synchronized(uSession) { 364 uSession.addObject(KewApiConstants.UPDATE_ACTION_LIST_ATTR_NAME, Boolean.FALSE); 365 uSession.addObject(KewApiConstants.CURRENT_PAGE_ATTR_NAME, form.getCurrentPage()); 366 uSession.addObject(KewApiConstants.SORT_CRITERIA_ATTR_NAME, form.getSort()); 367 uSession.addObject(KewApiConstants.SORT_ORDER_ATTR_NAME, form.getCurrentDir()); 368 } 369 plog.log("finished setting attributes, finishing action list fetch"); 370 } catch (Exception e) { 371 LOG.error("Error loading action list.", e); 372 } 373 374 LOG.debug("end start ActionListAction"); 375 return mapping.findForward("viewActionList"); 376 } 377 378 /** 379 * Sets the maxActionItemDate and actionItemcount for user in the session 380 * @param request 381 * @param principalId 382 * @param actionListSrv 383 */ 384 private void setCountAndMaxDate(HttpServletRequest request,String principalId,ActionListService actionListSrv ){ 385 SimpleDateFormat dFormatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.S"); 386 List<Object> countAndMaxDate = actionListSrv.getMaxActionItemDateAssignedAndCountForUser(principalId); 387 String maxActionItemDateAssignedForUserKey = ""; 388 if(countAndMaxDate.get(0)!= null){ 389 maxActionItemDateAssignedForUserKey = dFormatter.format(countAndMaxDate.get(0)); 390 } 391 request.getSession().setAttribute(MAX_ACTION_ITEM_DATE_ASSIGNED_FOR_USER_KEY, maxActionItemDateAssignedForUserKey); 392 request.getSession().setAttribute(ACTION_ITEM_COUNT_FOR_USER_KEY, (Integer)countAndMaxDate.get(1)); 393 } 394 395 private boolean refreshList(HttpServletRequest request,String principalId ){ 396 List<Object> maxActionItemDateAssignedAndCount = KEWServiceLocator.getActionListService().getMaxActionItemDateAssignedAndCountForUser( 397 principalId); 398 int count = (Integer) maxActionItemDateAssignedAndCount.get(1); 399 int previousCount = 0; 400 Object actionItemCountFromSession = request.getSession().getAttribute(ACTION_ITEM_COUNT_FOR_USER_KEY); 401 if ( actionItemCountFromSession != null ) { 402 previousCount = Integer.parseInt(actionItemCountFromSession.toString()); 403 } 404 SimpleDateFormat dFormatter = new SimpleDateFormat(MAX_ACTION_ITEM_DATE_FORMAT); 405 Date maxActionItemDateAssigned = (Date) maxActionItemDateAssignedAndCount.get(0); 406 if ( maxActionItemDateAssigned == null ) { 407 maxActionItemDateAssigned = new Date(0); 408 } 409 Date previousMaxActionItemDateAssigned= null; 410 try{ 411 Object dateAttributeFromSession = request.getSession().getAttribute(MAX_ACTION_ITEM_DATE_ASSIGNED_FOR_USER_KEY); 412 if ( dateAttributeFromSession != null ) { 413 previousMaxActionItemDateAssigned = dFormatter.parse(dateAttributeFromSession.toString()); 414 } 415 } catch (ParseException e){ 416 LOG.warn( MAX_ACTION_ITEM_DATE_ASSIGNED_FOR_USER_KEY + " in session did not have expected date format. " 417 + "Was: " + request.getSession().getAttribute(MAX_ACTION_ITEM_DATE_ASSIGNED_FOR_USER_KEY), e ); 418 } 419 if(previousCount!= count){ 420 request.getSession().setAttribute(MAX_ACTION_ITEM_DATE_ASSIGNED_FOR_USER_KEY, dFormatter.format( 421 maxActionItemDateAssigned)); 422 request.getSession().setAttribute(ACTION_ITEM_COUNT_FOR_USER_KEY, count); 423 return true; 424 }else if(previousMaxActionItemDateAssigned == null || previousMaxActionItemDateAssigned.compareTo(maxActionItemDateAssigned)!=0){ 425 request.getSession().setAttribute(MAX_ACTION_ITEM_DATE_ASSIGNED_FOR_USER_KEY, dFormatter.format( 426 maxActionItemDateAssigned)); 427 request.getSession().setAttribute(ACTION_ITEM_COUNT_FOR_USER_KEY, count); 428 return true; 429 } else{ 430 return false; 431 } 432 433 } 434 435 private SortOrderEnum parseSortOrder(String dir) throws WorkflowException { 436 if ("asc".equals(dir)) { 437 return SortOrderEnum.ASCENDING; 438 } else if ("desc".equals(dir)) { 439 return SortOrderEnum.DESCENDING; 440 } 441 throw new WorkflowException("Invalid sort direction: " + dir); 442 } 443 444 private String getSortOrderValue(SortOrderEnum sortOrder) { 445 if (SortOrderEnum.ASCENDING.equals(sortOrder)) { 446 return "asc"; 447 } else if (SortOrderEnum.DESCENDING.equals(sortOrder)) { 448 return "desc"; 449 } 450 return null; 451 } 452 453 private static final String OUT_BOX_MODE = "_OUT_BOX_MODE"; 454 455 /** 456 * this method is setting 2 props on the {@link ActionListForm} that controls outbox behavior. 457 * alForm.setViewOutbox("false"); -> this is set by user preferences and the actionlist.outbox.off config prop 458 * alForm.setShowOutbox(false); -> this is set by user action clicking the ActionList vs. Outbox links. 459 * 460 * @param alForm 461 * @param request 462 * @return boolean indication whether the outbox should be fetched 463 */ 464 private boolean isOutboxMode(ActionListForm alForm, HttpServletRequest request, Preferences preferences) { 465 466 boolean outBoxView = false; 467 468 if (! preferences.isUsingOutbox() || ! ConfigContext.getCurrentContextConfig().getOutBoxOn()) { 469 request.getSession().setAttribute(OUT_BOX_MODE, Boolean.FALSE); 470 alForm.setViewOutbox("false"); 471 alForm.setShowOutbox(false); 472 return false; 473 } 474 475 alForm.setShowOutbox(true); 476 if (StringUtils.isNotEmpty(alForm.getViewOutbox())) { 477 if (!Boolean.valueOf(alForm.getViewOutbox())) { 478 request.getSession().setAttribute(OUT_BOX_MODE, Boolean.FALSE); 479 outBoxView = false; 480 } else { 481 request.getSession().setAttribute(OUT_BOX_MODE, Boolean.TRUE); 482 outBoxView = true; 483 } 484 } else { 485 486 if (request.getSession().getAttribute(OUT_BOX_MODE) == null) { 487 outBoxView = false; 488 } else { 489 outBoxView = (Boolean) request.getSession().getAttribute(OUT_BOX_MODE); 490 } 491 } 492 if (outBoxView) { 493 alForm.setViewOutbox("true"); 494 } else { 495 alForm.setViewOutbox("false"); 496 } 497 return outBoxView; 498 } 499 500 private void sortActionList(List<? extends ActionItemActionListExtension> actionList, String sortName, SortOrderEnum sortOrder) { 501 if (StringUtils.isEmpty(sortName)) { 502 return; 503 } 504 Comparator<ActionItemActionListExtension> comparator = new ActionItemComparator(sortName); 505 if (SortOrderEnum.DESCENDING.equals(sortOrder)) { 506 comparator = ComparatorUtils.reversedComparator(comparator); 507 } 508 Collections.sort(actionList, comparator); 509 // re-index the action items 510 int index = 0; 511 for (ActionItemActionListExtension actionItem : actionList) { 512 actionItem.setActionListIndex(index++); 513 } 514 } 515 516 private void initializeActionList(List<? extends ActionItemActionListExtension> actionList, Preferences preferences) { 517 List<String> actionItemProblemIds = new ArrayList<String>(); 518 int index = 0; 519 generateActionItemErrors(actionList); 520 for (Iterator<? extends ActionItemActionListExtension> iterator = actionList.iterator(); iterator.hasNext();) { 521 ActionItemActionListExtension actionItem = iterator.next(); 522 if (actionItem.getDocumentId() == null) { 523 LOG.error("Somehow there exists an ActionItem with a null document id! actionItemId=" + actionItem.getId()); 524 iterator.remove(); 525 continue; 526 } 527 try { 528 actionItem.initialize(preferences); 529 actionItem.setActionListIndex(index); 530 index++; 531 } catch (Exception e) { 532 // 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 533 // and display an appropriate error message to the user 534 LOG.error("Error loading action list for action item " + actionItem.getId(), e); 535 iterator.remove(); 536 actionItemProblemIds.add(actionItem.getDocumentId()); 537 } 538 } 539 generateActionItemErrors(ACTIONITEM_PROP, ACTIONLIST_BAD_ACTION_ITEMS_ERRKEY, actionItemProblemIds); 540 } 541 542 /** 543 * Gets the page size of the Action List. Uses the user's preferences for page size unless the action list 544 * has been throttled by an application constant, in which case it uses the smaller of the two values. 545 */ 546 protected int getPageSize(Preferences preferences) { 547 return Integer.parseInt(preferences.getPageSize()); 548 } 549 550 protected PaginatedList buildCurrentPage(List<? extends ActionItemActionListExtension> actionList, Integer page, String sortCriterion, String sortDirection, 551 int pageSize, Preferences preferences, ActionListForm form) throws WorkflowException { 552 List<ActionItemActionListExtension> currentPage = new ArrayList<ActionItemActionListExtension>(pageSize); 553 554 boolean haveCustomActions = false; 555 boolean haveDisplayParameters = false; 556 557 final boolean showClearFyi = KewApiConstants.PREFERENCES_YES_VAL.equalsIgnoreCase(preferences.getShowClearFyi()); 558 559 // collects all the actions for items on this page 560 Set<ActionType> pageActions = new HashSet<ActionType>(); 561 562 List<String> customActionListProblemIds = new ArrayList<String>(); 563 SortOrderEnum sortOrder = parseSortOrder(sortDirection); 564 int startIndex = (page - 1) * pageSize; 565 int endIndex = startIndex + pageSize; 566 generateActionItemErrors(actionList); 567 568 LOG.info("Beginning processing of Action List Customizations (total: " + actionList.size() + " Action Items)"); 569 long start = System.currentTimeMillis(); 570 571 Map<String, ActionItemCustomization> customizationMap = 572 getActionListCustomizationMediator().getActionListCustomizations( 573 getUserSession().getPrincipalId(), convertToApiActionItems(actionList) 574 ); 575 576 long end = System.currentTimeMillis(); 577 LOG.info("Finished processing of Action List Customizations (total time: " + (end - start) + " ms)"); 578 579 for (int index = startIndex; index < endIndex && index < actionList.size(); index++) { 580 ActionItemActionListExtension actionItem = actionList.get(index); 581 // evaluate custom action list component for mass actions 582 try { 583 ActionItemCustomization customization = customizationMap.get(actionItem.getId()); 584 if (customization != null) { 585 ActionSet actionSet = customization.getActionSet(); 586 587 // If only it were this easy: actionItem.setCustomActions(customization.getActionSet()); 588 589 Map<String, String> customActions = new LinkedHashMap<String, String>(); 590 customActions.put("NONE", "NONE"); 591 592 for (ActionType actionType : actionListActionTypes) { 593 if (actionSet.hasAction(actionType.getCode()) && 594 isActionCompatibleRequest(actionItem, actionType.getCode())) { 595 596 final boolean isFyi = ActionType.FYI == actionType; // make the conditional easier to read 597 598 if (!isFyi || (isFyi && showClearFyi)) { // deal with special FYI preference 599 customActions.put(actionType.getCode(), actionType.getLabel()); 600 pageActions.add(actionType); 601 } 602 } 603 } 604 605 if (customActions.size() > 1) { 606 actionItem.setCustomActions(customActions); 607 haveCustomActions = true; 608 } 609 610 actionItem.setDisplayParameters(customization.getDisplayParameters()); 611 612 haveDisplayParameters = haveDisplayParameters || (actionItem.getDisplayParameters() != null); 613 } 614 615 } catch (Exception e) { 616 // if there's a problem loading the custom action list attribute, let's go ahead and display the vanilla action item 617 LOG.error("Problem loading custom action list attribute", e); 618 customActionListProblemIds.add(actionItem.getDocumentId()); 619 } 620 currentPage.add(actionItem); 621 } 622 623 // configure custom actions on form 624 form.setHasCustomActions(haveCustomActions); 625 626 Map<String, String> defaultActions = new LinkedHashMap<String, String>(); 627 defaultActions.put("NONE", "NONE"); 628 629 for (ActionType actionType : actionListActionTypes) { 630 if (pageActions.contains(actionType)) { 631 632 final boolean isFyi = ActionType.FYI == actionType; 633 // special logic for FYIs: 634 if (isFyi) { 635 // clearing FYIs can be done in any action list not just a customized one 636 if(showClearFyi) { 637 defaultActions.put(actionType.getCode(), actionType.getLabel()); 638 } 639 } else { // all the other actions 640 defaultActions.put(actionType.getCode(), actionType.getLabel()); 641 form.setCustomActionList(Boolean.TRUE); 642 } 643 } 644 } 645 646 if (defaultActions.size() > 1) { 647 form.setDefaultActions(defaultActions); 648 } 649 650 form.setHasDisplayParameters(haveDisplayParameters); 651 652 generateActionItemErrors(CUSTOMACTIONLIST_PROP, ACTIONLIST_BAD_CUSTOM_ACTION_LIST_ITEMS_ERRKEY, customActionListProblemIds); 653 return new PaginatedActionList(currentPage, actionList.size(), page, pageSize, "actionList", sortCriterion, sortOrder); 654 } 655 656 // convert a List of org.kuali.rice.kew.actionitem.ActionItemS to org.kuali.rice.kew.api.action.ActionItemS 657 private List<org.kuali.rice.kew.api.action.ActionItem> convertToApiActionItems(List<? extends ActionItemActionListExtension> actionList) { 658 List<org.kuali.rice.kew.api.action.ActionItem> apiActionItems = new ArrayList<org.kuali.rice.kew.api.action.ActionItem>(actionList.size()); 659 660 for (ActionItemActionListExtension actionItemObj : actionList) { 661 apiActionItems.add( 662 org.kuali.rice.kew.api.action.ActionItem.Builder.create(actionItemObj).build()); 663 } 664 return apiActionItems; 665 } 666 667 private void generateActionItemErrors(String propertyName, String errorKey, List<String> documentIds) { 668 if (!documentIds.isEmpty()) { 669 String documentIdsString = StringUtils.join(documentIds.iterator(), ", "); 670 GlobalVariables.getMessageMap().putError(propertyName, errorKey, documentIdsString); 671 } 672 } 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_PROP,ACTIONITEM_ACTIONREQUESTCD_INVALID_ERRKEY,actionItem.getId()+""); 678 } 679 } 680 } 681 682 683 public ActionForward takeMassActions(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 684 ActionListForm actionListForm = (ActionListForm) form; 685 List<? extends ActionItemActionListExtension> actionList = (List<? extends ActionItemActionListExtension>) request.getSession().getAttribute(ACTION_LIST_KEY); 686 if (actionList == null) { 687 return start(mapping, new ActionListForm(), request, response); 688 } 689 ActionMessages messages = new ActionMessages(); 690 List<ActionInvocation> invocations = new ArrayList<ActionInvocation>(); 691 int index = 0; 692 for (Object element : actionListForm.getActionsToTake()) { 693 ActionToTake actionToTake = (ActionToTake) element; 694 if (actionToTake != null && actionToTake.getActionTakenCd() != null && 695 !"".equals(actionToTake.getActionTakenCd()) && 696 !"NONE".equalsIgnoreCase(actionToTake.getActionTakenCd()) && 697 actionToTake.getActionItemId() != null) { 698 ActionItemActionListExtension actionItem = getActionItemFromActionList(actionList, actionToTake.getActionItemId()); 699 if (actionItem == null) { 700 LOG.warn("Could not locate the ActionItem to take mass action against in the action list: " + actionToTake.getActionItemId()); 701 continue; 702 } 703 invocations.add(ActionInvocation.create(ActionType.fromCode(actionToTake.getActionTakenCd()), actionItem.getId())); 704 } 705 index++; 706 } 707 KEWServiceLocator.getWorkflowDocumentService().takeMassActions(getUserSession().getPrincipalId(), invocations); 708 messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("general.routing.processed")); 709 saveMessages(request, messages); 710 ActionListForm cleanForm = new ActionListForm(); 711 request.setAttribute(mapping.getName(), cleanForm); 712 request.getSession().setAttribute(KewApiConstants.REQUERY_ACTION_LIST_KEY, "true"); 713 return start(mapping, cleanForm, request, response); 714 } 715 716 protected ActionItemActionListExtension getActionItemFromActionList(List<? extends ActionItemActionListExtension> actionList, String actionItemId) { 717 for (ActionItemActionListExtension actionItem : actionList) { 718 if (actionItem.getId().equals(actionItemId)) { 719 return actionItem; 720 } 721 } 722 return null; 723 } 724 725 public ActionForward helpDeskActionListLogin(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 726 ActionListForm actionListForm = (ActionListForm) form; 727 String name = actionListForm.getHelpDeskActionListUserName(); 728 if (!"true".equals(request.getAttribute("helpDeskActionList"))) { 729 throw new AuthorizationException(getUserSession().getPrincipalId(), "helpDeskActionListLogin", getClass().getSimpleName()); 730 } 731 try 732 { 733 final Principal helpDeskActionListPrincipal = KEWServiceLocator.getIdentityHelperService().getPrincipalByPrincipalName(name); 734 final Person helpDeskActionListPerson = KEWServiceLocator.getIdentityHelperService().getPersonByPrincipalName(name); 735 736 GlobalVariables.getUserSession().addObject(KewApiConstants.HELP_DESK_ACTION_LIST_PRINCIPAL_ATTR_NAME, helpDeskActionListPrincipal); 737 GlobalVariables.getUserSession().addObject(KewApiConstants.HELP_DESK_ACTION_LIST_PERSON_ATTR_NAME, helpDeskActionListPerson); 738 } 739 catch (RiceRuntimeException rre) 740 { 741 GlobalVariables.getMessageMap().putError(HELPDESK_ACTIONLIST_USERNAME, HELPDESK_LOGIN_INVALID_ERRKEY, name); 742 } 743 catch (RiceIllegalArgumentException e) { 744 GlobalVariables.getMessageMap().putError(HELPDESK_ACTIONLIST_USERNAME, HELPDESK_LOGIN_INVALID_ERRKEY, name); 745 } 746 catch (NullPointerException npe) 747 { 748 GlobalVariables.getMessageMap().putError("null", HELPDESK_LOGIN_EMPTY_ERRKEY, name); 749 } 750 actionListForm.setDelegator(null); 751 request.getSession().setAttribute(KewApiConstants.REQUERY_ACTION_LIST_KEY, "true"); 752 return start(mapping, form, request, response); 753 } 754 755 public ActionForward clearFilter(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 756 LOG.debug("clearFilter ActionListAction"); 757 final org.kuali.rice.krad.UserSession commonUserSession = getUserSession(); 758 commonUserSession.removeObject(KewApiConstants.ACTION_LIST_FILTER_ATTR_NAME); 759 request.getSession().setAttribute(KewApiConstants.REQUERY_ACTION_LIST_KEY, "true"); 760 LOG.debug("end clearFilter ActionListAction"); 761 return start(mapping, form, request, response); 762 } 763 764 public ActionForward clearHelpDeskActionListUser(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 765 LOG.debug("clearHelpDeskActionListUser ActionListAction"); 766 GlobalVariables.getUserSession().removeObject(KewApiConstants.HELP_DESK_ACTION_LIST_PRINCIPAL_ATTR_NAME); 767 GlobalVariables.getUserSession().removeObject(KewApiConstants.HELP_DESK_ACTION_LIST_PERSON_ATTR_NAME); 768 LOG.debug("end clearHelpDeskActionListUser ActionListAction"); 769 return start(mapping, form, request, response); 770 } 771 772 /** 773 * Generates an Action List count. 774 */ 775 public ActionForward count(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 776 ActionListForm alForm = (ActionListForm)form; 777 Person user = getUserSession().getPerson(); 778 alForm.setCount(KEWServiceLocator.getActionListService().getCount(user.getPrincipalId())); 779 LOG.info("Fetched Action List count of " + alForm.getCount() + " for user " + user.getPrincipalName()); 780 return mapping.findForward("count"); 781 } 782 783 public ActionForward removeOutboxItems(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 784 ActionListForm alForm = (ActionListForm)form; 785 if (alForm.getOutboxItems() != null) { 786 KEWServiceLocator.getActionListService().removeOutboxItems(getUserSession().getPrincipalId(), Arrays.asList(alForm.getOutboxItems())); 787 } 788 789 alForm.setViewOutbox("true"); 790 return start(mapping, form, request, response); 791 } 792 793 /** 794 * Navigate to the Action List Filter page, preserving any newly-modified primary/secondary delegation filters as necessary. 795 */ 796 public ActionForward viewFilter(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception { 797 start(mapping, actionForm, request, response); 798 return mapping.findForward("viewFilter"); 799 } 800 801 /** 802 * Navigate to the user's Preferences page, preserving any newly-modified primary/secondary delegation filters as necessary. 803 */ 804 public ActionForward viewPreferences(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception { 805 start(mapping, actionForm, request, response); 806 return mapping.findForward("viewPreferences"); 807 } 808 809 private boolean isActionCompatibleRequest(ActionItemActionListExtension actionItem, String actionTakenCode) { 810 boolean actionCompatible = false; 811 String requestCd = actionItem.getActionRequestCd(); 812 813 //FYI request matches FYI 814 if (KewApiConstants.ACTION_REQUEST_FYI_REQ.equals(requestCd) && KewApiConstants.ACTION_TAKEN_FYI_CD.equals(actionTakenCode)) { 815 actionCompatible = true || actionCompatible; 816 } 817 818 // ACK request matches ACK 819 if (KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ.equals(requestCd) && KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD.equals(actionTakenCode)) { 820 actionCompatible = true || actionCompatible; 821 } 822 823 // APPROVE request matches all but FYI and ACK 824 if (KewApiConstants.ACTION_REQUEST_APPROVE_REQ.equals(requestCd) && !(KewApiConstants.ACTION_TAKEN_FYI_CD.equals(actionTakenCode) || KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD.equals(actionTakenCode))) { 825 actionCompatible = true || actionCompatible; 826 } 827 828 // COMPLETE request matches all but FYI and ACK 829 if (KewApiConstants.ACTION_REQUEST_COMPLETE_REQ.equals(requestCd) && !(KewApiConstants.ACTION_TAKEN_FYI_CD.equals(actionTakenCode) || KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD.equals(actionTakenCode))) { 830 actionCompatible = true || actionCompatible; 831 } 832 833 return actionCompatible; 834 } 835 836 private UserSession getUserSession(){ 837 return GlobalVariables.getUserSession(); 838 } 839 840 private static class ActionItemComparator implements Comparator<ActionItemActionListExtension> { 841 842 private static final String ACTION_LIST_DEFAULT_SORT = "routeHeader.createDate"; 843 844 private final String sortName; 845 846 public ActionItemComparator(String sortName) { 847 if (StringUtils.isEmpty(sortName)) { 848 sortName = ACTION_LIST_DEFAULT_SORT; 849 } 850 this.sortName = sortName; 851 } 852 853 @Override 854 public int compare(ActionItemActionListExtension actionItem1, ActionItemActionListExtension actionItem2) { 855 try { 856 // invoke the power of the lookup functionality provided by the display tag library, this LookupUtil method allows for us 857 // to evaulate nested bean properties (like workgroup.groupNameId.nameId) in a null-safe manner. For example, in the 858 // example if workgroup evaluated to NULL then LookupUtil.getProperty would return null rather than blowing an exception 859 Object property1 = LookupUtil.getProperty(actionItem1, sortName); 860 Object property2 = LookupUtil.getProperty(actionItem2, sortName); 861 if (property1 == null && property2 == null) { 862 return 0; 863 } else if (property1 == null) { 864 return -1; 865 } else if (property2 == null) { 866 return 1; 867 } 868 if (property1 instanceof Comparable) { 869 return ((Comparable)property1).compareTo(property2); 870 } 871 return property1.toString().compareTo(property2.toString()); 872 } catch (Exception e) { 873 if (e instanceof RuntimeException) { 874 throw (RuntimeException) e; 875 } 876 throw new RuntimeException("Could not sort for the given sort name: " + sortName, e); 877 } 878 } 879 } 880 881 // Lazy initialization holder class (see Effective Java Item #71) 882 private static class ActionListCustomizationMediatorHolder { 883 static final ActionListCustomizationMediator actionListCustomizationMediator = 884 KewFrameworkServiceLocator.getActionListCustomizationMediator(); 885 } 886 887 private ActionListCustomizationMediator getActionListCustomizationMediator() { 888 return ActionListCustomizationMediatorHolder.actionListCustomizationMediator; 889 } 890 891 /** 892 * Simple class which defines the key of a partition of Action Items associated with an Application ID. 893 * 894 * <p>This class allows direct field access since it is intended for internal use only.</p> 895 */ 896 private static final class PartitionKey { 897 String applicationId; 898 Set<String> customActionListAttributeNames; 899 900 PartitionKey(String applicationId, Collection<ExtensionDefinition> extensionDefinitions) { 901 this.applicationId = applicationId; 902 this.customActionListAttributeNames = new HashSet<String>(); 903 for (ExtensionDefinition extensionDefinition : extensionDefinitions) { 904 this.customActionListAttributeNames.add(extensionDefinition.getName()); 905 } 906 } 907 908 List<String> getCustomActionListAttributeNameList() { 909 return new ArrayList<String>(customActionListAttributeNames); 910 } 911 912 @Override 913 public boolean equals(Object o) { 914 if (!(o instanceof PartitionKey)) { 915 return false; 916 } 917 PartitionKey key = (PartitionKey) o; 918 EqualsBuilder builder = new EqualsBuilder(); 919 builder.append(applicationId, key.applicationId); 920 builder.append(customActionListAttributeNames, key.customActionListAttributeNames); 921 return builder.isEquals(); 922 } 923 924 @Override 925 public int hashCode() { 926 HashCodeBuilder builder = new HashCodeBuilder(); 927 builder.append(applicationId); 928 builder.append(customActionListAttributeNames); 929 return builder.hashCode(); 930 } 931 } 932}