View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.maintenance;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.ojb.broker.core.proxy.ProxyHelper;
21  import org.kuali.rice.core.api.util.RiceKeyConstants;
22  import org.kuali.rice.kew.api.WorkflowDocument;
23  import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
24  import org.kuali.rice.kim.api.identity.Person;
25  import org.kuali.rice.krad.bo.DocumentAttachment;
26  import org.kuali.rice.krad.bo.DocumentHeader;
27  import org.kuali.rice.krad.bo.GlobalBusinessObject;
28  import org.kuali.rice.krad.bo.MultiDocumentAttachment;
29  import org.kuali.rice.krad.bo.Note;
30  import org.kuali.rice.krad.bo.PersistableAttachment;
31  import org.kuali.rice.krad.bo.PersistableAttachmentList;
32  import org.kuali.rice.krad.bo.PersistableBusinessObject;
33  import org.kuali.rice.krad.datadictionary.DocumentEntry;
34  import org.kuali.rice.krad.datadictionary.WorkflowAttributes;
35  import org.kuali.rice.krad.datadictionary.WorkflowProperties;
36  import org.kuali.rice.krad.document.DocumentBase;
37  import org.kuali.rice.krad.document.SessionDocument;
38  import org.kuali.rice.krad.exception.PessimisticLockingException;
39  import org.kuali.rice.krad.exception.ValidationException;
40  import org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent;
41  import org.kuali.rice.krad.rules.rule.event.SaveDocumentEvent;
42  import org.kuali.rice.krad.service.DocumentDictionaryService;
43  import org.kuali.rice.krad.service.DocumentHeaderService;
44  import org.kuali.rice.krad.service.DocumentService;
45  import org.kuali.rice.krad.service.KRADServiceLocator;
46  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
47  import org.kuali.rice.krad.service.MaintenanceDocumentService;
48  import org.kuali.rice.krad.util.GlobalVariables;
49  import org.kuali.rice.krad.util.KRADConstants;
50  import org.kuali.rice.krad.util.NoteType;
51  import org.kuali.rice.krad.util.ObjectUtils;
52  import org.kuali.rice.krad.util.documentserializer.PropertySerializabilityEvaluator;
53  import org.w3c.dom.Document;
54  import org.w3c.dom.Node;
55  import org.w3c.dom.NodeList;
56  import org.xml.sax.InputSource;
57  import org.xml.sax.SAXException;
58  
59  import javax.persistence.CascadeType;
60  import javax.persistence.Column;
61  import javax.persistence.Entity;
62  import javax.persistence.FetchType;
63  import javax.persistence.JoinColumn;
64  import javax.persistence.ManyToMany;
65  import javax.persistence.ManyToOne;
66  import javax.persistence.Table;
67  import javax.persistence.Transient;
68  import javax.xml.parsers.DocumentBuilder;
69  import javax.xml.parsers.DocumentBuilderFactory;
70  import javax.xml.parsers.ParserConfigurationException;
71  import java.io.IOException;
72  import java.io.StringReader;
73  import java.util.ArrayList;
74  import java.util.Collections;
75  import java.util.List;
76  
77  /**
78   * Document class for all maintenance documents which wraps the maintenance object in
79   * a <code>Maintainable</code> that is also used for various callbacks
80   *
81   * <p>
82   * The maintenance xml structure will be: <maintainableDocumentContents maintainableImplClass="className">
83   * <oldMaintainableObject>... </oldMaintainableObject> <newMaintainableObject>... </newMaintainableObject>
84   * </maintainableDocumentContents> Maintenance Document
85   * </p>
86   *
87   * @author Kuali Rice Team (rice.collab@kuali.org)
88   */
89  @Entity
90  @Table(name = "KRNS_MAINT_DOC_T")
91  public class MaintenanceDocumentBase extends DocumentBase implements MaintenanceDocument, SessionDocument {
92      private static final long serialVersionUID = -505085142412593305L;
93      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintenanceDocumentBase.class);
94  
95      public static final String MAINTAINABLE_IMPL_CLASS = "maintainableImplClass";
96      public static final String OLD_MAINTAINABLE_TAG_NAME = "oldMaintainableObject";
97      public static final String NEW_MAINTAINABLE_TAG_NAME = "newMaintainableObject";
98      public static final String MAINTENANCE_ACTION_TAG_NAME = "maintenanceAction";
99      public static final String NOTES_TAG_NAME = "notes";
100 
101     @Transient
102     private static transient DocumentDictionaryService documentDictionaryService;
103     @Transient
104     private static transient MaintenanceDocumentService maintenanceDocumentService;
105     @Transient
106     private static transient DocumentHeaderService documentHeaderService;
107     @Transient
108     private static transient DocumentService documentService;
109 
110     @Transient
111     protected Maintainable oldMaintainableObject;
112     @Transient
113     protected Maintainable newMaintainableObject;
114 
115     @Column(name = "DOC_CNTNT", length = 4096)
116     protected String xmlDocumentContents;
117     @Transient
118     protected boolean fieldsClearedOnCopy;
119     @Transient
120     protected boolean displayTopicFieldInNotes = false;
121     @Transient
122     protected String attachmentPropertyName;
123     @Transient
124     protected String attachmentListPropertyName;
125     @Transient
126     protected String attachmentCollectionName;
127 
128     @ManyToOne(fetch = FetchType.LAZY,
129             cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}) @JoinColumn(name = "DOC_HDR_ID",
130             insertable = false, updatable = false)
131     protected DocumentAttachment attachment;
132 
133     @ManyToMany(fetch = FetchType.LAZY,
134             cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}) @JoinColumn(name = "DOC_HDR_ID",
135             insertable = false, updatable = false)
136     protected List<MultiDocumentAttachment> attachments;
137 
138     public String getAttachmentPropertyName() {
139         return this.attachmentPropertyName;
140     }
141 
142     public void setAttachmentPropertyName(String attachmentPropertyName) {
143         this.attachmentPropertyName = attachmentPropertyName;
144     }
145 
146     public String getAttachmentListPropertyName() {
147         return this.attachmentListPropertyName;
148     }
149 
150     public void setAttachmentListPropertyName(String attachmentListPropertyName) {
151         this.attachmentListPropertyName = attachmentListPropertyName;
152     }
153 
154     public String getAttachmentCollectionName() {
155         return this.attachmentCollectionName;
156     }
157 
158     public void setAttachmentCollectionName(String attachmentCollectionName) {
159         this.attachmentCollectionName = attachmentCollectionName;
160     }
161 
162     public MaintenanceDocumentBase() {
163         super();
164         fieldsClearedOnCopy = false;
165     }
166 
167     /**
168      * Initializies the maintainables.
169      */
170     public MaintenanceDocumentBase(String documentTypeName) {
171         this();
172         Class clazz = getDocumentDictionaryService().getMaintainableClass(documentTypeName);
173         try {
174             oldMaintainableObject = (Maintainable) clazz.newInstance();
175             newMaintainableObject = (Maintainable) clazz.newInstance();
176 
177             // initialize maintainable with a data object
178             Class<?> dataObjectClazz = getDocumentDictionaryService().getMaintenanceDataObjectClass(documentTypeName);
179             oldMaintainableObject.setDataObject(dataObjectClazz.newInstance());
180             oldMaintainableObject.setDataObjectClass(dataObjectClazz);
181             newMaintainableObject.setDataObject(dataObjectClazz.newInstance());
182             newMaintainableObject.setDataObjectClass(dataObjectClazz);
183         } catch (InstantiationException e) {
184             LOG.error("Unable to initialize maintainables of type " + clazz.getName());
185             throw new RuntimeException("Unable to initialize maintainables of type " + clazz.getName());
186         } catch (IllegalAccessException e) {
187             LOG.error("Unable to initialize maintainables of type " + clazz.getName());
188             throw new RuntimeException("Unable to initialize maintainables of type " + clazz.getName());
189         }
190     }
191 
192     /**
193      * Builds out the document title for maintenance documents
194      *
195      * <p>This will get loaded into the flex doc and passed into
196      * workflow. It will be searchable.
197      * </p>
198      *
199      * @return document title
200      */
201     @Override
202     public String getDocumentTitle() {
203         String documentTitle = "";
204 
205         documentTitle = newMaintainableObject.getDocumentTitle(this);
206         if (StringUtils.isNotBlank(documentTitle)) {
207             // if doc title has been overridden by maintainable, use it
208             return documentTitle;
209         }
210 
211         // TODO - build out with bo label once we get the data dictionary stuff in place
212         // build out the right classname
213         String className = newMaintainableObject.getDataObject().getClass().getName();
214         String truncatedClassName = className.substring(className.lastIndexOf('.') + 1);
215         if (isOldDataObjectInDocument()) {
216             documentTitle = "Edit ";
217         } else {
218             documentTitle = "New ";
219         }
220         documentTitle += truncatedClassName + " - ";
221         documentTitle += this.getDocumentHeader().getDocumentDescription() + " ";
222         return documentTitle;
223     }
224 
225     /**
226      * Check if oldMaintainable is specified in the XML of the maintenance document
227      *
228      * @param xmlDocument Maintenance document in XML form
229      * @return true if an oldMainainable exists in the xmlDocument, false otherwise
230      */
231     protected boolean isOldMaintainableInDocument(Document xmlDocument) {
232         boolean isOldMaintainableInExistence = false;
233         if (xmlDocument.getElementsByTagName(OLD_MAINTAINABLE_TAG_NAME).getLength() > 0) {
234             isOldMaintainableInExistence = true;
235         }
236         return isOldMaintainableInExistence;
237     }
238 
239     /**
240      * @see org.kuali.rice.krad.maintenance.Maintainable#isOldDataObjectInDocument()
241      */
242     @Override
243     public boolean isOldDataObjectInDocument() {
244         boolean isOldBusinessObjectInExistence = false;
245         if (oldMaintainableObject == null || oldMaintainableObject.getDataObject() == null) {
246             isOldBusinessObjectInExistence = false;
247         } else {
248             isOldBusinessObjectInExistence = oldMaintainableObject.isOldDataObjectInDocument();
249         }
250         return isOldBusinessObjectInExistence;
251     }
252 
253     /**
254      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#isNew()
255      */
256     @Override
257     public boolean isNew() {
258         return MaintenanceUtils.isMaintenanceDocumentCreatingNewRecord(newMaintainableObject.getMaintenanceAction());
259     }
260 
261     /**
262      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#isEdit()
263      */
264     @Override
265     public boolean isEdit() {
266         if (KRADConstants.MAINTENANCE_EDIT_ACTION.equalsIgnoreCase(newMaintainableObject.getMaintenanceAction())) {
267             return true;
268         } else {
269             return false;
270         }
271     }
272 
273     /**
274      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#isNewWithExisting()
275      */
276     @Override
277     public boolean isNewWithExisting() {
278         if (KRADConstants.MAINTENANCE_NEWWITHEXISTING_ACTION.equalsIgnoreCase(
279                 newMaintainableObject.getMaintenanceAction())) {
280             return true;
281         } else {
282             return false;
283         }
284     }
285 
286     /**
287      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#populateMaintainablesFromXmlDocumentContents()
288      */
289     @Override
290     public void populateMaintainablesFromXmlDocumentContents() {
291         // get a hold of the parsed xml document, then read the classname,
292         // then instantiate one to two instances depending on content
293         // then populate those instances
294         if (!StringUtils.isEmpty(xmlDocumentContents)) {
295             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
296             try {
297                 DocumentBuilder builder = factory.newDocumentBuilder();
298                 Document xmlDocument = builder.parse(new InputSource(new StringReader(xmlDocumentContents)));
299                 String clazz = xmlDocument.getDocumentElement().getAttribute(MAINTAINABLE_IMPL_CLASS);
300                 if (isOldMaintainableInDocument(xmlDocument)) {
301                     oldMaintainableObject = (Maintainable) Class.forName(clazz).newInstance();
302                     Object dataObject = getDataObjectFromXML(OLD_MAINTAINABLE_TAG_NAME);
303 
304                     String oldMaintenanceAction = getMaintenanceAction(xmlDocument, OLD_MAINTAINABLE_TAG_NAME);
305                     oldMaintainableObject.setMaintenanceAction(oldMaintenanceAction);
306 
307                     oldMaintainableObject.setDataObject(dataObject);
308                     oldMaintainableObject.setDataObjectClass(dataObject.getClass());
309                 }
310                 newMaintainableObject = (Maintainable) Class.forName(clazz).newInstance();
311                 Object bo = getDataObjectFromXML(NEW_MAINTAINABLE_TAG_NAME);
312                 newMaintainableObject.setDataObject(bo);
313                 newMaintainableObject.setDataObjectClass(bo.getClass());
314 
315                 String newMaintenanceAction = getMaintenanceAction(xmlDocument, NEW_MAINTAINABLE_TAG_NAME);
316                 newMaintainableObject.setMaintenanceAction(newMaintenanceAction);
317 
318                 if (newMaintainableObject.isNotesEnabled()) {
319                     List<Note> notes = getNotesFromXml(NOTES_TAG_NAME);
320                     setNotes(notes);
321                 }
322             } catch (ParserConfigurationException e) {
323                 LOG.error("Error while parsing document contents", e);
324                 throw new RuntimeException("Could not load document contents from xml", e);
325             } catch (SAXException e) {
326                 LOG.error("Error while parsing document contents", e);
327                 throw new RuntimeException("Could not load document contents from xml", e);
328             } catch (IOException e) {
329                 LOG.error("Error while parsing document contents", e);
330                 throw new RuntimeException("Could not load document contents from xml", e);
331             } catch (InstantiationException e) {
332                 LOG.error("Error while parsing document contents", e);
333                 throw new RuntimeException("Could not load document contents from xml", e);
334             } catch (IllegalAccessException e) {
335                 LOG.error("Error while parsing document contents", e);
336                 throw new RuntimeException("Could not load document contents from xml", e);
337             } catch (ClassNotFoundException e) {
338                 LOG.error("Error while parsing document contents", e);
339                 throw new RuntimeException("Could not load document contents from xml", e);
340             }
341         }
342     }
343 
344     /**
345      * This method is a lame containment of ugly DOM walking code. This is ONLY necessary because of the version
346      * conflicts between Xalan.jar in 2.6.x and 2.7. As soon as we can upgrade to 2.7, this will be switched to using
347      * XPath, which is faster and much easier on the eyes.
348      *
349      * @param xmlDocument
350      * @param oldOrNewElementName - String oldMaintainableObject or newMaintainableObject
351      * @return the value of the element, or null if none was there
352      */
353     protected String getMaintenanceAction(Document xmlDocument, String oldOrNewElementName) {
354         if (StringUtils.isBlank(oldOrNewElementName)) {
355             throw new IllegalArgumentException("oldOrNewElementName may not be blank, null, or empty-string.");
356         }
357 
358         String maintenanceAction = null;
359         NodeList rootChildren = xmlDocument.getDocumentElement().getChildNodes();
360         for (int i = 0; i < rootChildren.getLength(); i++) {
361             Node rootChild = rootChildren.item(i);
362             if (oldOrNewElementName.equalsIgnoreCase(rootChild.getNodeName())) {
363                 NodeList maintChildren = rootChild.getChildNodes();
364                 for (int j = 0; j < maintChildren.getLength(); j++) {
365                     Node maintChild = maintChildren.item(j);
366                     if (MAINTENANCE_ACTION_TAG_NAME.equalsIgnoreCase(maintChild.getNodeName())) {
367                         maintenanceAction = maintChild.getChildNodes().item(0).getNodeValue();
368                     }
369                 }
370             }
371         }
372         return maintenanceAction;
373     }
374 
375     /**
376      * Get notes from XML
377      *
378      * @param notesTagName the xml tag name of the notes
379      * @return list of <code>Note</code>s
380      */
381     private List<Note> getNotesFromXml(String notesTagName) {
382         String notesXml = StringUtils.substringBetween(xmlDocumentContents, "<" + notesTagName + ">",
383                 "</" + notesTagName + ">");
384         if (StringUtils.isBlank(notesXml)) {
385             return Collections.emptyList();
386         }
387         List<Note> notes = (List<Note>) KRADServiceLocator.getXmlObjectSerializerService().fromXml(notesXml);
388         if (notes == null) {
389             return Collections.emptyList();
390         }
391         return notes;
392     }
393 
394     /**
395      * Get data object from XML
396      *
397      * <p>
398      * Retrieves substring of document contents from maintainable tag name. Then use xml service to translate xml into
399      * a business object.
400      * </p>
401      *
402      * @param maintainableTagName the xml tag name of the maintainable
403      * @return data object
404      */
405     protected Object getDataObjectFromXML(String maintainableTagName) {
406         String maintXml = StringUtils.substringBetween(xmlDocumentContents, "<" + maintainableTagName + ">",
407                 "</" + maintainableTagName + ">");
408         Object businessObject = KRADServiceLocator.getXmlObjectSerializerService().fromXml(maintXml);
409         return businessObject;
410     }
411 
412     /**
413      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#populateXmlDocumentContentsFromMaintainables()
414      */
415     @Override
416     public void populateXmlDocumentContentsFromMaintainables() {
417         StringBuilder docContentBuffer = new StringBuilder();
418         docContentBuffer.append("<maintainableDocumentContents maintainableImplClass=\"").append(
419                 newMaintainableObject.getClass().getName()).append("\">");
420 
421         // if business objects notes are enabled then we need to persist notes to the XML
422         if (getNewMaintainableObject().isNotesEnabled()) {
423             docContentBuffer.append("<" + NOTES_TAG_NAME + ">");
424             // copy notes to a non-ojb Proxied ArrayList to get rid of the usage of those proxies
425             // note: XmlObjectSerializerServiceImpl should be doing this for us but it does not
426             // appear to be working (at least in this case) and the xml comes through
427             // with the fully qualified ListProxyDefault class name from OJB embedded inside it.
428             List<Note> noteList = new ArrayList<Note>();
429             for (Note note : getNotes()) {
430                 noteList.add(note);
431             }
432             docContentBuffer.append(KRADServiceLocator.getXmlObjectSerializerService().toXml(noteList));
433             docContentBuffer.append("</" + NOTES_TAG_NAME + ">");
434         }
435         if (oldMaintainableObject != null && oldMaintainableObject.getDataObject() != null) {
436             // TODO: refactor this out into a method
437             docContentBuffer.append("<" + OLD_MAINTAINABLE_TAG_NAME + ">");
438 
439             Object oldBo = oldMaintainableObject.getDataObject();
440 
441             // hack to resolve XStream not dealing well with Proxies
442             if (oldBo instanceof PersistableBusinessObject) {
443                 ObjectUtils.materializeAllSubObjects((PersistableBusinessObject) oldBo);
444             }
445 
446             docContentBuffer.append(
447                     KRADServiceLocator.getBusinessObjectSerializerService().serializeBusinessObjectToXml(oldBo));
448 
449             // add the maintainable's maintenanceAction
450             docContentBuffer.append("<" + MAINTENANCE_ACTION_TAG_NAME + ">");
451             docContentBuffer.append(oldMaintainableObject.getMaintenanceAction());
452             docContentBuffer.append("</" + MAINTENANCE_ACTION_TAG_NAME + ">\n");
453 
454             docContentBuffer.append("</" + OLD_MAINTAINABLE_TAG_NAME + ">");
455         }
456         docContentBuffer.append("<" + NEW_MAINTAINABLE_TAG_NAME + ">");
457 
458         Object newBo = newMaintainableObject.getDataObject();
459 
460         if (newBo instanceof PersistableBusinessObject) {
461             // hack to resolve XStream not dealing well with Proxies
462             ObjectUtils.materializeAllSubObjects((PersistableBusinessObject) newBo);
463         }
464 
465         docContentBuffer.append(KRADServiceLocator.getBusinessObjectSerializerService().serializeBusinessObjectToXml(
466                 newBo));
467 
468         // add the maintainable's maintenanceAction
469         docContentBuffer.append("<" + MAINTENANCE_ACTION_TAG_NAME + ">");
470         docContentBuffer.append(newMaintainableObject.getMaintenanceAction());
471         docContentBuffer.append("</" + MAINTENANCE_ACTION_TAG_NAME + ">\n");
472 
473         docContentBuffer.append("</" + NEW_MAINTAINABLE_TAG_NAME + ">");
474         docContentBuffer.append("</maintainableDocumentContents>");
475         xmlDocumentContents = docContentBuffer.toString();
476     }
477 
478     /**
479      * @see org.kuali.rice.krad.document.DocumentBase#doRouteStatusChange(org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange)
480      */
481     @Override
482     public void doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) {
483         super.doRouteStatusChange(statusChangeEvent);
484 
485         WorkflowDocument workflowDocument = getDocumentHeader().getWorkflowDocument();
486         getNewMaintainableObject().doRouteStatusChange(getDocumentHeader());
487         // commit the changes to the Maintainable BusinessObject when it goes to Processed (ie, fully approved),
488         // and also unlock it
489         if (workflowDocument.isProcessed()) {
490             String documentNumber = getDocumentHeader().getDocumentNumber();
491             newMaintainableObject.setDocumentNumber(documentNumber);
492 
493             //Populate Attachment Property
494             if (newMaintainableObject.getDataObject() instanceof PersistableAttachment) {
495                 populateAttachmentBeforeSave();
496             }
497 
498             //Populate Attachment Property
499             if (newMaintainableObject.getDataObject() instanceof PersistableAttachmentList) {
500                 populateBoAttachmentListBeforeSave();
501             }
502 
503             newMaintainableObject.saveDataObject();
504 
505             if (!getDocumentService().saveDocumentNotes(this)) {
506                 throw new IllegalStateException(
507                         "Failed to save document notes, this means that the note target was not ready for notes to be attached when it should have been.");
508             }
509 
510             //Attachment should be deleted from Maintenance Document attachment table
511             deleteDocumentAttachment();
512             deleteDocumentAttachmentList();
513 
514             getMaintenanceDocumentService().deleteLocks(documentNumber);
515 
516             //for issue 3070, check if delete record
517             if (this.checkAllowsRecordDeletion() && this.checkMaintenanceAction() &&
518                     this.checkDeletePermission(newMaintainableObject.getDataObject())) {
519                 newMaintainableObject.deleteDataObject();
520             }
521         }
522 
523         // unlock the document when its canceled or disapproved
524         if (workflowDocument.isCanceled() || workflowDocument.isDisapproved() || workflowDocument.isRecalled()) {
525             //Attachment should be deleted from Maintenance Document attachment table
526             deleteDocumentAttachment();
527             deleteDocumentAttachmentList();
528 
529             String documentNumber = getDocumentHeader().getDocumentNumber();
530             getMaintenanceDocumentService().deleteLocks(documentNumber);
531         }
532     }
533 
534     /**
535      * @see org.kuali.rice.krad.document.DocumentBase#getWorkflowEngineDocumentIdsToLock()
536      */
537     @Override
538     public List<String> getWorkflowEngineDocumentIdsToLock() {
539         if (newMaintainableObject != null) {
540             return newMaintainableObject.getWorkflowEngineDocumentIdsToLock();
541         }
542         return Collections.emptyList();
543     }
544 
545     /**
546      * @see org.kuali.rice.krad.document.Document#prepareForSave()
547      */
548     @Override
549     public void prepareForSave() {
550         if (newMaintainableObject != null) {
551             newMaintainableObject.prepareForSave();
552         }
553     }
554 
555     /**
556      * @see org.kuali.rice.krad.document.DocumentBase#processAfterRetrieve()
557      */
558     @Override
559     public void processAfterRetrieve() {
560 
561         super.processAfterRetrieve();
562 
563         populateMaintainablesFromXmlDocumentContents();
564         if (oldMaintainableObject != null) {
565             oldMaintainableObject.setDocumentNumber(documentNumber);
566         }
567         if (newMaintainableObject != null) {
568             newMaintainableObject.setDocumentNumber(documentNumber);
569             newMaintainableObject.processAfterRetrieve();
570             if (newMaintainableObject.getDataObject() instanceof PersistableAttachment) {
571                 populateAttachmentForBO();
572             }
573             if (newMaintainableObject.getDataObject() instanceof PersistableAttachmentList) {
574                 populateAttachmentListForBO();
575             }
576             // If a maintenance lock exists, warn the user.
577             checkForLockingDocument(false);
578         }
579     }
580 
581     /**
582      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#getNewMaintainableObject()
583      */
584     @Override
585     public Maintainable getNewMaintainableObject() {
586         return newMaintainableObject;
587     }
588 
589     /**
590      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#setNewMaintainableObject(Maintainable)
591      */
592     @Override
593     public void setNewMaintainableObject(Maintainable newMaintainableObject) {
594         this.newMaintainableObject = newMaintainableObject;
595     }
596 
597     /**
598      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#getOldMaintainableObject()
599      */
600     @Override
601     public Maintainable getOldMaintainableObject() {
602         return oldMaintainableObject;
603     }
604 
605     /**
606      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#setOldMaintainableObject(Maintainable)
607      */
608     @Override
609     public void setOldMaintainableObject(Maintainable oldMaintainableObject) {
610         this.oldMaintainableObject = oldMaintainableObject;
611     }
612 
613     /**
614      * @see org.kuali.rice.krad.document.DocumentBase#setDocumentNumber(java.lang.String)
615      */
616     @Override
617     public void setDocumentNumber(String documentNumber) {
618         super.setDocumentNumber(documentNumber);
619 
620         // set the finDocNumber on the Maintainable
621         oldMaintainableObject.setDocumentNumber(documentNumber);
622         newMaintainableObject.setDocumentNumber(documentNumber);
623     }
624 
625     /**
626      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#isFieldsClearedOnCopy()
627      */
628     @Override
629     public final boolean isFieldsClearedOnCopy() {
630         return fieldsClearedOnCopy;
631     }
632 
633     /**
634      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#setFieldsClearedOnCopy(boolean)
635      */
636     @Override
637     public final void setFieldsClearedOnCopy(boolean fieldsClearedOnCopy) {
638         this.fieldsClearedOnCopy = fieldsClearedOnCopy;
639     }
640 
641     /**
642      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#getXmlDocumentContents()
643      */
644     @Override
645     public String getXmlDocumentContents() {
646         return xmlDocumentContents;
647     }
648 
649     /**
650      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#setXmlDocumentContents(String)
651      */
652     @Override
653     public void setXmlDocumentContents(String xmlDocumentContents) {
654         this.xmlDocumentContents = xmlDocumentContents;
655     }
656 
657     /**
658      * @see org.kuali.rice.krad.document.Document#getAllowsCopy()
659      */
660     @Override
661     public boolean getAllowsCopy() {
662         return getDocumentDictionaryService().getAllowsCopy(this);
663     }
664 
665     /**
666      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#isDisplayTopicFieldInNotes()
667      */
668     @Override
669     public boolean isDisplayTopicFieldInNotes() {
670         return displayTopicFieldInNotes;
671     }
672 
673     /**
674      * @see MaintenanceDocument#setDisplayTopicFieldInNotes(boolean)
675      */
676     @Override
677     public void setDisplayTopicFieldInNotes(boolean displayTopicFieldInNotes) {
678         this.displayTopicFieldInNotes = displayTopicFieldInNotes;
679     }
680 
681     /**
682      * Overridden to avoid serializing the xml twice, because of the xmlDocumentContents property of this object
683      */
684     @Override
685     public String serializeDocumentToXml() {
686         String tempXmlDocumentContents = xmlDocumentContents;
687         xmlDocumentContents = null;
688         String xmlForWorkflow = super.serializeDocumentToXml();
689         xmlDocumentContents = tempXmlDocumentContents;
690         return xmlForWorkflow;
691     }
692 
693     /**
694      * @see DocumentBase#prepareForSave(org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent)
695      */
696     @Override
697     public void prepareForSave(KualiDocumentEvent event) {
698         super.prepareForSave(event);
699         if (newMaintainableObject.getDataObject() instanceof PersistableAttachment) {
700             populateDocumentAttachment();
701             populateAttachmentForBO();
702             //clear out attachment file for old data object so it isn't serialized in doc content
703             if (oldMaintainableObject.getDataObject() instanceof PersistableAttachment) {
704                 ((PersistableAttachment) oldMaintainableObject.getDataObject()).setAttachmentContent(null);
705             }
706         }
707         if (newMaintainableObject.getDataObject() instanceof PersistableAttachmentList) {
708             populateDocumentAttachmentList();
709             populateAttachmentListForBO();
710             if (oldMaintainableObject.getDataObject() instanceof PersistableAttachmentList) {
711                 for (PersistableAttachment pa : ((PersistableAttachmentList<PersistableAttachment>) oldMaintainableObject
712                         .getDataObject()).getAttachments()) {
713                     pa.setAttachmentContent(null);
714                 }
715             }
716         }
717         populateXmlDocumentContentsFromMaintainables();
718     }
719 
720     /**
721      * The attachment BO is proxied in OJB.  For some reason when an attachment does not yet exist,
722      * refreshReferenceObject is not returning null and the proxy cannot be materialized. So, this method exists to
723      * properly handle the proxied attachment BO.  This is a hack and should be removed post JPA migration.
724      */
725     protected void refreshAttachment() {
726         if (ObjectUtils.isNull(attachment)) {
727             this.refreshReferenceObject("attachment");
728             final boolean isProxy = attachment != null && ProxyHelper.isProxy(attachment);
729             if (isProxy && ProxyHelper.getRealObject(attachment) == null) {
730                 attachment = null;
731             }
732         }
733     }
734 
735     protected void refreshAttachmentList() {
736         if (ObjectUtils.isNull(attachments)) {
737             this.refreshReferenceObject("attachments");
738             final boolean isProxy = attachments != null && ProxyHelper.isProxy(attachments);
739             if (isProxy && ProxyHelper.getRealObject(attachments) == null) {
740                 attachments = null;
741             }
742         }
743     }
744 
745     public void populateAttachmentForBO() {
746         // TODO: need to convert this from using struts form file
747 
748     }
749 
750     public void populateDocumentAttachment() {
751         // TODO: need to convert this from using struts form file
752         //        refreshAttachment();
753         //
754         //        if (fileAttachment != null && StringUtils.isNotEmpty(fileAttachment.getFileName())) {
755         //            //Populate DocumentAttachment BO
756         //            if (attachment == null) {
757         //                attachment = new DocumentAttachment();
758         //            }
759         //
760         //            byte[] fileContents;
761         //            try {
762         //                fileContents = fileAttachment.getFileData();
763         //                if (fileContents.length > 0) {
764         //                    attachment.setFileName(fileAttachment.getFileName());
765         //                    attachment.setContentType(fileAttachment.getContentType());
766         //                    attachment.setAttachmentContent(fileAttachment.getFileData());
767         //                    attachment.setDocumentNumber(getDocumentNumber());
768         //                }
769         //            } catch (FileNotFoundException e) {
770         //                LOG.error("Error while populating the Document Attachment", e);
771         //                throw new RuntimeException("Could not populate DocumentAttachment object", e);
772         //            } catch (IOException e) {
773         //                LOG.error("Error while populating the Document Attachment", e);
774         //                throw new RuntimeException("Could not populate DocumentAttachment object", e);
775         //            }
776         //        }
777         ////        else if(attachment != null) {
778         ////            //Attachment has been deleted - Need to delete the Attachment Reference Object
779         ////            deleteAttachment();
780         ////        }
781     }
782 
783     public void populateAttachmentListForBO() { }
784 
785     public void populateAttachmentBeforeSave() { }
786 
787     public void populateDocumentAttachmentList() { }
788 
789     public void populateBoAttachmentListBeforeSave() { }
790 
791     public void deleteDocumentAttachment() {
792         KRADServiceLocator.getBusinessObjectService().delete(attachment);
793         attachment = null;
794     }
795 
796     public void deleteDocumentAttachmentList() {
797         if (CollectionUtils.isNotEmpty(attachments)) {
798             KRADServiceLocator.getBusinessObjectService().delete(attachments);
799             attachments = null;
800         }
801     }
802 
803     /**
804      * Explicitly NOT calling super here.  This is a complete override of the validation rules behavior.
805      *
806      * @see org.kuali.rice.krad.document.DocumentBase#validateBusinessRules(org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent)
807      */
808     @Override
809     public void validateBusinessRules(KualiDocumentEvent event) {
810         if (GlobalVariables.getMessageMap().hasErrors()) {
811             logErrors();
812             throw new ValidationException("errors occured before business rule");
813         }
814 
815         // check for locking documents for MaintenanceDocuments
816         checkForLockingDocument(true);
817 
818         // Make sure the business object's version number matches that of the databases copy.
819         if (newMaintainableObject != null) {
820             if (KRADServiceLocator.getPersistenceStructureService().isPersistable(
821                     newMaintainableObject.getDataObject().getClass())) {
822                 PersistableBusinessObject pbObject = KRADServiceLocator.getBusinessObjectService().retrieve(
823                         (PersistableBusinessObject) newMaintainableObject.getDataObject());
824                 Long pbObjectVerNbr = ObjectUtils.isNull(pbObject) ? null : pbObject.getVersionNumber();
825                 Long newObjectVerNbr =
826                         ((PersistableBusinessObject) newMaintainableObject.getDataObject()).getVersionNumber();
827 
828                 if (pbObjectVerNbr != null && !(pbObjectVerNbr.equals(newObjectVerNbr))) {
829                     GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS,
830                             RiceKeyConstants.ERROR_VERSION_MISMATCH);
831                     throw new ValidationException(
832                             "Version mismatch between the local business object and the database business object");
833                 }
834             }
835         }
836 
837         // perform validation against rules engine
838         if (LOG.isInfoEnabled()) {
839             LOG.info("invoking rules engine on document " + getDocumentNumber());
840         }
841 
842         boolean isValid = true;
843         isValid = KRADServiceLocatorWeb.getKualiRuleService().applyRules(event);
844 
845         // check to see if the br eval passed or failed
846         if (!isValid) {
847             logErrors();
848             // TODO: better error handling at the lower level and a better error message are
849             // needed here
850             throw new ValidationException("business rule evaluation failed");
851         } else if (GlobalVariables.getMessageMap().hasErrors()) {
852             logErrors();
853             if (event instanceof SaveDocumentEvent) {
854                 // for maintenance documents, we want to always actually do a save if the
855                 // user requests a save, even if there are validation or business rules
856                 // failures. this empty if does this, and allows the document to be saved,
857                 // even if there are failures.
858                 // BR or validation failures on a ROUTE even should always stop the route,
859                 // that has not changed
860             } else {
861                 throw new ValidationException(
862                         "Unreported errors occured during business rule evaluation (rule developer needs to put meaningful error messages into global ErrorMap)");
863             }
864         }
865 
866         LOG.debug("validation completed");
867     }
868 
869     protected void checkForLockingDocument(boolean throwExceptionIfLocked) {
870         MaintenanceUtils.checkForLockingDocument(this, throwExceptionIfLocked);
871     }
872 
873     /**
874      * this needs to happen after the document itself is saved, to preserve consistency of the ver_nbr and in the case
875      * of initial save, because this can't be saved until the document is saved initially
876      *
877      * @see org.kuali.rice.krad.document.DocumentBase#postProcessSave(org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent)
878      */
879     @Override
880     public void postProcessSave(KualiDocumentEvent event) {
881         if (getNewMaintainableObject().getDataObject() instanceof PersistableBusinessObject) {
882             PersistableBusinessObject bo = (PersistableBusinessObject) getNewMaintainableObject().getDataObject();
883             if (bo instanceof GlobalBusinessObject) {
884                 KRADServiceLocator.getBusinessObjectService().save(bo);
885             }
886         }
887 
888         //currently only global documents could change the list of what they're affecting during routing,
889         //so could restrict this to only happening with them, but who knows if that will change, so safest
890         //to always do the delete and re-add...seems a bit inefficient though if nothing has changed, which is
891         //most of the time...could also try to only add/update/delete what's changed, but this is easier
892         if (!(event instanceof SaveDocumentEvent)) { //don't lock until they route
893             getMaintenanceDocumentService().deleteLocks(this.getDocumentNumber());
894             getMaintenanceDocumentService().storeLocks(this.getNewMaintainableObject().generateMaintenanceLocks());
895         }
896     }
897 
898     /**
899      * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#getDocumentDataObject()
900      */
901     @Override
902     public Object getDocumentDataObject() {
903         return getNewMaintainableObject().getDataObject();
904     }
905 
906     /**
907      * <p>The Note target for maintenance documents is determined by whether or not the underlying {@link Maintainable}
908      * supports business object notes or not.  This is determined via a call to {@link
909      * Maintainable#isBoNotesEnabled()}.
910      * The note target is then derived as follows: <p/> <ul> <li>If the {@link Maintainable} supports business object
911      * notes, delegate to {@link #getDocumentDataObject()}. <li>Otherwise, delegate to the default implementation of
912      * getNoteTarget on the superclass which will effectively return a reference to the {@link DocumentHeader}. </ul>
913      *
914      * @see org.kuali.rice.krad.document.Document#getNoteTarget()
915      */
916     @Override
917     public PersistableBusinessObject getNoteTarget() {
918         if (getNewMaintainableObject() == null) {
919             throw new IllegalStateException(
920                     "Failed to acquire the note target.  The new maintainable object on this document is null.");
921         }
922         if (getNewMaintainableObject().isNotesEnabled()) {
923             return (PersistableBusinessObject) getDocumentDataObject();
924         }
925         return super.getNoteTarget();
926     }
927 
928     /**
929      * The {@link NoteType} for maintenance documents is determined by whether or not the underlying {@link
930      * Maintainable} supports business object notes or not.  This is determined via a call to {@link
931      * Maintainable#   isBoNotesEnabled()}.  The {@link NoteType} is then derived as follows: <p/> <ul> <li>If the
932      * {@link
933      * Maintainable} supports business object notes, return {@link NoteType#BUSINESS_OBJECT}. <li>Otherwise, delegate
934      * to
935      * {@link DocumentBase#getNoteType()} </ul>
936      *
937      * @see org.kuali.rice.krad.document.Document#getNoteType()
938      * @see org.kuali.rice.krad.document.Document#getNoteTarget()
939      */
940     @Override
941     public NoteType getNoteType() {
942         if (getNewMaintainableObject().isNotesEnabled()) {
943             return NoteType.BUSINESS_OBJECT;
944         }
945         return super.getNoteType();
946     }
947 
948     @Override
949     public PropertySerializabilityEvaluator getDocumentPropertySerizabilityEvaluator() {
950         String docTypeName = "";
951         if (newMaintainableObject != null) {
952             docTypeName = getDocumentDictionaryService().getMaintenanceDocumentTypeName(
953                     this.newMaintainableObject.getDataObjectClass());
954         } else { // I don't know why we aren't just using the header in the first place
955             // but, in the case where we can't get it in the way above, attempt to get
956             // it off the workflow document header
957             if (getDocumentHeader() != null && getDocumentHeader().getWorkflowDocument() != null) {
958                 docTypeName = getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
959             }
960         }
961         if (!StringUtils.isBlank(docTypeName)) {
962             DocumentEntry documentEntry = getDocumentDictionaryService().getMaintenanceDocumentEntry(docTypeName);
963             if (documentEntry != null) {
964                 WorkflowProperties workflowProperties = documentEntry.getWorkflowProperties();
965                 WorkflowAttributes workflowAttributes = documentEntry.getWorkflowAttributes();
966                 return createPropertySerializabilityEvaluator(workflowProperties, workflowAttributes);
967             } else {
968                 LOG.error("Unable to obtain DD DocumentEntry for document type: '" + docTypeName + "'");
969             }
970         } else {
971             LOG.error("Unable to obtain document type name for this document: " + this);
972         }
973         LOG.error("Returning null for the PropertySerializabilityEvaluator");
974         return null;
975     }
976 
977     public DocumentAttachment getAttachment() {
978         return this.attachment;
979     }
980 
981     public void setAttachment(DocumentAttachment attachment) {
982         this.attachment = attachment;
983     }
984 
985     public List<MultiDocumentAttachment> getAttachments() {
986         return this.attachments;
987     }
988 
989     public void setAttachments(List<MultiDocumentAttachment> attachments) {
990         this.attachments = attachments;
991     }
992 
993     /**
994      * This overridden method is used to delete the {@link DocumentHeader} object due to the system not being able to
995      * manage the {@link DocumentHeader} object via mapping files
996      *
997      * @see org.kuali.rice.krad.bo.PersistableBusinessObjectBase#postRemove()
998      */
999     @Override
1000     protected void postRemove() {
1001         super.postRemove();
1002         getDocumentHeaderService().deleteDocumentHeader(getDocumentHeader());
1003     }
1004 
1005     /**
1006      * This overridden method is used to retrieve the {@link DocumentHeader} object due to the system not being able to
1007      * manage the {@link DocumentHeader} object via mapping files
1008      *
1009      * @see org.kuali.rice.krad.bo.PersistableBusinessObjectBase#postLoad()
1010      */
1011     @Override
1012     protected void postLoad() {
1013         super.postLoad();
1014         setDocumentHeader(getDocumentHeaderService().getDocumentHeaderById(getDocumentNumber()));
1015     }
1016 
1017     /**
1018      * This overridden method is used to insert the {@link DocumentHeader} object due to the system not being able to
1019      * manage the {@link DocumentHeader} object via mapping files
1020      *
1021      * @see org.kuali.rice.krad.bo.PersistableBusinessObjectBase#prePersist()
1022      */
1023     @Override
1024     protected void prePersist() {
1025         super.prePersist();
1026         getDocumentHeaderService().saveDocumentHeader(getDocumentHeader());
1027     }
1028 
1029     /**
1030      * This overridden method is used to save the {@link DocumentHeader} object due to the system not being able to
1031      * manage the {@link DocumentHeader} object via mapping files
1032      *
1033      * @see org.kuali.rice.krad.bo.PersistableBusinessObjectBase#preUpdate()
1034      */
1035     @Override
1036     protected void preUpdate() {
1037         super.preUpdate();
1038         getDocumentHeaderService().saveDocumentHeader(getDocumentHeader());
1039     }
1040 
1041     /**
1042      * This method to check whether the document class implements SessionDocument
1043      *
1044      * TODO: move to KNS maintenance document base
1045      *
1046      * @return
1047      */
1048     public boolean isSessionDocument() {
1049         return SessionDocument.class.isAssignableFrom(this.getClass());
1050     }
1051 
1052     /**
1053      * Returns whether or not the new maintainable object supports custom lock descriptors. Will always return false if
1054      * the new maintainable is null.
1055      *
1056      * @see org.kuali.rice.krad.document.Document#useCustomLockDescriptors()
1057      * @see org.kuali.rice.krad.maintenance.Maintainable#useCustomLockDescriptors()
1058      */
1059     @Override
1060     public boolean useCustomLockDescriptors() {
1061         return (newMaintainableObject != null && newMaintainableObject.useCustomLockDescriptors());
1062     }
1063 
1064     /**
1065      * Returns the custom lock descriptor generated by the new maintainable object, if defined. Will throw a
1066      * PessimisticLockingException if the new maintainable is null.
1067      *
1068      * @see org.kuali.rice.krad.document.Document#getCustomLockDescriptor(org.kuali.rice.kim.api.identity.Person)
1069      * @see org.kuali.rice.krad.maintenance.Maintainable#getCustomLockDescriptor(org.kuali.rice.kim.api.identity.Person)
1070      */
1071     @Override
1072     public String getCustomLockDescriptor(Person user) {
1073         if (newMaintainableObject == null) {
1074             throw new PessimisticLockingException("Maintenance Document " + getDocumentNumber() +
1075                     " is using pessimistic locking with custom lock descriptors, but no new maintainable object has been defined");
1076         }
1077         return newMaintainableObject.getCustomLockDescriptor(user);
1078     }
1079 
1080     protected DocumentDictionaryService getDocumentDictionaryService() {
1081         if (documentDictionaryService == null) {
1082             documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
1083         }
1084         return documentDictionaryService;
1085     }
1086 
1087     protected MaintenanceDocumentService getMaintenanceDocumentService() {
1088         if (maintenanceDocumentService == null) {
1089             maintenanceDocumentService = KRADServiceLocatorWeb.getMaintenanceDocumentService();
1090         }
1091         return maintenanceDocumentService;
1092     }
1093 
1094     protected DocumentHeaderService getDocumentHeaderService() {
1095         if (documentHeaderService == null) {
1096             documentHeaderService = KRADServiceLocatorWeb.getDocumentHeaderService();
1097         }
1098         return documentHeaderService;
1099     }
1100 
1101     protected DocumentService getDocumentService() {
1102         if (documentService == null) {
1103             documentService = KRADServiceLocatorWeb.getDocumentService();
1104         }
1105         return documentService;
1106     }
1107 
1108     //for issue KULRice3070
1109     protected boolean checkAllowsRecordDeletion() {
1110         Boolean allowsRecordDeletion = KRADServiceLocatorWeb.getDocumentDictionaryService().getAllowsRecordDeletion(
1111                 this.getNewMaintainableObject().getDataObjectClass());
1112         if (allowsRecordDeletion != null) {
1113             return allowsRecordDeletion.booleanValue();
1114         } else {
1115             return false;
1116         }
1117     }
1118 
1119     //for KULRice3070
1120     protected boolean checkMaintenanceAction() {
1121         return this.getNewMaintainableObject().getMaintenanceAction().equals(KRADConstants.MAINTENANCE_DELETE_ACTION);
1122     }
1123 
1124     //for KULRice3070
1125     protected boolean checkDeletePermission(Object dataObject) {
1126         boolean allowsMaintain = false;
1127 
1128         String maintDocTypeName = KRADServiceLocatorWeb.getDocumentDictionaryService().getMaintenanceDocumentTypeName(
1129                 dataObject.getClass());
1130 
1131         if (StringUtils.isNotBlank(maintDocTypeName)) {
1132             allowsMaintain = KRADServiceLocatorWeb.getDataObjectAuthorizationService().canMaintain(dataObject,
1133                     GlobalVariables.getUserSession().getPerson(), maintDocTypeName);
1134         }
1135         return allowsMaintain;
1136     }
1137 }