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 */ 016package org.kuali.rice.krad.data.jpa; 017 018import org.kuali.rice.core.api.data.DataType; 019import org.kuali.rice.krad.data.metadata.DataObjectAttribute; 020import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship; 021import org.kuali.rice.krad.data.metadata.DataObjectCollection; 022import org.kuali.rice.krad.data.metadata.DataObjectMetadata; 023import org.kuali.rice.krad.data.metadata.DataObjectRelationship; 024import org.kuali.rice.krad.data.metadata.impl.DataObjectAttributeImpl; 025import org.kuali.rice.krad.data.metadata.impl.DataObjectAttributeRelationshipImpl; 026import org.kuali.rice.krad.data.metadata.impl.DataObjectCollectionImpl; 027import org.kuali.rice.krad.data.metadata.impl.DataObjectMetadataImpl; 028import org.kuali.rice.krad.data.metadata.impl.DataObjectRelationshipImpl; 029import org.kuali.rice.krad.data.provider.annotation.ExtensionFor; 030import org.kuali.rice.krad.data.provider.impl.MetadataProviderBase; 031 032import javax.persistence.EntityManager; 033import javax.persistence.metamodel.Attribute.PersistentAttributeType; 034import javax.persistence.metamodel.EmbeddableType; 035import javax.persistence.metamodel.EntityType; 036import javax.persistence.metamodel.IdentifiableType; 037import javax.persistence.metamodel.PluralAttribute; 038import javax.persistence.metamodel.SingularAttribute; 039import java.lang.reflect.Field; 040import java.util.ArrayList; 041import java.util.Collection; 042import java.util.Collections; 043import java.util.HashMap; 044import java.util.List; 045import java.util.Map; 046import java.util.Set; 047 048/** 049 * A superclass which handles most of the JPA metadata extraction. 050 * 051 * <p> 052 * It handles everything which can be done via the standard javax.persistence annotations. Any implementation-specific 053 * annotations must be processed in the provided abstract hook methods. 054 * </p> 055 * 056 * @author Kuali Rice Team (rice.collab@kuali.org) 057 */ 058public abstract class JpaMetadataProviderImpl extends MetadataProviderBase implements JpaMetadataProvider { 059 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(JpaMetadataProviderImpl.class); 060 061 /** 062 * The entity manager used in interacting with the database. 063 */ 064 protected EntityManager entityManager; 065 066 /** 067 * Hook called after all "standard" annotations are processed to perform any further extraction based on the 068 * internals of the JPA implementation. 069 * 070 * @param metadata The metadata for the data object. 071 * @param entityType The entity type of the data object. 072 */ 073 protected abstract void populateImplementationSpecificEntityLevelMetadata(DataObjectMetadataImpl metadata, 074 EntityType<?> entityType); 075 076 /** 077 * Hook called after all "standard" attribute-level annotations are processed to perform any further extraction 078 * based on the internals of the JPA implementation. 079 * 080 * @param attribute The attribute metadata for the data object. 081 * @param attr The persistent single-valued property or field. 082 */ 083 protected abstract void populateImplementationSpecificAttributeLevelMetadata(DataObjectAttributeImpl attribute, 084 SingularAttribute<?, ?> attr); 085 086 /** 087 * Hook called after all "standard" field-level annotations are processed on attributes identified as "plural" to 088 * perform any further extraction based on the internals of the JPA implementation. 089 * 090 * @param collection The collection metadata for the data object. 091 * @param cd The persistent collection-valued attribute. 092 */ 093 protected abstract void populateImplementationSpecificCollectionLevelMetadata(DataObjectCollectionImpl collection, 094 PluralAttribute<?, ?, ?> cd); 095 096 /** 097 * Hook called after all "standard" field-level annotations are processed on attributes identified as "associations" 098 * to perform any further extraction based on the internals of the JPA implementation. 099 * 100 * @param relationship The relationship metadata for the data object. 101 * @param rd The persistent single-valued property or field. 102 */ 103 protected abstract void populateImplementationSpecificRelationshipLevelMetadata( 104 DataObjectRelationshipImpl relationship, SingularAttribute<?, ?> rd); 105 106 /** 107 * {@inheritDoc} 108 */ 109 @Override 110 public abstract DataObjectRelationship addExtensionRelationship(Class<?> entityClass, String extensionPropertyName, 111 Class<?> extensionEntity); 112 113 /** 114 * {@inheritDoc} 115 */ 116 @Override 117 protected synchronized void initializeMetadata(Collection<Class<?>> types) { 118 LOG.info("Initializing JPA Metadata from " + entityManager); 119 120 masterMetadataMap.clear(); 121 // QUESTION: When is JPA loaded so this service can initialize itself? 122 // Build and store the map 123 124 for ( IdentifiableType<?> identifiableType : entityManager.getMetamodel().getEntities() ) { 125 //Only extract the metadata if EntityType and not a MappedSuperClass 126 if(identifiableType instanceof EntityType<?>){ 127 EntityType<?> type = (EntityType<?>)identifiableType; 128 try { 129 masterMetadataMap.put(type.getBindableJavaType(), getMetadataForClass(type.getBindableJavaType())); 130 if (LOG.isDebugEnabled()) { 131 LOG.debug("Added Metadata For: " + type.getBindableJavaType()); 132 } 133 } catch (Exception ex) { 134 LOG.error("Error obtaining JPA metadata for type: " + type.getJavaType(), ex); 135 } 136 } 137 } 138 } 139 140 /** 141 * Extracts the data from the JPA Persistence Unit. This code assumes that the given class is persistable. 142 * 143 * @param persistableClass Class which will be looked up in OJB's static descriptor repository. 144 * @return the metadata for the class 145 */ 146 @SuppressWarnings("unchecked") 147 public DataObjectMetadata getMetadataForClass(Class<?> persistableClass) { 148 // first, let's scan for extensions 149 List<DataObjectRelationship> relationships = new ArrayList<DataObjectRelationship>(); 150 Map<Class<?>, Class<?>> extensionMap = new HashMap<Class<?>, Class<?>>(); 151 for (EntityType<?> entityType : getEntityManager().getMetamodel().getEntities()) { 152 if (entityType.getJavaType().isAnnotationPresent(ExtensionFor.class)) { 153 ExtensionFor extensionFor = entityType.getJavaType().getAnnotation(ExtensionFor.class); 154 if (extensionFor.value().equals(persistableClass)) { 155 DataObjectRelationship relationship = 156 addExtensionRelationship(persistableClass, extensionFor.extensionPropertyName(), entityType.getJavaType()); 157 // have to do this because even though we've added the DatabaseMapping in EclipseLink, it will not 158 // rebuild the JPA metamodel for us 159 relationships.add(relationship); 160 } 161 } 162 } 163 // now let's build us some metadata! 164 DataObjectMetadataImpl metadata = new DataObjectMetadataImpl(); 165 EntityType<?> entityType = entityManager.getMetamodel().entity(persistableClass); 166 metadata.setProviderName(this.getClass().getSimpleName()); 167 metadata.setType(persistableClass); 168 metadata.setName(persistableClass.getSimpleName()); 169 metadata.setReadOnly(false); 170 171 metadata.setSupportsOptimisticLocking(entityType.hasVersionAttribute()); 172 populateImplementationSpecificEntityLevelMetadata(metadata, entityType); 173 174 // PK Extraction 175 try { 176 metadata.setPrimaryKeyAttributeNames(getPrimaryKeyAttributeNames(entityType)); 177 } catch (RuntimeException ex) { 178 LOG.error("Error processing PK metadata for " + entityType.getBindableJavaType().getName()); 179 throw new RuntimeException( 180 "Error processing PK metadata for " + entityType.getBindableJavaType().getName(), ex); 181 } 182 183 // Main Attribute Extraction 184 try { 185 List<DataObjectAttribute> attributes = getSingularAttributes(persistableClass, 186 entityType.getSingularAttributes(), metadata.getPrimaryKeyAttributeNames()); 187 for (DataObjectAttribute attr : attributes) { 188 metadata.getOrderedAttributeList().add(attr.getName()); 189 } 190 metadata.setAttributes(attributes); 191 } catch (RuntimeException ex) { 192 LOG.error("Error processing attribute metadata for " + entityType.getBindableJavaType().getName()); 193 throw ex; 194 } 195 196 // Collection Extraction 197 try { 198 metadata.setCollections(getCollectionsFromMetadata((Set) entityType.getPluralAttributes())); 199 } catch (RuntimeException ex) { 200 LOG.error("Error processing collection metadata for " + entityType.getBindableJavaType().getName()); 201 throw ex; 202 } 203 204 // Reference/Relationship Extraction 205 try { 206 relationships.addAll(getRelationships(entityType.getSingularAttributes())); 207 metadata.setRelationships(relationships); 208 } catch (RuntimeException ex) { 209 LOG.error("Error processing relationship metadata for " + entityType.getBindableJavaType().getName()); 210 throw ex; 211 } 212 213 return metadata; 214 } 215 216 /** 217 * Gets the attribute names for the primary keys from the given entity type. 218 * 219 * @param entityType The entity type of the data object. 220 * @return A list of primary key attribute names. 221 */ 222 protected List<String> getPrimaryKeyAttributeNames(EntityType<?> entityType) { 223 List<String> primaryKeyAttributeNames = new ArrayList<String>(); 224 // JHK: After examining of the metadata structures of EclipseLink, I determined that there 225 // was nothing in those which preserved the order of the original annotations. 226 // We *need* to know the order of PK fields for KNS/KRAD functionality. 227 // So, I'm falling back to checking the annotations and fields on the referenced objects. 228 // Yes, the Javadoc states that the getDeclaredFields() method does not guarantee order, 229 // But, it's the best we have. And, as of Java 6, it is returning them in declaration order. 230 231 if (entityType.getIdType() instanceof EmbeddableType) { 232 for (Field pkField : entityType.getIdType().getJavaType().getDeclaredFields()) { 233 primaryKeyAttributeNames.add(pkField.getName()); 234 } 235 } else { 236 // First, get the ID attributes from the metadata 237 List<String> unsortedPkFields = new ArrayList<String>(); 238 for (SingularAttribute attr : entityType.getSingularAttributes()) { 239 if (attr.isId()) { 240 unsortedPkFields.add(attr.getName()); 241 } 242 } 243 244 getPrimaryKeyNamesInOrder(primaryKeyAttributeNames, unsortedPkFields, entityType.getJavaType().getDeclaredFields(), entityType.getJavaType()); 245 } 246 return primaryKeyAttributeNames; 247 } 248 249 /** 250 * Sorts the list of primary key names. 251 * 252 * @param pkFieldNames The final list to which the primary key field names will be added in order. 253 * @param unsortedPks The current list of unsorted primary keys. 254 * @param fields The fields on the current object. 255 * @param type The class of the current object. 256 */ 257 private void getPrimaryKeyNamesInOrder(List<String> pkFieldNames, List<String> unsortedPks, Field[] fields, Class<?> type) { 258 for (Field field : type.getDeclaredFields()) { 259 if (unsortedPks.contains(field.getName())) { 260 pkFieldNames.add(field.getName()); 261 } 262 } 263 264 if (pkFieldNames.isEmpty() && type.getSuperclass() != null) { 265 getPrimaryKeyNamesInOrder(pkFieldNames, unsortedPks, type.getSuperclass().getDeclaredFields(), type.getSuperclass()); 266 } 267 } 268 269 /** 270 * Gets a list of attributes for this data object. 271 * 272 * @param persistableClass The class of the data object. 273 * @param fields The collection of singular attributes to process. 274 * @param primaryKeyAttributes The list of primary key attribute names. 275 * @return The list of attributes for this data object. 276 */ 277 protected List<DataObjectAttribute> getSingularAttributes(Class<?> persistableClass, Collection<?> fields, 278 List<String> primaryKeyAttributes) { 279 if (fields == null) { 280 fields = Collections.emptySet(); 281 } 282 // Put them all into a map by their property name so we can find them 283 // We want to add them to the list in appearance order in the class 284 Map<String, SingularAttribute> attrs = new HashMap<String, SingularAttribute>(fields.size()); 285 for (SingularAttribute attr : (Collection<SingularAttribute>) fields) { 286 if (!attr.isAssociation()) { 287 attrs.put(attr.getName(), attr); 288 } 289 } 290 List<DataObjectAttribute> attributes = new ArrayList<DataObjectAttribute>(fields.size()); 291 // This will process them in appearance order 292 for (Field f : persistableClass.getDeclaredFields()) { 293 SingularAttribute attr = attrs.get(f.getName()); 294 if (attr != null) { 295 attributes.add(getAttributeMetadata(persistableClass, attr, primaryKeyAttributes)); 296 attrs.remove(f.getName()); // to note that it's been used - see below 297 } 298 } 299 // Just in case there are others which don't match, we don't want to miss them and will add them at the end 300 for (SingularAttribute attr : attrs.values()) { 301 attributes.add(getAttributeMetadata(persistableClass, attr, primaryKeyAttributes)); 302 } 303 return attributes; 304 } 305 306 /** 307 * Gets a single field's metadata from the property descriptor. 308 * 309 * @param persistableClass The class of the data object. 310 * @param attr The singular attribute to process. 311 * @param primaryKeyAttributes The list of primary key attribute names. 312 * @return The DataObjectAttribute containing the metadata for the given attribute on the provided Class 313 */ 314 protected DataObjectAttribute getAttributeMetadata(Class<?> persistableClass, SingularAttribute<?, ?> attr, 315 List<String> primaryKeyAttributes) { 316 DataObjectAttributeImpl attribute = new DataObjectAttributeImpl(); 317 318 attribute.setOwningType(persistableClass); 319 attribute.setName(attr.getName()); 320 Class<?> propertyType = attr.getJavaType(); 321 attribute.setType(propertyType); 322 DataType dataType = DataType.getDataTypeFromClass(propertyType); 323 if (dataType == null) { 324 dataType = DataType.STRING; 325 } 326 attribute.setDataType(dataType); 327 attribute.setRequired(!attr.isOptional() && !attr.isId() && !primaryKeyAttributes.contains(attr.getName())); 328 329 populateImplementationSpecificAttributeLevelMetadata(attribute, attr); 330 331 return attribute; 332 } 333 334 /** 335 * Gets a collection's metadata from the property descriptor. 336 * 337 * @param collections The list of plural attributes to process. 338 * @return The list of collections for this data object. 339 */ 340 protected List<DataObjectCollection> getCollectionsFromMetadata(Set<PluralAttribute> collections) { 341 List<DataObjectCollection> colls = new ArrayList<DataObjectCollection>(collections.size()); 342 for (PluralAttribute cd : collections) { 343 colls.add(getCollectionMetadataFromCollectionAttribute(cd)); 344 } 345 return colls; 346 } 347 348 /** 349 * Extracts the collection metadata from a single JPA {@link PluralAttribute} object. 350 * 351 * @param cd The plural attribute to process. 352 * @return The collection metadata from a single JPA {@link PluralAttribute} object. 353 */ 354 protected DataObjectCollection getCollectionMetadataFromCollectionAttribute(PluralAttribute cd) { 355 try { 356 DataObjectCollectionImpl collection = new DataObjectCollectionImpl(); 357 358 // OJB stores the related class object name. We need to go into the repository and grab the table name. 359 Class<?> collectionElementClass = cd.getElementType().getJavaType(); 360 EntityType<?> elementEntityType = entityManager.getMetamodel().entity(collectionElementClass); 361 collection.setName(cd.getName()); 362 collection.setRelatedType(collectionElementClass); 363 populateImplementationSpecificCollectionLevelMetadata(collection, cd); 364 365 // Set to read only if store (save) operations should not be pushed through 366 PersistentAttributeType persistentAttributeType = cd.getPersistentAttributeType(); 367 368 // default case: Without any mapping attributes, collections are linked by their primary key 369 if (persistentAttributeType == PersistentAttributeType.ONE_TO_MANY) { 370 // TODO: We probably still need to handle the "mappedBy" property on the OneToMany definition 371 372 // We only perform this logic here if we did not populate it in the implementation-specific call above 373 if (collection.getAttributeRelationships().isEmpty()) { 374 // need to obtain the keys for the relationship 375 List<String> pkFields = getPrimaryKeyAttributeNames((EntityType<?>) cd.getDeclaringType()); 376 List<String> fkFields = getPrimaryKeyAttributeNames(elementEntityType); 377 List<DataObjectAttributeRelationship> attributeRelationships = new ArrayList<DataObjectAttributeRelationship>(); 378 for (int i = 0; i < pkFields.size(); i++) { 379 attributeRelationships.add(new DataObjectAttributeRelationshipImpl(pkFields.get(i), fkFields 380 .get(i))); 381 } 382 collection.setAttributeRelationships(attributeRelationships); 383 } 384 } else if ( persistentAttributeType == PersistentAttributeType.MANY_TO_MANY ) { 385 // OK, this is an assumption 386 collection.setIndirectCollection( true ); 387 // And, since the connection is set at the *database* level through the @JoinTable anotation 388 // we do not have any field names with which to make the connection 389 collection.setAttributeRelationships(null); 390 } 391 392 return collection; 393 } catch (RuntimeException ex) { 394 LOG.error("Unable to process Collection metadata: " + cd); 395 throw ex; 396 } 397 } 398 399 /** 400 * Gets the list of relationships for this data object. 401 * 402 * @param references The list of singular attribute references. 403 * @return The list of relationships for this data object. 404 */ 405 protected List<DataObjectRelationship> getRelationships(Set<?> references) { 406 List<DataObjectRelationship> rels = new ArrayList<DataObjectRelationship>(references.size()); 407 for (SingularAttribute rd : (Set<SingularAttribute>) references) { 408 if (rd.isAssociation()) { 409 rels.add(getRelationshipMetadata(rd)); 410 } 411 } 412 return rels; 413 } 414 415 /** 416 * Gets a single field's relationship metadata. 417 * 418 * @param rd The singular attribute to process. 419 * @return The single field's relationship metadata. 420 */ 421 protected DataObjectRelationship getRelationshipMetadata(SingularAttribute rd) { 422 try { 423 DataObjectRelationshipImpl relationship = new DataObjectRelationshipImpl(); 424 425 // OJB stores the related class object name. We need to go into the repository and grab the table name. 426 Class<?> referencedClass = rd.getBindableJavaType(); 427 EntityType<?> referencedEntityType = entityManager.getMetamodel().entity(referencedClass); 428 relationship.setName(rd.getName()); 429 relationship.setRelatedType(referencedClass); 430 populateImplementationSpecificRelationshipLevelMetadata(relationship, rd); 431 432 return relationship; 433 } catch (RuntimeException ex) { 434 LOG.error("Unable to process Relationship metadata: " + rd); 435 throw ex; 436 } 437 } 438 439 /** 440 * {@inheritDoc} 441 */ 442 @Override 443 public boolean isClassPersistable(Class<?> type) { 444 return handles(type); 445 } 446 447 /** 448 * Setter for the entity manager. 449 * 450 * @param entityManager The entity manager to set. 451 */ 452 public void setEntityManager(EntityManager entityManager) { 453 this.entityManager = entityManager; 454 } 455 456 /** 457 * Gets the entity manager for interacting with the database. 458 * 459 * @return The entity manager for interacting with the database. 460 */ 461 public EntityManager getEntityManager() { 462 return entityManager; 463 } 464}