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