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.service.impl; 017 018import org.apache.commons.collections.CollectionUtils; 019import org.apache.commons.lang.StringUtils; 020import org.kuali.rice.core.api.config.property.ConfigContext; 021import org.kuali.rice.core.api.criteria.Predicate; 022import org.kuali.rice.core.api.criteria.QueryByCriteria; 023import org.kuali.rice.core.api.criteria.QueryResults; 024import org.kuali.rice.core.api.datetime.DateTimeService; 025import org.kuali.rice.core.api.delegation.DelegationType; 026import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator; 027import org.kuali.rice.kew.actionitem.ActionItem; 028import org.kuali.rice.kew.actionitem.ActionItemBase; 029import org.kuali.rice.kew.actionitem.OutboxItem; 030import org.kuali.rice.kew.actionlist.ActionListFilter; 031import org.kuali.rice.kew.actionlist.dao.ActionListDAO; 032import org.kuali.rice.kew.actionlist.dao.impl.ActionListPriorityComparator; 033import org.kuali.rice.kew.actionlist.service.ActionListService; 034import org.kuali.rice.kew.actionrequest.ActionRequestValue; 035import org.kuali.rice.kew.actionrequest.KimGroupRecipient; 036import org.kuali.rice.kew.actionrequest.Recipient; 037import org.kuali.rice.kew.actionrequest.service.ActionRequestService; 038import org.kuali.rice.kew.actiontaken.ActionTakenValue; 039import org.kuali.rice.kew.api.KewApiConstants; 040import org.kuali.rice.kew.doctype.bo.DocumentType; 041import org.kuali.rice.kew.doctype.service.DocumentTypeService; 042import org.kuali.rice.kew.notification.service.NotificationService; 043import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue; 044import org.kuali.rice.kew.routeheader.service.RouteHeaderService; 045import org.kuali.rice.kew.useroptions.UserOptions; 046import org.kuali.rice.kew.useroptions.UserOptionsService; 047import org.kuali.rice.kew.util.WebFriendlyRecipient; 048import org.kuali.rice.kim.api.group.GroupService; 049import org.kuali.rice.kim.api.services.KimApiServiceLocator; 050import org.kuali.rice.krad.data.DataObjectService; 051import org.kuali.rice.krad.util.KRADConstants; 052 053import java.sql.Timestamp; 054import java.util.ArrayList; 055import java.util.Calendar; 056import java.util.Collection; 057import java.util.Collections; 058import java.util.Date; 059import java.util.HashMap; 060import java.util.List; 061import java.util.Map; 062 063import static org.kuali.rice.core.api.criteria.PredicateFactory.*; 064 065/** 066 * Default implementation of the {@link ActionListService}. 067 * 068 * @author Kuali Rice Team (rice.collab@kuali.org) 069 */ 070public class ActionListServiceImpl implements ActionListService { 071 072 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ActionListServiceImpl.class); 073 private static final Integer DEFAULT_OUTBOX_ITEM_LIMIT = Integer.valueOf(10000); 074 075 protected DataObjectService dataObjectService; 076 protected NotificationService notificationService; 077 protected DateTimeService dateTimeService; 078 protected ActionRequestService actionRequestService; 079 protected DocumentTypeService documentTypeService; 080 protected UserOptionsService userOptionsService; 081 protected RouteHeaderService routeHeaderService; 082 083 protected ActionListDAO actionListDAO; 084 085 @Override 086 public Collection<Recipient> findUserSecondaryDelegators(String principalId) { 087 088 QueryByCriteria query = QueryByCriteria.Builder.fromPredicates( 089 equal("principalId", principalId), 090 equal("delegationType", DelegationType.SECONDARY.getCode()), 091 or(isNotNull("delegatorPrincipalId"), isNotNull("delegatorGroupId"))); 092 093 QueryResults<ActionItem> results = dataObjectService.findMatching(ActionItem.class, query); 094 095 Map<Object, Recipient> delegators = new HashMap<Object, Recipient>(results.getResults().size()); 096 097 for ( ActionItem actionItem : results.getResults() ) { 098 String delegatorPrincipalId = actionItem.getDelegatorPrincipalId(); 099 String delegatorGroupId = actionItem.getDelegatorGroupId(); 100 101 if (delegatorPrincipalId != null && !delegators.containsKey(delegatorPrincipalId)) { 102 delegators.put(delegatorPrincipalId,new WebFriendlyRecipient(KimApiServiceLocator.getPersonService().getPerson(delegatorPrincipalId))); 103 } else if (delegatorGroupId != null && !delegators.containsKey(delegatorGroupId)) { 104 delegators.put(delegatorGroupId, new KimGroupRecipient(KimApiServiceLocator.getGroupService().getGroup(delegatorGroupId))); 105 } 106 } 107 108 return delegators.values(); 109 } 110 111 @Override 112 public Collection<Recipient> findUserPrimaryDelegations(String principalId) { 113 List<String> workgroupIds = KimApiServiceLocator.getGroupService().getGroupIdsByPrincipalId(principalId); 114 115 Predicate whoPredicate = null; 116 if (CollectionUtils.isNotEmpty(workgroupIds)) { 117 whoPredicate = or( equal("delegatorPrincipalId", principalId), in("delegatorGroupId", workgroupIds ) ); 118 } else { 119 whoPredicate = equal("delegatorPrincipalId", principalId); 120 } 121 QueryByCriteria query = QueryByCriteria.Builder.fromPredicates(whoPredicate, equal("delegationType", DelegationType.PRIMARY.getCode() ) ); 122 123 QueryResults<ActionItem> results = dataObjectService.findMatching(ActionItem.class, query); 124 125 Map<String, Recipient> delegators = new HashMap<String, Recipient>(results.getResults().size()); 126 127 for ( ActionItem actionItem : results.getResults() ) { 128 String recipientPrincipalId = actionItem.getPrincipalId(); 129 if (recipientPrincipalId != null && !delegators.containsKey(recipientPrincipalId)) { 130 delegators.put(recipientPrincipalId, new WebFriendlyRecipient( 131 KimApiServiceLocator.getPersonService().getPerson(recipientPrincipalId))); 132 } 133 } 134 135 return delegators.values(); 136 } 137 138 @Override 139 public Collection<ActionItem> getActionList(String principalId, ActionListFilter filter) { 140 List<String> filteredByItems = new ArrayList<String>(); 141 142 List<Predicate> crit = handleActionItemCriteria(principalId, filter, filteredByItems); 143 144 if ( LOG.isDebugEnabled() ) { 145 LOG.debug("running query to get action list for criteria " + crit); 146 } 147 QueryByCriteria query = QueryByCriteria.Builder.fromPredicates(crit); 148 QueryResults<ActionItem> results = dataObjectService.findMatching(ActionItem.class, query); 149 if ( LOG.isDebugEnabled() ) { 150 LOG.debug("found " + results.getResults().size() + " action items for user " + principalId); 151 } 152 153 if (filter != null) { 154 boolean filterOn = !filteredByItems.isEmpty(); 155 filter.setFilterOn(filterOn); 156 filter.setFilterLegend(StringUtils.join(filteredByItems, ", ")); 157 } 158 159 return createActionListForUser(results.getResults()); 160 } 161 162 protected List<Predicate> handleActionItemCriteria( String principalId, ActionListFilter filter, List<String> filteredByItems ) { 163 LOG.debug("setting up Action List criteria"); 164 ArrayList<Predicate> crit = new ArrayList<Predicate>(); 165 166 if ( filter != null ) { 167 handleActionRequestedCriteria(filter, crit, filteredByItems); 168 handleDocumentCreateDateCriteria(filter, crit, filteredByItems); 169 handleAssignedDateCriteria(filter, crit, filteredByItems); 170 handleRouteStatusCriteria(filter, crit, filteredByItems); 171 handleDocumentTitleCriteria(filter, crit, filteredByItems); 172 handleDocumentTypeCriteria(filter, crit, filteredByItems); 173 handleWorkgroupCriteria(filter, crit, filteredByItems); 174 handleRecipientCriteria(principalId, filter, crit, filteredByItems); 175 } else { 176 crit.add( equal("principalId", principalId) ); 177 } 178 LOG.debug( "Completed setting up Action List criteria"); 179 return crit; 180 } 181 182 protected void handleActionRequestedCriteria( ActionListFilter filter, Collection<Predicate> crit, List<String> filteredByItems ) { 183 if ( StringUtils.isNotBlank(filter.getActionRequestCd()) 184 && !filter.getActionRequestCd().equals(KewApiConstants.ALL_CODE)) { 185 if (filter.isExcludeActionRequestCd()) { 186 crit.add( notEqual("actionRequestCd", filter.getActionRequestCd())); 187 } else { 188 crit.add( equal("actionRequestCd", filter.getActionRequestCd())); 189 } 190 filteredByItems.add( "Action Requested" ); 191 } 192 } 193 194 protected void handleDateCriteria( String propertyPath, String filterLabel, Date fromDate, Date toDate, boolean excludeDates, Collection<Predicate> crit, List<String> filteredByItems ) { 195 if (fromDate != null || toDate != null) { 196 Timestamp fromDateTimestamp = beginningOfDay(fromDate); 197 Timestamp toDateTimestamp = endOfDay(toDate); 198 if (excludeDates) { 199 if (fromDate != null && toDate != null) { 200 crit.add( notBetween(propertyPath, fromDateTimestamp, toDateTimestamp ) ); 201 } else if (fromDate != null && toDate == null) { 202 crit.add( lessThanOrEqual(propertyPath, fromDateTimestamp ) ); 203 } else if (fromDate == null && toDate != null) { 204 crit.add( greaterThanOrEqual(propertyPath, toDateTimestamp ) ); 205 } 206 } else { 207 if (fromDate != null && toDate != null) { 208 crit.add( between(propertyPath, fromDateTimestamp, toDateTimestamp ) ); 209 } else if (fromDate != null && toDate == null) { 210 crit.add( greaterThanOrEqual(propertyPath, fromDateTimestamp ) ); 211 } else if (fromDate == null && toDate != null) { 212 crit.add( lessThanOrEqual(propertyPath, toDateTimestamp ) ); 213 } 214 } 215 filteredByItems.add("Date Created"); 216 } 217 } 218 219 protected void handleDocumentCreateDateCriteria( ActionListFilter filter, Collection<Predicate> crit, List<String> filteredByItems ) { 220 handleDateCriteria("routeHeader.createDate", "Date Created", filter.getCreateDateFrom(), filter.getCreateDateTo(), filter.isExcludeCreateDate(), crit, filteredByItems); 221 } 222 223 protected void handleAssignedDateCriteria( ActionListFilter filter, Collection<Predicate> crit, List<String> filteredByItems ) { 224 handleDateCriteria("dateAssigned", "Date Last Assigned", filter.getLastAssignedDateFrom(), filter.getLastAssignedDateTo(), filter.isExcludeLastAssignedDate(), crit, filteredByItems); 225 } 226 227 protected void handleRouteStatusCriteria( ActionListFilter filter, Collection<Predicate> crit, List<String> filteredByItems ) { 228 if ( StringUtils.isNotBlank(filter.getDocRouteStatus()) 229 && !filter.getDocRouteStatus().equals(KewApiConstants.ALL_CODE)) { 230 if (filter.isExcludeRouteStatus()) { 231 crit.add( notEqual("routeHeader.docRouteStatus", filter.getDocRouteStatus() ) ); 232 } else { 233 crit.add( equal("routeHeader.docRouteStatus", filter.getDocRouteStatus() ) ); 234 } 235 filteredByItems.add( "Document Route Status" ); 236 } 237 } 238 239 protected void handleDocumentTitleCriteria( ActionListFilter filter, Collection<Predicate> crit, List<String> filteredByItems ) { 240 if ( StringUtils.isNotBlank(filter.getDocumentTitle()) ) { 241 String docTitle = filter.getDocumentTitle().trim(); 242 if (docTitle.endsWith("*")) { 243 docTitle = docTitle.substring(0, docTitle.length() - 1); 244 } 245 if (filter.isExcludeDocumentTitle()) { 246 crit.add( notLike("docTitle", "%" + docTitle + "%" ) ); 247 } else { 248 crit.add( like("docTitle", "%" + docTitle + "%" ) ); 249 } 250 filteredByItems.add( "Document Title" ); 251 } 252 } 253 254 protected void handleDocumentTypeCriteria( ActionListFilter filter, Collection<Predicate> crit, List<String> filteredByItems ) { 255 if ( StringUtils.isNotBlank(filter.getDocumentType()) ) { 256 String documentTypeName = filter.getDocumentType(); 257 if (filter.isExcludeDocumentType()) { 258 crit.add( notLike( "docName", "%" + documentTypeName + "%" ) ); 259 } else { 260 DocumentType documentType = documentTypeService.findByName(documentTypeName); 261 // not an exact document type - just use it as is 262 if (documentType == null) { 263 crit.add( like( "docName", "%" + documentTypeName + "%" ) ); 264 } else { 265 266 Collection<DocumentType> docs = getAllChildDocumentTypes(documentType); 267 Collection<String> docNames = new ArrayList<String>(docs.size()+1); 268 docNames.add(documentType.getName()); 269 for ( DocumentType doc : docs ) { 270 docNames.add(doc.getName()); 271 } 272 crit.add( in("docName", docNames) ); 273 } 274 } 275 filteredByItems.add( "Document Type" ); 276 } 277 } 278 279 protected Collection<DocumentType> getAllChildDocumentTypes( DocumentType docType ) { 280 Collection<DocumentType> allChildren = new ArrayList<DocumentType>(); 281 282 List<DocumentType> immediateChildren = documentTypeService.getChildDocumentTypes(docType.getId()); 283 if ( immediateChildren != null ) { 284 allChildren.addAll(immediateChildren); 285 286 for ( DocumentType childDoc : immediateChildren ) { 287 allChildren.addAll( getAllChildDocumentTypes(childDoc)); 288 } 289 } 290 291 return allChildren; 292 } 293 294 protected void handleWorkgroupCriteria( ActionListFilter filter, Collection<Predicate> crit, List<String> filteredByItems ) { 295 filter.setGroupId(null); 296 if ( StringUtils.isNotBlank(filter.getGroupIdString()) 297 && !filter.getGroupIdString().trim().equals(KewApiConstants.NO_FILTERING)) { 298 299 filter.setGroupId(filter.getGroupIdString().trim()); 300 301 if (filter.isExcludeGroupId()) { 302 crit.add( or( 303 notEqual("groupId", filter.getGroupId()), 304 isNull("groupId") ) ); 305 } else { 306 crit.add( equal("groupId", filter.getGroupId()) ); 307 } 308 filteredByItems.add( "Action Request Workgroup" ); 309 } 310 } 311 312 protected void applyPrimaryDelegationCriteria( String actionListUserPrincipalId, ActionListFilter filter, Collection<Predicate> crit, List<String> filteredByItems ) { 313 // get the groups the user is a part of 314 List<String> delegatorGroupIds = KimApiServiceLocator.getGroupService().getGroupIdsByPrincipalId(actionListUserPrincipalId); 315 // add filter for requests where the current user was the primary delegator 316 if (delegatorGroupIds != null && !delegatorGroupIds.isEmpty()) { 317 crit.add( or( equal("delegatorPrincipalId", actionListUserPrincipalId), in("delegatorGroupId", delegatorGroupIds) ) ); 318 } else { 319 crit.add( equal("delegatorPrincipalId", actionListUserPrincipalId) ); 320 } 321 crit.add( equal("delegationType", DelegationType.PRIMARY.getCode() ) ); 322 filter.setDelegationType(DelegationType.PRIMARY.getCode()); 323 filter.setExcludeDelegationType(false); 324 filteredByItems.add("Primary Delegator Id"); 325 } 326 327 /** 328 * Apply criteria related to primary delegations. 329 * 330 * Called only after detecting that the user is filtering on primary validations. 331 * 332 * @return <b>true</b> if any criteria were applied, <b>false</b> otherwise 333 */ 334 protected boolean handlePrimaryDelegation( String actionListUserPrincipalId, ActionListFilter filter, Collection<Predicate> crit, List<String> filteredByItems ) { 335 if ( StringUtils.isBlank(filter.getPrimaryDelegateId()) 336 || filter.getPrimaryDelegateId().trim().equals(KewApiConstants.ALL_CODE) ) { 337 // user wishes to see all primary delegations 338 applyPrimaryDelegationCriteria(actionListUserPrincipalId, filter, crit, filteredByItems); 339 340 return true; 341 } else if (!filter.getPrimaryDelegateId().trim().equals(KewApiConstants.PRIMARY_DELEGATION_DEFAULT)) { 342 // user wishes to see primary delegation for a single user 343 crit.add( equal("principalId", filter.getPrimaryDelegateId() ) ); 344 applyPrimaryDelegationCriteria(actionListUserPrincipalId, filter, crit, filteredByItems); 345 346 return true; 347 } 348 349 return false; 350 } 351 352 /** 353 * Apply criteria related to secondary delegations. 354 * 355 * Called only after detecting that the user is filtering on secondary validations. 356 * 357 * @return <b>true</b> if any criteria were applied, <b>false</b> otherwise 358 */ 359 protected boolean handleSecondaryDelegation( String actionListUserPrincipalId, ActionListFilter filter, Collection<Predicate> crit, List<String> filteredByItems ) { 360 crit.add( equal("principalId", actionListUserPrincipalId) ); 361 if (StringUtils.isBlank(filter.getDelegatorId())) { 362 filter.setDelegationType(DelegationType.SECONDARY.getCode()); 363 364 // if isExcludeDelegationType() we want to show the default action list which is set up later in this method 365 if (!filter.isExcludeDelegationType()) { 366 crit.add( equal("delegationType", DelegationType.SECONDARY.getCode() ) ); 367 filteredByItems.add("Secondary Delegator Id"); 368 369 return true; 370 } 371 } else if (filter.getDelegatorId().trim().equals(KewApiConstants.ALL_CODE)) { 372 // user wishes to see all secondary delegations 373 crit.add( equal("delegationType", DelegationType.SECONDARY.getCode() ) ); 374 filter.setDelegationType(DelegationType.SECONDARY.getCode()); 375 filter.setExcludeDelegationType(false); 376 filteredByItems.add("Secondary Delegator Id"); 377 378 return true; 379 } else if (!filter.getDelegatorId().trim().equals(KewApiConstants.DELEGATION_DEFAULT)) { 380 // user has specified an id to see for secondary delegation 381 filter.setDelegationType(DelegationType.SECONDARY.getCode()); 382 filter.setExcludeDelegationType(false); 383 384 if (filter.isExcludeDelegatorId()) { 385 crit.add( or( notEqual("delegatorPrincipalId", filter.getDelegatorId()), isNull("delegatorPrincipalId") ) ); 386 crit.add( or( notEqual("delegatorGroupId", filter.getDelegatorId()), isNull("delegatorGroupId") ) ); 387 } else { 388 crit.add( or( equal("delegatorPrincipalId", filter.getDelegatorId()), equal("delegatorGroupId", filter.getDelegatorId()) ) ); 389 } 390 filteredByItems.add("Secondary Delegator Id"); 391 392 return true; 393 } 394 395 return false; 396 } 397 398 /** 399 * Handle the general recipient criteria (user, delegate) 400 * 401 * @param actionListUserPrincipalId 402 * @param filter 403 * @param crit 404 * @param filteredByItems 405 */ 406 protected void handleRecipientCriteria( String actionListUserPrincipalId, ActionListFilter filter, Collection<Predicate> crit, List<String> filteredByItems ) { 407 if (StringUtils.isBlank(filter.getDelegationType()) 408 && StringUtils.isBlank(filter.getPrimaryDelegateId()) 409 && StringUtils.isBlank(filter.getDelegatorId())) { 410 crit.add( equal("principalId", actionListUserPrincipalId) ); 411 return; 412 } 413 if ( StringUtils.equals(filter.getDelegationType(), DelegationType.PRIMARY.getCode() ) 414 || StringUtils.isNotBlank(filter.getPrimaryDelegateId())) { 415 // using a primary delegation 416 if ( handlePrimaryDelegation(actionListUserPrincipalId, filter, crit, filteredByItems)) { 417 return; 418 } 419 } 420 421 if (StringUtils.equals(filter.getDelegationType(), DelegationType.SECONDARY.getCode()) 422 || StringUtils.isNotBlank(filter.getDelegatorId()) ) { 423 // using a secondary delegation 424 if ( handleSecondaryDelegation(actionListUserPrincipalId, filter, crit, filteredByItems) ) { 425 return; 426 } 427 } 428 429 // if we haven't added delegation criteria above then use the default criteria below 430 filter.setDelegationType(DelegationType.SECONDARY.getCode()); 431 filter.setExcludeDelegationType(true); 432 crit.add( equal("principalId", actionListUserPrincipalId) ); 433 crit.add( or( notEqual("delegationType", DelegationType.SECONDARY.getCode()), isNull("delegationType") ) ); 434 } 435 436 /** 437 * Creates an Action List from the given collection of Action Items. The Action List should 438 * contain only one action item per document. The action item chosen should be the most "critical" 439 * or "important" one on the document. 440 * 441 * @return the Action List as a Collection of ActionItems 442 */ 443 private Collection<ActionItem> createActionListForUser(Collection<ActionItem> actionItems) { 444 Map<String, ActionItem> actionItemMap = new HashMap<String, ActionItem>(); 445 ActionListPriorityComparator comparator = new ActionListPriorityComparator(); 446 for (ActionItem potentialActionItem: actionItems) { 447 ActionItem existingActionItem = actionItemMap.get(potentialActionItem.getDocumentId()); 448 if (existingActionItem == null || comparator.compare(potentialActionItem, existingActionItem) > 0) { 449 actionItemMap.put(potentialActionItem.getDocumentId(), potentialActionItem); 450 } 451 } 452 return actionItemMap.values(); 453 } 454 455 456 /** 457 * {@inheritDoc} 458 */ 459 @Override 460 public Collection<ActionItem> getActionListForSingleDocument(String documentId) { 461 if ( LOG.isDebugEnabled() ) { 462 LOG.debug("getting action list for document id " + documentId); 463 } 464 Collection<ActionItem> collection = findByDocumentId(documentId); 465 if ( LOG.isDebugEnabled() ) { 466 LOG.debug("found " + collection.size() + " action items for document id " + documentId); 467 } 468 return createActionListForRouteHeader(collection); 469 } 470 471 /** 472 * Creates an Action List from the given collection of Action Items. The Action List should 473 * contain only one action item per user. The action item chosen should be the most "critical" 474 * or "important" one on the document. 475 * 476 * @return the Action List as a Collection of ActionItems 477 */ 478 protected Collection<ActionItem> createActionListForRouteHeader(Collection<ActionItem> actionItems) { 479 Map<String, ActionItem> actionItemMap = new HashMap<String, ActionItem>(); 480 ActionListPriorityComparator comparator = new ActionListPriorityComparator(); 481 for (ActionItem potentialActionItem: actionItems) { 482 ActionItem existingActionItem = actionItemMap.get(potentialActionItem.getPrincipalId()); 483 if (existingActionItem == null || comparator.compare(potentialActionItem, existingActionItem) > 0) { 484 actionItemMap.put(potentialActionItem.getPrincipalId(), potentialActionItem); 485 } 486 } 487 return actionItemMap.values(); 488 } 489 490 public void setActionListDAO(ActionListDAO actionListDAO) { 491 this.actionListDAO = actionListDAO; 492 } 493 494 public ActionListDAO getActionListDAO() { 495 return actionListDAO; 496 } 497 498 @Override 499 public void deleteActionItemNoOutbox(ActionItem actionItem) { 500 deleteActionItem(actionItem, false, false); 501 } 502 503 @Override 504 public void deleteActionItem(ActionItem actionItem) { 505 deleteActionItem(actionItem, false); 506 } 507 508 @Override 509 public void deleteActionItem(ActionItem actionItem, boolean forceIntoOutbox) { 510 deleteActionItem(actionItem, forceIntoOutbox, true); 511 } 512 513 protected void deleteActionItem(ActionItem actionItem, boolean forceIntoOutbox, boolean putInOutbox) { 514 dataObjectService.delete(actionItem); 515 // remove notification from KCB 516 notificationService.removeNotification(Collections.singletonList(ActionItem.to(actionItem))); 517 if (putInOutbox) { 518 saveOutboxItem(actionItem, forceIntoOutbox); 519 } 520 } 521 522 @Override 523 public void deleteByDocumentId(String documentId) { 524 dataObjectService.deleteMatching(ActionItem.class, QueryByCriteria.Builder.forAttribute("documentId", documentId).build()); 525 } 526 527 @Override 528 public Collection<ActionItem> findByDocumentId(String documentId) { 529 QueryResults<ActionItem> results = dataObjectService.findMatching(ActionItem.class, 530 QueryByCriteria.Builder.forAttribute("documentId", documentId).build()); 531 532 return results.getResults(); 533 } 534 535 @Override 536 public Collection<ActionItem> findByActionRequestId(String actionRequestId) { 537 QueryResults<ActionItem> results = dataObjectService.findMatching(ActionItem.class, 538 QueryByCriteria.Builder.forAttribute("actionRequestId", actionRequestId).build()); 539 540 return results.getResults(); 541 } 542 543 @Override 544 public Collection<ActionItem> findByWorkflowUserDocumentId(String workflowUserId, String documentId) { 545 Map<String,String> criteria = new HashMap<String, String>(2); 546 criteria.put( "principalId", workflowUserId ); 547 criteria.put( "documentId", documentId ); 548 QueryResults<ActionItem> results = dataObjectService.findMatching(ActionItem.class, 549 QueryByCriteria.Builder.andAttributes(criteria).build()); 550 551 return results.getResults(); 552 } 553 554 @Override 555 public Collection<ActionItem> findByDocumentTypeName(String documentTypeName) { 556 QueryResults<ActionItem> results = dataObjectService.findMatching(ActionItem.class, 557 QueryByCriteria.Builder.forAttribute("docName", documentTypeName).build()); 558 559 return results.getResults(); 560 } 561 562 @Override 563 public ActionItem createActionItemForActionRequest(ActionRequestValue actionRequest) { 564 ActionItem actionItem = new ActionItem(); 565 566 DocumentRouteHeaderValue routeHeader = actionRequest.getRouteHeader(); 567 DocumentType docType = routeHeader.getDocumentType(); 568 569 actionItem.setActionRequestCd(actionRequest.getActionRequested()); 570 actionItem.setActionRequestId(actionRequest.getActionRequestId()); 571 actionItem.setDocName(docType.getName()); 572 actionItem.setRoleName(actionRequest.getQualifiedRoleName()); 573 actionItem.setPrincipalId(actionRequest.getPrincipalId()); 574 actionItem.setDocumentId(actionRequest.getDocumentId()); 575 actionItem.setDateAssigned(new Timestamp(new Date().getTime())); 576 actionItem.setDocHandlerURL(docType.getResolvedDocumentHandlerUrl()); 577 actionItem.setDocLabel(docType.getLabel()); 578 actionItem.setDocTitle(routeHeader.getDocTitle()); 579 actionItem.setGroupId(actionRequest.getGroupId()); 580 actionItem.setResponsibilityId(actionRequest.getResponsibilityId()); 581 actionItem.setDelegationType(actionRequest.getDelegationType()); 582 actionItem.setRequestLabel(actionRequest.getRequestLabel()); 583 584 ActionRequestValue delegatorActionRequest = actionRequestService.findDelegatorRequest(actionRequest); 585 if (delegatorActionRequest != null) { 586 actionItem.setDelegatorPrincipalId(delegatorActionRequest.getPrincipalId()); 587 actionItem.setDelegatorGroupId(delegatorActionRequest.getGroupId()); 588 } 589 590 return actionItem; 591 } 592 593 594 @Override 595 public void updateActionItemsForTitleChange(String documentId, String newTitle) { 596 Collection<ActionItem> items = findByDocumentId(documentId); 597 for ( ActionItem item : items ) { 598 item.setDocTitle(newTitle); 599 saveActionItem(item); 600 } 601 } 602 603 @Override 604 public ActionItem saveActionItem(ActionItem actionItem) { 605 return saveActionItemBase(actionItem); 606 } 607 608 @Override 609 public OutboxItem saveOutboxItem(OutboxItem outboxItem) { 610 return saveActionItemBase(outboxItem); 611 } 612 613 protected <T extends ActionItemBase> T saveActionItemBase(T actionItemBase) { 614 if (actionItemBase.getDateAssigned() == null) { 615 actionItemBase.setDateAssigned(dateTimeService.getCurrentTimestamp()); 616 } 617 return dataObjectService.save(actionItemBase); 618 } 619 620 public GroupService getGroupService(){ 621 return KimApiServiceLocator.getGroupService(); 622 } 623 624 @Override 625 public ActionItem findByActionItemId(String actionItemId) { 626 return dataObjectService.find(ActionItem.class, actionItemId); 627 } 628 629 @Override 630 public int getCount(String principalId) { 631 return actionListDAO.getCount(principalId); 632 } 633 634 /** 635 * {@inheritDoc} 636 */ 637 @Override 638 public List<Object> getMaxActionItemDateAssignedAndCountForUser(String principalId) { 639 // KULRICE-12318 IU contribution, not sure if this is still needed with the JPA implementation 640 // as no result should cause a no result exception, going to add it to make sure. 641 List<Object> verifiedList = new ArrayList<Object>(); 642 List<Object> maxDateAndUserCount = getActionListDAO().getMaxActionItemDateAssignedAndCountForUser(principalId); 643 644 verifiedList.add(0, verifyMaxActionItemDateAssigned(maxDateAndUserCount)); 645 verifiedList.add(1, verifyCountForUser(maxDateAndUserCount)); 646 647 return verifiedList; 648 } 649 650 /** 651 * Ensures the max action item date assigned is a valid {@link Timestamp} otherwise the current time stamp is 652 * returned 653 * @param maxDateAndUserCount the list containing the max action item date assigned 654 * @return the max action item date assigned, or the current time stamp if one is not found 655 */ 656 private Object verifyMaxActionItemDateAssigned(List<Object> maxDateAndUserCount) { 657 if (maxDateAndUserCount != null && maxDateAndUserCount.size() > 0 && 658 maxDateAndUserCount.get(0) != null && (maxDateAndUserCount.get(0) instanceof Timestamp)) { 659 660 return maxDateAndUserCount.get(0); 661 } else { 662 return new Timestamp(new Date().getTime()); 663 } 664 } 665 666 /** 667 * Ensures the user count is valid, otherwise zero is returned. 668 * @param maxDateAndUserCount the list containing the user count 669 * @return the user count, or zero if the user count is not found 670 */ 671 private Object verifyCountForUser(List<Object> maxDateAndUserCount) { 672 if (maxDateAndUserCount != null && maxDateAndUserCount.size() > 1 && maxDateAndUserCount.get(1) != null) { 673 return maxDateAndUserCount.get(1); 674 } else { 675 return Long.valueOf(0); 676 } 677 } 678 679 /** 680 * {@inheritDoc} 681 */ 682 @Override 683 public Collection<OutboxItem> getOutbox(String principalId, ActionListFilter filter) { 684 List<String> filteredByItems = new ArrayList<String>(); 685 686 List<Predicate> crit = handleActionItemCriteria(principalId, filter, filteredByItems); 687 688 if ( LOG.isDebugEnabled() ) { 689 LOG.debug("running query to get outbox list for criteria " + crit); 690 } 691 QueryByCriteria.Builder query = QueryByCriteria.Builder.create(QueryByCriteria.Builder.fromPredicates(crit)); 692 query.setMaxResults(getOutboxItemLimit()); 693 QueryResults<OutboxItem> results = dataObjectService.findMatching(OutboxItem.class, query.build()); 694 if ( LOG.isDebugEnabled() ) { 695 LOG.debug("found " + results.getResults().size() + " outbox items for user " + principalId); 696 } 697 698 if ( filteredByItems.isEmpty() ) { 699 filter.setFilterOn(false); 700 } else { 701 filter.setFilterOn(true); 702 } 703 filter.setFilterLegend(StringUtils.join(filteredByItems, ", ")); 704 705 return results.getResults(); 706 } 707 708 /** 709 * Retrieves the outbox item limit from the parameter service, if the parameter is not found a default is returned. 710 * @return the outbox item limit. 711 */ 712 private Integer getOutboxItemLimit() { 713 String fetchSizeParam = 714 CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, 715 KRADConstants.DetailTypes.ACTION_LIST_DETAIL_TYPE, KewApiConstants.OUTBOX_ITEM_LIMIT); 716 717 if(StringUtils.isNotBlank(fetchSizeParam)) { 718 return Integer.parseInt(fetchSizeParam); 719 } else { 720 return DEFAULT_OUTBOX_ITEM_LIMIT; 721 } 722 } 723 724 /** 725 * {@inheritDoc} 726 */ 727 @Override 728 public Collection<OutboxItem> getOutboxItemsByDocumentType(String documentTypeName) { 729 QueryResults<OutboxItem> results = dataObjectService.findMatching(OutboxItem.class, 730 QueryByCriteria.Builder.forAttribute("docName", documentTypeName).build()); 731 732 return results.getResults(); 733 } 734 735 /** 736 * {@inheritDoc} 737 */ 738 @Override 739 public void removeOutboxItems(String principalId, List<String> outboxItems) { 740 QueryByCriteria query = QueryByCriteria.Builder.fromPredicates( 741 in("id", outboxItems)); 742 743 dataObjectService.deleteMatching(OutboxItem.class, query); 744 } 745 746 @Override 747 public OutboxItem saveOutboxItem(ActionItem actionItem) { 748 return saveOutboxItem(actionItem, false); 749 } 750 751 /** 752 * 753 * save the ouboxitem unless the document is saved or the user already has the item in their outbox. 754 * 755 * @see org.kuali.rice.kew.actionlist.service.ActionListService#saveOutboxItem(org.kuali.rice.kew.actionitem.ActionItem, boolean) 756 */ 757 @Override 758 public OutboxItem saveOutboxItem(ActionItem actionItem, boolean forceIntoOutbox) { 759 Boolean isUsingOutBox = true; 760 List<UserOptions> options = userOptionsService.findByUserQualified(actionItem.getPrincipalId(), KewApiConstants.USE_OUT_BOX); 761 if (options == null || options.isEmpty()){ 762 isUsingOutBox = true; 763 } else { 764 for ( UserOptions u : options ) { 765 if ( !StringUtils.equals(u.getOptionVal(), "yes") ) { 766 isUsingOutBox = false; 767 break; 768 } 769 } 770 } 771 772 if (isUsingOutBox 773 && ConfigContext.getCurrentContextConfig().getOutBoxOn() 774 && getOutboxItemByDocumentIdUserId(actionItem.getDocumentId(), actionItem.getPrincipalId()) == null 775 && !routeHeaderService.getRouteHeader(actionItem.getDocumentId()).getDocRouteStatus().equals( 776 KewApiConstants.ROUTE_HEADER_SAVED_CD)) { 777 778 // only create an outbox item if this user has taken action on the document 779 ActionRequestValue actionRequest = actionRequestService.findByActionRequestId( 780 actionItem.getActionRequestId()); 781 ActionTakenValue actionTaken = actionRequest.getActionTaken(); 782 // if an action was taken... 783 if (forceIntoOutbox || (actionTaken != null && actionTaken.getPrincipalId().equals(actionItem.getPrincipalId()))) { 784 return dataObjectService.save(new OutboxItem(actionItem)); 785 } 786 787 } 788 return null; 789 } 790 791 protected OutboxItem getOutboxItemByDocumentIdUserId(String documentId, String principalId) { 792 Map<String,String> criteria = new HashMap<String, String>(2); 793 criteria.put( "principalId", principalId ); 794 criteria.put( "documentId", documentId ); 795 QueryResults<OutboxItem> results = dataObjectService.findMatching(OutboxItem.class, 796 QueryByCriteria.Builder.andAttributes(criteria).build()); 797 if ( results.getResults().isEmpty() ) { 798 return null; 799 } 800 return results.getResults().get(0); 801 } 802 803 @Override 804 public Collection<ActionItem> findByPrincipalId(String principalId) { 805 QueryResults<ActionItem> results = dataObjectService.findMatching(ActionItem.class, 806 QueryByCriteria.Builder.forAttribute("principalId", principalId) 807 .setOrderByAscending("documentId").build()); 808 809 return results.getResults(); 810 } 811 812 /** 813 * {@inheritDoc} 814 */ 815 @Override 816 public DocumentRouteHeaderValue getMinimalRouteHeader(String documentId) { 817 return actionListDAO.getMinimalRouteHeader(documentId); 818 } 819 820 protected Timestamp beginningOfDay(Date date) { 821 if ( date == null ) { 822 return null; 823 } 824 Calendar cal = Calendar.getInstance(); 825 cal.setTime(date); 826 cal.set(Calendar.HOUR_OF_DAY, 0); 827 cal.set(Calendar.MINUTE, 0); 828 cal.set(Calendar.SECOND, 0); 829 return new Timestamp( cal.getTimeInMillis() ); 830 } 831 832 protected Timestamp endOfDay(Date date) { 833 if ( date == null ) { 834 return null; 835 } 836 Calendar cal = Calendar.getInstance(); 837 cal.setTime(date); 838 cal.set(Calendar.HOUR_OF_DAY, 23); 839 cal.set(Calendar.MINUTE, 59); 840 cal.set(Calendar.SECOND, 59); 841 return new Timestamp( cal.getTimeInMillis() ); 842 } 843 844 public void setDataObjectService(DataObjectService dataObjectService) { 845 this.dataObjectService = dataObjectService; 846 } 847 848 public void setNotificationService(NotificationService notificationService) { 849 this.notificationService = notificationService; 850 } 851 852 public void setDateTimeService(DateTimeService dateTimeService) { 853 this.dateTimeService = dateTimeService; 854 } 855 856 public void setActionRequestService(ActionRequestService actionRequestService) { 857 this.actionRequestService = actionRequestService; 858 } 859 860 public void setDocumentTypeService(DocumentTypeService documentTypeService) { 861 this.documentTypeService = documentTypeService; 862 } 863 864 public void setUserOptionsService(UserOptionsService userOptionsService) { 865 this.userOptionsService = userOptionsService; 866 } 867 868 public void setRouteHeaderService(RouteHeaderService routeHeaderService) { 869 this.routeHeaderService = routeHeaderService; 870 } 871}