001: /*
002: * Copyright 2005-2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.opensource.org/licenses/ecl1.php
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.kuali.core.web.struts.form;
017:
018: import java.io.Serializable;
019: import java.util.HashMap;
020: import java.util.List;
021: import java.util.Map;
022:
023: import javax.servlet.http.HttpServletRequest;
024:
025: import org.apache.commons.lang.StringUtils;
026: import org.apache.struts.action.ActionErrors;
027: import org.apache.struts.action.ActionMapping;
028: import org.apache.struts.upload.FormFile;
029: import org.kuali.RiceConstants;
030: import org.kuali.RiceKeyConstants;
031: import org.kuali.core.authorization.AuthorizationConstants;
032: import org.kuali.core.bo.AdHocRoutePerson;
033: import org.kuali.core.bo.AdHocRouteRecipient;
034: import org.kuali.core.bo.AdHocRouteWorkgroup;
035: import org.kuali.core.bo.Note;
036: import org.kuali.core.bo.user.UniversalUser;
037: import org.kuali.core.document.Document;
038: import org.kuali.core.document.authorization.DocumentActionFlags;
039: import org.kuali.core.document.authorization.DocumentAuthorizer;
040: import org.kuali.core.exceptions.DocumentAuthorizationException;
041: import org.kuali.core.util.GlobalVariables;
042: import org.kuali.core.web.format.NoOpStringFormatter;
043: import org.kuali.core.web.format.TimestampAMPMFormatter;
044: import org.kuali.core.workflow.service.KualiWorkflowDocument;
045: import org.kuali.rice.KNSServiceLocator;
046: import org.springframework.util.AutoPopulatingList;
047:
048: import edu.iu.uis.eden.EdenConstants;
049: import edu.iu.uis.eden.exception.WorkflowException;
050:
051: /**
052: * TODO we should not be referencing eden constants from this class and wedding ourselves to that workflow application This class is
053: * the base action form for all documents.
054: */
055: public abstract class KualiDocumentFormBase extends KualiForm {
056: private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
057: .getLogger(KualiDocumentFormBase.class);
058:
059: private Document document;
060: private String annotation = "";
061: private String command;
062:
063: private String docId;
064: private String docTypeName;
065:
066: private List<String> additionalScriptFiles;
067:
068: private AdHocRouteRecipient newAdHocRoutePerson;
069: private AdHocRouteRecipient newAdHocRouteWorkgroup;
070:
071: private Note newNote;
072:
073: //TODO: is this still needed? I think it's obsolete now
074: private List boNotes;
075:
076: protected FormFile attachmentFile;
077:
078: protected Map editingMode;
079: protected boolean suppressAllButtons;
080: protected DocumentActionFlags documentActionFlags;
081:
082: private boolean returnToActionList;
083:
084: /** Special list class which doesn't blow up when setting an index which doesn't exist. */
085: private static class AutoExpandingList extends AutoPopulatingList
086: implements Serializable {
087: AutoExpandingList(Class listClass) {
088: super (listClass);
089: }
090:
091: public Object set(int index, Object value) {
092: get(index);
093: return super .set(index, value);
094: }
095: }
096:
097: /**
098: * no args constructor that just initializes things for us
099: */
100: public KualiDocumentFormBase() {
101: super ();
102: newNote = new Note();
103: this .editingMode = new HashMap();
104: this .additionalScriptFiles = new AutoExpandingList(String.class);
105:
106: // set the initial record for persons up
107: newAdHocRoutePerson = new AdHocRoutePerson();
108:
109: // set the initial record for workgroups up
110: newAdHocRouteWorkgroup = new AdHocRouteWorkgroup();
111:
112: // to make sure it posts back the correct time
113: setFormatterType(
114: "document.documentHeader.note.finDocNotePostedDttmStamp",
115: TimestampAMPMFormatter.class);
116: setFormatterType(
117: "document.documentHeader.note.attachment.finDocNotePostedDttmStamp",
118: TimestampAMPMFormatter.class);
119: //TODO: Chris - Notes: remove the above and change the below from boNotes when notes are finished
120: //overriding note formatter to make sure they post back the full timestamp
121: setFormatterType(
122: "document.documentHeader.boNote.notePostedTimestamp",
123: TimestampAMPMFormatter.class);
124: setFormatterType(
125: "document.documentBusinessObject.boNote.notePostedTimestamp",
126: TimestampAMPMFormatter.class);
127:
128: setFormatterType("editingMode", NoOpStringFormatter.class);
129: setFormatterType("editableAccounts", NoOpStringFormatter.class);
130:
131: // create a blank DocumentActionFlags instance, since form-recreation needs it
132: setDocumentActionFlags(new DocumentActionFlags());
133: suppressAllButtons = false;
134: }
135:
136: /**
137: * Setup workflow doc in the document.
138: */
139: @Override
140: public void populate(HttpServletRequest request) {
141: super .populate(request);
142:
143: if (hasDocumentId()) {
144: // populate workflowDocument in documentHeader, if needed
145: try {
146: KualiWorkflowDocument workflowDocument = null;
147: if (GlobalVariables.getUserSession()
148: .getWorkflowDocument(
149: getDocument().getDocumentNumber()) != null) {
150: workflowDocument = GlobalVariables.getUserSession()
151: .getWorkflowDocument(
152: getDocument().getDocumentNumber());
153: } else {
154: // gets the workflow document from doc service, doc service will also set the workflow document in the user's session
155: Document retrievedDocument = KNSServiceLocator
156: .getDocumentService()
157: .getByDocumentHeaderId(
158: getDocument().getDocumentNumber());
159: if (retrievedDocument == null) {
160: throw new WorkflowException(
161: "Unable to get retrieve document # "
162: + getDocument()
163: .getDocumentNumber()
164: + " from document service getByDocumentHeaderId");
165: }
166: workflowDocument = retrievedDocument
167: .getDocumentHeader().getWorkflowDocument();
168: }
169:
170: getDocument().getDocumentHeader().setWorkflowDocument(
171: workflowDocument);
172: } catch (WorkflowException e) {
173: LOG.warn("Error while instantiating workflowDoc", e);
174: throw new RuntimeException(
175: "error populating documentHeader.workflowDocument",
176: e);
177: }
178: }
179: }
180:
181: /**
182: * Updates authorization-related form fields based on the current form contents
183: */
184: public void populateAuthorizationFields(
185: DocumentAuthorizer documentAuthorizer) {
186: if (isFormDocumentInitialized()) {
187: useDocumentAuthorizer(documentAuthorizer);
188:
189: // graceless hack which takes advantage of the fact that here and only here will we have guaranteed access to the
190: // correct DocumentAuthorizer
191: if (getEditingMode().containsKey(
192: AuthorizationConstants.EditMode.UNVIEWABLE)) {
193: throw new DocumentAuthorizationException(
194: GlobalVariables.getUserSession()
195: .getUniversalUser().getPersonName(),
196: "view", document.getDocumentHeader()
197: .getDocumentNumber());
198: }
199: }
200: }
201:
202: /**
203: * @see org.apache.struts.action.ActionForm#validate(org.apache.struts.action.ActionMapping,
204: * javax.servlet.http.HttpServletRequest)
205: */
206: @Override
207: public ActionErrors validate(ActionMapping mapping,
208: HttpServletRequest request) {
209: // check that annotation does not exceed 2000 characters
210: setAnnotation(StringUtils.stripToNull(getAnnotation()));
211: int diff = StringUtils.defaultString(getAnnotation()).length()
212: - RiceConstants.DOCUMENT_ANNOTATION_MAX_LENGTH;
213: if (diff > 0) {
214: GlobalVariables
215: .getErrorMap()
216: .putError(
217: "annotation",
218: RiceKeyConstants.ERROR_DOCUMENT_ANNOTATION_MAX_LENGTH_EXCEEDED,
219: new String[] {
220: Integer
221: .toString(RiceConstants.DOCUMENT_ANNOTATION_MAX_LENGTH),
222: Integer.toString(diff) });
223: }
224: return super .validate(mapping, request);
225: }
226:
227: /**
228: * Refactored out actually calling the documentAuthorizer methods, since TransactionalDocuments call a differently-parameterized
229: * version of getEditMode
230: *
231: * @param documentAuthorizer
232: */
233: protected void useDocumentAuthorizer(
234: DocumentAuthorizer documentAuthorizer) {
235: UniversalUser kualiUser = GlobalVariables.getUserSession()
236: .getUniversalUser();
237:
238: setEditingMode(documentAuthorizer.getEditMode(document,
239: kualiUser));
240: setDocumentActionFlags(documentAuthorizer
241: .getDocumentActionFlags(document, kualiUser));
242: }
243:
244: /**
245: * @return true if this document was properly initialized with a DocumentHeader and related KualiWorkflowDocument
246: */
247: final protected boolean isFormDocumentInitialized() {
248: boolean initialized = false;
249:
250: if (document != null) {
251: if (document.getDocumentHeader() != null) {
252: initialized = document.getDocumentHeader()
253: .hasWorkflowDocument();
254: }
255: }
256:
257: return initialized;
258: }
259:
260: /**
261: * @return Map of editingModes for this document, as set during the most recent call to
262: * populate(javax.servlet.http.HttpServletRequest)
263: */
264: public Map getEditingMode() {
265: return editingMode;
266: }
267:
268: /**
269: * Set editingMode for this document - unfortunately necessary, since validation failures bypass the normal
270: * populateAuthorizationFields call. (Unfortunate because it makes the UI just a bit easier to hack, until we have the back-end
271: * checks of editingMode et al in place.)
272: */
273: public void setEditingMode(Map editingMode) {
274: this .editingMode = editingMode;
275: }
276:
277: /**
278: * @return DocumentActionFlags instance indicating what actions the current user can take on this document
279: */
280: public DocumentActionFlags getDocumentActionFlags() {
281: return documentActionFlags;
282: }
283:
284: /**
285: * Protected, since no external code should need to set it.
286: */
287: protected void setDocumentActionFlags(
288: DocumentActionFlags documentActionFlags) {
289: this .documentActionFlags = documentActionFlags;
290: }
291:
292: /**
293: * @return a map of the possible action request codes that takes into account the users context on the document
294: */
295: public Map getAdHocActionRequestCodes() {
296: Map adHocActionRequestCodes = new HashMap();
297: if (getWorkflowDocument() != null) {
298: if (getWorkflowDocument().isFYIRequested()) {
299: adHocActionRequestCodes.put(
300: EdenConstants.ACTION_REQUEST_FYI_REQ,
301: EdenConstants.ACTION_REQUEST_FYI_REQ_LABEL);
302: } else if (getWorkflowDocument().isAcknowledgeRequested()) {
303: adHocActionRequestCodes
304: .put(
305: EdenConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ,
306: EdenConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ_LABEL);
307: adHocActionRequestCodes.put(
308: EdenConstants.ACTION_REQUEST_FYI_REQ,
309: EdenConstants.ACTION_REQUEST_FYI_REQ_LABEL);
310: } else if (getWorkflowDocument().isApprovalRequested()
311: || getWorkflowDocument().isCompletionRequested()
312: || getWorkflowDocument().stateIsInitiated()
313: || getWorkflowDocument().stateIsSaved()) {
314: adHocActionRequestCodes
315: .put(
316: EdenConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ,
317: EdenConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ_LABEL);
318: adHocActionRequestCodes.put(
319: EdenConstants.ACTION_REQUEST_FYI_REQ,
320: EdenConstants.ACTION_REQUEST_FYI_REQ_LABEL);
321: adHocActionRequestCodes.put(
322: EdenConstants.ACTION_REQUEST_APPROVE_REQ,
323: EdenConstants.ACTION_REQUEST_APPROVE_REQ_LABEL);
324: }
325: }
326: return adHocActionRequestCodes;
327: }
328:
329: /**
330: * @return the list of ad hoc routing persons
331: */
332: public List getAdHocRoutePersons() {
333: return document.getAdHocRoutePersons();
334: }
335:
336: /**
337: * @return attachmentFile
338: */
339: public FormFile getAttachmentFile() {
340: return attachmentFile;
341: }
342:
343: /**
344: * @param attachmentFile The attachmentFile to set.
345: */
346: public void setAttachmentFile(FormFile attachmentFile) {
347: this .attachmentFile = attachmentFile;
348: }
349:
350: /**
351: * set the ad hoc routing persons list
352: *
353: * @param adHocRouteRecipients
354: */
355: public void setAdHocRoutePersons(List adHocRouteRecipients) {
356: document.setAdHocRoutePersons(adHocRouteRecipients);
357: }
358:
359: /**
360: * get the ad hoc routing workgroup requests
361: *
362: * @return
363: */
364: public List getAdHocRouteWorkgroups() {
365: return document.getAdHocRouteWorkgroups();
366: }
367:
368: /**
369: * set the ad hoc routing workgroup requests
370: *
371: * @param adHocRouteWorkgroups
372: */
373: public void setAdHocRouteWorkgroups(List adHocRouteWorkgroups) {
374: document.setAdHocRouteWorkgroups(adHocRouteWorkgroups);
375: }
376:
377: /**
378: * Special getter based on index to work with multi rows for ad hoc routing to persons struts page
379: *
380: * @param index
381: * @return
382: */
383: public AdHocRoutePerson getAdHocRoutePerson(int index) {
384: while (getAdHocRoutePersons().size() <= index) {
385: getAdHocRoutePersons().add(new AdHocRoutePerson());
386: }
387: return (AdHocRoutePerson) getAdHocRoutePersons().get(index);
388: }
389:
390: /**
391: * Special getter based on index to work with multi rows for ad hoc routing to workgroups struts page
392: *
393: * @param index
394: * @return
395: */
396: public AdHocRouteWorkgroup getAdHocRouteWorkgroup(int index) {
397: while (getAdHocRouteWorkgroups().size() <= index) {
398: getAdHocRouteWorkgroups().add(new AdHocRouteWorkgroup());
399: }
400: return (AdHocRouteWorkgroup) getAdHocRouteWorkgroups().get(
401: index);
402: }
403:
404: /**
405: * @return the new ad hoc route person object
406: */
407: public AdHocRouteRecipient getNewAdHocRoutePerson() {
408: return newAdHocRoutePerson;
409: }
410:
411: /**
412: * set the new ad hoc route person object
413: *
414: * @param newAdHocRoutePerson
415: */
416: public void setNewAdHocRoutePerson(
417: AdHocRoutePerson newAdHocRoutePerson) {
418: this .newAdHocRoutePerson = newAdHocRoutePerson;
419: }
420:
421: /**
422: * @return the new ad hoc route workgroup object
423: */
424: public AdHocRouteRecipient getNewAdHocRouteWorkgroup() {
425: return newAdHocRouteWorkgroup;
426: }
427:
428: /**
429: * set the new ad hoc route workgroup object
430: *
431: * @param newAdHocRouteWorkgroup
432: */
433: public void setNewAdHocRouteWorkgroup(
434: AdHocRouteWorkgroup newAdHocRouteWorkgroup) {
435: this .newAdHocRouteWorkgroup = newAdHocRouteWorkgroup;
436: }
437:
438: /**
439: * @return Returns the Document
440: */
441: public Document getDocument() {
442: return document;
443: }
444:
445: /**
446: * @param document
447: */
448: public void setDocument(Document document) {
449: this .document = document;
450: }
451:
452: /**
453: * @return FlexDoc for this form's document
454: */
455: public KualiWorkflowDocument getWorkflowDocument() {
456: return getDocument().getDocumentHeader().getWorkflowDocument();
457: }
458:
459: /**
460: * TODO rk implemented to account for caps coming from kuali user service from workflow
461: */
462: public boolean isUserDocumentInitiator() {
463: if (getWorkflowDocument() != null) {
464: return getWorkflowDocument().getInitiatorNetworkId()
465: .equalsIgnoreCase(
466: GlobalVariables.getUserSession()
467: .getNetworkId());
468: }
469: return false;
470: }
471:
472: /**
473: * @return true if the workflowDocument associated with this form is currently enroute
474: */
475: public boolean isDocumentEnRoute() {
476: return getWorkflowDocument().stateIsEnroute();
477: }
478:
479: /**
480: * @param annotation The annotation to set.
481: */
482: public void setAnnotation(String annotation) {
483: this .annotation = annotation;
484: }
485:
486: /**
487: * @return Returns the annotation.
488: */
489: public String getAnnotation() {
490: return annotation;
491: }
492:
493: /**
494: * @return returns the command that was passed from workflow
495: */
496: public String getCommand() {
497: return command;
498: }
499:
500: /**
501: * setter for the command that was passed from workflow on the url
502: *
503: * @param command
504: */
505: public void setCommand(String command) {
506: this .command = command;
507: }
508:
509: /**
510: * @return returns the docId that was passed from workflow on the url
511: */
512: public String getDocId() {
513: return docId;
514: }
515:
516: /**
517: * setter for the docId that was passed from workflow on the url
518: *
519: * @param docId
520: */
521: public void setDocId(String docId) {
522: this .docId = docId;
523: }
524:
525: /**
526: * getter for the docTypeName that was passed from workflow on the url
527: *
528: * @return
529: */
530: public String getDocTypeName() {
531: return docTypeName;
532: }
533:
534: /**
535: * setter for the docTypeName that was passed from workflow on the url
536: *
537: * @param docTypeName
538: */
539: public void setDocTypeName(String docTypeName) {
540: this .docTypeName = docTypeName;
541: }
542:
543: /**
544: * getter for convenience that will return the initiators network id
545: *
546: * @return
547: */
548: public String getInitiatorNetworkId() {
549: return this .getWorkflowDocument().getInitiatorNetworkId();
550: }
551:
552: /**
553: * Gets the suppressAllButtons attribute.
554: *
555: * @return Returns the suppressAllButtons.
556: */
557: public final boolean isSuppressAllButtons() {
558: return suppressAllButtons;
559: }
560:
561: /**
562: * Sets the suppressAllButtons attribute value.
563: *
564: * @param suppressAllButtons The suppressAllButtons to set.
565: */
566: public final void setSuppressAllButtons(boolean suppressAllButtons) {
567: this .suppressAllButtons = suppressAllButtons;
568: }
569:
570: /**
571: * @return true if this form's getDocument() method returns a Document, and if that Document's getDocumentHeaderId method
572: * returns a non-null
573: */
574: public boolean hasDocumentId() {
575: boolean hasDocId = false;
576:
577: Document d = getDocument();
578: if (d != null) {
579: String docHeaderId = d.getDocumentNumber();
580:
581: hasDocId = StringUtils.isNotBlank(docHeaderId);
582: }
583:
584: return hasDocId;
585: }
586:
587: /**
588: * Sets flag indicating whether upon completion of approve, blanketApprove, cancel, or disapprove, the user should be returned
589: * to the actionList instead of to the portal
590: *
591: * @param returnToActionList
592: */
593: public void setReturnToActionList(boolean returnToActionList) {
594: this .returnToActionList = returnToActionList;
595: }
596:
597: public boolean isReturnToActionList() {
598: return returnToActionList;
599: }
600:
601: public List<String> getAdditionalScriptFiles() {
602: return additionalScriptFiles;
603: }
604:
605: public void setAdditionalScriptFiles(
606: List<String> additionalScriptFiles) {
607: this .additionalScriptFiles = additionalScriptFiles;
608: }
609:
610: public void setAdditionalScriptFile(int index, String scriptFile) {
611: additionalScriptFiles.set(index, scriptFile);
612: }
613:
614: public String getAdditionalScriptFile(int index) {
615: return additionalScriptFiles.get(index);
616: }
617:
618: public Note getNewNote() {
619: return newNote;
620: }
621:
622: public void setNewNote(Note newNote) {
623: this .newNote = newNote;
624: }
625:
626: /**
627: * Gets the boNotes attribute.
628: * @return Returns the boNotes.
629: */
630: public List getBoNotes() {
631: return boNotes;
632: }
633:
634: /**
635: * Sets the boNotes attribute value.
636: * @param boNotes The boNotes to set.
637: */
638: public void setBoNotes(List boNotes) {
639: this .boNotes = boNotes;
640: }
641:
642: /**
643: * Adds the attachment file size to the list of max file sizes.
644: *
645: * @see org.kuali.core.web.struts.pojo.PojoFormBase#customInitMaxUploadSizes()
646: */
647: @Override
648: protected void customInitMaxUploadSizes() {
649: super.customInitMaxUploadSizes();
650: addMaxUploadSize(KNSServiceLocator
651: .getKualiConfigurationService().getParameterValue(
652: RiceConstants.KNS_NAMESPACE,
653: RiceConstants.DetailTypes.DOCUMENT_DETAIL_TYPE,
654: RiceConstants.ATTACHMENT_MAX_FILE_SIZE_PARM_NM));
655: }
656: }
|