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