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 }