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    }