Coverage Report - org.kuali.rice.kew.service.WorkflowDocument
 
Classes in this File Line Coverage Branch Coverage Complexity
WorkflowDocument
0%
0/358
0%
0/116
1.778
 
 1  
 /*
 2  
  * Copyright 2005-2007 The Kuali Foundation
 3  
  *
 4  
  *
 5  
  * Licensed under the Educational Community License, Version 2.0 (the "License");
 6  
  * you may not use this file except in compliance with the License.
 7  
  * You may obtain a copy of the License at
 8  
  *
 9  
  * http://www.opensource.org/licenses/ecl2.php
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.kuali.rice.kew.service;
 18  
 
 19  
 import java.sql.Timestamp;
 20  
 import java.util.ArrayList;
 21  
 import java.util.Calendar;
 22  
 import java.util.List;
 23  
 
 24  
 import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
 25  
 import org.kuali.rice.kew.api.WorkflowRuntimeException;
 26  
 import org.kuali.rice.kew.dto.ActionRequestDTO;
 27  
 import org.kuali.rice.kew.dto.ActionTakenDTO;
 28  
 import org.kuali.rice.kew.dto.AdHocRevokeDTO;
 29  
 import org.kuali.rice.kew.dto.DocumentContentDTO;
 30  
 import org.kuali.rice.kew.dto.DocumentDetailDTO;
 31  
 import org.kuali.rice.kew.dto.DocumentLinkDTO;
 32  
 import org.kuali.rice.kew.dto.ModifiableDocumentContentDTO;
 33  
 import org.kuali.rice.kew.dto.MovePointDTO;
 34  
 import org.kuali.rice.kew.dto.NoteDTO;
 35  
 import org.kuali.rice.kew.dto.ReturnPointDTO;
 36  
 import org.kuali.rice.kew.dto.RouteHeaderDTO;
 37  
 import org.kuali.rice.kew.dto.RouteNodeInstanceDTO;
 38  
 import org.kuali.rice.kew.dto.UserIdDTO;
 39  
 import org.kuali.rice.kew.dto.WorkflowAttributeDefinitionDTO;
 40  
 import org.kuali.rice.kew.dto.WorkflowAttributeValidationErrorDTO;
 41  
 import org.kuali.rice.kew.exception.WorkflowException;
 42  
 import org.kuali.rice.kew.util.KEWConstants;
 43  
 
 44  
 /**
 45  
  * Represents a document in Workflow from the perspective of the client.  This class is one of two
 46  
  * (Java) client interfaces to the KEW system (the other being {@link WorkflowInfo} class).  The
 47  
  * first time an instance of this class is created, it will read the client configuration to determine
 48  
  * how to connect to KEW.
 49  
  *
 50  
  * <p>This class is used by creating new instances using the appropriate constructor.  To create a new
 51  
  * document in KEW, create an instance of this class passing a UserIdVO and a
 52  
  * document type name.  To load an existing document, create an instance of this class passing a
 53  
  * UserIdVO and a document ID number.
 54  
  *
 55  
  * <p>Internally, this wrapper interacts with the {@link WorkflowDocumentActions} service exported
 56  
  * over the KSB, maintaining state.
 57  
  *
 58  
  * <p>This class is not thread safe and must by synchronized externally for concurrent access.
 59  
  *
 60  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 61  
  */
 62  
 public class WorkflowDocument implements java.io.Serializable {
 63  
 
 64  
         private static final long serialVersionUID = -3672966990721719088L;
 65  
 
 66  
         /**
 67  
          * The principal ID of the user as whom actions will be taken on the KEW document
 68  
          */
 69  
     private String principalId;
 70  
     /**
 71  
      * RouteHeader VO of the KEW document this WorkflowDocument represents
 72  
      */
 73  
     private RouteHeaderDTO routeHeader;
 74  
     /**
 75  
      * Flag that indicates whether the document content currently loaded needs to be refreshed.
 76  
      * This is the case either if the document content has not yet been loaded, or an action
 77  
      * that might possibly affect the document content (which is potentially any action) has
 78  
      * subsequently been taken on the document through this API.
 79  
      */
 80  0
     private boolean documentContentDirty = false;
 81  
     /**
 82  
      * Value Object encapsulating the document content
 83  
      */
 84  
     private ModifiableDocumentContentDTO documentContent;
 85  
 
 86  
     /**
 87  
      * This method constructs a WorkflowDocument representing a new document in the 
 88  
      * workflow system.  Creation/committing of the new document is deferred until
 89  
      *  the first action is taken on the document.
 90  
      * 
 91  
      * @param principalId the user as which to take actions on the document
 92  
      * @param documentType the type of the document to create
 93  
      * @throws WorkflowException if anything goes awry
 94  
      */
 95  
         public static WorkflowDocument createDocument(String principalId, String documentType) throws WorkflowException {
 96  0
                 return new WorkflowDocument(principalId, documentType, null);
 97  
         }
 98  
         
 99  
     /**
 100  
      * This method loads a workflow document with the given document ID for the 
 101  
      * given principalId.  If no document can be found with the 
 102  
      * given ID, then the {@link getRouteHeader()} method of the WorkflowDocument
 103  
      * which is created will return null.
 104  
      * 
 105  
      * @param principalId the user as which to take actions on the document
 106  
      * @param documentId the document id of the document to load
 107  
      * @throws WorkflowException if anything goes awry
 108  
      */
 109  
         public static WorkflowDocument loadDocument(String principalId, String documentId) throws WorkflowException {
 110  0
                 return new WorkflowDocument(principalId, null, documentId);
 111  
         }
 112  
         
 113  0
         protected WorkflowDocument(String principalId, String documentType, String documentId) throws WorkflowException {
 114  0
         init(principalId, documentType, documentId);
 115  0
     }
 116  
     
 117  
     /**
 118  
      * Initializes this WorkflowDocument object, by either attempting to load an existing document by documentId
 119  
      * if one is supplied (non-null), or by constructing an empty document of the specified type.
 120  
      * @param principalId the user under which actions via this API on the specified document will be taken
 121  
      * @param documentType the type of document this WorkflowDocument should represent (either this parameter or documentId must be specified, non-null)
 122  
      * @param documentId the id of an existing document to load (either this parameter or documentType must be specified, non-null)
 123  
      * @throws WorkflowException if a documentId is specified but an exception occurs trying to load the document route header
 124  
      */
 125  
     private void init(String principalId, String documentType, String documentId) throws WorkflowException {
 126  0
             this.principalId = principalId;
 127  0
             routeHeader = new RouteHeaderDTO();
 128  0
             routeHeader.setDocTypeName(documentType);
 129  0
             if (documentId != null) {
 130  0
                     routeHeader = getWorkflowUtility().getRouteHeaderWithPrincipal(principalId, documentId);
 131  
             }
 132  0
     }
 133  
 
 134  
     /**
 135  
      * Retrieves the WorkflowUtility proxy from the locator.  The locator will cache this for us.
 136  
      */
 137  
     private WorkflowUtility getWorkflowUtility() throws WorkflowException {
 138  0
         WorkflowUtility workflowUtility = 
 139  
                 (WorkflowUtility)GlobalResourceLoader.getService(KEWConstants.WORKFLOW_UTILITY_SERVICE);
 140  0
             if (workflowUtility == null) {
 141  0
                     throw new WorkflowException("Could not locate the WorkflowUtility service.  Please ensure that KEW client is configured properly!");
 142  
             }
 143  0
             return workflowUtility;
 144  
 
 145  
     }
 146  
 
 147  
     /**
 148  
      * Retrieves the WorkflowDocumentActions proxy from the locator.  The locator will cache this for us.
 149  
      */
 150  
     private WorkflowDocumentActions getWorkflowDocumentActions() throws WorkflowException {
 151  0
             WorkflowDocumentActions workflowDocumentActions = 
 152  
                     (WorkflowDocumentActions) GlobalResourceLoader.getService(KEWConstants.WORKFLOW_DOCUMENT_ACTIONS_SERVICE);
 153  0
             if (workflowDocumentActions == null) {
 154  0
                     throw new WorkflowException("Could not locate the WorkflowDocumentActions service.  Please ensure that KEW client is configured properly!");
 155  
             }
 156  0
             return workflowDocumentActions;
 157  
     }
 158  
 
 159  
     // ########################
 160  
     // Document Content methods
 161  
     // ########################
 162  
 
 163  
     /**
 164  
      * Returns an up-to-date DocumentContent of this document.
 165  
      * @see WorkflowUtility#getDocumentContent(Long)
 166  
      */
 167  
     public DocumentContentDTO getDocumentContent() {
 168  
             try {
 169  
                     // create the document if it hasn't already been created
 170  0
                     if (getRouteHeader().getDocumentId() == null) {
 171  0
                         routeHeader = getWorkflowDocumentActions().createDocument(principalId, getRouteHeader());
 172  
                 }
 173  0
                     if (documentContent == null || documentContentDirty) {
 174  0
                             documentContent = new ModifiableDocumentContentDTO(getWorkflowUtility().getDocumentContent(routeHeader.getDocumentId()));
 175  0
                             documentContentDirty = false;
 176  
                     }
 177  0
             } catch (Exception e) {
 178  0
                     throw handleExceptionAsRuntime(e);
 179  0
             }
 180  0
             return documentContent;
 181  
     }
 182  
 
 183  
     /**
 184  
      * Returns the application specific section of the document content. This is
 185  
      * a convenience method that delegates to the {@link DocumentContentDTO}.
 186  
      *
 187  
      * For documents routed prior to Workflow 2.0:
 188  
      * If the application did NOT use attributes for XML generation, this method will
 189  
      * return the entire document content XML.  Otherwise it will return the empty string.
 190  
      * @see DocumentContentDTO#getApplicationContent()
 191  
      */
 192  
     public String getApplicationContent() {
 193  0
         return getDocumentContent().getApplicationContent();
 194  
     }
 195  
 
 196  
     /**
 197  
      * Sets the application specific section of the document content. This is
 198  
      * a convenience method that delegates to the {@link DocumentContentDTO}.
 199  
      */
 200  
     public void setApplicationContent(String applicationContent) {
 201  0
         getDocumentContent().setApplicationContent(applicationContent);
 202  0
     }
 203  
 
 204  
     /**
 205  
      * Clears all attribute document content from the document.
 206  
      * Typically, this will be used if it is necessary to update the attribute doc content on
 207  
      * the document.  This can be accomplished by clearing the content and then adding the
 208  
      * desired attribute definitions.
 209  
      *
 210  
      * This is a convenience method that delegates to the {@link DocumentContentDTO}.
 211  
      *
 212  
      * In order for these changes to take effect, an action must be performed on the document (such as "save").
 213  
      */
 214  
     public void clearAttributeContent() {
 215  0
         getDocumentContent().setAttributeContent("");
 216  0
     }
 217  
 
 218  
     /**
 219  
      * Returns the attribute-generated section of the document content. This is
 220  
      * a convenience method that delegates to the {@link DocumentContentDTO}.
 221  
      * @see DocumentContentDTO#getAttributeContent()
 222  
      */
 223  
     public String getAttributeContent() {
 224  0
         return getDocumentContent().getAttributeContent();
 225  
     }
 226  
 
 227  
     /**
 228  
      * Adds an attribute definition which defines creation parameters for a WorkflowAttribute
 229  
      * implementation.  The created attribute will be used to generate attribute document content.
 230  
      * When the document is sent to the server, this will be appended to the existing attribute
 231  
      * doc content.  If it is required to replace the attribute document content, then the
 232  
      * clearAttributeContent() method should be invoked prior to adding attribute definitions.
 233  
      *
 234  
      * This is a convenience method that delegates to the {@link DocumentContentDTO}.
 235  
      * @see DocumentContentDTO#addAttributeDefinition(WorkflowAttributeDefinitionDTO)
 236  
      */
 237  
     public void addAttributeDefinition(WorkflowAttributeDefinitionDTO attributeDefinition) {
 238  0
         getDocumentContent().addAttributeDefinition(attributeDefinition);
 239  0
     }
 240  
 
 241  
     /**
 242  
      * Validate the WorkflowAttributeDefinition against it's attribute on the server.  This will validate
 243  
      * the inputs that will eventually become xml.
 244  
      *
 245  
      * Only applies to attributes implementing WorkflowAttributeXmlValidator.
 246  
      *
 247  
      * This is a call through to the WorkflowInfo object and is here for convenience.
 248  
      *
 249  
      * @param attributeDefinition the workflow attribute definition VO to validate
 250  
      * @return WorkflowAttributeValidationErrorVO[] of error from the attribute
 251  
      * @throws WorkflowException when attribute doesn't implement WorkflowAttributeXmlValidator
 252  
      * @see WorkflowUtility#validateWorkflowAttributeDefinitionVO(WorkflowAttributeDefinitionDTO)
 253  
      */
 254  
     public WorkflowAttributeValidationErrorDTO[] validateAttributeDefinition(WorkflowAttributeDefinitionDTO attributeDefinition) throws WorkflowException {
 255  0
             return getWorkflowUtility().validateWorkflowAttributeDefinitionVO(attributeDefinition);
 256  
     }
 257  
 
 258  
     /**
 259  
      * Removes an attribute definition from the document content.  This is
 260  
      * a convenience method that delegates to the {@link DocumentContentDTO}.
 261  
      * @param attributeDefinition the attribute definition VO to remove
 262  
      */
 263  
     public void removeAttributeDefinition(WorkflowAttributeDefinitionDTO attributeDefinition) {
 264  0
         getDocumentContent().removeAttributeDefinition(attributeDefinition);
 265  0
     }
 266  
 
 267  
     /**
 268  
      * Removes all attribute definitions from the document content. This is
 269  
      * a convenience method that delegates to the {@link DocumentContentDTO}.
 270  
      */
 271  
     public void clearAttributeDefinitions() {
 272  0
             getDocumentContent().setAttributeDefinitions(new WorkflowAttributeDefinitionDTO[0]);
 273  0
     }
 274  
 
 275  
     /**
 276  
      * Returns the attribute definition VOs currently defined on the document content. This is
 277  
      * a convenience method that delegates to the {@link DocumentContentDTO}.
 278  
      * @return the attribute definition VOs currently defined on the document content.
 279  
      * @see DocumentContentDTO#getAttributeDefinitions()
 280  
      */
 281  
     public WorkflowAttributeDefinitionDTO[] getAttributeDefinitions() {
 282  0
         return getDocumentContent().getAttributeDefinitions();
 283  
     }
 284  
 
 285  
     /**
 286  
      * Adds a searchable attribute definition which defines creation parameters for a SearchableAttribute
 287  
      * implementation.  The created attribute will be used to generate searchable document content.
 288  
      * When the document is sent to the server, this will be appended to the existing searchable
 289  
      * doc content.  If it is required to replace the searchable document content, then the
 290  
      * clearSearchableContent() method should be invoked prior to adding definitions. This is
 291  
      * a convenience method that delegates to the {@link DocumentContentDTO}.
 292  
      */
 293  
     public void addSearchableDefinition(WorkflowAttributeDefinitionDTO searchableDefinition) {
 294  0
         getDocumentContent().addSearchableDefinition(searchableDefinition);
 295  0
     }
 296  
 
 297  
     /**
 298  
      * Removes a searchable attribute definition from the document content. This is
 299  
      * a convenience method that delegates to the {@link DocumentContentDTO}.
 300  
      * @param searchableDefinition the searchable attribute definition to remove
 301  
      */
 302  
     public void removeSearchableDefinition(WorkflowAttributeDefinitionDTO searchableDefinition) {
 303  0
         getDocumentContent().removeSearchableDefinition(searchableDefinition);
 304  0
     }
 305  
 
 306  
     /**
 307  
      * Removes all searchable attribute definitions from the document content. This is
 308  
      * a convenience method that delegates to the {@link DocumentContentDTO}.
 309  
      */
 310  
     public void clearSearchableDefinitions() {
 311  0
         getDocumentContent().setSearchableDefinitions(new WorkflowAttributeDefinitionDTO[0]);
 312  0
     }
 313  
 
 314  
     /**
 315  
      * Clears the searchable content from the document content. This is
 316  
      * a convenience method that delegates to the {@link DocumentContentDTO}.
 317  
      */
 318  
     public void clearSearchableContent() {
 319  0
             getDocumentContent().setSearchableContent("");
 320  0
     }
 321  
 
 322  
     /**
 323  
      * Returns the searchable attribute definitions on the document content. This is
 324  
      * a convenience method that delegates to the {@link DocumentContentDTO}.
 325  
      * @return the searchable attribute definitions on the document content.
 326  
      * @see DocumentContentDTO#getSearchableDefinitions()
 327  
      */
 328  
     public WorkflowAttributeDefinitionDTO[] getSearchableDefinitions() {
 329  0
         return getDocumentContent().getSearchableDefinitions();
 330  
     }
 331  
 
 332  
     // ########################
 333  
     // END Document Content methods
 334  
     // ########################
 335  
 
 336  
     /**
 337  
      * Returns the RouteHeaderVO for the workflow document this WorkflowDocument represents
 338  
      */
 339  
     public RouteHeaderDTO getRouteHeader() {
 340  0
         return routeHeader;
 341  
     }
 342  
 
 343  
     /**
 344  
      * Returns the id of the workflow document this WorkflowDocument represents.  If this is a new document
 345  
      * that has not yet been created, the document is first created (and therefore this will return a new id)
 346  
      * @return the id of the workflow document this WorkflowDocument represents
 347  
      * @throws WorkflowException if an error occurs during document creation
 348  
      */
 349  
     public String getDocumentId() throws WorkflowException {
 350  0
             createDocumentIfNeccessary();
 351  0
             return getRouteHeader().getDocumentId();
 352  
     }
 353  
 
 354  
     /**
 355  
      * Returns VOs of the pending ActionRequests on this document.  If this object represents a new document
 356  
      * that has not yet been created, then an empty array will be returned.  The ordering of ActionRequests
 357  
      * returned by this method is not guaranteed.
 358  
      *
 359  
      * This method relies on the WorkflowUtility service
 360  
      *
 361  
      * @return VOs of the pending ActionRequests on this document
 362  
      * @throws WorkflowException if an error occurs obtaining the pending action requests for this document
 363  
      * @see WorkflowUtility#getActionRequests(Long)
 364  
      */
 365  
     public ActionRequestDTO[] getActionRequests() throws WorkflowException {
 366  0
         if (getDocumentId() == null) {
 367  0
             return new ActionRequestDTO[0];
 368  
         }
 369  0
         return getWorkflowUtility().getAllActionRequests(getDocumentId());
 370  
     }
 371  
 
 372  
     /**
 373  
      * Returns VOs of the actions taken on this document.  If this object represents a new document
 374  
      * that has not yet been created, then an empty array will be returned.  The ordering of actions taken
 375  
      * returned by this method is not guaranteed.
 376  
      *
 377  
      * This method relies on the WorkflowUtility service
 378  
      *
 379  
      * @return VOs of the actions that have been taken on this document
 380  
      * @throws WorkflowException if an error occurs obtaining the actions taken on this document
 381  
      * @see WorkflowUtility#getActionsTaken(Long)
 382  
      */
 383  
     public ActionTakenDTO[] getActionsTaken() throws WorkflowException {
 384  0
         if (getDocumentId() == null) {
 385  0
             return new ActionTakenDTO[0];
 386  
         }
 387  0
         return getWorkflowUtility().getActionsTaken(getDocumentId());
 388  
     }
 389  
 
 390  
     /**
 391  
      * Sets the "application doc id" on the document
 392  
      * @param appDocId the "application doc id" to set on the workflow document
 393  
      */
 394  
     public void setAppDocId(String appDocId) {
 395  0
         routeHeader.setAppDocId(appDocId);
 396  0
     }
 397  
 
 398  
     /**
 399  
      * Returns the "application doc id" set on this workflow document (if any)
 400  
      * @return the "application doc id" set on this workflow document (if any)
 401  
      */
 402  
     public String getAppDocId() {
 403  0
         return routeHeader.getAppDocId();
 404  
     }
 405  
 
 406  
     /**
 407  
      * Returns the date/time the document was created, or null if the document has not yet been created
 408  
      * @return the date/time the document was created, or null if the document has not yet been created
 409  
      */
 410  
     public Timestamp getDateCreated() {
 411  0
             if (routeHeader.getDateCreated() == null) {
 412  0
                     return null;
 413  
             }
 414  0
             return new Timestamp(routeHeader.getDateCreated().getTime().getTime());
 415  
     }
 416  
 
 417  
     /**
 418  
      * Returns the title of the document
 419  
      * @return the title of the document
 420  
      */
 421  
     public String getTitle() {
 422  0
         return getRouteHeader().getDocTitle();
 423  
     }
 424  
 
 425  
     /**
 426  
      * Performs the 'save' action on the document this WorkflowDocument represents.  If this is a new document,
 427  
      * the document is created first.
 428  
      * @param annotation the message to log for the action
 429  
      * @throws WorkflowException in case an error occurs saving the document
 430  
      * @see WorkflowDocumentActions#saveDocument(UserIdDTO, RouteHeaderDTO, String)
 431  
      */
 432  
     public void saveDocument(String annotation) throws WorkflowException {
 433  0
             createDocumentIfNeccessary();
 434  0
             routeHeader = getWorkflowDocumentActions().saveDocument(principalId, getRouteHeader(), annotation);
 435  0
             documentContentDirty = true;
 436  0
     }
 437  
 
 438  
     /**
 439  
      * Performs the 'route' action on the document this WorkflowDocument represents.  If this is a new document,
 440  
      * the document is created first.
 441  
      * @param annotation the message to log for the action
 442  
      * @throws WorkflowException in case an error occurs routing the document
 443  
      * @see WorkflowDocumentActions#routeDocument(UserIdDTO, RouteHeaderDTO, String)
 444  
      */
 445  
     public void routeDocument(String annotation) throws WorkflowException {
 446  0
             createDocumentIfNeccessary();
 447  0
             routeHeader = getWorkflowDocumentActions().routeDocument(principalId, routeHeader, annotation);
 448  0
             documentContentDirty = true;
 449  0
     }
 450  
 
 451  
     /**
 452  
      * Performs the 'disapprove' action on the document this WorkflowDocument represents.  If this is a new document,
 453  
      * the document is created first.
 454  
      * @param annotation the message to log for the action
 455  
      * @throws WorkflowException in case an error occurs disapproving the document
 456  
      * @see WorkflowDocumentActions#disapproveDocument(UserIdDTO, RouteHeaderDTO, String)
 457  
      */
 458  
     public void disapprove(String annotation) throws WorkflowException {
 459  0
             createDocumentIfNeccessary();
 460  0
             routeHeader = getWorkflowDocumentActions().disapproveDocument(principalId, getRouteHeader(), annotation);
 461  0
             documentContentDirty = true;
 462  0
     }
 463  
 
 464  
     /**
 465  
      * Performs the 'approve' action on the document this WorkflowDocument represents.  If this is a new document,
 466  
      * the document is created first.
 467  
      * @param annotation the message to log for the action
 468  
      * @throws WorkflowException in case an error occurs approving the document
 469  
      * @see WorkflowDocumentActions#approveDocument(UserIdDTO, RouteHeaderDTO, String)
 470  
      */
 471  
     public void approve(String annotation) throws WorkflowException {
 472  0
             createDocumentIfNeccessary();
 473  0
             routeHeader = getWorkflowDocumentActions().approveDocument(principalId, getRouteHeader(), annotation);
 474  0
             documentContentDirty = true;
 475  0
     }
 476  
 
 477  
     /**
 478  
      * Performs the 'cancel' action on the document this WorkflowDocument represents.  If this is a new document,
 479  
      * the document is created first.
 480  
      * @param annotation the message to log for the action
 481  
      * @throws WorkflowException in case an error occurs canceling the document
 482  
      * @see WorkflowDocumentActions#cancelDocument(UserIdDTO, RouteHeaderDTO, String)
 483  
      */
 484  
     public void cancel(String annotation) throws WorkflowException {
 485  0
             createDocumentIfNeccessary();
 486  0
             routeHeader = getWorkflowDocumentActions().cancelDocument(principalId, getRouteHeader(), annotation);
 487  0
             documentContentDirty = true;
 488  0
     }
 489  
 
 490  
     /**
 491  
      * Performs the 'blanket-approve' action on the document this WorkflowDocument represents.  If this is a new document,
 492  
      * the document is created first.
 493  
      * @param annotation the message to log for the action
 494  
      * @throws WorkflowException in case an error occurs blanket-approving the document
 495  
      * @see WorkflowDocumentActions#blanketApprovalToNodes(UserIdDTO, RouteHeaderDTO, String, String[])
 496  
      */
 497  
     public void blanketApprove(String annotation) throws WorkflowException {
 498  0
         blanketApprove(annotation, (String)null);
 499  0
     }
 500  
 
 501  
     /**
 502  
      * Commits any changes made to the local copy of this document to the workflow system.  If this is a new document,
 503  
      * the document is created first.
 504  
      * @throws WorkflowException in case an error occurs saving the document
 505  
      * @see WorkflowDocumentActions#saveRoutingData(UserIdDTO, RouteHeaderDTO)
 506  
      */
 507  
     public void saveRoutingData() throws WorkflowException {
 508  0
             createDocumentIfNeccessary();
 509  0
             routeHeader = getWorkflowDocumentActions().saveRoutingData(principalId, getRouteHeader());
 510  0
             documentContentDirty = true;
 511  0
     }
 512  
 
 513  
     /**
 514  
      * 
 515  
      * This method sets the Application Document Status and then calls saveRoutingData() to commit 
 516  
      * the changes to the workflow system.
 517  
      * 
 518  
      * @param appDocStatus
 519  
      * @throws WorkflowException
 520  
      */
 521  
     public void updateAppDocStatus(String appDocStatus) throws WorkflowException {
 522  0
                getRouteHeader().setAppDocStatus(appDocStatus);
 523  0
                saveRoutingData();
 524  0
     }
 525  
     
 526  
     /**
 527  
      * Performs the 'acknowledge' action on the document this WorkflowDocument represents.  If this is a new document,
 528  
      * the document is created first.
 529  
      * @param annotation the message to log for the action
 530  
      * @throws WorkflowException in case an error occurs acknowledging the document
 531  
      * @see WorkflowDocumentActions#acknowledgeDocument(UserIdDTO, RouteHeaderDTO, String)
 532  
      */
 533  
     public void acknowledge(String annotation) throws WorkflowException {
 534  0
             createDocumentIfNeccessary();
 535  0
             routeHeader = getWorkflowDocumentActions().acknowledgeDocument(principalId, getRouteHeader(), annotation);
 536  0
             documentContentDirty = true;
 537  0
     }
 538  
 
 539  
     /**
 540  
      * Performs the 'fyi' action on the document this WorkflowDocument represents.  If this is a new document,
 541  
      * the document is created first.
 542  
      * @param annotation the message to log for the action
 543  
      * @throws WorkflowException in case an error occurs fyi-ing the document
 544  
      */
 545  
     public void fyi() throws WorkflowException {
 546  0
             createDocumentIfNeccessary();
 547  0
             routeHeader = getWorkflowDocumentActions().clearFYIDocument(principalId, getRouteHeader());
 548  0
             documentContentDirty = true;
 549  0
     }
 550  
 
 551  
     /**
 552  
      * Performs the 'delete' action on the document this WorkflowDocument represents.  If this is a new document,
 553  
      * the document is created first.
 554  
      * @param annotation the message to log for the action
 555  
      * @throws WorkflowException in case an error occurs deleting the document
 556  
      * @see WorkflowDocumentActions#deleteDocument(UserIdDTO, RouteHeaderDTO)
 557  
      */
 558  
     public void delete() throws WorkflowException {
 559  0
             createDocumentIfNeccessary();
 560  0
             getWorkflowDocumentActions().deleteDocument(principalId, getRouteHeader());
 561  0
             documentContentDirty = true;
 562  0
     }
 563  
 
 564  
     /**
 565  
      * Reloads the document route header.  If this is a new document, the document is created first.
 566  
      * Next time document content is accessed, an up-to-date copy will be retrieved from workflow.
 567  
      * @throws WorkflowException in case an error occurs retrieving the route header
 568  
      */
 569  
     public void refreshContent() throws WorkflowException {
 570  0
             createDocumentIfNeccessary();
 571  0
             routeHeader = getWorkflowUtility().getRouteHeader(getDocumentId());
 572  0
             documentContentDirty = true;
 573  0
     }
 574  
 
 575  
     /**
 576  
      * Sends an ad hoc request to the specified user at the current active node on the document.  If the document is
 577  
      * in a terminal state, the request will be attached to the terminal node.
 578  
      */
 579  
     public void adHocRouteDocumentToPrincipal(String actionRequested, String annotation, String principalId, String responsibilityDesc, boolean forceAction) throws WorkflowException {
 580  0
             adHocRouteDocumentToPrincipal(actionRequested, null, annotation, principalId, responsibilityDesc, forceAction);
 581  0
     }
 582  
 
 583  
     /**
 584  
      * Sends an ad hoc request to the specified user at the specified node on the document.  If the document is
 585  
      * in a terminal state, the request will be attached to the terminal node.
 586  
      */
 587  
     public void adHocRouteDocumentToPrincipal(String actionRequested, String nodeName, String annotation, String principalId, String responsibilityDesc, boolean forceAction) throws WorkflowException {
 588  0
             adHocRouteDocumentToPrincipal(actionRequested, nodeName, annotation, principalId, responsibilityDesc, forceAction, null);
 589  0
     }
 590  
 
 591  
     /**
 592  
      * Sends an ad hoc request to the specified user at the specified node on the document.  If the document is
 593  
      * in a terminal state, the request will be attached to the terminal node.
 594  
      */
 595  
     public void adHocRouteDocumentToPrincipal(String actionRequested, String nodeName, String annotation, String principalId, String responsibilityDesc, boolean forceAction, String requestLabel) throws WorkflowException {
 596  0
             createDocumentIfNeccessary();
 597  0
             routeHeader = getWorkflowDocumentActions().adHocRouteDocumentToPrincipal(principalId, getRouteHeader(), actionRequested, nodeName, annotation, principalId, responsibilityDesc, forceAction, requestLabel);
 598  0
             documentContentDirty = true;
 599  0
     }
 600  
 
 601  
     /**
 602  
      * Sends an ad hoc request to the specified workgroup at the current active node on the document.  If the document is
 603  
      * in a terminal state, the request will be attached to the terminal node.
 604  
      */
 605  
     public void adHocRouteDocumentToGroup(String actionRequested, String annotation, String groupId, String responsibilityDesc, boolean forceAction) throws WorkflowException {
 606  0
             adHocRouteDocumentToGroup(actionRequested, null, annotation, groupId, responsibilityDesc, forceAction);
 607  0
     }
 608  
 
 609  
     /**
 610  
      * Sends an ad hoc request to the specified workgroup at the specified node on the document.  If the document is
 611  
      * in a terminal state, the request will be attached to the terminal node.
 612  
      */
 613  
     public void adHocRouteDocumentToGroup(String actionRequested, String nodeName, String annotation, String groupId, String responsibilityDesc, boolean forceAction) throws WorkflowException {
 614  0
             adHocRouteDocumentToGroup(actionRequested, nodeName, annotation, groupId, responsibilityDesc, forceAction, null);
 615  0
     }
 616  
 
 617  
     /**
 618  
      * Sends an ad hoc request to the specified workgroup at the specified node on the document.  If the document is
 619  
      * in a terminal state, the request will be attached to the terminal node.
 620  
      */
 621  
     public void adHocRouteDocumentToGroup(String actionRequested, String nodeName, String annotation, String groupId, String responsibilityDesc, boolean forceAction, String requestLabel) throws WorkflowException {
 622  0
             createDocumentIfNeccessary();
 623  0
             routeHeader = getWorkflowDocumentActions().adHocRouteDocumentToGroup(principalId, getRouteHeader(), actionRequested, nodeName, annotation, groupId, responsibilityDesc, forceAction, requestLabel);
 624  0
             documentContentDirty = true;
 625  0
     }
 626  
 
 627  
     /**
 628  
      * Revokes AdHoc request(s) according to the given AdHocRevokeVO which is passed in.
 629  
      *
 630  
      * If a specific action request ID is specified on the revoke bean, and that ID is not a valid ID, this method should throw a
 631  
      * WorkflowException.
 632  
      * @param revoke AdHocRevokeVO
 633  
      * @param annotation message to note for this action
 634  
      * @throws WorkflowException if an error occurs revoking adhoc requests
 635  
      * @see WorkflowDocumentActions#revokeAdHocRequests(UserIdDTO, RouteHeaderDTO, AdHocRevokeDTO, String)
 636  
      */
 637  
     public void revokeAdHocRequests(AdHocRevokeDTO revoke, String annotation) throws WorkflowException {
 638  0
             if (getRouteHeader().getDocumentId() == null) {
 639  0
                     throw new WorkflowException("Can't revoke request, the workflow document has not yet been created!");
 640  
             }
 641  0
             createDocumentIfNeccessary();
 642  0
             routeHeader = getWorkflowDocumentActions().revokeAdHocRequests(principalId, getRouteHeader(), revoke, annotation);
 643  0
             documentContentDirty = true;
 644  0
     }
 645  
 
 646  
     /**
 647  
      * Sets the title of the document, empty string if null is specified.
 648  
      * @param title title of the document to set, or null
 649  
      */
 650  
     // WorkflowException is declared but not thrown...
 651  
     public void setTitle(String title) throws WorkflowException {
 652  0
         if (title == null) {
 653  0
             title = "";
 654  
         }
 655  0
         if (title.length() > KEWConstants.TITLE_MAX_LENGTH) {
 656  0
             title = title.substring(0, KEWConstants.TITLE_MAX_LENGTH);
 657  
         }
 658  0
         getRouteHeader().setDocTitle(title);
 659  0
     }
 660  
 
 661  
     /**
 662  
      * Returns the document type of the workflow document
 663  
      * @return the document type of the workflow document
 664  
      * @throws RuntimeException if document does not exist (is not yet created)
 665  
      * @see RouteHeaderDTO#getDocTypeName()
 666  
      */
 667  
     public String getDocumentType() {
 668  0
         if (getRouteHeader() == null) {
 669  
             // HACK: FIXME: we should probably proscribe, or at least handle consistently, these corner cases
 670  
             // NPEs are not nice
 671  0
             throw new RuntimeException("No such document!");
 672  
         }
 673  0
         return getRouteHeader().getDocTypeName();
 674  
     }
 675  
 
 676  
     /**
 677  
      * Returns whether an acknowledge is requested of the user for this document.  This is
 678  
      * a convenience method that delegates to {@link RouteHeaderDTO#isAckRequested()}.
 679  
      * @return whether an acknowledge is requested of the user for this document
 680  
      * @see RouteHeaderDTO#isAckRequested()
 681  
      */
 682  
     public boolean isAcknowledgeRequested() {
 683  0
         return getRouteHeader().isAckRequested();
 684  
     }
 685  
 
 686  
     /**
 687  
      * Returns whether an approval is requested of the user for this document.  This is
 688  
      * a convenience method that delegates to {@link RouteHeaderDTO#isApproveRequested()}.
 689  
      * @return whether an approval is requested of the user for this document
 690  
      * @see RouteHeaderDTO#isApproveRequested()
 691  
      */
 692  
     public boolean isApprovalRequested() {
 693  0
         return getRouteHeader().isApproveRequested();
 694  
     }
 695  
 
 696  
     /**
 697  
      * Returns whether a completion is requested of the user for this document.  This is
 698  
      * a convenience method that delegates to {@link RouteHeaderDTO#isCompleteRequested()}.
 699  
      * @return whether an approval is requested of the user for this document
 700  
      * @see RouteHeaderDTO#isCompleteRequested()
 701  
      */
 702  
     public boolean isCompletionRequested() {
 703  0
         return getRouteHeader().isCompleteRequested();
 704  
     }
 705  
 
 706  
     /**
 707  
      * Returns whether an FYI is requested of the user for this document.  This is
 708  
      * a convenience method that delegates to {@link RouteHeaderDTO#isFyiRequested()}.
 709  
      * @return whether an FYI is requested of the user for this document
 710  
      * @see RouteHeaderDTO#isFyiRequested()
 711  
      */
 712  
     public boolean isFYIRequested() {
 713  0
         return getRouteHeader().isFyiRequested();
 714  
     }
 715  
 
 716  
     /**
 717  
      * Returns whether the user can blanket approve the document
 718  
      * @return whether the user can blanket approve the document
 719  
      * @see RouteHeaderDTO#getValidActions()
 720  
      */
 721  
     public boolean isBlanketApproveCapable() {
 722  
         // TODO delyea - refactor this to take into account non-initiator owned documents
 723  0
             return getRouteHeader().getValidActions().contains(KEWConstants.ACTION_TAKEN_BLANKET_APPROVE_CD) && (isCompletionRequested() || isApprovalRequested() || stateIsInitiated());
 724  
     }
 725  
 
 726  
     /**
 727  
      * Returns whether the specified action code is valid for the current user and document
 728  
      * @return whether the user can blanket approve the document
 729  
      * @see RouteHeaderDTO#getValidActions()
 730  
      */
 731  
     public boolean isActionCodeValidForDocument(String actionTakenCode) {
 732  0
             return getRouteHeader().getValidActions().contains(actionTakenCode);
 733  
     }
 734  
 
 735  
     /**
 736  
      * Performs the 'super-user-approve' action on the document this WorkflowDocument represents.  If this is a new document,
 737  
      * the document is created first.
 738  
      * @param annotation the message to log for the action
 739  
      * @throws WorkflowException in case an error occurs super-user-approve-ing the document
 740  
      * @see WorkflowDocumentActions#superUserApprove(UserIdDTO, RouteHeaderDTO, String)
 741  
      */
 742  
     public void superUserApprove(String annotation) throws WorkflowException {
 743  0
             createDocumentIfNeccessary();
 744  0
             routeHeader = getWorkflowDocumentActions().superUserApprove(principalId, getRouteHeader(), annotation);
 745  0
             documentContentDirty = true;
 746  0
     }
 747  
 
 748  
     /**
 749  
      * Performs the 'super-user-action-request-approve' action on the document this WorkflowDocument represents and the action
 750  
      * request the id represents.
 751  
      * @param actionRequestId the action request id for the action request the super user is approved
 752  
      * @param annotation the message to log for the action
 753  
      * @throws WorkflowException in case an error occurs super-user-action-request-approve-ing the document
 754  
      * @see WorkflowDocumentActions#superUserApprove(UserIdVO, RouteHeaderVO, String)(UserIdVO, RouteHeaderVO, String)
 755  
      */
 756  
     public void superUserActionRequestApprove(Long actionRequestId, String annotation) throws WorkflowException {
 757  0
             createDocumentIfNeccessary();
 758  0
             routeHeader = getWorkflowDocumentActions().superUserActionRequestApprove(principalId, getRouteHeader(), actionRequestId, annotation);
 759  0
             documentContentDirty = true;
 760  0
     }
 761  
 
 762  
     /**
 763  
      * Performs the 'super-user-disapprove' action on the document this WorkflowDocument represents.  If this is a new document,
 764  
      * the document is created first.
 765  
      * @param annotation the message to log for the action
 766  
      * @throws WorkflowException in case an error occurs super-user-disapprove-ing the document
 767  
      * @see WorkflowDocumentActions#superUserDisapprove(UserIdDTO, RouteHeaderDTO, String)
 768  
      */
 769  
     public void superUserDisapprove(String annotation) throws WorkflowException {
 770  0
             createDocumentIfNeccessary();
 771  0
             routeHeader = getWorkflowDocumentActions().superUserDisapprove(principalId, getRouteHeader(), annotation);
 772  0
             documentContentDirty = true;
 773  0
     }
 774  
 
 775  
     /**
 776  
      * Performs the 'super-user-cancel' action on the document this WorkflowDocument represents.  If this is a new document,
 777  
      * the document is created first.
 778  
      * @param annotation the message to log for the action
 779  
      * @throws WorkflowException in case an error occurs super-user-cancel-ing the document
 780  
      * @see WorkflowDocumentActions#superUserCancel(UserIdDTO, RouteHeaderDTO, String)
 781  
      */
 782  
     public void superUserCancel(String annotation) throws WorkflowException {
 783  0
             createDocumentIfNeccessary();
 784  0
             routeHeader = getWorkflowDocumentActions().superUserCancel(principalId, getRouteHeader(), annotation);
 785  0
             documentContentDirty = true;
 786  0
     }
 787  
 
 788  
     /**
 789  
      * Returns whether the user is a super user on this document
 790  
      * @return whether the user is a super user on this document
 791  
      * @throws WorkflowException if an error occurs determining whether the user is a super user on this document
 792  
      * @see WorkflowUtility#isSuperUserForDocumentType(UserIdDTO, Long)
 793  
      */
 794  
     public boolean isSuperUser() throws WorkflowException {
 795  0
             createDocumentIfNeccessary();
 796  0
             return getWorkflowUtility().isSuperUserForDocumentType(principalId, getRouteHeader().getDocTypeId());
 797  
         }
 798  
 
 799  
     /**
 800  
      * Returns whether the user passed into WorkflowDocument at instantiation can route
 801  
      * the document.
 802  
          * @return if user passed into WorkflowDocument at instantiation can route
 803  
          *         the document.
 804  
          */
 805  
     public boolean isRouteCapable() {
 806  0
         return isActionCodeValidForDocument(KEWConstants.ACTION_TAKEN_ROUTED_CD);
 807  
     }
 808  
 
 809  
     /**
 810  
      * Performs the 'clearFYI' action on the document this WorkflowDocument represents.  If this is a new document,
 811  
      * the document is created first.
 812  
      * @param annotation the message to log for the action
 813  
      * @throws WorkflowException in case an error occurs clearing FYI on the document
 814  
      * @see WorkflowDocumentActions#clearFYIDocument(UserIdDTO, RouteHeaderDTO)
 815  
      */
 816  
     public void clearFYI() throws WorkflowException {
 817  0
             createDocumentIfNeccessary();
 818  0
             getWorkflowDocumentActions().clearFYIDocument(principalId, getRouteHeader());
 819  0
             documentContentDirty = true;
 820  0
     }
 821  
 
 822  
     /**
 823  
      * Performs the 'complete' action on the document this WorkflowDocument represents.  If this is a new document,
 824  
      * the document is created first.
 825  
      * @param annotation the message to log for the action
 826  
      * @throws WorkflowException in case an error occurs clearing completing the document
 827  
      * @see WorkflowDocumentActions#completeDocument(UserIdDTO, RouteHeaderDTO, String)
 828  
      */
 829  
     public void complete(String annotation) throws WorkflowException {
 830  0
             createDocumentIfNeccessary();
 831  0
             routeHeader = getWorkflowDocumentActions().completeDocument(principalId, getRouteHeader(), annotation);
 832  0
             documentContentDirty = true;
 833  0
     }
 834  
 
 835  
     /**
 836  
      * Performs the 'logDocumentAction' action on the document this WorkflowDocument represents.  If this is a new document,
 837  
      * the document is created first.  The 'logDocumentAction' simply logs a message on the document.
 838  
      * @param annotation the message to log for the action
 839  
      * @throws WorkflowException in case an error occurs logging a document action on the document
 840  
      * @see WorkflowDocumentActions#logDocumentAction(UserIdDTO, RouteHeaderDTO, String)
 841  
      */
 842  
     public void logDocumentAction(String annotation) throws WorkflowException {
 843  0
             createDocumentIfNeccessary();
 844  0
             getWorkflowDocumentActions().logDocumentAction(principalId, getRouteHeader(), annotation);
 845  0
             documentContentDirty = true;
 846  0
     }
 847  
 
 848  
     /**
 849  
      * Indicates if the document is in the initiated state or not.
 850  
      *
 851  
      * @return true if in the specified state
 852  
      */
 853  
     public boolean stateIsInitiated() {
 854  0
         return KEWConstants.ROUTE_HEADER_INITIATED_CD.equals(getRouteHeader().getDocRouteStatus());
 855  
     }
 856  
 
 857  
     /**
 858  
      * Indicates if the document is in the saved state or not.
 859  
      *
 860  
      * @return true if in the specified state
 861  
      */
 862  
     public boolean stateIsSaved() {
 863  0
         return KEWConstants.ROUTE_HEADER_SAVED_CD.equals(getRouteHeader().getDocRouteStatus());
 864  
     }
 865  
 
 866  
     /**
 867  
      * Indicates if the document is in the enroute state or not.
 868  
      *
 869  
      * @return true if in the specified state
 870  
      */
 871  
     public boolean stateIsEnroute() {
 872  0
         return KEWConstants.ROUTE_HEADER_ENROUTE_CD.equals(getRouteHeader().getDocRouteStatus());
 873  
     }
 874  
 
 875  
     /**
 876  
      * Indicates if the document is in the exception state or not.
 877  
      *
 878  
      * @return true if in the specified state
 879  
      */
 880  
     public boolean stateIsException() {
 881  0
         return KEWConstants.ROUTE_HEADER_EXCEPTION_CD.equals(getRouteHeader().getDocRouteStatus());
 882  
     }
 883  
 
 884  
     /**
 885  
      * Indicates if the document is in the canceled state or not.
 886  
      *
 887  
      * @return true if in the specified state
 888  
      */
 889  
     public boolean stateIsCanceled() {
 890  0
         return KEWConstants.ROUTE_HEADER_CANCEL_CD.equals(getRouteHeader().getDocRouteStatus());
 891  
     }
 892  
 
 893  
     /**
 894  
      * Indicates if the document is in the disapproved state or not.
 895  
      *
 896  
      * @return true if in the specified state
 897  
      */
 898  
     public boolean stateIsDisapproved() {
 899  0
         return KEWConstants.ROUTE_HEADER_DISAPPROVED_CD.equals(getRouteHeader().getDocRouteStatus());
 900  
     }
 901  
 
 902  
     /**
 903  
      * Indicates if the document is in the approved state or not. Will answer true is document is in Processed or Finalized state.
 904  
      *
 905  
      * @return true if in the specified state
 906  
      */
 907  
     public boolean stateIsApproved() {
 908  0
         return KEWConstants.ROUTE_HEADER_APPROVED_CD.equals(getRouteHeader().getDocRouteStatus()) || stateIsProcessed() || stateIsFinal();
 909  
     }
 910  
 
 911  
     /**
 912  
      * Indicates if the document is in the processed state or not.
 913  
      *
 914  
      * @return true if in the specified state
 915  
      */
 916  
     public boolean stateIsProcessed() {
 917  0
         return KEWConstants.ROUTE_HEADER_PROCESSED_CD.equals(getRouteHeader().getDocRouteStatus());
 918  
     }
 919  
 
 920  
     /**
 921  
      * Indicates if the document is in the final state or not.
 922  
      *
 923  
      * @return true if in the specified state
 924  
      */
 925  
     public boolean stateIsFinal() {
 926  0
         return KEWConstants.ROUTE_HEADER_FINAL_CD.equals(getRouteHeader().getDocRouteStatus());
 927  
     }
 928  
 
 929  
     /**
 930  
      * Returns the display value of the current document status
 931  
      * @return the display value of the current document status
 932  
      */
 933  
     public String getStatusDisplayValue() {
 934  0
         return (String) KEWConstants.DOCUMENT_STATUSES.get(getRouteHeader().getDocRouteStatus());
 935  
     }
 936  
 
 937  
     /**
 938  
      * Returns the principalId with which this WorkflowDocument was constructed
 939  
      * @return the principalId with which this WorkflowDocument was constructed
 940  
      */
 941  
     public String getPrincipalId() {
 942  0
         return principalId;
 943  
     }
 944  
 
 945  
     /**
 946  
      * Sets the principalId under which actions against this document should be taken
 947  
      * @param principalId principalId under which actions against this document should be taken
 948  
      */
 949  
     public void setPrincipalId(String principalId) {
 950  0
         this.principalId = principalId;
 951  0
     }
 952  
 
 953  
     /**
 954  
      * Checks if the document has been created or not (i.e. has a document id or not) and issues
 955  
      * a call to the server to create the document if it has not yet been created.
 956  
      *
 957  
      * Also checks if the document content has been updated and saves it if it has.
 958  
      */
 959  
     private void createDocumentIfNeccessary() throws WorkflowException {
 960  0
             if (getRouteHeader().getDocumentId() == null) {
 961  0
                     routeHeader = getWorkflowDocumentActions().createDocument(principalId, getRouteHeader());
 962  
             }
 963  0
             if (documentContent != null && documentContent.isModified()) {
 964  0
                     saveDocumentContent(documentContent);
 965  
             }
 966  0
     }
 967  
 
 968  
     /**
 969  
      * Like handleException except it returns a RuntimeException.
 970  
      */
 971  
     private RuntimeException handleExceptionAsRuntime(Exception e) {
 972  0
             if (e instanceof RuntimeException) {
 973  0
                     return (RuntimeException)e;
 974  
             }
 975  0
             return new WorkflowRuntimeException(e);
 976  
     }
 977  
 
 978  
     // WORKFLOW 2.1: new methods
 979  
 
 980  
     /**
 981  
      * Performs the 'blanketApprove' action on the document this WorkflowDocument represents.  If this is a new document,
 982  
      * the document is created first.
 983  
      * @param annotation the message to log for the action
 984  
      * @param nodeName the extent to which to blanket approve; blanket approval will stop at this node
 985  
      * @throws WorkflowException in case an error occurs blanket-approving the document
 986  
      * @see WorkflowDocumentActions#blanketApprovalToNodes(UserIdDTO, RouteHeaderDTO, String, String[])
 987  
      */
 988  
     public void blanketApprove(String annotation, String nodeName) throws WorkflowException {
 989  0
         blanketApprove(annotation, (nodeName == null ? new String[] {} : new String[] { nodeName }));
 990  0
     }
 991  
 
 992  
     /**
 993  
      * Performs the 'blanketApprove' action on the document this WorkflowDocument represents.  If this is a new document,
 994  
      * the document is created first.
 995  
      * @param annotation the message to log for the action
 996  
      * @param nodeNames the nodes at which blanket approval will stop (in case the blanket approval traverses a split, in which case there may be multiple "active" nodes)
 997  
      * @throws WorkflowException in case an error occurs blanket-approving the document
 998  
      * @see WorkflowDocumentActions#blanketApprovalToNodes(UserIdDTO, RouteHeaderDTO, String, String[])
 999  
      */
 1000  
     public void blanketApprove(String annotation, String[] nodeNames) throws WorkflowException {
 1001  0
             createDocumentIfNeccessary();
 1002  0
             routeHeader = getWorkflowDocumentActions().blanketApprovalToNodes(principalId, getRouteHeader(), annotation, nodeNames);
 1003  0
             documentContentDirty = true;
 1004  0
     }
 1005  
 
 1006  
     /**
 1007  
      * The user taking action removes the action items for this workgroup and document from all other
 1008  
      * group members' action lists.   If this is a new document, the document is created first.
 1009  
      *
 1010  
      * @param annotation the message to log for the action
 1011  
      * @param workgroupId the workgroup on which to take authority
 1012  
      * @throws WorkflowException user taking action is not in workgroup
 1013  
      */
 1014  
     public void takeGroupAuthority(String annotation, String groupId) throws WorkflowException {
 1015  0
             createDocumentIfNeccessary();
 1016  0
             routeHeader = getWorkflowDocumentActions().takeGroupAuthority(principalId, getRouteHeader(), groupId, annotation);
 1017  0
             documentContentDirty = true;
 1018  0
     }
 1019  
 
 1020  
     /**
 1021  
      * The user that took the group authority is putting the action items back in the other users action lists.
 1022  
      * If this is a new document, the document is created first.
 1023  
      *
 1024  
      * @param annotation the message to log for the action
 1025  
      * @param workgroupId the workgroup on which to take authority
 1026  
      * @throws WorkflowException user taking action is not in workgroup or did not take workgroup authority
 1027  
      */
 1028  
     public void releaseGroupAuthority(String annotation, String groupId) throws WorkflowException {
 1029  0
             createDocumentIfNeccessary();
 1030  0
             routeHeader = getWorkflowDocumentActions().releaseGroupAuthority(principalId, getRouteHeader(), groupId, annotation);
 1031  0
             documentContentDirty = true;
 1032  0
     }
 1033  
 
 1034  
     /**
 1035  
      * Returns names of all active nodes the document is currently at.
 1036  
      *
 1037  
      * @return names of all active nodes the document is currently at.
 1038  
      * @throws WorkflowException if there is an error obtaining the currently active nodes on the document
 1039  
      * @see WorkflowUtility#getActiveNodeInstances(Long)
 1040  
      */
 1041  
     public String[] getNodeNames() throws WorkflowException {
 1042  0
             RouteNodeInstanceDTO[] activeNodeInstances = getWorkflowUtility().getActiveNodeInstances(getDocumentId());
 1043  0
             String[] nodeNames = new String[(activeNodeInstances == null ? 0 : activeNodeInstances.length)];
 1044  0
             for (int index = 0; index < activeNodeInstances.length; index++) {
 1045  0
                     nodeNames[index] = activeNodeInstances[index].getName();
 1046  
             }
 1047  0
             return nodeNames;
 1048  
     }
 1049  
 
 1050  
     /**
 1051  
      * Performs the 'returnToPrevious' action on the document this WorkflowDocument represents.  If this is a new document,
 1052  
      * the document is created first.
 1053  
      * @param annotation the message to log for the action
 1054  
      * @param nodeName the node to return to
 1055  
      * @throws WorkflowException in case an error occurs returning to previous node
 1056  
      * @see WorkflowDocumentActions#returnDocumentToPreviousNode(UserIdDTO, RouteHeaderDTO, ReturnPointDTO, String)
 1057  
      */
 1058  
     public void returnToPreviousNode(String annotation, String nodeName) throws WorkflowException {
 1059  0
         ReturnPointDTO returnPoint = new ReturnPointDTO(nodeName);
 1060  0
         returnToPreviousNode(annotation, returnPoint);
 1061  0
     }
 1062  
 
 1063  
     /**
 1064  
      * Performs the 'returnToPrevious' action on the document this WorkflowDocument represents.  If this is a new document,
 1065  
      * the document is created first.
 1066  
      * @param annotation the message to log for the action
 1067  
      * @param ReturnPointDTO the node to return to
 1068  
      * @throws WorkflowException in case an error occurs returning to previous node
 1069  
      * @see WorkflowDocumentActions#returnDocumentToPreviousNode(UserIdDTO, RouteHeaderDTO, ReturnPointDTO, String)
 1070  
      */
 1071  
     public void returnToPreviousNode(String annotation, ReturnPointDTO returnPoint) throws WorkflowException {
 1072  0
             createDocumentIfNeccessary();
 1073  0
             routeHeader = getWorkflowDocumentActions().returnDocumentToPreviousNode(principalId, getRouteHeader(), returnPoint, annotation);
 1074  0
             documentContentDirty = true;
 1075  0
     }
 1076  
 
 1077  
     /**
 1078  
      * Moves the document from a current node in it's route to another node.  If this is a new document,
 1079  
      * the document is created first.
 1080  
      * @param MovePointDTO VO representing the node at which to start, and the number of steps to move (negative steps is reverse)
 1081  
      * @param annotation the message to log for the action
 1082  
      * @throws WorkflowException in case an error occurs moving the document
 1083  
      * @see WorkflowDocumentActions#moveDocument(UserIdDTO, RouteHeaderDTO, MovePointDTO, String)
 1084  
      */
 1085  
     public void moveDocument(MovePointDTO movePoint, String annotation) throws WorkflowException {
 1086  0
             createDocumentIfNeccessary();
 1087  0
             routeHeader =  getWorkflowDocumentActions().moveDocument(principalId, getRouteHeader(), movePoint, annotation);
 1088  0
             documentContentDirty = true;
 1089  0
     }
 1090  
 
 1091  
     /**
 1092  
      * Returns the route node instances that have been created so far during the life of this document.  This includes
 1093  
      * all previous instances which have already been processed and are no longer active.
 1094  
      * @return the route node instances that have been created so far during the life of this document
 1095  
      * @throws WorkflowException if there is an error getting the route node instances for the document
 1096  
      * @see WorkflowUtility#getDocumentRouteNodeInstances(Long)
 1097  
      */
 1098  
     public RouteNodeInstanceDTO[] getRouteNodeInstances() throws WorkflowException {
 1099  0
             return getWorkflowUtility().getDocumentRouteNodeInstances(getDocumentId());
 1100  
     }
 1101  
 
 1102  
     /**
 1103  
      * Returns Array of Route Nodes Names that can be safely returned to using the 'returnToPreviousXXX' methods.
 1104  
      * Names are sorted in reverse chronological order.
 1105  
      *
 1106  
      * @return array of Route Nodes Names that can be safely returned to using the 'returnToPreviousXXX' methods
 1107  
      * @throws WorkflowException if an error occurs obtaining the names of the previous route nodes for this document
 1108  
      * @see WorkflowUtility#getPreviousRouteNodeNames(Long)
 1109  
      */
 1110  
     public String[] getPreviousNodeNames() throws WorkflowException {
 1111  0
             return getWorkflowUtility().getPreviousRouteNodeNames(getDocumentId());
 1112  
         }
 1113  
 
 1114  
     /**
 1115  
      * Returns a document detail VO representing the route header along with action requests, actions taken,
 1116  
      * and route node instances.
 1117  
      * @return Returns a document detail VO representing the route header along with action requests, actions taken, and route node instances.
 1118  
      * @throws WorkflowException
 1119  
      */
 1120  
     public DocumentDetailDTO getDetail() throws WorkflowException {
 1121  0
             return getWorkflowUtility().getDocumentDetail(getDocumentId());
 1122  
     }
 1123  
 
 1124  
     /**
 1125  
      * Saves the given DocumentContentVO for this document.
 1126  
      * @param documentContent document content VO to store for this document
 1127  
      * @since 2.3
 1128  
      * @see WorkflowDocumentActions#saveDocumentContent(DocumentContentDTO)
 1129  
      */
 1130  
     public DocumentContentDTO saveDocumentContent(DocumentContentDTO documentContent) throws WorkflowException {
 1131  0
             if (documentContent.getDocumentId() == null) {
 1132  0
                     throw new WorkflowException("Document Content does not have a valid document ID.");
 1133  
             }
 1134  
             // important to check directly against getRouteHeader().getDocumentId() instead of just getDocumentId() because saveDocumentContent
 1135  
             // is called from createDocumentIfNeccessary which is called from getDocumentId().  If that method was used, we would have an infinite loop.
 1136  0
             if (!documentContent.getDocumentId().equals(getRouteHeader().getDocumentId())) {
 1137  0
                     throw new WorkflowException("Attempted to save content on this document with an invalid document id of " + documentContent.getDocumentId());
 1138  
             }
 1139  0
             DocumentContentDTO newDocumentContent = getWorkflowDocumentActions().saveDocumentContent(documentContent);
 1140  0
             this.documentContent = new ModifiableDocumentContentDTO(newDocumentContent);
 1141  0
             documentContentDirty = false;
 1142  0
             return this.documentContent;
 1143  
     }
 1144  
     
 1145  
     public void placeInExceptionRouting(String annotation) throws WorkflowException {
 1146  0
             createDocumentIfNeccessary();
 1147  0
             routeHeader = getWorkflowDocumentActions().placeInExceptionRouting(principalId, getRouteHeader(), annotation);
 1148  0
             documentContentDirty = true;
 1149  0
     }
 1150  
 
 1151  
     /**
 1152  
      * Returns a list of NoteVO representing the notes on the document
 1153  
      * @return a list of NoteVO representing the notes on the document
 1154  
      * @see RouteHeaderDTO#getNotes()
 1155  
      */
 1156  
     public List<NoteDTO> getNoteList(){
 1157  0
             List<NoteDTO> notesList = new ArrayList<NoteDTO>();
 1158  0
             NoteDTO[] notes = routeHeader.getNotes();
 1159  0
             if (notes != null){
 1160  0
                     for (int i=0; i<notes.length; i++){
 1161  0
                             if (! isDeletedNote(notes[i])){
 1162  0
                                     notesList.add(notes[i]);
 1163  
                             }
 1164  
                     }
 1165  
             }
 1166  0
             return notesList;
 1167  
     }
 1168  
 
 1169  
     /**
 1170  
      * Deletes a note from the document.  The deletion is deferred until the next time the document is committed (via an action).
 1171  
      * @param noteVO the note to remove from the document
 1172  
      */
 1173  
     public void deleteNote(NoteDTO noteVO){
 1174  0
             if (noteVO != null && noteVO.getNoteId()!=null){
 1175  0
                     NoteDTO noteToDelete = new NoteDTO();
 1176  0
                     noteToDelete.setNoteId(new Long(noteVO.getNoteId().longValue()));
 1177  
                     /*noteToDelete.setDocumentId(noteVO.getDocumentId());
 1178  
                     noteToDelete.setNoteAuthorWorkflowId(noteVO.getNoteAuthorWorkflowId());
 1179  
                     noteToDelete.setNoteCreateDate(noteVO.getNoteCreateDate());
 1180  
                     noteToDelete.setNoteText(noteVO.getNoteText());
 1181  
                     noteToDelete.setLockVerNbr(noteVO.getLockVerNbr());*/
 1182  0
                     increaseNotesToDeleteArraySizeByOne();
 1183  0
                     routeHeader.getNotesToDelete()[routeHeader.getNotesToDelete().length - 1]=noteToDelete;
 1184  
             }
 1185  0
     }
 1186  
 
 1187  
     /**
 1188  
      * Updates the note of the same note id, on the document. The update is deferred until the next time the document is committed (via an action).
 1189  
      * @param noteVO the note to update
 1190  
      */
 1191  
     public void updateNote (NoteDTO noteVO){
 1192  0
             boolean isUpdateNote = false;
 1193  0
             if (noteVO != null){
 1194  0
                     NoteDTO[] notes = routeHeader.getNotes();
 1195  0
                     NoteDTO  copyNote = new NoteDTO();
 1196  0
                         if (noteVO.getNoteId() != null){
 1197  0
                                 copyNote.setNoteId(new Long(noteVO.getNoteId().longValue()));
 1198  
                         }
 1199  
 
 1200  0
                         if (noteVO.getDocumentId() != null){
 1201  0
                                 copyNote.setDocumentId(noteVO.getDocumentId());
 1202  
                         } else {
 1203  0
                                 copyNote.setDocumentId(routeHeader.getDocumentId());
 1204  
                         }
 1205  
                         
 1206  0
                         if (noteVO.getNoteAuthorWorkflowId() != null){
 1207  0
                                 copyNote.setNoteAuthorWorkflowId(new String(noteVO.getNoteAuthorWorkflowId()));
 1208  
                         } else {
 1209  0
                             copyNote.setNoteAuthorWorkflowId(principalId.toString())        ;
 1210  
                         }
 1211  
 
 1212  0
                         if (noteVO.getNoteCreateDate() != null){
 1213  0
                                 Calendar cal = Calendar.getInstance();
 1214  0
                                 cal.setTimeInMillis(noteVO.getNoteCreateDate().getTimeInMillis());
 1215  0
                                 copyNote.setNoteCreateDate(cal);
 1216  0
                         } else {
 1217  0
                                 copyNote.setNoteCreateDate(Calendar.getInstance());
 1218  
                         }
 1219  
 
 1220  0
                         if (noteVO.getNoteText() != null){
 1221  0
                                 copyNote.setNoteText(new String(noteVO.getNoteText()));
 1222  
                         }
 1223  0
                         if (noteVO.getLockVerNbr() != null){
 1224  0
                                 copyNote.setLockVerNbr(new Integer(noteVO.getLockVerNbr().intValue()));
 1225  
                         }
 1226  0
                     if (notes != null){
 1227  0
                             for (int i=0; i<notes.length; i++){
 1228  0
                                     if (notes[i].getNoteId()!= null && notes[i].getNoteId().equals(copyNote.getNoteId())){
 1229  0
                                             notes[i] = copyNote;
 1230  0
                                             isUpdateNote = true;
 1231  0
                                             break;
 1232  
                                     }
 1233  
                             }
 1234  
                     }
 1235  
                     // add new note to the notes array
 1236  0
                     if (! isUpdateNote){
 1237  0
                             copyNote.setNoteId(null);
 1238  0
                             increaseNotesArraySizeByOne();
 1239  0
                             routeHeader.getNotes()[routeHeader.getNotes().length-1]= copyNote;
 1240  
                     }
 1241  
             }
 1242  0
     }
 1243  
 
 1244  
     /**
 1245  
      * Sets a variable on the document.  The assignment is deferred until the next time the document is committed (via an action).
 1246  
      * @param name name of the variable
 1247  
      * @param value value of the variable
 1248  
      */
 1249  
     public void setVariable(String name, String value) throws WorkflowException {
 1250  0
             createDocumentIfNeccessary();
 1251  0
         getRouteHeader().setVariable(name, value);
 1252  0
     }
 1253  
 
 1254  
     /**
 1255  
      * Gets the value of a variable on the document, creating the document first if it does not exist.
 1256  
      * @param name variable name
 1257  
      * @return variable value
 1258  
      */
 1259  
     public String getVariable(String name) throws WorkflowException {
 1260  0
             createDocumentIfNeccessary();
 1261  0
         return getRouteHeader().getVariable(name);
 1262  
     }
 1263  
 
 1264  
     /**
 1265  
      *
 1266  
      * Tells workflow that the current the document is constructed as will receive all future requests routed to them
 1267  
      * disregarding any force action flags set on the action request.  Uses the setVariable method behind the seens so
 1268  
      * an action needs taken on the document to set this state on the document.
 1269  
      *
 1270  
      * @throws WorkflowException
 1271  
      */
 1272  
     public void setReceiveFutureRequests() throws WorkflowException {
 1273  0
         WorkflowUtility workflowUtility = getWorkflowUtility();
 1274  0
         this.setVariable(workflowUtility.getFutureRequestsKey(principalId), workflowUtility.getReceiveFutureRequestsValue());
 1275  0
     }
 1276  
 
 1277  
     /**
 1278  
      * Tell workflow that the current document is constructed as will not receive any future requests routed to them
 1279  
      * disregarding any force action flags set on action requests.  Uses the setVariable method behind the scenes so
 1280  
      * an action needs taken on the document to set this state on the document.
 1281  
      *
 1282  
      * @throws WorkflowException
 1283  
      */
 1284  
     public void setDoNotReceiveFutureRequests() throws WorkflowException {
 1285  0
         WorkflowUtility workflowUtility = getWorkflowUtility();
 1286  0
         this.setVariable(workflowUtility.getFutureRequestsKey(principalId), workflowUtility.getDoNotReceiveFutureRequestsValue());
 1287  0
     }
 1288  
 
 1289  
     /**
 1290  
      * Clears any state set on the document regarding a user receiving or not receiving action requests.  Uses the setVariable method
 1291  
      * behind the seens so an action needs taken on the document to set this state on the document.
 1292  
      *
 1293  
      * @throws WorkflowException
 1294  
      */
 1295  
     public void setClearFutureRequests() throws WorkflowException {
 1296  0
         WorkflowUtility workflowUtility = getWorkflowUtility();
 1297  0
         this.setVariable(workflowUtility.getFutureRequestsKey(principalId), workflowUtility.getClearFutureRequestsValue());
 1298  0
     }
 1299  
 
 1300  
     /**
 1301  
      * Deletes the note of with the same id as that of the argument on the document.
 1302  
      * @param noteVO the note to test for deletion
 1303  
      * @return whether the note is already marked for deletion.
 1304  
      */
 1305  
     private boolean isDeletedNote(NoteDTO noteVO) {
 1306  0
             NoteDTO[] notesToDelete = routeHeader.getNotesToDelete();
 1307  0
             if (notesToDelete != null){
 1308  0
                     for (int i=0; i<notesToDelete.length; i++){
 1309  0
                             if (notesToDelete[i].getNoteId().equals(noteVO.getNoteId())){
 1310  0
                                     return true;
 1311  
                             }
 1312  
                     }
 1313  
             }
 1314  0
             return false;
 1315  
     }
 1316  
 
 1317  
     /**
 1318  
      * Increases the size of the routeHeader notes VO array
 1319  
      */
 1320  
    private void increaseNotesArraySizeByOne() {
 1321  
            NoteDTO[] tempArray;
 1322  0
            NoteDTO[] notes = routeHeader.getNotes();
 1323  0
            if (notes == null){
 1324  0
                    tempArray = new NoteDTO[1];
 1325  
            } else {
 1326  0
                    tempArray = new NoteDTO[notes.length + 1];
 1327  0
                    for (int i=0; i<notes.length; i++){
 1328  0
                            tempArray[i] = notes[i];
 1329  
                    }
 1330  
            }
 1331  0
            routeHeader.setNotes(tempArray);
 1332  0
    }
 1333  
 
 1334  
    /**
 1335  
     * Increases the size of the routeHeader notesToDelete VO array
 1336  
     */
 1337  
    private void increaseNotesToDeleteArraySizeByOne() {
 1338  
            NoteDTO[] tempArray;
 1339  0
            NoteDTO[] notesToDelete = routeHeader.getNotesToDelete();
 1340  0
            if (notesToDelete == null){
 1341  0
                    tempArray = new NoteDTO[1];
 1342  
            } else {
 1343  0
                    tempArray = new NoteDTO[notesToDelete.length + 1];
 1344  0
                    for (int i=0; i<notesToDelete.length; i++){
 1345  0
                            tempArray[i] = notesToDelete[i];
 1346  
                    }
 1347  
            }
 1348  0
            routeHeader.setNotesToDelete(tempArray);
 1349  0
    }
 1350  
    
 1351  
    //add 1 link between 2 docs by DTO, double link added
 1352  
    public void addLinkedDocument(DocumentLinkDTO docLinkVO) throws WorkflowException{
 1353  
            try{
 1354  0
                    if(DocumentLinkDTO.checkDocLink(docLinkVO))
 1355  0
                            getWorkflowUtility().addDocumentLink(docLinkVO);
 1356  
            }
 1357  0
            catch(Exception e){
 1358  0
                    throw handleExceptionAsRuntime(e); 
 1359  0
            } 
 1360  0
    }
 1361  
    
 1362  
    //get link from orgn doc to a specifc doc
 1363  
    public DocumentLinkDTO getLinkedDocument(DocumentLinkDTO docLinkVO) throws WorkflowException{
 1364  
            try{
 1365  0
                    if(DocumentLinkDTO.checkDocLink(docLinkVO))
 1366  0
                            return getWorkflowUtility().getLinkedDocument(docLinkVO);
 1367  
                    else
 1368  0
                            return null;
 1369  
            }
 1370  0
            catch(Exception e){
 1371  0
                    throw handleExceptionAsRuntime(e); 
 1372  
            }
 1373  
    }
 1374  
    
 1375  
    //get all links to orgn doc
 1376  
    public List<DocumentLinkDTO> getLinkedDocumentsByDocId(String documentId) throws WorkflowException{
 1377  0
            if(documentId == null)
 1378  0
                    throw new WorkflowException("document Id is null");
 1379  
            try{   
 1380  0
                    return getWorkflowUtility().getLinkedDocumentsByDocId(documentId);
 1381  
            } 
 1382  0
            catch (Exception e) {
 1383  0
                    throw handleExceptionAsRuntime(e);
 1384  
            }
 1385  
    }
 1386  
    
 1387  
    //remove all links from orgn: double links removed
 1388  
    public void removeLinkedDocuments(String docId) throws WorkflowException{
 1389  
            
 1390  0
            if(docId == null)
 1391  0
                    throw new WorkflowException("doc id is null");
 1392  
            
 1393  
            try{   
 1394  0
                    getWorkflowUtility().deleteDocumentLinksByDocId(docId);
 1395  
            } 
 1396  0
            catch (Exception e) {
 1397  0
                    throw handleExceptionAsRuntime(e);
 1398  0
            }
 1399  0
    }
 1400  
    
 1401  
    //remove link between 2 docs, double link removed
 1402  
    public void removeLinkedDocument(DocumentLinkDTO docLinkVO) throws WorkflowException{
 1403  
            
 1404  
            try{
 1405  0
                    if(DocumentLinkDTO.checkDocLink(docLinkVO))
 1406  0
                            getWorkflowUtility().deleteDocumentLink(docLinkVO);
 1407  
            }
 1408  0
            catch(Exception e){
 1409  0
                    throw handleExceptionAsRuntime(e); 
 1410  0
            } 
 1411  0
    }
 1412  
 
 1413  
 }