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.service.impl; 017 018import java.io.Serializable; 019import java.util.Arrays; 020import java.util.List; 021import java.util.Map; 022 023import org.apache.commons.lang.StringUtils; 024import org.apache.log4j.Logger; 025import org.kuali.rice.core.framework.persistence.jta.TransactionalNoValidationExceptionRollback; 026import org.kuali.rice.kew.api.exception.WorkflowException; 027import org.kuali.rice.kim.api.identity.Person; 028import org.kuali.rice.krad.bo.BusinessObject; 029import org.kuali.rice.krad.bo.PersistableBusinessObject; 030import org.kuali.rice.krad.dao.MaintenanceDocumentDao; 031import org.kuali.rice.krad.maintenance.MaintenanceDocument; 032import org.kuali.rice.krad.maintenance.MaintenanceLock; 033import org.kuali.rice.krad.exception.DocumentTypeAuthorizationException; 034import org.kuali.rice.krad.maintenance.Maintainable; 035import org.kuali.rice.krad.service.DataObjectAuthorizationService; 036import org.kuali.rice.krad.service.DataObjectMetaDataService; 037import org.kuali.rice.krad.service.DocumentDictionaryService; 038import org.kuali.rice.krad.service.DocumentService; 039import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 040import org.kuali.rice.krad.service.MaintenanceDocumentService; 041import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 042import org.kuali.rice.krad.util.GlobalVariables; 043import org.kuali.rice.krad.util.KRADConstants; 044import org.kuali.rice.krad.util.KRADUtils; 045import org.kuali.rice.krad.util.ObjectUtils; 046 047/** 048 * Service implementation for the MaintenanceDocument structure. This is the 049 * default implementation, that is delivered with Kuali 050 * 051 * @author Kuali Rice Team (rice.collab@kuali.org) 052 */ 053@TransactionalNoValidationExceptionRollback 054public class MaintenanceDocumentServiceImpl implements MaintenanceDocumentService { 055 protected static final Logger LOG = Logger.getLogger(MaintenanceDocumentServiceImpl.class); 056 057 private MaintenanceDocumentDao maintenanceDocumentDao; 058 private DataObjectAuthorizationService dataObjectAuthorizationService; 059 private DocumentService documentService; 060 private DataObjectMetaDataService dataObjectMetaDataService; 061 private DocumentDictionaryService documentDictionaryService; 062 063 /** 064 * @see org.kuali.rice.krad.service.MaintenanceDocumentService#setupNewMaintenanceDocument(java.lang.String, 065 * java.lang.String, java.lang.String) 066 */ 067 @SuppressWarnings("unchecked") 068 public MaintenanceDocument setupNewMaintenanceDocument(String objectClassName, String documentTypeName, 069 String maintenanceAction) { 070 if (StringUtils.isEmpty(objectClassName) && StringUtils.isEmpty(documentTypeName)) { 071 throw new IllegalArgumentException("Document type name or bo class not given!"); 072 } 073 074 // get document type if not passed 075 if (StringUtils.isEmpty(documentTypeName)) { 076 try { 077 documentTypeName = 078 getDocumentDictionaryService().getMaintenanceDocumentTypeName(Class.forName(objectClassName)); 079 } catch (ClassNotFoundException e) { 080 throw new RuntimeException(e); 081 } 082 083 if (StringUtils.isEmpty(documentTypeName)) { 084 throw new RuntimeException( 085 "documentTypeName is empty; does this Business Object have a maintenance document definition? " + 086 objectClassName); 087 } 088 } 089 090 // check doc type allows new or copy if that action was requested 091 if (KRADConstants.MAINTENANCE_NEW_ACTION.equals(maintenanceAction) || 092 KRADConstants.MAINTENANCE_COPY_ACTION.equals(maintenanceAction)) { 093 Class<?> boClass = 094 getDocumentDictionaryService().getMaintenanceDataObjectClass(documentTypeName); 095 boolean allowsNewOrCopy = getDataObjectAuthorizationService() 096 .canCreate(boClass, GlobalVariables.getUserSession().getPerson(), documentTypeName); 097 if (!allowsNewOrCopy) { 098 LOG.error("Document type " + documentTypeName + " does not allow new or copy actions."); 099 throw new DocumentTypeAuthorizationException( 100 GlobalVariables.getUserSession().getPerson().getPrincipalId(), "newOrCopy", documentTypeName); 101 } 102 } 103 104 // get new document from service 105 try { 106 return (MaintenanceDocument) getDocumentService().getNewDocument(documentTypeName); 107 } catch (WorkflowException e) { 108 LOG.error("Cannot get new maintenance document instance for doc type: " + documentTypeName, e); 109 throw new RuntimeException("Cannot get new maintenance document instance for doc type: " + documentTypeName, 110 e); 111 } 112 } 113 114 /** 115 * @see org.kuali.rice.krad.service.impl.MaintenanceDocumentServiceImpl#setupMaintenanceObject 116 */ 117 @Override 118 public void setupMaintenanceObject(MaintenanceDocument document, String maintenanceAction, 119 Map<String, String[]> requestParameters) { 120 document.getNewMaintainableObject().setMaintenanceAction(maintenanceAction); 121 document.getOldMaintainableObject().setMaintenanceAction(maintenanceAction); 122 123 // if action is edit or copy first need to retrieve the old record 124 if (!KRADConstants.MAINTENANCE_NEW_ACTION.equals(maintenanceAction) && 125 !KRADConstants.MAINTENANCE_NEWWITHEXISTING_ACTION.equals(maintenanceAction)) { 126 Object oldDataObject = retrieveObjectForMaintenance(document, requestParameters); 127 128 // TODO should we be using ObjectUtils? also, this needs dictionary 129 // enhancement to indicate fields to/not to copy 130 Object newDataObject = ObjectUtils.deepCopy((Serializable) oldDataObject); 131 132 // set object instance for editing 133 document.getOldMaintainableObject().setDataObject(oldDataObject); 134 document.getNewMaintainableObject().setDataObject(newDataObject); 135 136 // process further object preparations for copy action 137 if (KRADConstants.MAINTENANCE_COPY_ACTION.equals(maintenanceAction)) { 138 processMaintenanceObjectForCopy(document, newDataObject, requestParameters); 139 } else { 140 checkMaintenanceActionAuthorization(document, oldDataObject, maintenanceAction, requestParameters); 141 } 142 } 143 144 // if new with existing we need to populate with passed in parameters 145 if (KRADConstants.MAINTENANCE_NEWWITHEXISTING_ACTION.equals(maintenanceAction)) { 146 Object newBO = document.getNewMaintainableObject().getDataObject(); 147 Map<String, String> parameters = 148 buildKeyMapFromRequest(requestParameters, document.getNewMaintainableObject().getDataObjectClass()); 149 ObjectPropertyUtils.copyPropertiesToObject(parameters, newBO); 150 if (newBO instanceof PersistableBusinessObject) { 151 ((PersistableBusinessObject) newBO).refresh(); 152 } 153 154 document.getNewMaintainableObject().setupNewFromExisting(document, requestParameters); 155 } else if (KRADConstants.MAINTENANCE_NEW_ACTION.equals(maintenanceAction)) { 156 document.getNewMaintainableObject().processAfterNew(document, requestParameters); 157 } 158 } 159 160 /** 161 * For the edit and delete maintenance actions checks with the 162 * <code>BusinessObjectAuthorizationService</code> to check whether the 163 * action is allowed for the record data. In action is allowed invokes the 164 * custom processing hook on the <code>Maintainble</code>. 165 * 166 * @param document - document instance for the maintenance object 167 * @param oldBusinessObject - the old maintenance record 168 * @param maintenanceAction - type of maintenance action requested 169 * @param requestParameters - map of parameters from the request 170 */ 171 protected void checkMaintenanceActionAuthorization(MaintenanceDocument document, Object oldBusinessObject, 172 String maintenanceAction, Map<String, String[]> requestParameters) { 173 if (KRADConstants.MAINTENANCE_EDIT_ACTION.equals(maintenanceAction)) { 174 boolean allowsEdit = getDataObjectAuthorizationService() 175 .canMaintain(oldBusinessObject, GlobalVariables.getUserSession().getPerson(), 176 document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName()); 177 if (!allowsEdit) { 178 LOG.error("Document type " + document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName() + 179 " does not allow edit actions."); 180 throw new DocumentTypeAuthorizationException( 181 GlobalVariables.getUserSession().getPerson().getPrincipalId(), "edit", 182 document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName()); 183 } 184 185 // invoke custom processing method 186 document.getNewMaintainableObject().processAfterEdit(document, requestParameters); 187 } 188 // 3070 189 else if (KRADConstants.MAINTENANCE_DELETE_ACTION.equals(maintenanceAction)) { 190 boolean allowsDelete = getDataObjectAuthorizationService() 191 .canMaintain((BusinessObject) oldBusinessObject, GlobalVariables.getUserSession().getPerson(), 192 document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName()); 193 if (!allowsDelete) { 194 LOG.error("Document type " + document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName() + 195 " does not allow delete actions."); 196 throw new DocumentTypeAuthorizationException( 197 GlobalVariables.getUserSession().getPerson().getPrincipalId(), "delete", 198 document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName()); 199 } 200 } 201 } 202 203 /** 204 * For the edit or copy actions retrieves the record that is to be 205 * maintained 206 * 207 * <p> 208 * Based on the persistence metadata for the maintenance object class 209 * retrieves the primary key values from the given request parameters map 210 * (if the class is persistable). With those key values attempts to find the 211 * record using the <code>LookupService</code>. 212 * </p> 213 * 214 * @param document - document instance for the maintenance object 215 * @param requestParameters - Map of parameters from the request 216 * @return Object the retrieved old object 217 */ 218 protected Object retrieveObjectForMaintenance(MaintenanceDocument document, 219 Map<String, String[]> requestParameters) { 220 Map<String, String> keyMap = 221 buildKeyMapFromRequest(requestParameters, document.getNewMaintainableObject().getDataObjectClass()); 222 223 Object oldDataObject = document.getNewMaintainableObject().retrieveObjectForEditOrCopy(document, keyMap); 224 225 if (oldDataObject == null && !document.getOldMaintainableObject().isExternalBusinessObject()) { 226 throw new RuntimeException( 227 "Cannot retrieve old record for maintenance document, incorrect parameters passed on maint url: " + 228 requestParameters); 229 } 230 231 if (document.getOldMaintainableObject().isExternalBusinessObject()) { 232 if (oldDataObject == null) { 233 try { 234 oldDataObject = document.getOldMaintainableObject().getDataObjectClass().newInstance(); 235 } catch (Exception ex) { 236 throw new RuntimeException( 237 "External BO maintainable was null and unable to instantiate for old maintainable object.", 238 ex); 239 } 240 } 241 242 populateMaintenanceObjectWithCopyKeyValues(KRADUtils.translateRequestParameterMap(requestParameters), 243 oldDataObject, document.getOldMaintainableObject()); 244 document.getOldMaintainableObject().prepareExternalBusinessObject((PersistableBusinessObject) oldDataObject); 245 oldDataObject = document.getOldMaintainableObject().getDataObject(); 246 } 247 248 return oldDataObject; 249 } 250 251 /** 252 * For the copy action clears out primary key values for the old record and 253 * does authorization checks on the remaining fields. Also invokes the 254 * custom processing method on the <code>Maintainble</code> 255 * 256 * @param document - document instance for the maintenance object 257 * @param maintenanceObject - the object instance being maintained 258 * @param requestParameters - map of parameters from the request 259 */ 260 protected void processMaintenanceObjectForCopy(MaintenanceDocument document, Object maintenanceObject, 261 Map<String, String[]> requestParameters) { 262 if (!document.isFieldsClearedOnCopy()) { 263 Maintainable maintainable = document.getNewMaintainableObject(); 264 if (!getDocumentDictionaryService().getPreserveLockingKeysOnCopy(maintainable.getDataObjectClass())) { 265 clearPrimaryKeyFields(maintenanceObject, maintainable.getDataObjectClass()); 266 } 267 268 clearUnauthorizedNewFields(document); 269 270 maintainable.processAfterCopy(document, requestParameters); 271 272 // mark so that this clearing does not happen again 273 document.setFieldsClearedOnCopy(true); 274 } 275 } 276 277 /** 278 * Clears the value of the primary key fields on the maintenance object 279 * 280 * @param document - document to clear the pk fields on 281 * @param dataObjectClass - class to use for retrieving primary key metadata 282 */ 283 protected void clearPrimaryKeyFields(Object maintenanceObject, Class<?> dataObjectClass) { 284 List<String> keyFieldNames = getDataObjectMetaDataService().listPrimaryKeyFieldNames(dataObjectClass); 285 for (String keyFieldName : keyFieldNames) { 286 ObjectPropertyUtils.setPropertyValue(maintenanceObject, keyFieldName, null); 287 } 288 } 289 290 /** 291 * Used as part of the Copy functionality, to clear any field values that 292 * the user making the copy does not have permissions to modify. This will 293 * prevent authorization errors on a copy. 294 * 295 * @param document - document to be adjusted 296 */ 297 protected void clearUnauthorizedNewFields(MaintenanceDocument document) { 298 // get a reference to the current user 299 Person user = GlobalVariables.getUserSession().getPerson(); 300 301 // get a new instance of MaintenanceDocumentAuthorizations for context 302 // TODO: rework for KRAD 303// MaintenanceDocumentRestrictions maintenanceDocumentRestrictions = 304// getBusinessObjectAuthorizationService().getMaintenanceDocumentRestrictions(document, user); 305// 306// clearBusinessObjectOfRestrictedValues(maintenanceDocumentRestrictions); 307 } 308 309 /** 310 * Based on the maintenance object class retrieves the key field names from 311 * the <code>BusinessObjectMetaDataService</code> (or alternatively from the 312 * request parameters), then retrieves any matching key value pairs from the 313 * request parameters 314 * 315 * @param requestParameters - map of parameters from the request 316 * @param dataObjectClass - class to use for checking security parameter restrictions 317 * @return Map<String, String> key value pairs 318 */ 319 protected Map<String, String> buildKeyMapFromRequest(Map<String, String[]> requestParameters, 320 Class<?> dataObjectClass) { 321 List<String> keyFieldNames = null; 322 323 // translate request parameters 324 Map<String, String> parameters = KRADUtils.translateRequestParameterMap(requestParameters); 325 326 // are override keys listed in the request? If so, then those need to be 327 // our keys, not the primary key fields for the BO 328 if (!StringUtils.isBlank(parameters.get(KRADConstants.OVERRIDE_KEYS))) { 329 String[] overrideKeys = 330 parameters.get(KRADConstants.OVERRIDE_KEYS).split(KRADConstants.FIELD_CONVERSIONS_SEPARATOR); 331 keyFieldNames = Arrays.asList(overrideKeys); 332 } else { 333 keyFieldNames = getDataObjectMetaDataService().listPrimaryKeyFieldNames(dataObjectClass); 334 } 335 336 return KRADUtils.getParametersFromRequest(keyFieldNames, dataObjectClass, parameters); 337 } 338 339 /** 340 * Looks for a special request parameters giving the names of the keys that 341 * should be retrieved from the request parameters and copied to the 342 * maintenance object 343 * 344 * @param parameters - map of parameters from the request 345 * @param oldBusinessObject - the old maintenance object 346 * @param oldMaintainableObject - the old maintainble object (used to get object class for 347 * security checks) 348 */ 349 protected void populateMaintenanceObjectWithCopyKeyValues(Map<String, String> parameters, Object oldBusinessObject, 350 Maintainable oldMaintainableObject) { 351 List<String> keyFieldNamesToCopy = null; 352 Map<String, String> parametersToCopy = null; 353 354 if (!StringUtils.isBlank(parameters.get(KRADConstants.COPY_KEYS))) { 355 String[] copyKeys = 356 parameters.get(KRADConstants.COPY_KEYS).split(KRADConstants.FIELD_CONVERSIONS_SEPARATOR); 357 keyFieldNamesToCopy = Arrays.asList(copyKeys); 358 parametersToCopy = KRADUtils 359 .getParametersFromRequest(keyFieldNamesToCopy, oldMaintainableObject.getDataObjectClass(), 360 parameters); 361 } 362 363 if (parametersToCopy != null) { 364 // TODO: make sure we are doing formatting here eventually 365 ObjectPropertyUtils.copyPropertiesToObject(parametersToCopy, oldBusinessObject); 366 } 367 } 368 369 /** 370 * @see org.kuali.rice.krad.service.MaintenanceDocumentService#getLockingDocumentId(org.kuali.rice.krad.maintenance.MaintenanceDocument) 371 */ 372 public String getLockingDocumentId(MaintenanceDocument document) { 373 return getLockingDocumentId(document.getNewMaintainableObject(), document.getDocumentNumber()); 374 } 375 376 /** 377 * @see org.kuali.rice.krad.service.MaintenanceDocumentService#getLockingDocumentId(org.kuali.rice.krad.maintenance.Maintainable, 378 * java.lang.String) 379 */ 380 public String getLockingDocumentId(Maintainable maintainable, String documentNumber) { 381 String lockingDocId = null; 382 List<MaintenanceLock> maintenanceLocks = maintainable.generateMaintenanceLocks(); 383 for (MaintenanceLock maintenanceLock : maintenanceLocks) { 384 lockingDocId = maintenanceDocumentDao 385 .getLockingDocumentNumber(maintenanceLock.getLockingRepresentation(), documentNumber); 386 if (StringUtils.isNotBlank(lockingDocId)) { 387 break; 388 } 389 } 390 return lockingDocId; 391 } 392 393 /** 394 * @see org.kuali.rice.krad.service.MaintenanceDocumentService#deleteLocks(String) 395 */ 396 public void deleteLocks(String documentNumber) { 397 maintenanceDocumentDao.deleteLocks(documentNumber); 398 } 399 400 /** 401 * @see org.kuali.rice.krad.service.MaintenanceDocumentService#saveLocks(List) 402 */ 403 public void storeLocks(List<MaintenanceLock> maintenanceLocks) { 404 maintenanceDocumentDao.storeLocks(maintenanceLocks); 405 } 406 407 public MaintenanceDocumentDao getMaintenanceDocumentDao() { 408 return maintenanceDocumentDao; 409 } 410 411 public void setMaintenanceDocumentDao(MaintenanceDocumentDao maintenanceDocumentDao) { 412 this.maintenanceDocumentDao = maintenanceDocumentDao; 413 } 414 415 protected DataObjectAuthorizationService getDataObjectAuthorizationService() { 416 if (dataObjectAuthorizationService == null) { 417 this.dataObjectAuthorizationService = KRADServiceLocatorWeb.getDataObjectAuthorizationService(); 418 } 419 return dataObjectAuthorizationService; 420 } 421 422 public void setDataObjectAuthorizationService(DataObjectAuthorizationService dataObjectAuthorizationService) { 423 this.dataObjectAuthorizationService = dataObjectAuthorizationService; 424 } 425 426 protected DocumentService getDocumentService() { 427 return this.documentService; 428 } 429 430 public void setDocumentService(DocumentService documentService) { 431 this.documentService = documentService; 432 } 433 434 protected DataObjectMetaDataService getDataObjectMetaDataService() { 435 if (dataObjectMetaDataService == null) { 436 dataObjectMetaDataService = KRADServiceLocatorWeb.getDataObjectMetaDataService(); 437 } 438 return dataObjectMetaDataService; 439 } 440 441 public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) { 442 this.dataObjectMetaDataService = dataObjectMetaDataService; 443 } 444 445 public DocumentDictionaryService getDocumentDictionaryService() { 446 if (documentDictionaryService == null) { 447 this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService(); 448 } 449 return documentDictionaryService; 450 } 451 452 public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) { 453 this.documentDictionaryService = documentDictionaryService; 454 } 455}