View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.data.jpa;
17  
18  import org.kuali.rice.core.api.data.DataType;
19  import org.kuali.rice.krad.data.metadata.DataObjectAttribute;
20  import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship;
21  import org.kuali.rice.krad.data.metadata.DataObjectCollection;
22  import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
23  import org.kuali.rice.krad.data.metadata.DataObjectRelationship;
24  import org.kuali.rice.krad.data.metadata.impl.DataObjectAttributeImpl;
25  import org.kuali.rice.krad.data.metadata.impl.DataObjectAttributeRelationshipImpl;
26  import org.kuali.rice.krad.data.metadata.impl.DataObjectCollectionImpl;
27  import org.kuali.rice.krad.data.metadata.impl.DataObjectMetadataImpl;
28  import org.kuali.rice.krad.data.metadata.impl.DataObjectRelationshipImpl;
29  import org.kuali.rice.krad.data.provider.annotation.ExtensionFor;
30  import org.kuali.rice.krad.data.provider.impl.MetadataProviderBase;
31  
32  import javax.persistence.EntityManager;
33  import javax.persistence.metamodel.Attribute.PersistentAttributeType;
34  import javax.persistence.metamodel.EmbeddableType;
35  import javax.persistence.metamodel.EntityType;
36  import javax.persistence.metamodel.IdentifiableType;
37  import javax.persistence.metamodel.PluralAttribute;
38  import javax.persistence.metamodel.SingularAttribute;
39  import java.lang.reflect.Field;
40  import java.util.ArrayList;
41  import java.util.Collection;
42  import java.util.Collections;
43  import java.util.HashMap;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.Set;
47  
48  /**
49   * A superclass which handles most of the JPA metadata extraction.
50   *
51   * <p>
52   * It handles everything which can be done via the standard javax.persistence annotations. Any implementation-specific
53   * annotations must be processed in the provided abstract hook methods.
54   * </p>
55   *
56   * @author Kuali Rice Team (rice.collab@kuali.org)
57   */
58  public abstract class JpaMetadataProviderImpl extends MetadataProviderBase implements JpaMetadataProvider {
59  	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(JpaMetadataProviderImpl.class);
60  
61      /**
62       * The entity manager used in interacting with the database.
63       */
64  	protected EntityManager entityManager;
65  
66  	/**
67  	 * Hook called after all "standard" annotations are processed to perform any further extraction based on the
68  	 * internals of the JPA implementation.
69       *
70       * @param metadata The metadata for the data object.
71       * @param entityType The entity type of the data object.
72  	 */
73  	protected abstract void populateImplementationSpecificEntityLevelMetadata(DataObjectMetadataImpl metadata,
74  			EntityType<?> entityType);
75  
76  	/**
77  	 * Hook called after all "standard" attribute-level annotations are processed to perform any further extraction
78  	 * based on the internals of the JPA implementation.
79       *
80       * @param attribute The attribute metadata for the data object.
81       * @param attr The persistent single-valued property or field.
82  	 */
83      protected abstract void populateImplementationSpecificAttributeLevelMetadata(DataObjectAttributeImpl attribute,
84  			SingularAttribute<?, ?> attr);
85  
86  	/**
87  	 * Hook called after all "standard" field-level annotations are processed on attributes identified as "plural" to
88  	 * perform any further extraction based on the internals of the JPA implementation.
89       *
90       * @param collection The collection metadata for the data object.
91       * @param cd The persistent collection-valued attribute.
92  	 */
93      protected abstract void populateImplementationSpecificCollectionLevelMetadata(DataObjectCollectionImpl collection,
94  			PluralAttribute<?, ?, ?> cd);
95  
96  	/**
97  	 * Hook called after all "standard" field-level annotations are processed on attributes identified as "associations"
98  	 * to perform any further extraction based on the internals of the JPA implementation.
99       *
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 }