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}