View Javadoc

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