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.data.provider.annotation.impl; 017 018import java.lang.annotation.Annotation; 019import java.lang.reflect.Field; 020import java.lang.reflect.Method; 021import java.lang.reflect.ParameterizedType; 022import java.lang.reflect.Type; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.HashSet; 028import java.util.List; 029 030import javax.validation.constraints.NotNull; 031import javax.validation.constraints.Size; 032 033import org.apache.commons.lang.StringUtils; 034import org.kuali.rice.core.api.data.DataType; 035import org.kuali.rice.krad.data.DataObjectService; 036import org.kuali.rice.krad.data.KradDataServiceLocator; 037import org.kuali.rice.krad.data.metadata.DataObjectAttribute; 038import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship; 039import org.kuali.rice.krad.data.metadata.DataObjectCollection; 040import org.kuali.rice.krad.data.metadata.DataObjectCollectionSortAttribute; 041import org.kuali.rice.krad.data.metadata.DataObjectMetadata; 042import org.kuali.rice.krad.data.metadata.DataObjectRelationship; 043import org.kuali.rice.krad.data.metadata.MetadataConfigurationException; 044import org.kuali.rice.krad.data.metadata.MetadataMergeAction; 045import org.kuali.rice.krad.data.metadata.MetadataRepository; 046import org.kuali.rice.krad.data.metadata.impl.DataObjectAttributeImpl; 047import org.kuali.rice.krad.data.metadata.impl.DataObjectAttributeRelationshipImpl; 048import org.kuali.rice.krad.data.metadata.impl.DataObjectCollectionImpl; 049import org.kuali.rice.krad.data.metadata.impl.DataObjectCollectionSortAttributeImpl; 050import org.kuali.rice.krad.data.metadata.impl.DataObjectMetadataImpl; 051import org.kuali.rice.krad.data.metadata.impl.DataObjectRelationshipImpl; 052import org.kuali.rice.krad.data.metadata.impl.MetadataCommonBase; 053import org.kuali.rice.krad.data.provider.annotation.AttributeRelationship; 054import org.kuali.rice.krad.data.provider.annotation.BusinessKey; 055import org.kuali.rice.krad.data.provider.annotation.CollectionRelationship; 056import org.kuali.rice.krad.data.provider.annotation.CollectionSortAttribute; 057import org.kuali.rice.krad.data.provider.annotation.Description; 058import org.kuali.rice.krad.data.provider.annotation.ForceUppercase; 059import org.kuali.rice.krad.data.provider.annotation.InheritProperties; 060import org.kuali.rice.krad.data.provider.annotation.InheritProperty; 061import org.kuali.rice.krad.data.provider.annotation.KeyValuesFinderClass; 062import org.kuali.rice.krad.data.provider.annotation.Label; 063import org.kuali.rice.krad.data.provider.annotation.MergeAction; 064import org.kuali.rice.krad.data.provider.annotation.NonPersistentProperty; 065import org.kuali.rice.krad.data.provider.annotation.PropertyEditorClass; 066import org.kuali.rice.krad.data.provider.annotation.ReadOnly; 067import org.kuali.rice.krad.data.provider.annotation.Relationship; 068import org.kuali.rice.krad.data.provider.annotation.Sensitive; 069import org.kuali.rice.krad.data.provider.annotation.ShortLabel; 070import org.kuali.rice.krad.data.provider.annotation.UifAutoCreateViews; 071import org.kuali.rice.krad.data.provider.annotation.UifDisplayHint; 072import org.kuali.rice.krad.data.provider.annotation.UifDisplayHints; 073import org.kuali.rice.krad.data.provider.annotation.UifValidCharactersConstraintBeanName; 074import org.kuali.rice.krad.data.provider.impl.MetadataProviderBase; 075 076/** 077 * Parses custom krad-data annotations for additional metadata to layer on top of that provided by the persistence 078 * metadata provider which should have run before this one. 079 * 080 * <p> 081 * At the moment, it will only process classes which were previously identified by the JPA implementation. 082 * </p> 083 * 084 * <p> 085 * TODO: Addition of a new Annotation which will need to be scanned for in order to process non-persistent classes as data objects. 086 * </p> 087 * 088 * @author Kuali Rice Team (rice.collab@kuali.org) 089 */ 090public class AnnotationMetadataProviderImpl extends MetadataProviderBase { 091 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger 092 .getLogger(AnnotationMetadataProviderImpl.class); 093 094 private boolean initializationAttempted = false; 095 private DataObjectService dataObjectService; 096 097 /** 098 * {@inheritDoc} 099 */ 100 @Override 101 protected void initializeMetadata(Collection<Class<?>> types) { 102 if (initializationAttempted) { 103 return; 104 } 105 initializationAttempted = true; 106 if (LOG.isDebugEnabled()) { 107 LOG.debug("Processing annotations for the given list of data objects: " + types); 108 } 109 if (types == null || types.isEmpty()) { 110 LOG.warn(getClass().getSimpleName() + " was passed an empty list of types to initialize, doing nothing"); 111 return; 112 } 113 LOG.info("Started Scanning For Metadata Annotations"); 114 for (Class<?> type : types) { 115 if (LOG.isDebugEnabled()) { 116 LOG.debug("Processing Annotations on : " + type); 117 } 118 boolean annotationsFound = false; 119 DataObjectMetadataImpl metadata = new DataObjectMetadataImpl(); 120 metadata.setProviderName(this.getClass().getSimpleName()); 121 metadata.setType(type); 122 // check for class level annotations 123 annotationsFound |= processClassLevelAnnotations(type, metadata); 124 // check for field level annotations 125 annotationsFound |= processFieldLevelAnnotations(type, metadata); 126 // check for method (getter) level annotations 127 annotationsFound |= processMethodLevelAnnotations(type, metadata); 128 // Look for inherited properties 129 annotationsFound |= processInheritedAttributes(type, metadata); 130 if (annotationsFound) { 131 masterMetadataMap.put(type, metadata); 132 } 133 } 134 LOG.info("Completed Scanning For Metadata Annotations"); 135 if (LOG.isDebugEnabled()) { 136 LOG.debug("Annotation Metadata: " + masterMetadataMap); 137 } 138 } 139 140 /** 141 * Handle annotations made at the class level and add their data to the given metadata object. 142 * 143 * @param clazz the class to process. 144 * @param metadata the metadata for the class. 145 * @return <b>true</b> if any annotations are found. 146 */ 147 protected boolean processClassLevelAnnotations(Class<?> clazz, DataObjectMetadataImpl metadata) { 148 boolean classAnnotationFound = false; 149 boolean fieldAnnotationsFound = false; 150 // get the class annotations 151 List<DataObjectAttribute> attributes = new ArrayList<DataObjectAttribute>(metadata.getAttributes()); 152 Annotation[] classAnnotations = clazz.getAnnotations(); 153 if (LOG.isDebugEnabled()) { 154 LOG.debug("Class-level annotations: " + Arrays.asList(classAnnotations)); 155 } 156 for (Annotation a : classAnnotations) { 157 // check if it's one we can handle 158 // do something with it 159 if (processAnnotationsforCommonMetadata(a, metadata)) { 160 classAnnotationFound = true; 161 continue; 162 } 163 if (a instanceof MergeAction) { 164 MetadataMergeAction mma = ((MergeAction) a).value(); 165 if (!(mma == MetadataMergeAction.MERGE || mma == MetadataMergeAction.REMOVE)) { 166 throw new MetadataConfigurationException( 167 "Only the MERGE and REMOVE merge actions are supported since the annotation metadata provider can not specify all required properties and may only be used as an overlay."); 168 } 169 metadata.setMergeAction(mma); 170 classAnnotationFound = true; 171 continue; 172 } 173 if (a instanceof UifAutoCreateViews) { 174 metadata.setAutoCreateUifViewTypes(Arrays.asList(((UifAutoCreateViews) a).value())); 175 classAnnotationFound = true; 176 } 177 } 178 if (fieldAnnotationsFound) { 179 metadata.setAttributes(attributes); 180 } 181 return classAnnotationFound; 182 } 183 184 /** 185 * Handle annotations made at the field level and add their data to the given metadata object. 186 * 187 * @param clazz the class to process. 188 * @param metadata the metadata for the class. 189 * @return <b>true</b> if any annotations are found. 190 */ 191 protected boolean processFieldLevelAnnotations(Class<?> clazz, DataObjectMetadataImpl metadata) { 192 boolean fieldAnnotationsFound = false; 193 boolean additionalClassAnnotationsFound = false; 194 List<DataObjectAttribute> attributes = new ArrayList<DataObjectAttribute>(); 195 for (Field f : clazz.getDeclaredFields()) { 196 boolean fieldAnnotationFound = false; 197 String propertyName = f.getName(); 198 DataObjectAttributeImpl attr = (DataObjectAttributeImpl) metadata.getAttribute(propertyName); 199 boolean existingAttribute = attr != null; 200 if (!existingAttribute) { 201 attr = new DataObjectAttributeImpl(); 202 attr.setName(propertyName); 203 attr.setType(f.getType()); 204 DataType dataType = DataType.getDataTypeFromClass(f.getType()); 205 if (dataType == null) { 206 dataType = DataType.STRING; 207 } 208 attr.setDataType(dataType); 209 attr.setOwningType(metadata.getType()); 210 } 211 Annotation[] fieldAnnotations = f.getDeclaredAnnotations(); 212 if (LOG.isDebugEnabled()) { 213 LOG.debug(f.getDeclaringClass() + "." + f.getName() + " Field-level annotations: " 214 + Arrays.asList(fieldAnnotations)); 215 } 216 for (Annotation a : fieldAnnotations) { 217 // check if it's one we can handle then do something with it 218 fieldAnnotationFound |= processAnnotationForAttribute(a, attr, metadata); 219 if (!fieldAnnotationFound) { 220 if (a instanceof BusinessKey) { 221 ArrayList<String> businessKeys = new ArrayList<String>(metadata.getBusinessKeyAttributeNames()); 222 businessKeys.add(f.getName()); 223 metadata.setBusinessKeyAttributeNames(businessKeys); 224 // We are not altering the field definition, so dont set the flag 225 // fieldAnnotationFound = true; 226 additionalClassAnnotationsFound = true; 227 continue; 228 } 229 if (a instanceof Relationship) { 230 addDataObjectRelationship(metadata, f, (Relationship) a); 231 232 additionalClassAnnotationsFound = true; 233 continue; 234 } 235 if (a instanceof CollectionRelationship) { 236 addDataObjectCollection(metadata, f, (CollectionRelationship) a); 237 238 additionalClassAnnotationsFound = true; 239 continue; 240 } 241 } 242 } 243 if (fieldAnnotationFound) { 244 attributes.add(attr); 245 fieldAnnotationsFound = true; 246 } 247 } 248 if (fieldAnnotationsFound) { 249 metadata.setAttributes(attributes); 250 } 251 return fieldAnnotationsFound || additionalClassAnnotationsFound; 252 } 253 254 /** 255 * Helper method to process the annotations which can be present on attributes or classes. 256 * 257 * @param a the annotation to process. 258 * @param metadata the metadata for the class. 259 * @return <b>true</b> if a valid annotation is found 260 */ 261 protected boolean processAnnotationsforCommonMetadata(Annotation a, MetadataCommonBase metadata) { 262 if (a instanceof Label) { 263 if (StringUtils.isNotBlank(((Label) a).value())) { 264 metadata.setLabel(((Label) a).value()); 265 return true; 266 } 267 } 268 if (a instanceof ShortLabel) { 269 metadata.setShortLabel(((ShortLabel) a).value()); 270 return true; 271 } 272 if (a instanceof Description) { 273 metadata.setDescription(((Description) a).value()); 274 return true; 275 } 276 return false; 277 } 278 279 /** 280 * Helper method to process the annotations which can be present on attributes. 281 * 282 * <p>Used to abstract the logic so it can be applied to both field and method-level annotations.</p> 283 * 284 * @param a the annotation to process. 285 * @param attr the attribute for the field. 286 * @param metadata the metadata for the class. 287 * 288 * @return true if any annotations were processed, false if not 289 */ 290 protected boolean processAnnotationForAttribute(Annotation a, DataObjectAttributeImpl attr, 291 DataObjectMetadataImpl metadata) { 292 if (a == null) { 293 return false; 294 } 295 if (a instanceof NonPersistentProperty) { 296 attr.setPersisted(false); 297 return true; 298 } 299 if (processAnnotationsforCommonMetadata(a, attr)) { 300 return true; 301 } 302 if (a instanceof ReadOnly) { 303 attr.setReadOnly(true); 304 return true; 305 } 306 if (a instanceof UifValidCharactersConstraintBeanName) { 307 attr.setValidCharactersConstraintBeanName(((UifValidCharactersConstraintBeanName) a).value()); 308 return true; 309 } 310 if (a instanceof KeyValuesFinderClass) { 311 try { 312 attr.setValidValues(((KeyValuesFinderClass) a).value().newInstance()); 313 return true; 314 } catch (Exception ex) { 315 LOG.error("Unable to instantiate options finder: " + ((KeyValuesFinderClass) a).value(), ex); 316 } 317 } 318 if (a instanceof NotNull) { 319 attr.setRequired(true); 320 return true; 321 } 322 if (a instanceof ForceUppercase) { 323 attr.setForceUppercase(true); 324 return true; 325 } 326 if (a instanceof PropertyEditorClass) { 327 try { 328 attr.setPropertyEditor(((PropertyEditorClass) a).value().newInstance()); 329 return true; 330 } catch (Exception ex) { 331 LOG.warn("Unable to instantiate property editor class for " + metadata.getTypeClassName() 332 + "." + attr.getName() + " : " + ((PropertyEditorClass) a).value()); 333 } 334 } 335 if (a instanceof Size) { 336 // We only process it at the moment if the max length has been set 337 // Otherwise, we want the JPA value (max column length) to pass through 338 if (((Size) a).max() != Integer.MAX_VALUE) { 339 attr.setMaxLength((long) ((Size) a).max()); 340 return true; 341 } 342 } 343 if (a instanceof Sensitive) { 344 attr.setSensitive(true); 345 return true; 346 } 347 if (a instanceof UifDisplayHints) { 348 attr.setDisplayHints(new HashSet<UifDisplayHint>(Arrays.asList(((UifDisplayHints) a).value()))); 349 return true; 350 } 351 if (a instanceof MergeAction) { 352 MetadataMergeAction mma = ((MergeAction) a).value(); 353 if (!(mma == MetadataMergeAction.MERGE || mma == MetadataMergeAction.REMOVE)) { 354 throw new MetadataConfigurationException( 355 "Only the MERGE and REMOVE merge actions are supported since the annotation metadata provider can not specify all required properties and may only be used as an overlay."); 356 } 357 attr.setMergeAction(mma); 358 return true; 359 } 360 return false; 361 } 362 363 /** 364 * Used to find the property name from a getter method. 365 * 366 * <p>(Not using PropertyUtils since it required an instance of the class.)</p> 367 * 368 * @param m the method from which to get the property name. 369 * @return the property name. 370 */ 371 protected String getPropertyNameFromGetterMethod(Method m) { 372 String propertyName = ""; 373 if (m.getName().startsWith("get")) { 374 propertyName = StringUtils.uncapitalize(StringUtils.removeStart(m.getName(), "get")); 375 } else { // must be "is" 376 propertyName = StringUtils.uncapitalize(StringUtils.removeStart(m.getName(), "is")); 377 } 378 return propertyName; 379 } 380 381 /** 382 * Handle annotations made at the method level and add their data to the given metadata object. 383 * 384 * @param clazz the class to process. 385 * @param metadata the metadata for the class. 386 * 387 * @return <b>true</b> if any annotations are found. 388 */ 389 protected boolean processMethodLevelAnnotations(Class<?> clazz, DataObjectMetadataImpl metadata) { 390 boolean fieldAnnotationsFound = false; 391 if (LOG.isDebugEnabled()) { 392 LOG.debug("Processing Method Annotations on " + clazz); 393 } 394 List<DataObjectAttribute> attributes = new ArrayList<DataObjectAttribute>(metadata.getAttributes()); 395 for (Method m : clazz.getDeclaredMethods()) { 396 // we only care about properties which are designated as non-persistent 397 // we don't want to load metadata about everything just because it's there 398 // (E.g., we don't know how expensive all method calls are) 399 if (!m.isAnnotationPresent(NonPersistentProperty.class)) { 400 if (LOG.isTraceEnabled()) { 401 LOG.trace("Rejecting method " + m.getName() 402 + " because does not have NonPersistentProperty annotation"); 403 } 404 continue; 405 } 406 // we only care about getters 407 if (!m.getName().startsWith("get") && !m.getName().startsWith("is")) { 408 if (LOG.isDebugEnabled()) { 409 LOG.debug("Rejecting method " + m.getName() + " because name does not match getter pattern"); 410 } 411 continue; 412 } 413 // we also need it to return a value and have no arguments to be a proper getter 414 if (m.getReturnType() == null || m.getParameterTypes().length > 0) { 415 if (LOG.isDebugEnabled()) { 416 LOG.debug("Rejecting method " + m.getName() + " because has no return type or has arguments"); 417 } 418 continue; 419 } 420 String propertyName = getPropertyNameFromGetterMethod(m); 421 boolean fieldAnnotationFound = false; 422 boolean existingAttribute = true; 423 DataObjectAttributeImpl attr = (DataObjectAttributeImpl) metadata.getAttribute(propertyName); 424 if (attr == null) { 425 existingAttribute = false; 426 attr = new DataObjectAttributeImpl(); 427 attr.setName(propertyName); 428 attr.setType(m.getReturnType()); 429 DataType dataType = DataType.getDataTypeFromClass(m.getReturnType()); 430 if (dataType == null) { 431 dataType = DataType.STRING; 432 } 433 attr.setDataType(dataType); 434 attr.setOwningType(metadata.getType()); 435 } 436 Annotation[] methodAnnotations = m.getDeclaredAnnotations(); 437 if (LOG.isDebugEnabled()) { 438 LOG.debug(m.getDeclaringClass() + "." + m.getName() + " Method-level annotations: " 439 + Arrays.asList(methodAnnotations)); 440 } 441 for (Annotation a : methodAnnotations) { 442 fieldAnnotationFound |= processAnnotationForAttribute(a, attr, metadata); 443 } 444 if (fieldAnnotationFound) { 445 if (!existingAttribute) { 446 attributes.add(attr); 447 } 448 fieldAnnotationsFound = true; 449 } 450 } 451 if (fieldAnnotationsFound) { 452 metadata.setAttributes(attributes); 453 } 454 455 return fieldAnnotationsFound; 456 } 457 458 /** 459 * Adds a relationship for a field to the metadata object. 460 * 461 * @param metadata the metadata for the class. 462 * @param f the field to process. 463 * @param a the relationship to add. 464 */ 465 protected void addDataObjectRelationship(DataObjectMetadataImpl metadata, Field f, Relationship a) { 466 List<DataObjectRelationship> relationships = new ArrayList<DataObjectRelationship>(metadata.getRelationships()); 467 DataObjectRelationshipImpl relationship = new DataObjectRelationshipImpl(); 468 relationship.setName(f.getName()); 469 Class<?> childType = f.getType(); 470 relationship.setRelatedType(childType); 471 relationship.setReadOnly(true); 472 relationship.setSavedWithParent(false); 473 relationship.setDeletedWithParent(false); 474 relationship.setLoadedAtParentLoadTime(false); 475 relationship.setLoadedDynamicallyUponUse(true); 476 477 List<DataObjectAttributeRelationship> attributeRelationships = new ArrayList<DataObjectAttributeRelationship>(); 478 List<String> referencePkFields = Collections.emptyList(); 479 MetadataRepository metadataRepository = getDataObjectService().getMetadataRepository(); 480 if (metadataRepository.contains(childType)) { 481 DataObjectMetadata childMetadata = metadataRepository.getMetadata(childType); 482 referencePkFields = childMetadata.getPrimaryKeyAttributeNames(); 483 } else { 484 // HACK ALERT!!!!!!!! FIXME: can be removed once Person is annotated for JPA 485 if (f.getType().getName().equals("org.kuali.rice.kim.api.identity.Person")) { 486 referencePkFields = Collections.singletonList("principalId"); 487 } 488 } 489 if (!referencePkFields.isEmpty()) { 490 int index = 0; 491 for (String pkField : a.foreignKeyFields()) { 492 attributeRelationships.add(new DataObjectAttributeRelationshipImpl(pkField, referencePkFields 493 .get(index))); 494 index++; 495 } 496 relationship.setAttributeRelationships(attributeRelationships); 497 498 relationships.add(relationship); 499 } 500 metadata.setRelationships(relationships); 501 } 502 503 /** 504 * Adds a collection relationship for a field to the metadata object. 505 * 506 * @param metadata the metadata for the class. 507 * @param f the field to process. 508 * @param a the collection relationship to add. 509 */ 510 protected void addDataObjectCollection(DataObjectMetadataImpl metadata, Field f, CollectionRelationship a) { 511 List<DataObjectCollection> collections = new ArrayList<DataObjectCollection>(metadata.getCollections()); 512 DataObjectCollectionImpl collection = new DataObjectCollectionImpl(); 513 collection.setName(f.getName()); 514 515 if ( !Collection.class.isAssignableFrom(f.getType()) ) { 516 throw new IllegalArgumentException( 517 "@CollectionRelationship annotations can only be on attributes of Collection type. Field: " 518 + f.getDeclaringClass().getName() + "." + f.getName() + " (" + f.getType() + ")"); 519 } 520 521 if (a.collectionElementClass().equals(Object.class)) { // Object is the default (and meaningless anyway) 522 Type[] genericArgs = ((ParameterizedType) f.getGenericType()).getActualTypeArguments(); 523 if (genericArgs.length == 0) { 524 throw new IllegalArgumentException( 525 "You can only leave off the collectionElementClass annotation on a @CollectionRelationship when the Collection type has been <typed>. Field: " 526 + f.getDeclaringClass().getName() + "." + f.getName() + " (" + f.getType() + ")"); 527 } 528 collection.setRelatedType((Class<?>) genericArgs[0]); 529 } else { 530 collection.setRelatedType(a.collectionElementClass()); 531 } 532 533 List<DataObjectAttributeRelationship> attributeRelationships = new ArrayList<DataObjectAttributeRelationship>( 534 a.attributeRelationships().length); 535 for (AttributeRelationship rel : a.attributeRelationships()) { 536 attributeRelationships.add(new DataObjectAttributeRelationshipImpl(rel.parentAttributeName(), rel 537 .childAttributeName())); 538 } 539 collection.setAttributeRelationships(attributeRelationships); 540 541 collection.setReadOnly(false); 542 collection.setSavedWithParent(false); 543 collection.setDeletedWithParent(false); 544 collection.setLoadedAtParentLoadTime(true); 545 collection.setLoadedDynamicallyUponUse(false); 546 List<DataObjectCollectionSortAttribute> sortAttributes = new ArrayList<DataObjectCollectionSortAttribute>( 547 a.sortAttributes().length); 548 for (CollectionSortAttribute csa : a.sortAttributes()) { 549 sortAttributes.add(new DataObjectCollectionSortAttributeImpl(csa.value(), csa.sortDirection())); 550 } 551 collection.setDefaultCollectionOrderingAttributeNames(sortAttributes); 552 553 collection.setIndirectCollection(a.indirectCollection()); 554 collection.setMinItemsInCollection(a.minItemsInCollection()); 555 collection.setMaxItemsInCollection(a.maxItemsInCollection()); 556 if (StringUtils.isNotBlank(a.label())) { 557 collection.setLabel(a.label()); 558 } 559 if (StringUtils.isNotBlank(a.elementLabel())) { 560 collection.setLabel(a.elementLabel()); 561 } 562 563 collections.add(collection); 564 metadata.setCollections(collections); 565 } 566 567 /** 568 * Handle inherited properties and add their data to the given metadata object. 569 * 570 * @param clazz the class to process. 571 * @param metadata the metadata for the class. 572 * 573 * @return <b>true</b> if any annotations are found. 574 */ 575 protected boolean processInheritedAttributes(Class<?> clazz, DataObjectMetadataImpl metadata) { 576 if (LOG.isDebugEnabled()) { 577 LOG.debug("Processing InheritProperties field Annotations on " + clazz); 578 } 579 List<DataObjectAttribute> attributes = new ArrayList<DataObjectAttribute>(metadata.getAttributes()); 580 boolean fieldAnnotationsFound = false; 581 for (Field f : clazz.getDeclaredFields()) { 582 boolean fieldAnnotationFound = false; 583 String propertyName = f.getName(); 584 585 if (!f.isAnnotationPresent(InheritProperties.class) && !f.isAnnotationPresent(InheritProperty.class)) { 586 continue; 587 } 588 fieldAnnotationFound = true; 589 // Get the list of inherited properties, either from a single annotation or the "plural" version 590 InheritProperty[] propertyList = null; 591 InheritProperties a = f.getAnnotation(InheritProperties.class); 592 if (a != null) { 593 propertyList = a.value(); 594 } else { 595 // if the above is not present, then there must be an @InheritProperty annotation 596 InheritProperty ip = f.getAnnotation(InheritProperty.class); 597 propertyList = new InheritProperty[] { ip }; 598 } 599 if (LOG.isDebugEnabled()) { 600 LOG.debug("InheritProperties found on " + clazz + "." + f.getName() + " : " 601 + Arrays.toString(propertyList)); 602 } 603 for (InheritProperty inheritedProperty : propertyList) { 604 String inheritedPropertyName = inheritedProperty.name(); 605 String extendedPropertyName = propertyName + "." + inheritedPropertyName; 606 DataObjectAttributeImpl attr = (DataObjectAttributeImpl) metadata.getAttribute(extendedPropertyName); 607 boolean existingAttribute = attr != null; 608 if (!existingAttribute) { 609 // NOTE: dropping to reflection here as the related metadata may not be loaded yet... 610 // TODO: this may need to be reworked to allow for "real-time" inheritance 611 // since the values seen here should reflect overrides performed later in the chain 612 // (e.g., by the MessageServiceMetadataProvider) 613 attr = new DataObjectAttributeImpl(); 614 attr.setName(extendedPropertyName); 615 Class<?> relatedClass = f.getType(); 616 try { 617 attr.setType(getTypeOfProperty(relatedClass, inheritedPropertyName)); 618 DataType dataType = DataType.getDataTypeFromClass(attr.getType()); 619 if (dataType == null) { 620 dataType = DataType.STRING; 621 } 622 attr.setDataType(dataType); 623 } catch (Exception e) { 624 throw new IllegalArgumentException("no field with name " + inheritedPropertyName 625 + " exists on " + relatedClass, e); 626 } 627 // Since this attribute is really part of another object, we want to indicate that it's not 628 // persistent (as far as this object is concerned) 629 attr.setPersisted(false); 630 attr.setOwningType(metadata.getType()); 631 attr.setInheritedFromType(relatedClass); 632 attr.setInheritedFromAttributeName(inheritedPropertyName); 633 attr.setInheritedFromParentAttributeName(propertyName); 634 635 // Handle the label override, if present 636 processAnnotationForAttribute(inheritedProperty.label(), attr, metadata); 637 // Handle the UIF displayoverride, if present 638 processAnnotationForAttribute(inheritedProperty.displayHints(), attr, metadata); 639 640 attributes.add(attr); 641 } 642 } 643 644 fieldAnnotationsFound |= fieldAnnotationFound; 645 } 646 if (fieldAnnotationsFound) { 647 metadata.setAttributes(attributes); 648 } 649 return fieldAnnotationsFound; 650 } 651 652 /** 653 * Used to find the property type of a given attribute regardless of whether the attribute exists as a field or only 654 * as a getter method. 655 * 656 * <p>(Not using PropertyUtils since it required an instance of the class.)</p> 657 * 658 * @param clazz the class that contains the property. 659 * @param propertyName the name of the property. 660 * @return the type of the property. 661 */ 662 protected Class<?> getTypeOfProperty(Class<?> clazz, String propertyName) { 663 try { 664 Field f = clazz.getField(propertyName); 665 return f.getType(); 666 } catch (Exception e) { 667 // Do nothing = field does not exist 668 } 669 try { 670 Method m = clazz.getMethod("get" + StringUtils.capitalize(propertyName)); 671 return m.getReturnType(); 672 } catch (Exception e) { 673 // Do nothing = method does not exist 674 } 675 try { 676 Method m = clazz.getMethod("is" + StringUtils.capitalize(propertyName)); 677 return m.getReturnType(); 678 } catch (Exception e) { 679 // Do nothing = method does not exist 680 } 681 return null; 682 } 683 684 /** 685 * {@inheritDoc} 686 * 687 * Returns true in this implementation. This tells the composite metadata provider to pass in all known metadata to 688 * the initializeMetadata method. 689 */ 690 @Override 691 public boolean requiresListOfExistingTypes() { 692 return true; 693 } 694 695 /** 696 * Gets whether initialization was attempted. 697 * 698 * @return whether initialization was attempted. 699 */ 700 public boolean isInitializationAttempted() { 701 return initializationAttempted; 702 } 703 704 /** 705 * Gets the {@link DataObjectService}. 706 * @return the {@link DataObjectService}. 707 */ 708 public DataObjectService getDataObjectService() { 709 if (dataObjectService == null) { 710 dataObjectService = KradDataServiceLocator.getDataObjectService(); 711 } 712 return dataObjectService; 713 } 714 715 /** 716 * Setter for the the {@link DataObjectService}. 717 * 718 * @param dataObjectService the the {@link DataObjectService} to set. 719 */ 720 public void setDataObjectService(DataObjectService dataObjectService) { 721 this.dataObjectService = dataObjectService; 722 } 723}