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.util.ArrayList; 019import java.util.HashMap; 020import java.util.Iterator; 021import java.util.List; 022import java.util.Map; 023import java.util.TreeMap; 024import java.util.UUID; 025 026import org.apache.commons.lang.StringUtils; 027import org.kuali.rice.krad.bo.BusinessObject; 028import org.kuali.rice.krad.bo.DataObjectRelationship; 029import org.kuali.rice.krad.bo.PersistableBusinessObject; 030import org.kuali.rice.krad.datadictionary.BusinessObjectEntry; 031import org.kuali.rice.krad.datadictionary.DataDictionaryEntry; 032import org.kuali.rice.krad.datadictionary.DataObjectEntry; 033import org.kuali.rice.krad.datadictionary.PrimitiveAttributeDefinition; 034import org.kuali.rice.krad.datadictionary.RelationshipDefinition; 035import org.kuali.rice.krad.datadictionary.SupportAttributeDefinition; 036import org.kuali.rice.krad.service.DataDictionaryService; 037import org.kuali.rice.krad.service.DataObjectMetaDataService; 038import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 039import org.kuali.rice.krad.service.KualiModuleService; 040import org.kuali.rice.krad.service.ModuleService; 041import org.kuali.rice.krad.service.PersistenceStructureService; 042import org.kuali.rice.krad.uif.UifPropertyPaths; 043import org.kuali.rice.krad.uif.service.ViewDictionaryService; 044import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 045import org.kuali.rice.krad.util.ObjectUtils; 046import org.springframework.beans.BeanWrapper; 047 048/** 049 * @author Kuali Rice Team (rice.collab@kuali.org) 050 */ 051public class DataObjectMetaDataServiceImpl implements DataObjectMetaDataService { 052 053 private DataDictionaryService dataDictionaryService; 054 private KualiModuleService kualiModuleService; 055 private PersistenceStructureService persistenceStructureService; 056 private ViewDictionaryService viewDictionaryService; 057 058 /** 059 * @see org.kuali.rice.krad.service.DataObjectMetaDataService#listPrimaryKeyFieldNames(java.lang.Class) 060 */ 061 @Override 062 public List<String> listPrimaryKeyFieldNames(Class<?> clazz) { 063 if (persistenceStructureService.isPersistable(clazz)) { 064 return persistenceStructureService.listPrimaryKeyFieldNames(clazz); 065 } 066 067 ModuleService responsibleModuleService = getKualiModuleService().getResponsibleModuleService(clazz); 068 if (responsibleModuleService != null && responsibleModuleService.isExternalizable(clazz)) { 069 return responsibleModuleService.listPrimaryKeyFieldNames(clazz); 070 } 071 072 // check the Data Dictionary for PK's of non PBO/EBO 073 List<String> pks = dataDictionaryService.getDataDictionary().getDataObjectEntry(clazz.getName()) 074 .getPrimaryKeys(); 075 if (pks != null && !pks.isEmpty()) { 076 return pks; 077 } 078 079 return new ArrayList<String>(); 080 } 081 082 /** 083 * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getPrimaryKeyFieldValues(java.lang.Object) 084 */ 085 @Override 086 public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject) { 087 return getPrimaryKeyFieldValues(dataObject, false); 088 } 089 090 /** 091 * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getPrimaryKeyFieldValues(java.lang.Object, 092 * boolean) 093 */ 094 @Override 095 public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject, boolean sortFieldNames) { 096 Map<String, Object> keyValueMap; 097 098 if (sortFieldNames) { 099 keyValueMap = new TreeMap<String, Object>(); 100 } else { 101 keyValueMap = new HashMap<String, Object>(); 102 } 103 104 BeanWrapper wrapper = ObjectPropertyUtils.wrapObject(dataObject); 105 106 List<String> fields = listPrimaryKeyFieldNames(dataObject.getClass()); 107 for (String fieldName : fields) { 108 keyValueMap.put(fieldName, wrapper.getPropertyValue(fieldName)); 109 } 110 111 return keyValueMap; 112 } 113 114 /** 115 * @see org.kuali.rice.krad.service.DataObjectMetaDataService#equalsByPrimaryKeys(java.lang.Object, 116 * java.lang.Object) 117 */ 118 @Override 119 public boolean equalsByPrimaryKeys(Object do1, Object do2) { 120 boolean equal = true; 121 122 if (do1 == null && do2 == null) { 123 equal = true; 124 } else if (do1 == null || do2 == null) { 125 equal = false; 126 } else if (!do1.getClass().getName().equals(do2.getClass().getName())) { 127 equal = false; 128 } else { 129 Map<String, ?> do1Keys = getPrimaryKeyFieldValues(do1); 130 Map<String, ?> do2Keys = getPrimaryKeyFieldValues(do2); 131 for (Iterator<String> iter = do1Keys.keySet().iterator(); iter.hasNext(); ) { 132 String keyName = iter.next(); 133 if (do1Keys.get(keyName) != null && do2Keys.get(keyName) != null) { 134 if (!do1Keys.get(keyName).toString().equals(do2Keys.get(keyName).toString())) { 135 equal = false; 136 } 137 } else { 138 equal = false; 139 } 140 } 141 } 142 143 return equal; 144 } 145 146 /** 147 * @see org.kuali.rice.kns.service.BusinessObjectMetaDataService#getDataObjectRelationship(java.lang.Object, 148 * java.lang.Class, java.lang.String, java.lang.String, boolean, 149 * boolean, boolean) 150 */ 151 public DataObjectRelationship getDataObjectRelationship(Object dataObject, Class<?> dataObjectClass, 152 String attributeName, String attributePrefix, boolean keysOnly, boolean supportsLookup, 153 boolean supportsInquiry) { 154 RelationshipDefinition ddReference = getDictionaryRelationship(dataObjectClass, attributeName); 155 156 return getDataObjectRelationship(ddReference, dataObject, dataObjectClass, attributeName, attributePrefix, 157 keysOnly, supportsLookup, supportsInquiry); 158 } 159 160 protected DataObjectRelationship getDataObjectRelationship(RelationshipDefinition ddReference, Object dataObject, 161 Class<?> dataObjectClass, String attributeName, String attributePrefix, boolean keysOnly, 162 boolean supportsLookup, boolean supportsInquiry) { 163 DataObjectRelationship relationship = null; 164 165 // if it is nested then replace the data object and attributeName with the 166 // sub-refs 167 if (ObjectUtils.isNestedAttribute(attributeName)) { 168 if (ddReference != null) { 169 if (classHasSupportedFeatures(ddReference.getTargetClass(), supportsLookup, supportsInquiry)) { 170 relationship = populateRelationshipFromDictionaryReference(dataObjectClass, ddReference, 171 attributePrefix, keysOnly); 172 173 return relationship; 174 } 175 } 176 177 // recurse down to the next object to find the relationship 178 String localPrefix = StringUtils.substringBefore(attributeName, "."); 179 String localAttributeName = StringUtils.substringAfter(attributeName, "."); 180 if (dataObject == null) { 181 dataObject = ObjectUtils.createNewObjectFromClass(dataObjectClass); 182 } 183 184 Object nestedObject = ObjectPropertyUtils.getPropertyValue(dataObject, localPrefix); 185 Class<?> nestedClass = null; 186 if (nestedObject == null) { 187 nestedClass = ObjectPropertyUtils.getPropertyType(dataObject, localPrefix); 188 } else { 189 nestedClass = nestedObject.getClass(); 190 } 191 192 String fullPrefix = localPrefix; 193 if (StringUtils.isNotBlank(attributePrefix)) { 194 fullPrefix = attributePrefix + "." + localPrefix; 195 } 196 197 relationship = getDataObjectRelationship(nestedObject, nestedClass, localAttributeName, fullPrefix, 198 keysOnly, supportsLookup, supportsInquiry); 199 200 return relationship; 201 } 202 203 // non-nested reference, get persistence relationships first 204 int maxSize = Integer.MAX_VALUE; 205 206 // try persistable reference first 207 if (getPersistenceStructureService().isPersistable(dataObjectClass)) { 208 Map<String, DataObjectRelationship> rels = getPersistenceStructureService().getRelationshipMetadata( 209 dataObjectClass, attributeName, attributePrefix); 210 if (rels.size() > 0) { 211 for (DataObjectRelationship rel : rels.values()) { 212 if (rel.getParentToChildReferences().size() < maxSize) { 213 if (classHasSupportedFeatures(rel.getRelatedClass(), supportsLookup, supportsInquiry)) { 214 maxSize = rel.getParentToChildReferences().size(); 215 relationship = rel; 216 } 217 } 218 } 219 } 220 } else { 221 ModuleService moduleService = getKualiModuleService().getResponsibleModuleService(dataObjectClass); 222 if (moduleService != null && moduleService.isExternalizable(dataObjectClass)) { 223 relationship = getRelationshipMetadata(dataObjectClass, attributeName, attributePrefix); 224 if ((relationship != null) && classHasSupportedFeatures(relationship.getRelatedClass(), supportsLookup, 225 supportsInquiry)) { 226 return relationship; 227 } else { 228 return null; 229 } 230 } 231 } 232 233 if (ddReference != null && ddReference.getPrimitiveAttributes().size() < maxSize) { 234 if (classHasSupportedFeatures(ddReference.getTargetClass(), supportsLookup, supportsInquiry)) { 235 relationship = populateRelationshipFromDictionaryReference(dataObjectClass, ddReference, null, 236 keysOnly); 237 } 238 } 239 240 return relationship; 241 } 242 243 protected boolean classHasSupportedFeatures(Class relationshipClass, boolean supportsLookup, 244 boolean supportsInquiry) { 245 boolean hasSupportedFeatures = true; 246 if (supportsLookup && !getViewDictionaryService().isLookupable(relationshipClass)) { 247 hasSupportedFeatures = false; 248 } 249 if (supportsInquiry && !getViewDictionaryService().isInquirable(relationshipClass)) { 250 hasSupportedFeatures = false; 251 } 252 253 return hasSupportedFeatures; 254 } 255 256 public RelationshipDefinition getDictionaryRelationship(Class<?> c, String attributeName) { 257 DataDictionaryEntry entryBase = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry( 258 c.getName()); 259 if (entryBase == null) { 260 return null; 261 } 262 263 RelationshipDefinition relationship = null; 264 265 List<RelationshipDefinition> ddRelationships = entryBase.getRelationships(); 266 267 int minKeys = Integer.MAX_VALUE; 268 for (RelationshipDefinition def : ddRelationships) { 269 // favor key sizes of 1 first 270 if (def.getPrimitiveAttributes().size() == 1) { 271 for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) { 272 if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals( 273 attributeName)) { 274 relationship = def; 275 minKeys = 1; 276 break; 277 } 278 } 279 } else if (def.getPrimitiveAttributes().size() < minKeys) { 280 for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) { 281 if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals( 282 attributeName)) { 283 relationship = def; 284 minKeys = def.getPrimitiveAttributes().size(); 285 break; 286 } 287 } 288 } 289 } 290 291 // check the support attributes 292 if (relationship == null) { 293 for (RelationshipDefinition def : ddRelationships) { 294 if (def.hasIdentifier()) { 295 if (def.getIdentifier().getSourceName().equals(attributeName)) { 296 relationship = def; 297 } 298 } 299 } 300 } 301 302 return relationship; 303 } 304 305 protected DataObjectRelationship populateRelationshipFromDictionaryReference(Class<?> dataObjectClass, 306 RelationshipDefinition ddReference, String attributePrefix, boolean keysOnly) { 307 DataObjectRelationship relationship = new DataObjectRelationship(dataObjectClass, 308 ddReference.getObjectAttributeName(), ddReference.getTargetClass()); 309 310 for (PrimitiveAttributeDefinition def : ddReference.getPrimitiveAttributes()) { 311 if (StringUtils.isNotBlank(attributePrefix)) { 312 relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(), 313 def.getTargetName()); 314 } else { 315 relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName()); 316 } 317 } 318 319 if (!keysOnly) { 320 for (SupportAttributeDefinition def : ddReference.getSupportAttributes()) { 321 if (StringUtils.isNotBlank(attributePrefix)) { 322 relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(), 323 def.getTargetName()); 324 if (def.isIdentifier()) { 325 relationship.setUserVisibleIdentifierKey(attributePrefix + "." + def.getSourceName()); 326 } 327 } else { 328 relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName()); 329 if (def.isIdentifier()) { 330 relationship.setUserVisibleIdentifierKey(def.getSourceName()); 331 } 332 } 333 } 334 } 335 336 return relationship; 337 } 338 339 protected DataObjectRelationship getRelationshipMetadata(Class<?> dataObjectClass, String attributeName, 340 String attributePrefix) { 341 342 RelationshipDefinition relationshipDefinition = getDictionaryRelationship(dataObjectClass, attributeName); 343 if (relationshipDefinition == null) { 344 return null; 345 } 346 347 DataObjectRelationship dataObjectRelationship = new DataObjectRelationship( 348 relationshipDefinition.getSourceClass(), relationshipDefinition.getObjectAttributeName(), 349 relationshipDefinition.getTargetClass()); 350 351 if (!StringUtils.isEmpty(attributePrefix)) { 352 attributePrefix += "."; 353 } 354 355 List<PrimitiveAttributeDefinition> primitives = relationshipDefinition.getPrimitiveAttributes(); 356 for (PrimitiveAttributeDefinition primitiveAttributeDefinition : primitives) { 357 dataObjectRelationship.getParentToChildReferences().put( 358 attributePrefix + primitiveAttributeDefinition.getSourceName(), 359 primitiveAttributeDefinition.getTargetName()); 360 } 361 362 return dataObjectRelationship; 363 } 364 365 /** 366 * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getTitleAttribute(java.lang.Class) 367 */ 368 public String getTitleAttribute(Class<?> dataObjectClass) { 369 String titleAttribute = null; 370 371 DataObjectEntry entry = getDataObjectEntry(dataObjectClass); 372 if (entry != null) { 373 titleAttribute = entry.getTitleAttribute(); 374 } 375 376 return titleAttribute; 377 } 378 379 /** 380 * @see org.kuali.rice.krad.service.DataObjectMetaDataService#areNotesSupported(java.lang.Class) 381 */ 382 @Override 383 public boolean areNotesSupported(Class<?> dataObjectClass) { 384 boolean hasNotesSupport = false; 385 386 DataObjectEntry entry = getDataObjectEntry(dataObjectClass); 387 if (entry != null) { 388 hasNotesSupport = entry.isBoNotesEnabled(); 389 } 390 391 return hasNotesSupport; 392 } 393 394 /** 395 * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getDataObjectIdentifierString 396 */ 397 public String getDataObjectIdentifierString(Object dataObject) { 398 String identifierString = ""; 399 400 if (dataObject == null) { 401 identifierString = "Null"; 402 return identifierString; 403 } 404 405 Class<?> dataObjectClass = dataObject.getClass(); 406 407 // if PBO, use object id property 408 if (PersistableBusinessObject.class.isAssignableFrom(dataObjectClass)) { 409 String objectId = ObjectPropertyUtils.getPropertyValue(dataObject, UifPropertyPaths.OBJECT_ID); 410 if (StringUtils.isBlank(objectId)) { 411 objectId = UUID.randomUUID().toString(); 412 ObjectPropertyUtils.setPropertyValue(dataObject, UifPropertyPaths.OBJECT_ID, objectId); 413 } 414 415 identifierString = objectId; 416 } else { 417 // build identifier string from primary key values 418 Map<String, ?> primaryKeyFieldValues = getPrimaryKeyFieldValues(dataObject, true); 419 for (Map.Entry<String, ?> primaryKeyValue : primaryKeyFieldValues.entrySet()) { 420 if (primaryKeyValue.getValue() == null) { 421 identifierString += "Null"; 422 } else { 423 identifierString += primaryKeyValue.getValue(); 424 } 425 identifierString += ":"; 426 } 427 identifierString = StringUtils.removeEnd(identifierString, ":"); 428 } 429 430 return identifierString; 431 } 432 433 /** 434 * @param dataObjectClass 435 * @return DataObjectEntry for the given dataObjectClass, or null if 436 * there is none 437 * @throws IllegalArgumentException if the given Class is null 438 */ 439 protected DataObjectEntry getDataObjectEntry(Class<?> dataObjectClass) { 440 if (dataObjectClass == null) { 441 throw new IllegalArgumentException("invalid (null) dataObjectClass"); 442 } 443 444 DataObjectEntry entry = getDataDictionaryService().getDataDictionary().getDataObjectEntry( 445 dataObjectClass.getName()); 446 447 return entry; 448 } 449 450 public List<DataObjectRelationship> getDataObjectRelationships(Class<?> dataObjectClass) { 451 if (dataObjectClass == null) { 452 return null; 453 } 454 455 Map<String, Class> referenceClasses = null; 456 if (PersistableBusinessObject.class.isAssignableFrom(dataObjectClass) 457 && getPersistenceStructureService().isPersistable(dataObjectClass)) { 458 referenceClasses = getPersistenceStructureService().listReferenceObjectFields(dataObjectClass); 459 } 460 DataDictionaryEntry ddEntry = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry( 461 dataObjectClass.getName()); 462 List<RelationshipDefinition> ddRelationships = (ddEntry == null ? new ArrayList<RelationshipDefinition>() 463 : ddEntry.getRelationships()); 464 List<DataObjectRelationship> relationships = new ArrayList<DataObjectRelationship>(); 465 466 // loop over all relationships 467 if (referenceClasses != null) { 468 for (Map.Entry<String, Class> entry : referenceClasses.entrySet()) { 469 if (classHasSupportedFeatures(entry.getValue(), true, false)) { 470 Map<String, String> fkToPkRefs = getPersistenceStructureService().getForeignKeysForReference(dataObjectClass, 471 entry.getKey()); 472 DataObjectRelationship rel = new DataObjectRelationship(dataObjectClass, entry.getKey(), 473 entry.getValue()); 474 for (Map.Entry<String, String> ref : fkToPkRefs.entrySet()) { 475 rel.getParentToChildReferences().put(ref.getKey(), ref.getValue()); 476 } 477 relationships.add(rel); 478 } 479 } 480 } 481 482 for (RelationshipDefinition rd : ddRelationships) { 483 if (classHasSupportedFeatures(rd.getTargetClass(), true, false)) { 484 DataObjectRelationship rel = new DataObjectRelationship(dataObjectClass, rd.getObjectAttributeName(), 485 rd.getTargetClass()); 486 for (PrimitiveAttributeDefinition def : rd.getPrimitiveAttributes()) { 487 rel.getParentToChildReferences().put(def.getSourceName(), def.getTargetName()); 488 } 489 relationships.add(rel); 490 } 491 } 492 493 return relationships; 494 } 495 496 /** 497 * @param businessObjectClass - class of business object to return entry for 498 * @return BusinessObjectEntry for the given dataObjectClass, or null if 499 * there is none 500 */ 501 protected BusinessObjectEntry getBusinessObjectEntry(Class businessObjectClass) { 502 validateBusinessObjectClass(businessObjectClass); 503 504 BusinessObjectEntry entry = getDataDictionaryService().getDataDictionary().getBusinessObjectEntry( 505 businessObjectClass.getName()); 506 return entry; 507 } 508 509 /** 510 * @param businessObjectClass 511 * @throws IllegalArgumentException if the given Class is null or is not a BusinessObject class 512 */ 513 protected void validateBusinessObjectClass(Class businessObjectClass) { 514 if (businessObjectClass == null) { 515 throw new IllegalArgumentException("invalid (null) dataObjectClass"); 516 } 517 if (!BusinessObject.class.isAssignableFrom(businessObjectClass)) { 518 throw new IllegalArgumentException( 519 "class '" + businessObjectClass.getName() + "' is not a descendant of BusinessObject"); 520 } 521 } 522 523 /** 524 * Protected method to allow subclasses to access the dataDictionaryService. 525 * 526 * @return Returns the dataDictionaryService. 527 */ 528 protected DataDictionaryService getDataDictionaryService() { 529 return this.dataDictionaryService; 530 } 531 532 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { 533 this.dataDictionaryService = dataDictionaryService; 534 } 535 536 /** 537 * Protected method to allow subclasses to access the kualiModuleService. 538 * 539 * @return Returns the persistenceStructureService. 540 */ 541 protected KualiModuleService getKualiModuleService() { 542 return this.kualiModuleService; 543 } 544 545 public void setKualiModuleService(KualiModuleService kualiModuleService) { 546 this.kualiModuleService = kualiModuleService; 547 } 548 549 /** 550 * Protected method to allow subclasses to access the 551 * persistenceStructureService. 552 * 553 * @return Returns the persistenceStructureService. 554 */ 555 protected PersistenceStructureService getPersistenceStructureService() { 556 return this.persistenceStructureService; 557 } 558 559 public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) { 560 this.persistenceStructureService = persistenceStructureService; 561 } 562 563 protected ViewDictionaryService getViewDictionaryService() { 564 if (this.viewDictionaryService == null) { 565 this.viewDictionaryService = KRADServiceLocatorWeb.getViewDictionaryService(); 566 } 567 return this.viewDictionaryService; 568 } 569 570 public void setViewDictionaryService(ViewDictionaryService viewDictionaryService) { 571 this.viewDictionaryService = viewDictionaryService; 572 } 573 574}