Coverage Report - org.kuali.rice.krad.service.impl.PostProcessorServiceImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
PostProcessorServiceImpl
0%
0/108
0%
0/54
4.455
 
 1  
 /**
 2  
  * Copyright 2005-2011 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.krad.service.impl;
 17  
 
 18  
 import org.apache.log4j.Logger;
 19  
 import org.apache.ojb.broker.OptimisticLockException;
 20  
 import org.kuali.rice.kew.api.KewApiConstants;
 21  
 import org.kuali.rice.kew.api.exception.WorkflowException;
 22  
 import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent;
 23  
 import org.kuali.rice.kew.framework.postprocessor.AfterProcessEvent;
 24  
 import org.kuali.rice.kew.framework.postprocessor.BeforeProcessEvent;
 25  
 import org.kuali.rice.kew.framework.postprocessor.DeleteEvent;
 26  
 import org.kuali.rice.kew.framework.postprocessor.DocumentLockingEvent;
 27  
 import org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange;
 28  
 import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
 29  
 import org.kuali.rice.kew.framework.postprocessor.ProcessDocReport;
 30  
 import org.kuali.rice.krad.UserSession;
 31  
 import org.kuali.rice.krad.document.Document;
 32  
 import org.kuali.rice.krad.service.DocumentService;
 33  
 import org.kuali.rice.krad.service.PostProcessorService;
 34  
 import org.kuali.rice.krad.util.GlobalVariables;
 35  
 import org.kuali.rice.krad.util.KRADConstants;
 36  
 import org.kuali.rice.krad.util.ObjectUtils;
 37  
 import org.springframework.transaction.annotation.Transactional;
 38  
 
 39  
 import java.util.List;
 40  
 
 41  
 
 42  
 /**
 43  
  * This class is the postProcessor for the Kuali application, and it is responsible for plumbing events up to documents using the
 44  
  * built into the document methods for handling route status and other routing changes that take place asyncronously and potentially
 45  
  * on a different server.
 46  
  */
 47  
 @Transactional
 48  0
 public class PostProcessorServiceImpl implements PostProcessorService {
 49  
 
 50  0
     private static Logger LOG = Logger.getLogger(PostProcessorServiceImpl.class);
 51  
 
 52  
     private DocumentService documentService;
 53  
 
 54  
     /**
 55  
      * @see org.kuali.rice.kew.framework.postprocessor.PostProcessor#doRouteStatusChange(org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange)
 56  
      */
 57  
     @Override
 58  
     public ProcessDocReport doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) throws Exception {
 59  
         try {
 60  0
                 if ( LOG.isInfoEnabled() ) {
 61  0
                         LOG.info(new StringBuffer("started handling route status change from ").append(statusChangeEvent.getOldRouteStatus()).append(" to ").append(statusChangeEvent.getNewRouteStatus()).append(" for document ").append(statusChangeEvent.getDocumentId()));
 62  
                 }
 63  0
             establishGlobalVariables();
 64  0
             Document document = documentService.getByDocumentHeaderId(statusChangeEvent.getDocumentId());
 65  0
             if (document == null) {
 66  0
                 if (!KewApiConstants.ROUTE_HEADER_CANCEL_CD.equals(statusChangeEvent.getNewRouteStatus())) {
 67  0
                     throw new RuntimeException("unable to load document " + statusChangeEvent.getDocumentId());
 68  
                 }
 69  
             }
 70  
             else {
 71  0
                 document.doRouteStatusChange(statusChangeEvent);
 72  
                 // PLEASE READ BEFORE YOU MODIFY:
 73  
                 // we dont want to update the document on a Save, as this will cause an
 74  
                 // OptimisticLockException in many cases, because the DB versionNumber will be
 75  
                 // incremented one higher than the document in the browser, so when the user then
 76  
                 // hits Submit or Save again, the versionNumbers are out of synch, and the
 77  
                 // OptimisticLockException is thrown. This is not the optimal solution, and will
 78  
                 // be a problem anytime where the user can continue to edit the document after a
 79  
                 // workflow state change, without reloading the form.
 80  0
                 if (!document.getDocumentHeader().getWorkflowDocument().isSaved()) {
 81  0
                     documentService.updateDocument(document);
 82  
                 }
 83  
             }
 84  0
             if ( LOG.isInfoEnabled() ) {
 85  0
                     LOG.info(new StringBuffer("finished handling route status change from ").append(statusChangeEvent.getOldRouteStatus()).append(" to ").append(statusChangeEvent.getNewRouteStatus()).append(" for document ").append(statusChangeEvent.getDocumentId()));
 86  
             }
 87  
         }
 88  0
         catch (Exception e) {
 89  0
             logAndRethrow("route status", e);
 90  0
         }
 91  0
         return new ProcessDocReport(true, "");
 92  
     }
 93  
 
 94  
     /**
 95  
      * @see org.kuali.rice.kew.framework.postprocessor.PostProcessor#doRouteLevelChange(org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange)
 96  
      */
 97  
     public ProcessDocReport doRouteLevelChange(DocumentRouteLevelChange levelChangeEvent) throws Exception {
 98  
         // on route level change we'll serialize the XML for the document. we
 99  
         // are doing this here cause it's a heavy hitter, and we
 100  
         // want to avoid the user waiting for this during sync processing
 101  
         try {
 102  0
                 if ( LOG.isDebugEnabled() ) {
 103  0
                         LOG.debug(new StringBuffer("started handling route level change from ").append(levelChangeEvent.getOldNodeName()).append(" to ").append(levelChangeEvent.getNewNodeName()).append(" for document ").append(levelChangeEvent.getDocumentId()));
 104  
                 }
 105  0
             establishGlobalVariables();
 106  0
             Document document = documentService.getByDocumentHeaderId(levelChangeEvent.getDocumentId());
 107  0
             if (document == null) {
 108  0
                 throw new RuntimeException("unable to load document " + levelChangeEvent.getDocumentId());
 109  
             }
 110  0
             document.populateDocumentForRouting();
 111  0
             document.doRouteLevelChange(levelChangeEvent);
 112  0
             document.getDocumentHeader().getWorkflowDocument().saveDocumentData();
 113  0
             if ( LOG.isDebugEnabled() ) {
 114  0
                     LOG.debug(new StringBuffer("finished handling route level change from ").append(levelChangeEvent.getOldNodeName()).append(" to ").append(levelChangeEvent.getNewNodeName()).append(" for document ").append(levelChangeEvent.getDocumentId()));
 115  
             }
 116  
         }
 117  0
         catch (Exception e) {
 118  0
             logAndRethrow("route level", e);
 119  0
         }
 120  0
         return new ProcessDocReport(true, "");
 121  
     }
 122  
 
 123  
     /**
 124  
      * @see org.kuali.rice.kew.framework.postprocessor.PostProcessor#doDeleteRouteHeader(org.kuali.rice.kew.framework.postprocessor.DeleteEvent)
 125  
      */
 126  
     @Override
 127  
     public ProcessDocReport doDeleteRouteHeader(DeleteEvent event) throws Exception {
 128  0
         return new ProcessDocReport(true, "");
 129  
     }
 130  
 
 131  
     /**
 132  
      * @see org.kuali.rice.kew.framework.postprocessor.PostProcessor#doActionTaken(org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent)
 133  
      */
 134  
     @Override
 135  
     public ProcessDocReport doActionTaken(ActionTakenEvent event) throws Exception {
 136  
         try {
 137  0
                 if ( LOG.isDebugEnabled() ) {
 138  0
                         LOG.debug(new StringBuffer("started doing action taken for action taken code").append(event.getActionTaken().getActionTaken()).append(" for document ").append(event.getDocumentId()));
 139  
                 }
 140  0
             establishGlobalVariables();
 141  0
             Document document = documentService.getByDocumentHeaderId(event.getDocumentId());
 142  0
             if (ObjectUtils.isNull(document)) {
 143  
                 // only throw an exception if we are not cancelling
 144  0
                 if (!KewApiConstants.ACTION_TAKEN_CANCELED.equals(event.getActionTaken())) {
 145  0
                     LOG.warn("doActionTaken() Unable to load document with id " + event.getDocumentId() + 
 146  
                             " using action taken code '" + KewApiConstants.ACTION_TAKEN_CD.get(event.getActionTaken().getActionTaken()));
 147  
 //                    throw new RuntimeException("unable to load document " + event.getDocumentId());
 148  
                 }
 149  
             } else {
 150  0
                 document.doActionTaken(event);
 151  0
                 if ( LOG.isDebugEnabled() ) {
 152  0
                         LOG.debug(new StringBuffer("finished doing action taken for action taken code").append(event.getActionTaken().getActionTaken()).append(" for document ").append(event.getDocumentId()));
 153  
                 }
 154  
             }
 155  
         }
 156  0
         catch (Exception e) {
 157  0
             logAndRethrow("do action taken", e);
 158  0
         }
 159  0
         return new ProcessDocReport(true, "");
 160  
     }
 161  
 
 162  
     /**
 163  
      * This method first checks to see if the document can be retrieved by the {@link DocumentService}. If the document is
 164  
      * found the {@link Document#afterWorkflowEngineProcess(boolean)} method will be invoked on it
 165  
      * 
 166  
      * @see org.kuali.rice.kew.framework.postprocessor.PostProcessor#afterProcess(org.kuali.rice.kew.framework.postprocessor.AfterProcessEvent)
 167  
      */
 168  
     @Override
 169  
     public ProcessDocReport afterProcess(AfterProcessEvent event) throws Exception {
 170  
         try {
 171  0
                 if ( LOG.isDebugEnabled() ) {
 172  0
                         LOG.debug(new StringBuffer("started after process method for document ").append(event.getDocumentId()));
 173  
                 }
 174  0
             establishGlobalVariables();
 175  0
             Document document = documentService.getByDocumentHeaderId(event.getDocumentId());
 176  0
             if (ObjectUtils.isNull(document)) {
 177  
                 // no way to verify if this is the processing as a result of a cancel so assume null document is ok to process
 178  0
                 LOG.warn("afterProcess() Unable to load document with id " + event.getDocumentId() + "... ignoring post processing");
 179  
             } else {
 180  0
                 document.afterWorkflowEngineProcess(event.isSuccessfullyProcessed());
 181  0
                 if ( LOG.isDebugEnabled() ) {
 182  0
                         LOG.debug(new StringBuffer("finished after process method for document ").append(event.getDocumentId()));
 183  
                 }
 184  
             }
 185  
         }
 186  0
         catch (Exception e) {
 187  0
             logAndRethrow("after process", e);
 188  0
         }
 189  0
         return new ProcessDocReport(true, "");
 190  
     }
 191  
 
 192  
     /**
 193  
      * This method first checks to see if the document can be retrieved by the {@link DocumentService}. If the document is
 194  
      * found the {@link Document#beforeWorkflowEngineProcess()} method will be invoked on it
 195  
      * 
 196  
      * @see org.kuali.rice.kew.framework.postprocessor.PostProcessor#beforeProcess(org.kuali.rice.kew.framework.postprocessor.BeforeProcessEvent)
 197  
      */
 198  
     @Override
 199  
     public ProcessDocReport beforeProcess(BeforeProcessEvent event) throws Exception {
 200  
         try {
 201  0
                 if ( LOG.isDebugEnabled() ) {
 202  0
                         LOG.debug(new StringBuffer("started before process method for document ").append(event.getDocumentId()));
 203  
                 }
 204  0
             establishGlobalVariables();
 205  0
             Document document = documentService.getByDocumentHeaderId(event.getDocumentId());
 206  0
             if (ObjectUtils.isNull(document)) {
 207  
                 // no way to verify if this is the processing as a result of a cancel so assume null document is ok to process
 208  0
                 LOG.warn("beforeProcess() Unable to load document with id " + event.getDocumentId() + "... ignoring post processing");
 209  
             } else {
 210  0
                 document.beforeWorkflowEngineProcess();
 211  0
                 if ( LOG.isDebugEnabled() ) {
 212  0
                         LOG.debug(new StringBuffer("finished before process method for document ").append(event.getDocumentId()));
 213  
                 }
 214  
             }
 215  
         }
 216  0
         catch (Exception e) {
 217  0
             logAndRethrow("before process", e);
 218  0
         }
 219  0
         return new ProcessDocReport(true, "");
 220  
     }
 221  
     
 222  
     /**
 223  
      * This method first checks to see if the document can be retrieved by the {@link DocumentService}. If the document is
 224  
      * found the {@link Document#beforeWorkflowEngineProcess()} method will be invoked on it
 225  
      * 
 226  
      * @see org.kuali.rice.kew.framework.postprocessor.PostProcessor#beforeProcess(org.kuali.rice.kew.framework.postprocessor.BeforeProcessEvent)
 227  
      */
 228  
     public List<String> getDocumentIdsToLock(DocumentLockingEvent event) throws Exception {
 229  
         try {
 230  0
                 if ( LOG.isDebugEnabled() ) {
 231  0
                         LOG.debug(new StringBuffer("started get document ids to lock method for document ").append(event.getDocumentId()));
 232  
                 }
 233  0
             establishGlobalVariables();
 234  0
             Document document = documentService.getByDocumentHeaderId(event.getDocumentId());
 235  0
             if (ObjectUtils.isNull(document)) {
 236  
                 // no way to verify if this is the processing as a result of a cancel so assume null document is ok to process
 237  0
                 LOG.warn("getDocumentIdsToLock() Unable to load document with id " + event.getDocumentId() + "... ignoring post processing");
 238  
             } else {
 239  0
                 List<String> documentIdsToLock = document.getWorkflowEngineDocumentIdsToLock();
 240  0
                 if ( LOG.isDebugEnabled() ) {
 241  0
                         LOG.debug(new StringBuffer("finished get document ids to lock method for document ").append(event.getDocumentId()));
 242  
                 }
 243  0
                 if (documentIdsToLock == null) {
 244  0
                         return null;
 245  
                 }
 246  0
                 return documentIdsToLock;                
 247  
             }
 248  
         }
 249  0
         catch (Exception e) {
 250  0
             logAndRethrow("before process", e);
 251  0
         }
 252  0
         return null;
 253  
     }
 254  
 
 255  
     private void logAndRethrow(String changeType, Exception e) throws RuntimeException {
 256  0
         LOG.error("caught exception while handling " + changeType + " change", e);
 257  0
         logOptimisticDetails(5, e);
 258  
 
 259  0
         throw new RuntimeException("post processor caught exception while handling " + changeType + " change: " + e.getMessage(), e);
 260  
     }
 261  
 
 262  
     /**
 263  
      * Logs further details of OptimisticLockExceptions, using the given depth value to limit recursion Just In Case
 264  
      *
 265  
      * @param depth
 266  
      * @param t
 267  
      */
 268  
     private void logOptimisticDetails(int depth, Throwable t) {
 269  0
         if ((depth > 0) && (t != null)) {
 270  0
             if (t instanceof OptimisticLockException) {
 271  0
                 OptimisticLockException o = (OptimisticLockException) t;
 272  
 
 273  0
                 LOG.error("source of OptimisticLockException = " + o.getSourceObject().getClass().getName() + " ::= " + o.getSourceObject());
 274  0
             }
 275  
             else {
 276  0
                 Throwable cause = t.getCause();
 277  0
                 if (cause != t) {
 278  0
                     logOptimisticDetails(--depth, cause);
 279  
                 }
 280  
             }
 281  
         }
 282  0
     }
 283  
 
 284  
     /**
 285  
      * Sets the documentService attribute value.
 286  
      * @param documentService The documentService to set.
 287  
      */
 288  
     public final void setDocumentService(DocumentService documentService) {
 289  0
         this.documentService = documentService;
 290  0
     }
 291  
 
 292  
     /**
 293  
      * Establishes the UserSession if one does not already exist.
 294  
      */
 295  
     protected void establishGlobalVariables() throws WorkflowException {
 296  0
         if (GlobalVariables.getUserSession() == null) {
 297  0
             GlobalVariables.setUserSession(new UserSession(KRADConstants.SYSTEM_USER));
 298  
         }
 299  0
         GlobalVariables.clear();
 300  0
     }
 301  
 
 302  
 }