View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.actions;
17  
18  import org.apache.log4j.Logger;
19  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
20  import org.kuali.rice.kew.actionrequest.KimGroupRecipient;
21  import org.kuali.rice.kew.actionrequest.KimPrincipalRecipient;
22  import org.kuali.rice.kew.actionrequest.Recipient;
23  import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
24  import org.kuali.rice.kew.actiontaken.ActionTakenValue;
25  import org.kuali.rice.kew.api.KewApiServiceLocator;
26  import org.kuali.rice.kew.api.WorkflowRuntimeException;
27  import org.kuali.rice.kew.api.document.DocumentProcessingOptions;
28  import org.kuali.rice.kew.api.document.DocumentProcessingQueue;
29  import org.kuali.rice.kew.api.document.attribute.DocumentAttributeIndexingQueue;
30  import org.kuali.rice.kew.api.exception.InvalidActionTakenException;
31  import org.kuali.rice.kew.engine.RouteContext;
32  import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
33  import org.kuali.rice.kew.framework.postprocessor.PostProcessor;
34  import org.kuali.rice.kew.framework.postprocessor.ProcessDocReport;
35  import org.kuali.rice.kew.messaging.MessageServiceNames;
36  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
37  import org.kuali.rice.kew.service.KEWServiceLocator;
38  import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
39  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
40  
41  import java.util.List;
42  
43  
44  /**
45   * Super class containing mostly often used methods by all actions. Holds common
46   * state as well, {@link DocumentRouteHeaderValue} document,
47   * {@link ActionTakenValue} action taken (once saved), {@link PrincipalContract} principal
48   * that has taken the action
49   *
50   * @author Kuali Rice Team (rice.collab@kuali.org)
51   */
52  public abstract class ActionTakenEvent {
53  
54  	private static final Logger LOG = Logger.getLogger(ActionTakenEvent.class);
55  
56  	/**
57  	 * Used when saving an ActionTakenValue, and for validation in validateActionRules
58  	 */
59  	private String actionTakenCode;
60  
61  	protected final String annotation;
62  
63  	protected DocumentRouteHeaderValue routeHeader;
64  
65  	private final PrincipalContract principal;
66  
67      private final boolean runPostProcessorLogic;
68      
69      private List<String> groupIdsForPrincipal;
70      
71  
72      private boolean queueDocumentAfterAction = true;
73  
74  
75  	public ActionTakenEvent(String actionTakenCode, DocumentRouteHeaderValue routeHeader, PrincipalContract principal) {
76  		this(actionTakenCode, routeHeader, principal, null, true);
77  	}
78  
79      public ActionTakenEvent(String actionTakenCode, DocumentRouteHeaderValue routeHeader, PrincipalContract principal, String annotation) {
80          this(actionTakenCode, routeHeader, principal, annotation, true);
81      }
82  
83  	public ActionTakenEvent(String actionTakenCode, DocumentRouteHeaderValue routeHeader, PrincipalContract principal, String annotation, boolean runPostProcessorLogic) {
84  	    this.actionTakenCode = actionTakenCode;
85  	    this.routeHeader = routeHeader;
86          this.principal = principal;
87          this.annotation = annotation == null ? "" : annotation;
88  		this.runPostProcessorLogic = runPostProcessorLogic;
89  		this.queueDocumentAfterAction = true;
90  	}
91  
92  	public ActionRequestService getActionRequestService() {
93  		return (ActionRequestService) KEWServiceLocator.getService(KEWServiceLocator.ACTION_REQUEST_SRV);
94  	}
95  
96  	public DocumentRouteHeaderValue getRouteHeader() {
97  		return routeHeader;
98  	}
99  
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 }