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