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