001 /** 002 * Copyright 2005-2012 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 */ 016 package org.kuali.rice.kew.actions; 017 018 import org.apache.log4j.Logger; 019 import org.kuali.rice.kew.actionrequest.ActionRequestValue; 020 import org.kuali.rice.kew.actionrequest.KimGroupRecipient; 021 import org.kuali.rice.kew.actionrequest.KimPrincipalRecipient; 022 import org.kuali.rice.kew.actionrequest.Recipient; 023 import org.kuali.rice.kew.actionrequest.service.ActionRequestService; 024 import org.kuali.rice.kew.actiontaken.ActionTakenValue; 025 import org.kuali.rice.kew.api.KewApiServiceLocator; 026 import org.kuali.rice.kew.api.WorkflowRuntimeException; 027 import org.kuali.rice.kew.api.document.DocumentProcessingOptions; 028 import org.kuali.rice.kew.api.document.DocumentProcessingQueue; 029 import org.kuali.rice.kew.api.document.attribute.DocumentAttributeIndexingQueue; 030 import org.kuali.rice.kew.api.exception.InvalidActionTakenException; 031 import org.kuali.rice.kew.engine.RouteContext; 032 import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange; 033 import org.kuali.rice.kew.framework.postprocessor.PostProcessor; 034 import org.kuali.rice.kew.framework.postprocessor.ProcessDocReport; 035 import org.kuali.rice.kew.messaging.MessageServiceNames; 036 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue; 037 import org.kuali.rice.kew.service.KEWServiceLocator; 038 import org.kuali.rice.kim.api.identity.principal.PrincipalContract; 039 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 040 041 import java.util.List; 042 043 044 /** 045 * Super class containing mostly often used methods by all actions. Holds common 046 * state as well, {@link DocumentRouteHeaderValue} document, 047 * {@link ActionTakenValue} action taken (once saved), {@link PrincipalContract} principal 048 * that has taken the action 049 * 050 * @author Kuali Rice Team (rice.collab@kuali.org) 051 */ 052 public abstract class ActionTakenEvent { 053 054 private static final Logger LOG = Logger.getLogger(ActionTakenEvent.class); 055 056 /** 057 * Used when saving an ActionTakenValue, and for validation in validateActionRules 058 */ 059 private String actionTakenCode; 060 061 protected final String annotation; 062 063 protected DocumentRouteHeaderValue routeHeader; 064 065 private final PrincipalContract principal; 066 067 private final boolean runPostProcessorLogic; 068 069 private List<String> groupIdsForPrincipal; 070 071 072 private boolean queueDocumentAfterAction = true; 073 074 075 public ActionTakenEvent(String actionTakenCode, DocumentRouteHeaderValue routeHeader, PrincipalContract principal) { 076 this(actionTakenCode, routeHeader, principal, null, true); 077 } 078 079 public ActionTakenEvent(String actionTakenCode, DocumentRouteHeaderValue routeHeader, PrincipalContract principal, String annotation) { 080 this(actionTakenCode, routeHeader, principal, annotation, true); 081 } 082 083 public ActionTakenEvent(String actionTakenCode, DocumentRouteHeaderValue routeHeader, PrincipalContract principal, String annotation, boolean runPostProcessorLogic) { 084 this.actionTakenCode = actionTakenCode; 085 this.routeHeader = routeHeader; 086 this.principal = principal; 087 this.annotation = annotation == null ? "" : annotation; 088 this.runPostProcessorLogic = runPostProcessorLogic; 089 this.queueDocumentAfterAction = true; 090 } 091 092 public ActionRequestService getActionRequestService() { 093 return (ActionRequestService) KEWServiceLocator.getService(KEWServiceLocator.ACTION_REQUEST_SRV); 094 } 095 096 public DocumentRouteHeaderValue getRouteHeader() { 097 return routeHeader; 098 } 099 100 public void setRouteHeader(DocumentRouteHeaderValue routeHeader) { 101 this.routeHeader = routeHeader; 102 } 103 104 public PrincipalContract getPrincipal() { 105 return principal; 106 } 107 108 /** 109 * Code of the action performed by the user 110 * 111 * Method may be overriden is action performed will be different than action 112 * taken 113 * @return 114 */ 115 protected String getActionPerformedCode() { 116 return getActionTakenCode(); 117 } 118 119 /** 120 * Validates whether or not this action is valid for the given principal 121 * and DocumentRouteHeaderValue. 122 */ 123 protected boolean isActionValid() { 124 return org.apache.commons.lang.StringUtils.isEmpty(validateActionRules()); 125 } 126 127 /** 128 * Placeholder for validation rules for each action 129 * 130 * @return error message string of specific error message 131 */ 132 public abstract String validateActionRules(); 133 public abstract String validateActionRules(List<ActionRequestValue> actionRequests); 134 135 /** 136 * Filters action requests based on if they occur after the given requestCode, and if they relate to this 137 * event's principal 138 * @param actionRequests the List of ActionRequestValues to filter 139 * @param requestCode the request code for all ActionRequestValues to be after 140 * @return the filtered List of ActionRequestValues 141 */ 142 public List<ActionRequestValue> filterActionRequestsByCode(List<ActionRequestValue> actionRequests, String requestCode) { 143 return getActionRequestService().filterActionRequestsByCode(actionRequests, getPrincipal().getPrincipalId(), getGroupIdsForPrincipal(), requestCode); 144 } 145 146 protected boolean isActionCompatibleRequest(List<ActionRequestValue> requests) { 147 LOG.debug("isActionCompatibleRequest() Default method = returning true"); 148 return true; 149 } 150 151 public void performAction() throws InvalidActionTakenException { 152 try{ 153 recordAction(); 154 }catch(InvalidActionTakenException e){ 155 if(routeHeader.getDocumentType().getEnrouteErrorSuppression().getPolicyValue()){ 156 LOG.error("Invalid Action Taken Exception was thrown, but swallowed due to ENROUTE_ERROR_SUPPRESSION document type policy!"); 157 return; 158 }else{ 159 throw e; 160 } 161 } 162 if (queueDocumentAfterAction) { 163 queueDocumentProcessing(); 164 } 165 166 } 167 168 protected abstract void recordAction() throws InvalidActionTakenException; 169 170 public void performDeferredAction() { 171 172 } 173 174 protected void updateSearchableAttributesIfPossible() { 175 // queue the document up so that it can be indexed for searching if it 176 // has searchable attributes 177 RouteContext routeContext = RouteContext.getCurrentRouteContext(); 178 if (routeHeader.getDocumentType().hasSearchableAttributes() && !routeContext.isSearchIndexingRequestedForContext()) { 179 routeContext.requestSearchIndexingForContext(); 180 DocumentAttributeIndexingQueue queue = KewApiServiceLocator.getDocumentAttributeIndexingQueue(routeHeader.getDocumentType().getApplicationId()); 181 queue.indexDocument(getDocumentId()); 182 } 183 } 184 185 protected void notifyActionTaken(ActionTakenValue actionTaken) { 186 if (!isRunPostProcessorLogic()) { 187 return; 188 } 189 if (actionTaken == null) { 190 return; 191 } 192 try { 193 LOG.debug("Notifying post processor of action taken"); 194 PostProcessor postProcessor = routeHeader.getDocumentType().getPostProcessor(); 195 ProcessDocReport report = postProcessor.doActionTaken(new org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent(routeHeader.getDocumentId(), routeHeader.getAppDocId(), ActionTakenValue.to(actionTaken))); 196 if (!report.isSuccess()) { 197 LOG.warn(report.getMessage(), report.getProcessException()); 198 throw new InvalidActionTakenException(report.getMessage()); 199 } 200 201 } catch (Exception ex) { 202 processPostProcessorException(ex); 203 } 204 } 205 206 protected void notifyStatusChange(String newStatusCode, String oldStatusCode) throws InvalidActionTakenException { 207 if (!isRunPostProcessorLogic()) { 208 return; 209 } 210 DocumentRouteStatusChange statusChangeEvent = new DocumentRouteStatusChange(routeHeader.getDocumentId(), routeHeader.getAppDocId(), oldStatusCode, newStatusCode); 211 try { 212 LOG.debug("Notifying post processor of status change " + oldStatusCode + "->" + newStatusCode); 213 PostProcessor postProcessor = routeHeader.getDocumentType().getPostProcessor(); 214 ProcessDocReport report = postProcessor.doRouteStatusChange(statusChangeEvent); 215 if (!report.isSuccess()) { 216 LOG.warn(report.getMessage(), report.getProcessException()); 217 throw new InvalidActionTakenException(report.getMessage()); 218 } 219 } catch (Exception ex) { 220 processPostProcessorException(ex); 221 } 222 } 223 224 /** 225 * Asynchronously queues the documented to be processed by the workflow engine. 226 */ 227 protected void queueDocumentProcessing() { 228 DocumentProcessingQueue documentProcessingQueue = (DocumentProcessingQueue) MessageServiceNames.getDocumentProcessingQueue(getRouteHeader()); 229 DocumentProcessingOptions options = DocumentProcessingOptions.create(isRunPostProcessorLogic(), RouteContext.getCurrentRouteContext().isSearchIndexingRequestedForContext()); 230 documentProcessingQueue.processWithOptions(getDocumentId(), options); 231 } 232 233 protected ActionTakenValue saveActionTaken() { 234 return saveActionTaken(Boolean.TRUE); 235 } 236 237 protected ActionTakenValue saveActionTaken(Boolean currentInd) { 238 return saveActionTaken(currentInd, null); 239 } 240 241 protected ActionTakenValue saveActionTaken(Recipient delegator) { 242 return saveActionTaken(Boolean.TRUE, delegator); 243 } 244 245 protected ActionTakenValue saveActionTaken(Boolean currentInd, Recipient delegator) { 246 ActionTakenValue val = new ActionTakenValue(); 247 val.setActionTaken(getActionTakenCode()); 248 val.setAnnotation(annotation); 249 val.setDocVersion(routeHeader.getDocVersion()); 250 val.setDocumentId(routeHeader.getDocumentId()); 251 val.setPrincipalId(principal.getPrincipalId()); 252 if (delegator instanceof KimPrincipalRecipient) { 253 val.setDelegatorPrincipalId(((KimPrincipalRecipient)delegator).getPrincipalId()); 254 } else if (delegator instanceof KimGroupRecipient) { 255 val.setDelegatorGroupId(((KimGroupRecipient) delegator).getGroupId()); 256 } 257 //val.setRouteHeader(routeHeader); 258 val.setCurrentIndicator(currentInd); 259 KEWServiceLocator.getActionTakenService().saveActionTaken(val); 260 return val; 261 } 262 263 /** 264 * Returns the highest priority delegator in the list of action requests. 265 */ 266 protected Recipient findDelegatorForActionRequests(List actionRequests) { 267 return getActionRequestService().findDelegator(actionRequests); 268 } 269 270 public String getActionTakenCode() { 271 return actionTakenCode; 272 } 273 274 protected void setActionTakenCode(String string) { 275 actionTakenCode = string; 276 } 277 278 protected String getDocumentId() { 279 return this.routeHeader.getDocumentId(); 280 } 281 282 /*protected void delete() { 283 KEWServiceLocator.getActionTakenService().delete(actionTaken); 284 }*/ 285 286 protected boolean isRunPostProcessorLogic() { 287 return this.runPostProcessorLogic; 288 } 289 290 protected List<String> getGroupIdsForPrincipal() { 291 if (groupIdsForPrincipal == null) { 292 groupIdsForPrincipal = KimApiServiceLocator.getGroupService().getGroupIdsByPrincipalId( 293 getPrincipal().getPrincipalId()); 294 } 295 return groupIdsForPrincipal; 296 } 297 298 299 public void setQueueDocumentAfterAction(boolean queueDocumentAfterAction) { 300 this.queueDocumentAfterAction = queueDocumentAfterAction; 301 } 302 303 private void processPostProcessorException(Exception e) { 304 if (e instanceof RuntimeException) { 305 throw (RuntimeException)e; 306 } 307 throw new WorkflowRuntimeException(e); 308 } 309 }