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