001package org.kuali.rice.krad.maintenance; 002 003import java.io.IOException; 004import java.io.StringReader; 005import java.util.ArrayList; 006import java.util.Arrays; 007import java.util.Collections; 008import java.util.List; 009 010import javax.persistence.CascadeType; 011import javax.persistence.Column; 012import javax.persistence.Entity; 013import javax.persistence.FetchType; 014import javax.persistence.JoinColumn; 015import javax.persistence.Lob; 016import javax.persistence.ManyToOne; 017import javax.persistence.OneToMany; 018import javax.persistence.OneToOne; 019import javax.persistence.Table; 020import javax.persistence.Transient; 021import javax.persistence.UniqueConstraint; 022import javax.xml.parsers.DocumentBuilder; 023import javax.xml.parsers.DocumentBuilderFactory; 024import javax.xml.parsers.ParserConfigurationException; 025 026import org.apache.commons.collections.CollectionUtils; 027import org.apache.commons.lang.StringUtils; 028import org.kuali.rice.core.api.config.property.ConfigContext; 029import org.kuali.rice.core.api.mo.common.GloballyUnique; 030import org.kuali.rice.kew.api.KewApiServiceLocator; 031import org.kuali.rice.kew.api.WorkflowDocument; 032import org.kuali.rice.kew.api.doctype.DocumentType; 033import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange; 034import org.kuali.rice.kim.api.identity.Person; 035import org.kuali.rice.kim.api.services.KimApiServiceLocator; 036import org.kuali.rice.krad.bo.DocumentAttachment; 037import org.kuali.rice.krad.bo.DocumentHeader; 038import org.kuali.rice.krad.bo.MultiDocumentAttachment; 039import org.kuali.rice.krad.bo.Note; 040import org.kuali.rice.krad.bo.PersistableAttachment; 041import org.kuali.rice.krad.bo.PersistableAttachmentList; 042import org.kuali.rice.krad.data.KradDataServiceLocator; 043import org.kuali.rice.krad.datadictionary.DocumentEntry; 044import org.kuali.rice.krad.datadictionary.WorkflowAttributes; 045import org.kuali.rice.krad.datadictionary.WorkflowProperties; 046import org.kuali.rice.krad.document.DocumentBase; 047import org.kuali.rice.krad.document.SessionDocument; 048import org.kuali.rice.krad.exception.PessimisticLockingException; 049import org.kuali.rice.krad.exception.ValidationException; 050import org.kuali.rice.krad.rules.rule.event.DocumentEvent; 051import org.kuali.rice.krad.rules.rule.event.SaveDocumentEvent; 052import org.kuali.rice.krad.service.BusinessObjectSerializerService; 053import org.kuali.rice.krad.service.DocumentDictionaryService; 054import org.kuali.rice.krad.service.DocumentService; 055import org.kuali.rice.krad.service.KRADServiceLocator; 056import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 057import org.kuali.rice.krad.service.MaintenanceDocumentService; 058import org.kuali.rice.krad.util.GlobalVariables; 059import org.kuali.rice.krad.util.KRADConstants; 060import org.kuali.rice.krad.util.NoteType; 061import org.kuali.rice.krad.util.documentserializer.PropertySerializabilityEvaluator; 062import org.w3c.dom.Document; 063import org.w3c.dom.Node; 064import org.w3c.dom.NodeList; 065import org.xml.sax.InputSource; 066import org.xml.sax.SAXException; 067 068import com.thoughtworks.xstream.core.BaseException; 069 070/** 071 * Created by sheiksalahudeenm on 10/14/14. 072 */ 073@Entity 074@Table(name = "KRNS_MAINT_DOC_T",uniqueConstraints= { 075 @UniqueConstraint(name="KRNS_MAINT_DOC_TC0",columnNames="OBJ_ID") 076}) 077public class MaintenanceDocumentBase extends DocumentBase implements MaintenanceDocument, SessionDocument { 078 protected static final int SUB_OBJECT_MATERIALIZATION_DEPTH = 3; 079 private static final long serialVersionUID = -505085142412593305L; 080 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintenanceDocumentBase.class); 081 082 public static final String MAINTAINABLE_IMPL_CLASS = "maintainableImplClass"; 083 public static final String OLD_MAINTAINABLE_TAG_NAME = "oldMaintainableObject"; 084 public static final String NEW_MAINTAINABLE_TAG_NAME = "newMaintainableObject"; 085 public static final String MAINTENANCE_ACTION_TAG_NAME = "maintenanceAction"; 086 public static final String NOTES_TAG_NAME = "notes"; 087 088 @Transient 089 private static transient DocumentDictionaryService documentDictionaryService; 090 @Transient 091 private static transient MaintenanceDocumentService maintenanceDocumentService; 092 @Transient 093 private static transient DocumentService documentService; 094 095 @Transient 096 protected Maintainable oldMaintainableObject; 097 098 @Transient 099 protected Maintainable newMaintainableObject; 100 101 @Lob 102 @Column(name = "DOC_CNTNT") 103 protected String xmlDocumentContents; 104 @Transient 105 protected boolean fieldsClearedOnCopy; 106 @Transient 107 protected boolean displayTopicFieldInNotes = false; 108 @Transient 109 protected String attachmentPropertyName; 110 @Transient 111 protected String attachmentListPropertyName; 112 @Transient 113 protected String attachmentCollectionName; 114 115 @OneToOne(fetch = FetchType.LAZY, 116 cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}) 117 @JoinColumn(name = "DOC_HDR_ID", 118 insertable = false, updatable = false) 119 protected DocumentAttachment attachment; 120 121 @OneToMany(fetch = FetchType.LAZY, 122 cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}) 123 @JoinColumn(name = "DOC_HDR_ID", 124 insertable = false, updatable = false) 125 protected List<MultiDocumentAttachment> attachments; 126 127 public MaintenanceDocumentBase() { 128 super(); 129 fieldsClearedOnCopy = false; 130 } 131 132 /** 133 * Initializies the maintainables. 134 */ 135 public MaintenanceDocumentBase(String documentTypeName) { 136 this(); 137 Class clazz = getDocumentDictionaryService().getMaintainableClass(documentTypeName); 138 try { 139 oldMaintainableObject = (Maintainable) clazz.newInstance(); 140 newMaintainableObject = (Maintainable) clazz.newInstance(); 141 142 // initialize maintainable with a data object 143 Class<?> dataObjectClazz = getDocumentDictionaryService().getMaintenanceDataObjectClass(documentTypeName); 144 oldMaintainableObject.setDataObject(dataObjectClazz.newInstance()); 145 oldMaintainableObject.setDataObjectClass(dataObjectClazz); 146 newMaintainableObject.setDataObject(dataObjectClazz.newInstance()); 147 newMaintainableObject.setDataObjectClass(dataObjectClazz); 148 } catch (InstantiationException e) { 149 LOG.error("Unable to initialize maintainables of type " + clazz.getName()); 150 throw new RuntimeException("Unable to initialize maintainables of type " + clazz.getName()); 151 } catch (IllegalAccessException e) { 152 LOG.error("Unable to initialize maintainables of type " + clazz.getName()); 153 throw new RuntimeException("Unable to initialize maintainables of type " + clazz.getName()); 154 } 155 } 156 157 /** 158 * Builds out the document title for maintenance documents 159 * 160 * <p>This will get loaded into the flex doc and passed into 161 * workflow. It will be searchable. 162 * </p> 163 * 164 * @return document title 165 */ 166 @Override 167 public String getDocumentTitle() { 168 String documentTitle = ""; 169 170 documentTitle = newMaintainableObject.getDocumentTitle(this); 171 if (StringUtils.isNotBlank(documentTitle)) { 172 // if doc title has been overridden by maintainable, use it 173 return documentTitle; 174 } 175 176 // TODO - build out with bo label once we get the data dictionary stuff in place 177 // build out the right classname 178 String className = newMaintainableObject.getDataObject().getClass().getName(); 179 String truncatedClassName = className.substring(className.lastIndexOf('.') + 1); 180 if (isOldDataObjectInDocument()) { 181 if (KRADConstants.MAINTENANCE_COPY_ACTION.equals(oldMaintainableObject.getMaintenanceAction())) { 182 documentTitle = "Copy "; 183 } else { 184 documentTitle = "Edit "; 185 } 186 } else { 187 documentTitle = "New "; 188 } 189 documentTitle += truncatedClassName + " - "; 190 documentTitle += this.getDocumentHeader().getDocumentDescription() + " "; 191 return documentTitle; 192 } 193 194 /** 195 * Check if oldMaintainable is specified in the XML of the maintenance document 196 * 197 * @param xmlDocument Maintenance document in XML form 198 * @return true if an oldMainainable exists in the xmlDocument, false otherwise 199 */ 200 protected boolean isOldMaintainableInDocument(Document xmlDocument) { 201 boolean isOldMaintainableInExistence = false; 202 if (xmlDocument.getElementsByTagName(OLD_MAINTAINABLE_TAG_NAME).getLength() > 0) { 203 isOldMaintainableInExistence = true; 204 } 205 return isOldMaintainableInExistence; 206 } 207 208 /** 209 * @see org.kuali.rice.krad.maintenance.Maintainable#isOldDataObjectInDocument() 210 */ 211 @Override 212 public boolean isOldDataObjectInDocument() { 213 boolean isOldBusinessObjectInExistence = false; 214 if (oldMaintainableObject == null || oldMaintainableObject.getDataObject() == null) { 215 isOldBusinessObjectInExistence = false; 216 } else { 217 isOldBusinessObjectInExistence = oldMaintainableObject.isOldDataObjectInDocument(); 218 } 219 return isOldBusinessObjectInExistence; 220 } 221 222 /** 223 * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#isNew() 224 */ 225 @Override 226 public boolean isNew() { 227 return MaintenanceUtils.isMaintenanceDocumentCreatingNewRecord(newMaintainableObject.getMaintenanceAction()); 228 } 229 230 /** 231 * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#isEdit() 232 */ 233 @Override 234 public boolean isEdit() { 235 if (KRADConstants.MAINTENANCE_EDIT_ACTION.equalsIgnoreCase(newMaintainableObject.getMaintenanceAction())) { 236 return true; 237 } else { 238 return false; 239 } 240 } 241 242 /** 243 * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#isNewWithExisting() 244 */ 245 @Override 246 public boolean isNewWithExisting() { 247 if (KRADConstants.MAINTENANCE_NEWWITHEXISTING_ACTION.equalsIgnoreCase( 248 newMaintainableObject.getMaintenanceAction())) { 249 return true; 250 } else { 251 return false; 252 } 253 } 254 255 /** 256 * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#populateMaintainablesFromXmlDocumentContents() 257 */ 258 @Override 259 public void populateMaintainablesFromXmlDocumentContents() { 260 // get a hold of the parsed xml document, then read the classname, 261 // then instantiate one to two instances depending on content 262 // then populate those instances 263 if (!StringUtils.isEmpty(xmlDocumentContents)) { 264 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 265 /* KULRICE-12304: 266 * Modified this block to fetch the document type and use that 267 * document type to fetch the maintainable class from the document 268 * dictionary service. This was necessary since the maintainable 269 * class which is persisted in the document content XML may be out 270 * of date if it changes across version updates. 271 */ 272 try { 273 DocumentBuilder builder = factory.newDocumentBuilder(); 274 Document xmlDocument = builder.parse(new InputSource(new StringReader(xmlDocumentContents))); 275 String documentTypeName = KewApiServiceLocator.getWorkflowDocumentService().getDocument(this.getDocumentNumber()).getDocumentTypeName(); 276 Class<? extends Maintainable> maintainableClass = getDocumentDictionaryService().getMaintainableClass(documentTypeName); 277 if (isOldMaintainableInDocument(xmlDocument)) { 278 oldMaintainableObject = (Maintainable) maintainableClass.newInstance(); 279 Object dataObject = getDataObjectFromXML(OLD_MAINTAINABLE_TAG_NAME); 280 281 String oldMaintenanceAction = getMaintenanceAction(xmlDocument, OLD_MAINTAINABLE_TAG_NAME); 282 oldMaintainableObject.setMaintenanceAction(oldMaintenanceAction); 283 284 oldMaintainableObject.setDataObject(dataObject); 285 oldMaintainableObject.setDataObjectClass(dataObject.getClass()); 286 } 287 newMaintainableObject = (Maintainable) maintainableClass.newInstance(); 288 Object bo = getDataObjectFromXML(NEW_MAINTAINABLE_TAG_NAME); 289 newMaintainableObject.setDataObject(bo); 290 newMaintainableObject.setDataObjectClass(bo.getClass()); 291 292 String newMaintenanceAction = getMaintenanceAction(xmlDocument, NEW_MAINTAINABLE_TAG_NAME); 293 newMaintainableObject.setMaintenanceAction(newMaintenanceAction); 294 295 if (newMaintainableObject.isNotesEnabled()) { 296 List<Note> notes = getNotesFromXml(NOTES_TAG_NAME); 297 setNotes(notes); 298 } 299 } catch (ParserConfigurationException e) { 300 LOG.error("Error while parsing document contents", e); 301 throw new RuntimeException("Could not load document contents from xml", e); 302 } catch (SAXException e) { 303 LOG.error("Error while parsing document contents", e); 304 throw new RuntimeException("Could not load document contents from xml", e); 305 } catch (IOException e) { 306 LOG.error("Error while parsing document contents", e); 307 throw new RuntimeException("Could not load document contents from xml", e); 308 } catch (InstantiationException e) { 309 LOG.error("Error while parsing document contents", e); 310 throw new RuntimeException("Could not load document contents from xml", e); 311 } catch (IllegalAccessException e) { 312 LOG.error("Error while parsing document contents", e); 313 throw new RuntimeException("Could not load document contents from xml", e); 314 } 315 } 316 } 317 318 /** 319 * This method is a lame containment of ugly DOM walking code. This is ONLY necessary because of the version 320 * 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 321 * XPath, which is faster and much easier on the eyes. 322 * 323 * @param xmlDocument 324 * @param oldOrNewElementName - String oldMaintainableObject or newMaintainableObject 325 * @return the value of the element, or null if none was there 326 */ 327 protected String getMaintenanceAction(Document xmlDocument, String oldOrNewElementName) { 328 if (StringUtils.isBlank(oldOrNewElementName)) { 329 throw new IllegalArgumentException("oldOrNewElementName may not be blank, null, or empty-string."); 330 } 331 332 String maintenanceAction = null; 333 NodeList rootChildren = xmlDocument.getDocumentElement().getChildNodes(); 334 for (int i = 0; i < rootChildren.getLength(); i++) { 335 Node rootChild = rootChildren.item(i); 336 if (oldOrNewElementName.equalsIgnoreCase(rootChild.getNodeName())) { 337 NodeList maintChildren = rootChild.getChildNodes(); 338 for (int j = 0; j < maintChildren.getLength(); j++) { 339 Node maintChild = maintChildren.item(j); 340 if (MAINTENANCE_ACTION_TAG_NAME.equalsIgnoreCase(maintChild.getNodeName())) { 341 maintenanceAction = maintChild.getChildNodes().item(0).getNodeValue(); 342 } 343 } 344 } 345 } 346 return maintenanceAction; 347 } 348 349 /** 350 * Get notes from XML 351 * 352 * @param notesTagName the xml tag name of the notes 353 * @return list of <code>Note</code>s 354 */ 355 private List<Note> getNotesFromXml(String notesTagName) { 356 String notesXml = StringUtils.substringBetween(xmlDocumentContents, "<" + notesTagName + ">", 357 "</" + notesTagName + ">"); 358 if (StringUtils.isBlank(notesXml)) { 359 return Collections.emptyList(); 360 } 361 List<Note> notes = (List<Note>) KRADServiceLocator.getXmlObjectSerializerService().fromXml(notesXml); 362 if (notes == null) { 363 return Collections.emptyList(); 364 } 365 return notes; 366 } 367 368 /** 369 * Get data object from XML 370 * 371 * <p> 372 * Retrieves substring of document contents from maintainable tag name. Then use xml service to translate xml into 373 * a business object. 374 * </p> 375 * 376 * @param maintainableTagName the xml tag name of the maintainable 377 * @return data object 378 */ 379 protected Object getDataObjectFromXML(String maintainableTagName) { 380 String maintXml = StringUtils.substringBetween(xmlDocumentContents, "<" + maintainableTagName + ">", 381 "</" + maintainableTagName + ">"); 382 /*KULRICE-12304*/ 383 try { 384 boolean ignoreMissingFields = false; 385 String classAndDocTypeNames = ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.Config.IGNORE_MISSIONG_FIELDS_ON_DESERIALIZE); 386 if (!StringUtils.isEmpty(classAndDocTypeNames)) { 387 String classNameOnXML = StringUtils.substringBetween(xmlDocumentContents, "<" + maintainableTagName + "><", ">"); 388 String classNamesNoSpaces = removeSpacesAround(classAndDocTypeNames); 389 List<String> classAndDocTypeNamesList = Arrays.asList(org.apache.commons.lang.StringUtils.split(classNamesNoSpaces, ",")); 390 String originalDocTypeId = getDocumentHeader().getWorkflowDocument().getDocumentTypeId(); 391 DocumentType docType = KewApiServiceLocator.getDocumentTypeService().getDocumentTypeById(originalDocTypeId); 392 393 while (docType != null && !ignoreMissingFields) { 394 for(String classNameOrDocTypeName : classAndDocTypeNamesList){ 395 if (docType.getName().equalsIgnoreCase(classNameOrDocTypeName) || 396 classNameOnXML.equalsIgnoreCase(classNameOrDocTypeName)) { 397 ignoreMissingFields = true; 398 break; 399 } 400 } 401 if (!StringUtils.isEmpty(docType.getParentId())) { 402 docType = KewApiServiceLocator.getDocumentTypeService().getDocumentTypeById(docType.getParentId()); 403 } else { 404 docType = null; 405 } 406 } 407 } 408 if (!ignoreMissingFields) { 409 return KRADServiceLocator.getXmlObjectSerializerService().fromXml(maintXml); 410 } else { 411 return KRADServiceLocator.getXmlObjectSerializerIgnoreMissingFieldsService().fromXml(maintXml); 412 } 413 }catch (BaseException e) { 414 String convertedXml = KRADServiceLocatorWeb.getMaintainableXMLConversionService().transformMaintainableXML(maintXml); 415 return KRADServiceLocator.getXmlObjectSerializerService().fromXml(convertedXml); 416 }/*KULRICE-12304*/ 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=\"").append( 459 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 //KradDataServiceLocator.getDataObjectService().wrap(oldBo).materializeReferencedObjectsToDepth(SUB_OBJECT_MATERIALIZATION_DEPTH); 483 KRADServiceLocatorWeb.getLegacyDataAdapter().materializeAllSubObjects(oldBo); 484 485 docContentBuffer.append(getBusinessObjectSerializerService().serializeBusinessObjectToXml(oldBo)); 486 487 // add the maintainable's maintenanceAction 488 docContentBuffer.append("<" + MAINTENANCE_ACTION_TAG_NAME + ">"); 489 docContentBuffer.append(oldMaintainableObject.getMaintenanceAction()); 490 docContentBuffer.append("</" + MAINTENANCE_ACTION_TAG_NAME + ">\n"); 491 492 docContentBuffer.append("</" + OLD_MAINTAINABLE_TAG_NAME + ">"); 493 } 494 docContentBuffer.append("<" + NEW_MAINTAINABLE_TAG_NAME + ">"); 495 496 Object newBo = newMaintainableObject.getDataObject(); 497 498 //KradDataServiceLocator.getDataObjectService().wrap(newBo).materializeReferencedObjectsToDepth(SUB_OBJECT_MATERIALIZATION_DEPTH); 499 KRADServiceLocatorWeb.getLegacyDataAdapter().materializeAllSubObjects(newBo); 500 501 docContentBuffer.append(getBusinessObjectSerializerService().serializeBusinessObjectToXml(newBo)); 502 503 // add the maintainable's maintenanceAction 504 docContentBuffer.append("<" + MAINTENANCE_ACTION_TAG_NAME + ">"); 505 docContentBuffer.append(newMaintainableObject.getMaintenanceAction()); 506 docContentBuffer.append("</" + MAINTENANCE_ACTION_TAG_NAME + ">\n"); 507 508 docContentBuffer.append("</" + NEW_MAINTAINABLE_TAG_NAME + ">"); 509 docContentBuffer.append("</maintainableDocumentContents>"); 510 xmlDocumentContents = docContentBuffer.toString(); 511 } 512 513 /** 514 * @see org.kuali.rice.krad.document.DocumentBase#doRouteStatusChange(org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange) 515 */ 516 @Override 517 public void doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) { 518 super.doRouteStatusChange(statusChangeEvent); 519 520 WorkflowDocument workflowDocument = getDocumentHeader().getWorkflowDocument(); 521 getNewMaintainableObject().doRouteStatusChange(getDocumentHeader()); 522 // commit the changes to the Maintainable BusinessObject when it goes to Processed (ie, fully approved), 523 // and also unlock it 524 if (workflowDocument.isProcessed()) { 525 final String documentNumber = getDocumentHeader().getDocumentNumber(); 526 newMaintainableObject.setDocumentNumber(documentNumber); 527 528 //Populate Attachment Property 529 if (newMaintainableObject.getDataObject() instanceof PersistableAttachment) { 530 populateAttachmentBeforeSave(); 531 } 532 533 //Populate Attachment Property 534 if (newMaintainableObject.getDataObject() instanceof PersistableAttachmentList) { 535 populateBoAttachmentListBeforeSave(); 536 } 537 538 newMaintainableObject.saveDataObject(); 539 540 if (!getDocumentService().saveDocumentNotes(this)) { 541 throw new IllegalStateException( 542 "Failed to save document notes, this means that the note target was not ready for notes to be attached when it should have been."); 543 } 544 545 //Attachment should be deleted from Maintenance Document attachment table 546 deleteDocumentAttachment(); 547 deleteDocumentAttachmentList(); 548 549 getMaintenanceDocumentService().deleteLocks(documentNumber); 550 551 //for issue 3070, check if delete record 552 if (this.checkAllowsRecordDeletion() && this.checkMaintenanceAction() && 553 this.checkDeletePermission(newMaintainableObject.getDataObject())) { 554 newMaintainableObject.deleteDataObject(); 555 } 556 } 557 558 // unlock the document when its canceled or disapproved or placed inException status 559 if (workflowDocument.isCanceled() || workflowDocument.isDisapproved() || workflowDocument.isRecalled() || workflowDocument.isException()) { 560 //Attachment should be deleted from Maintenance Document attachment table 561 deleteDocumentAttachment(); 562 deleteDocumentAttachmentList(); 563 564 String documentNumber = getDocumentHeader().getDocumentNumber(); 565 getMaintenanceDocumentService().deleteLocks(documentNumber); 566 } 567 } 568 569 /** 570 * @see org.kuali.rice.krad.document.DocumentBase#getWorkflowEngineDocumentIdsToLock() 571 */ 572 @Override 573 public List<String> getWorkflowEngineDocumentIdsToLock() { 574 if (newMaintainableObject != null) { 575 return newMaintainableObject.getWorkflowEngineDocumentIdsToLock(); 576 } 577 return Collections.emptyList(); 578 } 579 580 /** 581 * @see org.kuali.rice.krad.document.Document#prepareForSave() 582 */ 583 @Override 584 public void prepareForSave() { 585 if (newMaintainableObject != null) { 586 newMaintainableObject.prepareForSave(); 587 } 588 } 589 590 /** 591 * @see org.kuali.rice.krad.document.DocumentBase#processAfterRetrieve() 592 */ 593 @Override 594 public void processAfterRetrieve() { 595 596 super.processAfterRetrieve(); 597 598 populateMaintainablesFromXmlDocumentContents(); 599 if (oldMaintainableObject != null) { 600 oldMaintainableObject.setDocumentNumber(documentNumber); 601 } 602 if (newMaintainableObject != null) { 603 newMaintainableObject.setDocumentNumber(documentNumber); 604 newMaintainableObject.processAfterRetrieve(); 605 if (newMaintainableObject.getDataObject() instanceof PersistableAttachment) { 606 populateAttachmentForBO(); 607 } 608 if (newMaintainableObject.getDataObject() instanceof PersistableAttachmentList) { 609 populateAttachmentListForBO(); 610 } 611 // If a maintenance lock exists, warn the user. 612 checkForLockingDocument(false); 613 } 614 } 615 616 /** 617 * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#getNewMaintainableObject() 618 */ 619 @Override 620 public Maintainable getNewMaintainableObject() { 621 return newMaintainableObject; 622 } 623 624 /** 625 * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#setNewMaintainableObject(Maintainable) 626 */ 627 @Override 628 public void setNewMaintainableObject(Maintainable newMaintainableObject) { 629 this.newMaintainableObject = newMaintainableObject; 630 } 631 632 /** 633 * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#getOldMaintainableObject() 634 */ 635 @Override 636 public Maintainable getOldMaintainableObject() { 637 return oldMaintainableObject; 638 } 639 640 /** 641 * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#setOldMaintainableObject(Maintainable) 642 */ 643 @Override 644 public void setOldMaintainableObject(Maintainable oldMaintainableObject) { 645 this.oldMaintainableObject = oldMaintainableObject; 646 } 647 648 /** 649 * @see org.kuali.rice.krad.document.DocumentBase#setDocumentNumber(java.lang.String) 650 */ 651 @Override 652 public void setDocumentNumber(String documentNumber) { 653 super.setDocumentNumber(documentNumber); 654 655 // set the finDocNumber on the Maintainable 656 oldMaintainableObject.setDocumentNumber(documentNumber); 657 newMaintainableObject.setDocumentNumber(documentNumber); 658 } 659 660 /** 661 * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#isFieldsClearedOnCopy() 662 */ 663 @Override 664 public final boolean isFieldsClearedOnCopy() { 665 return fieldsClearedOnCopy; 666 } 667 668 /** 669 * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#setFieldsClearedOnCopy(boolean) 670 */ 671 @Override 672 public final void setFieldsClearedOnCopy(boolean fieldsClearedOnCopy) { 673 this.fieldsClearedOnCopy = fieldsClearedOnCopy; 674 } 675 676 /** 677 * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#getXmlDocumentContents() 678 */ 679 @Override 680 public String getXmlDocumentContents() { 681 return xmlDocumentContents; 682 } 683 684 /** 685 * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#setXmlDocumentContents(String) 686 */ 687 @Override 688 public void setXmlDocumentContents(String xmlDocumentContents) { 689 this.xmlDocumentContents = xmlDocumentContents; 690 } 691 692 /** 693 * @see org.kuali.rice.krad.document.Document#getAllowsCopy() 694 */ 695 @Override 696 public boolean getAllowsCopy() { 697 return getDocumentDictionaryService().getAllowsCopy(this); 698 } 699 700 /** 701 * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#isDisplayTopicFieldInNotes() 702 */ 703 @Override 704 public boolean isDisplayTopicFieldInNotes() { 705 return displayTopicFieldInNotes; 706 } 707 708 /** 709 * @see MaintenanceDocument#setDisplayTopicFieldInNotes(boolean) 710 */ 711 @Override 712 public void setDisplayTopicFieldInNotes(boolean displayTopicFieldInNotes) { 713 this.displayTopicFieldInNotes = displayTopicFieldInNotes; 714 } 715 716 /** 717 * Overridden to avoid serializing the xml twice, because of the xmlDocumentContents property of this object 718 */ 719 @Override 720 public String serializeDocumentToXml() { 721 String tempXmlDocumentContents = xmlDocumentContents; 722 xmlDocumentContents = null; 723 String xmlForWorkflow = super.serializeDocumentToXml(); 724 xmlDocumentContents = tempXmlDocumentContents; 725 return xmlForWorkflow; 726 } 727 728 /** 729 * @see DocumentBase#prepareForSave(org.kuali.rice.krad.rules.rule.event.DocumentEvent) 730 */ 731 @Override 732 public void prepareForSave(DocumentEvent event) { 733 super.prepareForSave(event); 734 if (newMaintainableObject.getDataObject() instanceof PersistableAttachment) { 735 populateDocumentAttachment(); 736 populateAttachmentForBO(); 737 //clear out attachment file for old data object so it isn't serialized in doc content 738 if (oldMaintainableObject.getDataObject() instanceof PersistableAttachment) { 739 ((PersistableAttachment) oldMaintainableObject.getDataObject()).setAttachmentContent(null); 740 } 741 } 742 if (newMaintainableObject.getDataObject() instanceof PersistableAttachmentList) { 743 populateDocumentAttachmentList(); 744 populateAttachmentListForBO(); 745 if (oldMaintainableObject.getDataObject() instanceof PersistableAttachmentList) { 746 for (PersistableAttachment pa : ((PersistableAttachmentList<PersistableAttachment>) oldMaintainableObject 747 .getDataObject()).getAttachments()) { 748 pa.setAttachmentContent(null); 749 } 750 } 751 } 752 populateXmlDocumentContentsFromMaintainables(); 753 } 754 755 /** 756 * The attachment BO is proxied in OJB. For some reason when an attachment does not yet exist, 757 * refreshReferenceObject is not returning null and the proxy cannot be materialized. So, this method exists to 758 * properly handle the proxied attachment BO. This is a hack and should be removed post JPA migration. 759 */ 760 @Deprecated 761 protected void refreshAttachment() { 762 if (attachment == null) { 763 KradDataServiceLocator.getDataObjectService().wrap(this).fetchRelationship("attachment"); 764 } 765 } 766 767 @Deprecated 768 protected void refreshAttachmentList() { 769 if (attachments == null) { 770 KradDataServiceLocator.getDataObjectService().wrap(this).fetchRelationship("attachments"); 771 } 772 } 773 774 @Deprecated 775 public void populateAttachmentForBO() { } 776 777 @Deprecated 778 public void populateDocumentAttachment() { } 779 780 @Deprecated 781 public void populateAttachmentListForBO() { } 782 783 @Deprecated 784 public void populateAttachmentBeforeSave() { } 785 786 @Deprecated 787 public void populateDocumentAttachmentList() { } 788 789 @Deprecated 790 public void populateBoAttachmentListBeforeSave() { } 791 792 @Deprecated 793 public void deleteDocumentAttachment() { 794 if ( attachment != null ) { 795 KRADServiceLocatorWeb.getLegacyDataAdapter().delete(attachment); 796 attachment = null; 797 } 798 } 799 800 @Deprecated 801 public void deleteDocumentAttachmentList() { 802 if (CollectionUtils.isNotEmpty(attachments)) { 803 for (MultiDocumentAttachment attachment : attachments) { 804 KRADServiceLocatorWeb.getLegacyDataAdapter().delete(attachment); 805 } 806 attachments = null; 807 } 808 } 809 810 /** 811 * Explicitly NOT calling super here. This is a complete override of the validation rules behavior. 812 * 813 * @see org.kuali.rice.krad.document.DocumentBase#validateBusinessRules(org.kuali.rice.krad.rules.rule.event.DocumentEvent) 814 */ 815 @Override 816 public void validateBusinessRules(DocumentEvent event) { 817 if (GlobalVariables.getMessageMap().hasErrors()) { 818 logErrors(); 819 throw new ValidationException("errors occured before business rule"); 820 } 821 822 // check for locking documents for MaintenanceDocuments 823 checkForLockingDocument(true); 824 825 // Make sure the business object's version number matches that of the databases copy. 826 827 if (newMaintainableObject != null) { 828 KRADServiceLocatorWeb.getLegacyDataAdapter().verifyVersionNumber(newMaintainableObject.getDataObject()); 829 } 830 831 // perform validation against rules engine 832 if (LOG.isInfoEnabled()) { 833 LOG.info("invoking rules engine on document " + getDocumentNumber()); 834 } 835 836 boolean isValid = true; 837 isValid = KRADServiceLocatorWeb.getKualiRuleService().applyRules(event); 838 839 // check to see if the br eval passed or failed 840 if (!isValid) { 841 logErrors(); 842 // TODO: better error handling at the lower level and a better error message are 843 // needed here 844 throw new ValidationException("business rule evaluation failed"); 845 } else if (GlobalVariables.getMessageMap().hasErrors()) { 846 logErrors(); 847 if (event instanceof SaveDocumentEvent) { 848 // for maintenance documents, we want to always actually do a save if the 849 // user requests a save, even if there are validation or business rules 850 // failures. this empty if does this, and allows the document to be saved, 851 // even if there are failures. 852 // BR or validation failures on a ROUTE even should always stop the route, 853 // that has not changed 854 } else { 855 throw new ValidationException( 856 "Unreported errors occurred during business rule evaluation (rule developer needs to put meaningful error messages into global ErrorMap)"); 857 } 858 } 859 860 LOG.debug("validation completed"); 861 } 862 863 protected void checkForLockingDocument(boolean throwExceptionIfLocked) { 864 MaintenanceUtils.checkForLockingDocument(this, throwExceptionIfLocked); 865 } 866 867 /** 868 * this needs to happen after the document itself is saved, to preserve consistency of the ver_nbr and in the case 869 * of initial save, because this can't be saved until the document is saved initially 870 * 871 * @see org.kuali.rice.krad.document.DocumentBase#postProcessSave(org.kuali.rice.krad.rules.rule.event.DocumentEvent) 872 */ 873 @Override 874 public void postProcessSave(DocumentEvent event) { 875 //currently only global documents could change the list of what they're affecting during routing, 876 //so could restrict this to only happening with them, but who knows if that will change, so safest 877 //to always do the delete and re-add...seems a bit inefficient though if nothing has changed, which is 878 //most of the time...could also try to only add/update/delete what's changed, but this is easier 879 if (!(event instanceof SaveDocumentEvent)) { //don't lock until they route 880 getMaintenanceDocumentService().deleteLocks(MaintenanceDocumentBase.this.getDocumentNumber()); 881 getMaintenanceDocumentService().storeLocks(MaintenanceDocumentBase.this.getNewMaintainableObject().generateMaintenanceLocks()); 882 } 883 } 884 885 /** 886 * @see org.kuali.rice.krad.maintenance.MaintenanceDocument#getDocumentDataObject() 887 */ 888 @Override 889 public Object getDocumentDataObject() { 890 return getNewMaintainableObject().getDataObject(); 891 } 892 893 /** 894 * <p>The Note target for maintenance documents is determined by whether or not the underlying {@link Maintainable} 895 * supports business object notes or not. This is determined via a call to {@link 896 * org.kuali.rice.krad.maintenance.Maintainable#isNotesEnabled()}. 897 * The note target is then derived as follows: <p/> <ul> <li>If the {@link Maintainable} supports business object 898 * notes, delegate to {@link #getDocumentDataObject()}. <li>Otherwise, delegate to the default implementation of 899 * getNoteTarget on the superclass which will effectively return a reference to the {@link DocumentHeader}. </ul> 900 * 901 * @see org.kuali.rice.krad.document.Document#getNoteTarget() 902 */ 903 @Override 904 public GloballyUnique getNoteTarget() { 905 if (getNewMaintainableObject() == null) { 906 throw new IllegalStateException( 907 "Failed to acquire the note target. The new maintainable object on this document is null."); 908 } 909 if (getNewMaintainableObject().isNotesEnabled() && getDocumentDataObject() instanceof GloballyUnique ) { 910 return (GloballyUnique) getDocumentDataObject(); 911 } 912 return super.getNoteTarget(); 913 } 914 915 /** 916 * The {@link org.kuali.rice.krad.util.NoteType} for maintenance documents is determined by whether or not the underlying {@link 917 * Maintainable} supports business object notes or not. This is determined via a call to {@link 918 * Maintainable#isNotesEnabled()}. The {@link org.kuali.rice.krad.util.NoteType} is then derived as follows: <p/> <ul> <li>If the 919 * {@link 920 * Maintainable} supports business object notes, return {@link org.kuali.rice.krad.util.NoteType#BUSINESS_OBJECT}. <li>Otherwise, delegate 921 * to 922 * {@link DocumentBase#getNoteType()} </ul> 923 * 924 * @see org.kuali.rice.krad.document.Document#getNoteType() 925 * @see org.kuali.rice.krad.document.Document#getNoteTarget() 926 */ 927 @Override 928 public org.kuali.rice.krad.util.NoteType getNoteType() { 929 if (getNewMaintainableObject().isNotesEnabled()) { 930 return org.kuali.rice.krad.util.NoteType.BUSINESS_OBJECT; 931 } 932 return super.getNoteType(); 933 } 934 935 @Override 936 public PropertySerializabilityEvaluator getDocumentPropertySerizabilityEvaluator() { 937 String docTypeName = ""; 938 if (newMaintainableObject != null) { 939 docTypeName = getDocumentDictionaryService().getMaintenanceDocumentTypeName( 940 this.newMaintainableObject.getDataObjectClass()); 941 } else { // I don't know why we aren't just using the header in the first place 942 // but, in the case where we can't get it in the way above, attempt to get 943 // it off the workflow document header 944 if (getDocumentHeader() != null && getDocumentHeader().getWorkflowDocument() != null) { 945 docTypeName = getDocumentHeader().getWorkflowDocument().getDocumentTypeName(); 946 } 947 } 948 if (!StringUtils.isBlank(docTypeName)) { 949 DocumentEntry documentEntry = getDocumentDictionaryService().getMaintenanceDocumentEntry(docTypeName); 950 if (documentEntry != null) { 951 WorkflowProperties workflowProperties = documentEntry.getWorkflowProperties(); 952 WorkflowAttributes workflowAttributes = documentEntry.getWorkflowAttributes(); 953 return createPropertySerializabilityEvaluator(workflowProperties, workflowAttributes); 954 } else { 955 LOG.error("Unable to obtain DD DocumentEntry for document type: '" + docTypeName + "'"); 956 } 957 } else { 958 LOG.error("Unable to obtain document type name for this document: " + this); 959 } 960 LOG.error("Returning null for the PropertySerializabilityEvaluator"); 961 return null; 962 } 963 964 public DocumentAttachment getAttachment() { 965 return this.attachment; 966 } 967 968 public void setAttachment(DocumentAttachment attachment) { 969 this.attachment = attachment; 970 } 971 972 public List<MultiDocumentAttachment> getAttachments() { 973 return this.attachments; 974 } 975 976 public void setAttachments(List<MultiDocumentAttachment> attachments) { 977 this.attachments = attachments; 978 } 979 980 public String getAttachmentPropertyName() { 981 return this.attachmentPropertyName; 982 } 983 984 public void setAttachmentPropertyName(String attachmentPropertyName) { 985 this.attachmentPropertyName = attachmentPropertyName; 986 } 987 988 public String getAttachmentListPropertyName() { 989 return this.attachmentListPropertyName; 990 } 991 992 public void setAttachmentListPropertyName(String attachmentListPropertyName) { 993 this.attachmentListPropertyName = attachmentListPropertyName; 994 } 995 996 public String getAttachmentCollectionName() { 997 return this.attachmentCollectionName; 998 } 999 1000 public void setAttachmentCollectionName(String attachmentCollectionName) { 1001 this.attachmentCollectionName = attachmentCollectionName; 1002 } 1003 1004 /** 1005 * This method to check whether the document class implements SessionDocument 1006 * 1007 * TODO: move to KNS maintenance document base 1008 * 1009 * @return true if the document is a session document 1010 */ 1011 public boolean isSessionDocument() { 1012 return SessionDocument.class.isAssignableFrom(this.getClass()); 1013 } 1014 1015 /** 1016 * Returns whether or not the new maintainable object supports custom lock descriptors. Will always return false if 1017 * the new maintainable is null. 1018 * 1019 * @see org.kuali.rice.krad.document.Document#useCustomLockDescriptors() 1020 * @see org.kuali.rice.krad.maintenance.Maintainable#useCustomLockDescriptors() 1021 */ 1022 @Override 1023 public boolean useCustomLockDescriptors() { 1024 return (newMaintainableObject != null && newMaintainableObject.useCustomLockDescriptors()); 1025 } 1026 1027 /** 1028 * Returns the custom lock descriptor generated by the new maintainable object, if defined. Will throw a 1029 * PessimisticLockingException if the new maintainable is null. 1030 * 1031 * @see org.kuali.rice.krad.document.Document#getCustomLockDescriptor(org.kuali.rice.kim.api.identity.Person) 1032 * @see org.kuali.rice.krad.maintenance.Maintainable#getCustomLockDescriptor(org.kuali.rice.kim.api.identity.Person) 1033 */ 1034 @Override 1035 public String getCustomLockDescriptor(Person user) { 1036 if (newMaintainableObject == null) { 1037 throw new PessimisticLockingException("Maintenance Document " + getDocumentNumber() + 1038 " is using pessimistic locking with custom lock descriptors, but no new maintainable object has been defined"); 1039 } 1040 return newMaintainableObject.getCustomLockDescriptor(user); 1041 } 1042 1043 protected DocumentDictionaryService getDocumentDictionaryService() { 1044 if (documentDictionaryService == null) { 1045 documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService(); 1046 } 1047 return documentDictionaryService; 1048 } 1049 1050 protected MaintenanceDocumentService getMaintenanceDocumentService() { 1051 if (maintenanceDocumentService == null) { 1052 maintenanceDocumentService = KRADServiceLocatorWeb.getMaintenanceDocumentService(); 1053 } 1054 return maintenanceDocumentService; 1055 } 1056 1057 protected DocumentService getDocumentService() { 1058 if (documentService == null) { 1059 documentService = KRADServiceLocatorWeb.getDocumentService(); 1060 } 1061 return documentService; 1062 } 1063 1064 /** 1065 * @return the service used for serializing maintained business / data objects 1066 */ 1067 protected BusinessObjectSerializerService getBusinessObjectSerializerService() { 1068 return KRADServiceLocator.getDataObjectSerializerService(); 1069 } 1070 1071 //for issue KULRice3070 1072 protected boolean checkAllowsRecordDeletion() { 1073 Boolean allowsRecordDeletion = KRADServiceLocatorWeb.getDocumentDictionaryService().getAllowsRecordDeletion( 1074 this.getNewMaintainableObject().getDataObjectClass()); 1075 if (allowsRecordDeletion != null) { 1076 return allowsRecordDeletion.booleanValue(); 1077 } else { 1078 return false; 1079 } 1080 } 1081 1082 //for KULRice3070 1083 protected boolean checkMaintenanceAction() { 1084 return this.getNewMaintainableObject().getMaintenanceAction().equals(KRADConstants.MAINTENANCE_DELETE_ACTION); 1085 } 1086 1087 //for KULRice3070 1088 protected boolean checkDeletePermission(Object dataObject) { 1089 boolean allowsMaintain = false; 1090 1091 String maintDocTypeName = KRADServiceLocatorWeb.getDocumentDictionaryService().getMaintenanceDocumentTypeName( 1092 dataObject.getClass()); 1093 1094 if (StringUtils.isNotBlank(maintDocTypeName)) { 1095 allowsMaintain = KRADServiceLocatorWeb.getDataObjectAuthorizationService().canMaintain(dataObject, 1096 GlobalVariables.getUserSession().getPerson(), maintDocTypeName); 1097 } 1098 return allowsMaintain; 1099 } 1100 1101 /* *//** 1102 * Save the KRAD document header via the document header service. 1103 *//* 1104 @Override 1105 @PrePersist 1106 protected void prePersist() { 1107 super.prePersist(); 1108 // KRAD/JPA - have to change the handle to object to that just saved 1109 1110 *//* Modified by 'Sheik Salahudeen' for fixing the 'workflow document is null' issue in KRAD Transaction document with JPA. 1111 * Removed the code for saving document header. Because document header will be stored by document service.*//* 1112 1113 // documentHeader = KRADServiceLocatorWeb.getDocumentHeaderService().saveDocumentHeader(documentHeader); 1114 }*/ 1115 1116 /** 1117 * Copied this method from FinancialSystemMaintenanceDocument.java 1118 */ 1119 /** 1120 * This method is used for routing and simply returns the initiator's Chart code. 1121 * @return The Chart code of the document initiator 1122 */ 1123 public String getInitiatorChartOfAccountsCode() { 1124 String[] chartOrg = getInitiatorPrimaryDepartmentCode(); 1125 return chartOrg[0]; 1126 } 1127 1128 protected String[] getInitiatorPrimaryDepartmentCode() { 1129 1130 String netID = documentHeader.getWorkflowDocument().getInitiatorPrincipalId(); 1131 Person person = KimApiServiceLocator.getPersonService().getPerson(netID); 1132 1133 String deptCode = person.getPrimaryDepartmentCode(); 1134 String[] chartOrg = deptCode.split("-"); 1135 return chartOrg; 1136 1137 } 1138 1139 /** 1140 * This method is used for routing and simply returns the initiator's Organization code. 1141 * @return The Organization code of the document initiator 1142 */ 1143 public String getInitiatorOrganizationCode() { 1144 String[] chartOrg = getInitiatorPrimaryDepartmentCode(); 1145 return chartOrg[1]; 1146 } 1147 1148}