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.lang.StringUtils; 019import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException; 020import org.kuali.rice.core.api.CoreApiServiceLocator; 021import org.kuali.rice.core.api.encryption.EncryptionService; 022import org.kuali.rice.kim.api.identity.Person; 023import org.kuali.rice.krad.bo.BusinessObject; 024import org.kuali.rice.krad.bo.DocumentHeader; 025import org.kuali.rice.krad.bo.Note; 026import org.kuali.rice.krad.bo.PersistableBusinessObject; 027import org.kuali.rice.krad.exception.PessimisticLockingException; 028import org.kuali.rice.krad.service.BusinessObjectService; 029import org.kuali.rice.krad.service.DataObjectAuthorizationService; 030import org.kuali.rice.krad.service.DataObjectMetaDataService; 031import org.kuali.rice.krad.service.DocumentDictionaryService; 032import org.kuali.rice.krad.service.KRADServiceLocator; 033import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 034import org.kuali.rice.krad.service.LookupService; 035import org.kuali.rice.krad.service.MaintenanceDocumentService; 036import org.kuali.rice.krad.uif.component.BindingInfo; 037import org.kuali.rice.krad.uif.container.CollectionGroup; 038import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl; 039import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 040import org.kuali.rice.krad.uif.view.View; 041import org.kuali.rice.krad.util.KRADConstants; 042import org.kuali.rice.krad.util.ObjectUtils; 043import org.kuali.rice.krad.web.form.MaintenanceForm; 044 045import java.security.GeneralSecurityException; 046import java.util.ArrayList; 047import java.util.Collection; 048import java.util.Iterator; 049import java.util.List; 050import java.util.Map; 051 052/** 053 * Default implementation of the <code>Maintainable</code> interface 054 * 055 * @author Kuali Rice Team (rice.collab@kuali.org) 056 */ 057public class MaintainableImpl extends ViewHelperServiceImpl implements Maintainable { 058 private static final long serialVersionUID = 9125271369161634992L; 059 060 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintainableImpl.class); 061 062 private String documentNumber; 063 private Object dataObject; 064 private Class<?> dataObjectClass; 065 private String maintenanceAction; 066 067 private transient LookupService lookupService; 068 private transient DataObjectAuthorizationService dataObjectAuthorizationService; 069 private transient DataObjectMetaDataService dataObjectMetaDataService; 070 private transient DocumentDictionaryService documentDictionaryService; 071 private transient EncryptionService encryptionService; 072 private transient BusinessObjectService businessObjectService; 073 private transient MaintenanceDocumentService maintenanceDocumentService; 074 075 /** 076 * @see org.kuali.rice.krad.maintenance.Maintainable#retrieveObjectForEditOrCopy(MaintenanceDocument, java.util.Map) 077 */ 078 @Override 079 public Object retrieveObjectForEditOrCopy(MaintenanceDocument document, Map<String, String> dataObjectKeys) { 080 Object dataObject = null; 081 082 try { 083 dataObject = getLookupService().findObjectBySearch(getDataObjectClass(), dataObjectKeys); 084 } catch (ClassNotPersistenceCapableException ex) { 085 if (!document.getOldMaintainableObject().isExternalBusinessObject()) { 086 throw new RuntimeException("Data Object Class: " 087 + getDataObjectClass() 088 + " is not persistable and is not externalizable - configuration error"); 089 } 090 // otherwise, let fall through 091 } 092 093 return dataObject; 094 } 095 096 /** 097 * @see org.kuali.rice.krad.maintenance.Maintainable#setDocumentNumber 098 */ 099 @Override 100 public void setDocumentNumber(String documentNumber) { 101 this.documentNumber = documentNumber; 102 } 103 104 /** 105 * @see org.kuali.rice.krad.maintenance.Maintainable#getDocumentTitle 106 */ 107 @Override 108 public String getDocumentTitle(MaintenanceDocument document) { 109 // default implementation is to allow MaintenanceDocumentBase to 110 // generate the doc title 111 return ""; 112 } 113 114 /** 115 * @see org.kuali.rice.krad.maintenance.Maintainable#getDataObject 116 */ 117 @Override 118 public Object getDataObject() { 119 return dataObject; 120 } 121 122 /** 123 * @see org.kuali.rice.krad.maintenance.Maintainable#setDataObject 124 */ 125 @Override 126 public void setDataObject(Object object) { 127 this.dataObject = object; 128 } 129 130 /** 131 * @see org.kuali.rice.krad.maintenance.Maintainable#getDataObjectClass 132 */ 133 @Override 134 public Class getDataObjectClass() { 135 return dataObjectClass; 136 } 137 138 /** 139 * @see org.kuali.rice.krad.maintenance.Maintainable#setDataObjectClass 140 */ 141 @Override 142 public void setDataObjectClass(Class dataObjectClass) { 143 this.dataObjectClass = dataObjectClass; 144 } 145 146 /** 147 * Persistable business objects are lockable 148 * 149 * @see org.kuali.rice.krad.maintenance.Maintainable#isLockable 150 */ 151 @Override 152 public boolean isLockable() { 153 return KRADServiceLocator.getPersistenceStructureService().isPersistable(getDataObject().getClass()); 154 } 155 156 /** 157 * Returns the data object if its persistable, null otherwise 158 * 159 * @see org.kuali.rice.krad.maintenance.Maintainable#getPersistableBusinessObject 160 */ 161 @Override 162 public PersistableBusinessObject getPersistableBusinessObject() { 163 if (KRADServiceLocator.getPersistenceStructureService().isPersistable(getDataObject().getClass())) { 164 return (PersistableBusinessObject) getDataObject(); 165 } else { 166 return null; 167 } 168 169 } 170 171 /** 172 * @see org.kuali.rice.krad.maintenance.Maintainable#getMaintenanceAction 173 */ 174 @Override 175 public String getMaintenanceAction() { 176 return maintenanceAction; 177 } 178 179 /** 180 * @see org.kuali.rice.krad.maintenance.Maintainable#setMaintenanceAction 181 */ 182 @Override 183 public void setMaintenanceAction(String maintenanceAction) { 184 this.maintenanceAction = maintenanceAction; 185 } 186 187 /** 188 * Note: as currently implemented, every key field for a given 189 * data object class must have a visible getter 190 * 191 * @see org.kuali.rice.krad.maintenance.Maintainable#generateMaintenanceLocks 192 */ 193 @Override 194 public List<MaintenanceLock> generateMaintenanceLocks() { 195 List<MaintenanceLock> maintenanceLocks = new ArrayList<MaintenanceLock>(); 196 StringBuffer lockRepresentation = new StringBuffer(dataObjectClass.getName()); 197 lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_CLASS_DELIM); 198 199 Object bo = getDataObject(); 200 List keyFieldNames = getDocumentDictionaryService().getLockingKeys(getDocumentTypeName()); 201 202 for (Iterator i = keyFieldNames.iterator(); i.hasNext(); ) { 203 String fieldName = (String) i.next(); 204 Object fieldValue = ObjectUtils.getPropertyValue(bo, fieldName); 205 if (fieldValue == null) { 206 fieldValue = ""; 207 } 208 209 // check if field is a secure 210 if (getDataObjectAuthorizationService() 211 .attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObjectClass, fieldName)) { 212 try { 213 if(CoreApiServiceLocator.getEncryptionService().isEnabled()) { 214 fieldValue = getEncryptionService().encrypt(fieldValue); 215 } 216 } catch (GeneralSecurityException e) { 217 LOG.error("Unable to encrypt secure field for locking representation " + e.getMessage()); 218 throw new RuntimeException( 219 "Unable to encrypt secure field for locking representation " + e.getMessage()); 220 } 221 } 222 223 lockRepresentation.append(fieldName); 224 lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_FIELDNAME_DELIM); 225 lockRepresentation.append(String.valueOf(fieldValue)); 226 if (i.hasNext()) { 227 lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_VALUE_DELIM); 228 } 229 } 230 231 MaintenanceLock maintenanceLock = new MaintenanceLock(); 232 maintenanceLock.setDocumentNumber(documentNumber); 233 maintenanceLock.setLockingRepresentation(lockRepresentation.toString()); 234 maintenanceLocks.add(maintenanceLock); 235 236 return maintenanceLocks; 237 } 238 239 /** 240 * Retrieves the document type name from the data dictionary based on 241 * business object class 242 */ 243 protected String getDocumentTypeName() { 244 return getDocumentDictionaryService().getMaintenanceDocumentTypeName(dataObjectClass); 245 } 246 247 /** 248 * @see org.kuali.rice.krad.maintenance.Maintainable#saveDataObject 249 */ 250 @Override 251 public void saveDataObject() { 252 if (dataObject instanceof PersistableBusinessObject) { 253 getBusinessObjectService().linkAndSave((PersistableBusinessObject) dataObject); 254 } else { 255 throw new RuntimeException( 256 "Cannot save object of type: " + dataObjectClass + " with business object service"); 257 } 258 } 259 260 /** 261 * @see org.kuali.rice.krad.maintenance.Maintainable#deleteDataObject 262 */ 263 @Override 264 public void deleteDataObject() { 265 if (dataObject == null) { 266 return; 267 } 268 269 if (dataObject instanceof PersistableBusinessObject) { 270 getBusinessObjectService().delete((PersistableBusinessObject) dataObject); 271 dataObject = null; 272 } else { 273 throw new RuntimeException( 274 "Cannot delete object of type: " + dataObjectClass + " with business object service"); 275 } 276 } 277 278 /** 279 * @see org.kuali.rice.krad.maintenance.Maintainable#doRouteStatusChange 280 */ 281 @Override 282 public void doRouteStatusChange(DocumentHeader documentHeader) { 283 // no default implementation 284 } 285 286 /** 287 * @see org.kuali.rice.krad.maintenance.Maintainable#getLockingDocumentId 288 */ 289 @Override 290 public String getLockingDocumentId() { 291 return getMaintenanceDocumentService().getLockingDocumentId(this, documentNumber); 292 } 293 294 /** 295 * @see org.kuali.rice.krad.maintenance.Maintainable#getWorkflowEngineDocumentIdsToLock 296 */ 297 @Override 298 public List<String> getWorkflowEngineDocumentIdsToLock() { 299 return null; 300 } 301 302 /** 303 * Default implementation simply returns false to indicate that custom 304 * lock descriptors are not supported by MaintainableImpl. If custom 305 * lock descriptors are needed, the appropriate subclasses should override 306 * this method 307 * 308 * @see org.kuali.rice.krad.maintenance.Maintainable#useCustomLockDescriptors 309 */ 310 @Override 311 public boolean useCustomLockDescriptors() { 312 return false; 313 } 314 315 /** 316 * Default implementation just throws a PessimisticLockingException. 317 * Subclasses of MaintainableImpl that need support for custom lock 318 * descriptors should override this method 319 * 320 * @see org.kuali.rice.krad.maintenance.Maintainable#getCustomLockDescriptor 321 */ 322 @Override 323 public String getCustomLockDescriptor(Person user) { 324 throw new PessimisticLockingException("The Maintainable for document " + documentNumber + 325 " is using pessimistic locking with custom lock descriptors, but the Maintainable has not overridden the getCustomLockDescriptor method"); 326 } 327 328 /** 329 * @see org.kuali.rice.krad.maintenance.Maintainable#isNotesEnabled 330 */ 331 @Override 332 public boolean isNotesEnabled() { 333 return getDataObjectMetaDataService().areNotesSupported(dataObjectClass); 334 } 335 336 /** 337 * @see org.kuali.rice.krad.maintenance.MaintainableImpl#isExternalBusinessObject 338 */ 339 @Override 340 public boolean isExternalBusinessObject() { 341 return false; 342 } 343 344 /** 345 * @see org.kuali.rice.krad.maintenance.MaintainableImpl#prepareExternalBusinessObject 346 */ 347 @Override 348 public void prepareExternalBusinessObject(BusinessObject businessObject) { 349 // by default do nothing 350 } 351 352 /** 353 * Checks whether the data object is not null and has its primary key values populated 354 * 355 * @see org.kuali.rice.krad.maintenance.MaintainableImpl#isOldDataObjectInDocument 356 */ 357 @Override 358 public boolean isOldDataObjectInDocument() { 359 boolean isOldDataObjectInExistence = true; 360 361 if (getDataObject() == null) { 362 isOldDataObjectInExistence = false; 363 } else { 364 Map<String, ?> keyFieldValues = getDataObjectMetaDataService().getPrimaryKeyFieldValues(getDataObject()); 365 for (Object keyValue : keyFieldValues.values()) { 366 if (keyValue == null) { 367 isOldDataObjectInExistence = false; 368 } else if ((keyValue instanceof String) && StringUtils.isBlank((String) keyValue)) { 369 isOldDataObjectInExistence = false; 370 } 371 372 if (!isOldDataObjectInExistence) { 373 break; 374 } 375 } 376 } 377 378 return isOldDataObjectInExistence; 379 } 380 381 /** 382 * @see org.kuali.rice.krad.maintenance.Maintainable#prepareForSave 383 */ 384 @Override 385 public void prepareForSave() { 386 // by default do nothing 387 } 388 389 /** 390 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterRetrieve 391 */ 392 @Override 393 public void processAfterRetrieve() { 394 // by default do nothing 395 } 396 397 /** 398 * @see org.kuali.rice.krad.maintenance.MaintainableImpl#setupNewFromExisting 399 */ 400 @Override 401 public void setupNewFromExisting(MaintenanceDocument document, Map<String, String[]> parameters) { 402 // by default do nothing 403 } 404 405 /** 406 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterCopy 407 */ 408 @Override 409 public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> requestParameters) { 410 // by default do nothing 411 } 412 413 /** 414 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterEdit 415 */ 416 @Override 417 public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) { 418 // by default do nothing 419 } 420 421 /** 422 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterNew 423 */ 424 @Override 425 public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) { 426 // by default do nothing 427 } 428 429 /** 430 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterPost 431 */ 432 @Override 433 public void processAfterPost(MaintenanceDocument document, Map<String, String[]> requestParameters) { 434 // by default do nothing 435 } 436 437 /** 438 * In the case of edit maintenance adds a new blank line to the old side 439 * 440 * TODO: should this write some sort of missing message on the old side 441 * instead? 442 * 443 * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processAfterAddLine(org.kuali.rice.krad.uif.view.View, 444 * org.kuali.rice.krad.uif.container.CollectionGroup, java.lang.Object, 445 * java.lang.Object) 446 */ 447 @Override 448 protected void processAfterAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) { 449 super.processAfterAddLine(view, collectionGroup, model, addLine); 450 451 // Check for maintenance documents in edit but exclude notes 452 if (model instanceof MaintenanceForm && KRADConstants.MAINTENANCE_EDIT_ACTION.equals(((MaintenanceForm)model).getMaintenanceAction()) && !(addLine instanceof Note)) { 453 MaintenanceForm maintenanceForm = (MaintenanceForm) model; 454 MaintenanceDocument document = maintenanceForm.getDocument(); 455 456 // get the old object's collection 457 //KULRICE-7970 support multiple level objects 458 String bindingPrefix = collectionGroup.getBindingInfo().getBindByNamePrefix(); 459 String propertyPath = collectionGroup.getPropertyName(); 460 if(bindingPrefix!=""&&bindingPrefix!= null) { 461 propertyPath = bindingPrefix + "." + propertyPath; 462 } 463 464 Collection<Object> oldCollection = ObjectPropertyUtils 465 .getPropertyValue(document.getOldMaintainableObject().getDataObject(), 466 propertyPath); 467 468 try { 469 Object blankLine = collectionGroup.getCollectionObjectClass().newInstance(); 470 oldCollection.add(blankLine); 471 } catch (Exception e) { 472 throw new RuntimeException("Unable to create new line instance for old maintenance object", e); 473 } 474 } 475 } 476 477 /** 478 * In the case of edit maintenance deleted the item on the old side 479 * 480 * 481 * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processAfterDeleteLine(View, 482 * org.kuali.rice.krad.uif.container.CollectionGroup, java.lang.Object, int) 483 */ 484 @Override 485 protected void processAfterDeleteLine(View view, CollectionGroup collectionGroup, Object model, int lineIndex) { 486 super.processAfterDeleteLine(view, collectionGroup, model, lineIndex); 487 488 // Check for maintenance documents in edit but exclude notes 489 if (model instanceof MaintenanceForm && KRADConstants.MAINTENANCE_EDIT_ACTION.equals(((MaintenanceForm)model).getMaintenanceAction()) 490 && !collectionGroup.getCollectionObjectClass().getName().equals(Note.class.getName())) { 491 MaintenanceForm maintenanceForm = (MaintenanceForm) model; 492 MaintenanceDocument document = maintenanceForm.getDocument(); 493 494 // get the old object's collection 495 Collection<Object> oldCollection = ObjectPropertyUtils 496 .getPropertyValue(document.getOldMaintainableObject().getDataObject(), 497 collectionGroup.getPropertyName()); 498 try { 499 // Remove the object at lineIndex from the collection 500 oldCollection.remove(oldCollection.toArray()[lineIndex]); 501 } catch (Exception e) { 502 throw new RuntimeException("Unable to delete line instance for old maintenance object", e); 503 } 504 } 505 } 506 507 /** 508 * Retrieves the document number configured on this maintainable 509 * 510 * @return String document number 511 */ 512 protected String getDocumentNumber() { 513 return this.documentNumber; 514 } 515 516 protected LookupService getLookupService() { 517 if (lookupService == null) { 518 lookupService = KRADServiceLocatorWeb.getLookupService(); 519 } 520 return this.lookupService; 521 } 522 523 public void setLookupService(LookupService lookupService) { 524 this.lookupService = lookupService; 525 } 526 527 protected DataObjectAuthorizationService getDataObjectAuthorizationService() { 528 if (dataObjectAuthorizationService == null) { 529 this.dataObjectAuthorizationService = KRADServiceLocatorWeb.getDataObjectAuthorizationService(); 530 } 531 return dataObjectAuthorizationService; 532 } 533 534 public void setDataObjectAuthorizationService(DataObjectAuthorizationService dataObjectAuthorizationService) { 535 this.dataObjectAuthorizationService = dataObjectAuthorizationService; 536 } 537 538 protected DataObjectMetaDataService getDataObjectMetaDataService() { 539 if (dataObjectMetaDataService == null) { 540 this.dataObjectMetaDataService = KRADServiceLocatorWeb.getDataObjectMetaDataService(); 541 } 542 return dataObjectMetaDataService; 543 } 544 545 public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) { 546 this.dataObjectMetaDataService = dataObjectMetaDataService; 547 } 548 549 public DocumentDictionaryService getDocumentDictionaryService() { 550 if (documentDictionaryService == null) { 551 this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService(); 552 } 553 return documentDictionaryService; 554 } 555 556 public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) { 557 this.documentDictionaryService = documentDictionaryService; 558 } 559 560 protected EncryptionService getEncryptionService() { 561 if (encryptionService == null) { 562 encryptionService = CoreApiServiceLocator.getEncryptionService(); 563 } 564 return encryptionService; 565 } 566 567 public void setEncryptionService(EncryptionService encryptionService) { 568 this.encryptionService = encryptionService; 569 } 570 571 protected BusinessObjectService getBusinessObjectService() { 572 if (businessObjectService == null) { 573 businessObjectService = KRADServiceLocator.getBusinessObjectService(); 574 } 575 return businessObjectService; 576 } 577 578 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 579 this.businessObjectService = businessObjectService; 580 } 581 582 protected MaintenanceDocumentService getMaintenanceDocumentService() { 583 if (maintenanceDocumentService == null) { 584 maintenanceDocumentService = KRADServiceLocatorWeb.getMaintenanceDocumentService(); 585 } 586 return maintenanceDocumentService; 587 } 588 589 public void setMaintenanceDocumentService(MaintenanceDocumentService maintenanceDocumentService) { 590 this.maintenanceDocumentService = maintenanceDocumentService; 591 } 592}