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