001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.data.jpa.eclipselink;
017
018import org.apache.commons.lang.StringUtils;
019import org.eclipse.persistence.descriptors.ClassDescriptor;
020import org.eclipse.persistence.expressions.Expression;
021import org.eclipse.persistence.internal.expressions.FunctionExpression;
022import org.eclipse.persistence.internal.helper.DatabaseField;
023import org.eclipse.persistence.internal.jpa.metamodel.EmbeddableTypeImpl;
024import org.eclipse.persistence.internal.jpa.metamodel.EntityTypeImpl;
025import org.eclipse.persistence.internal.jpa.metamodel.ManagedTypeImpl;
026import org.eclipse.persistence.internal.jpa.metamodel.PluralAttributeImpl;
027import org.eclipse.persistence.internal.jpa.metamodel.SingularAttributeImpl;
028import org.eclipse.persistence.jpa.JpaEntityManager;
029import org.eclipse.persistence.mappings.AggregateObjectMapping;
030import org.eclipse.persistence.mappings.CollectionMapping;
031import org.eclipse.persistence.mappings.DatabaseMapping;
032import org.eclipse.persistence.mappings.DirectToFieldMapping;
033import org.eclipse.persistence.mappings.ForeignReferenceMapping;
034import org.eclipse.persistence.mappings.ManyToOneMapping;
035import org.eclipse.persistence.mappings.OneToManyMapping;
036import org.eclipse.persistence.mappings.OneToOneMapping;
037import org.eclipse.persistence.mappings.converters.Converter;
038import org.eclipse.persistence.mappings.converters.ConverterClass;
039import org.eclipse.persistence.queries.ObjectLevelReadQuery;
040import org.kuali.rice.krad.data.jpa.JpaMetadataProviderImpl;
041import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship;
042import org.kuali.rice.krad.data.metadata.DataObjectCollectionSortAttribute;
043import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
044import org.kuali.rice.krad.data.metadata.DataObjectRelationship;
045import org.kuali.rice.krad.data.metadata.MetadataConfigurationException;
046import org.kuali.rice.krad.data.metadata.SortDirection;
047import org.kuali.rice.krad.data.metadata.impl.DataObjectAttributeImpl;
048import org.kuali.rice.krad.data.metadata.impl.DataObjectAttributeRelationshipImpl;
049import org.kuali.rice.krad.data.metadata.impl.DataObjectCollectionImpl;
050import org.kuali.rice.krad.data.metadata.impl.DataObjectCollectionSortAttributeImpl;
051import org.kuali.rice.krad.data.metadata.impl.DataObjectMetadataImpl;
052import org.kuali.rice.krad.data.metadata.impl.DataObjectRelationshipImpl;
053import org.kuali.rice.krad.data.metadata.impl.MetadataChildBase;
054
055import javax.persistence.metamodel.Attribute.PersistentAttributeType;
056import javax.persistence.metamodel.EntityType;
057import javax.persistence.metamodel.ManagedType;
058import javax.persistence.metamodel.PluralAttribute;
059import javax.persistence.metamodel.SingularAttribute;
060import java.lang.reflect.Field;
061import java.util.ArrayList;
062import java.util.Collection;
063import java.util.List;
064import java.util.Map;
065import java.util.Set;
066
067/**
068 * Provides an EclipseLink-specific implementation for the {@link JpaMetadataProviderImpl}.
069 */
070public class EclipseLinkJpaMetadataProviderImpl extends JpaMetadataProviderImpl {
071        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
072                        .getLogger(EclipseLinkJpaMetadataProviderImpl.class);
073
074        /**
075         * {@inheritDoc}
076         */
077        @Override
078        protected void populateImplementationSpecificEntityLevelMetadata(DataObjectMetadataImpl metadata,
079                                                                                                                                         EntityType<?> entityType) {
080                if ( entityType instanceof EntityTypeImpl ) {
081                        metadata.setBackingObjectName(((EntityTypeImpl<?>) entityType).getDescriptor().getTableName());
082                }
083        }
084
085        /**
086         * {@inheritDoc}
087         */
088        @Override
089        protected void populateImplementationSpecificAttributeLevelMetadata(DataObjectAttributeImpl attribute,
090                                                                                                                                                SingularAttribute<?, ?> attr) {
091
092                if (attr instanceof SingularAttributeImpl) {
093                        DatabaseMapping mapping = ((SingularAttributeImpl<?, ?>) attr).getMapping();
094                        if (mapping != null && mapping.getField() != null) {
095                                attribute.setReadOnly(mapping.isReadOnly());
096                                attribute.setBackingObjectName(mapping.getField().getName());
097                                if (mapping.getField().getLength() != 0) {
098                                        attribute.setMaxLength((long) mapping.getField().getLength());
099                                }
100
101                                // Special check on the converters to attempt to default secure attributes from being shown on the UI
102                                // We check for a converter which has "encrypt" in its name and auto-set the attribute security
103                                // to mask the attribute.
104                                if (mapping instanceof DirectToFieldMapping) {
105                                        Converter converter = ((DirectToFieldMapping) mapping).getConverter();
106                                        // ConverterClass is the internal wrapper EclipseLink uses to wrap the JPA AttributeConverter
107                                        // classes
108                                        // and make them conform to the EclipseLink internal API
109                                        if (converter != null && converter instanceof ConverterClass) {
110                                                // Unfortunately, there is no access to the actual converter class, so we have to hack it
111                                                try {
112                                                        Field f = ConverterClass.class.getDeclaredField("attributeConverterClassName");
113                                                        f.setAccessible(true);
114                                                        String attributeConverterClassName = (String) f.get(converter);
115                                                        if (StringUtils.containsIgnoreCase(attributeConverterClassName, "encrypt")) {
116                                                                attribute.setSensitive(true);
117                                                        }
118                                                } catch (Exception e) {
119                                                        LOG.warn("Unable to access the converter name for attribute: "
120                                                                        + attribute.getOwningType().getName() + "." + attribute.getName()
121                                                                        + "  Skipping attempt to detect converter.");
122                                                }
123                                        }
124                                }
125
126                        }
127                }
128        }
129
130        /**
131         * {@inheritDoc}
132         */
133        @Override
134        protected void populateImplementationSpecificCollectionLevelMetadata(DataObjectCollectionImpl collection,
135                                                                                                                                                 PluralAttribute<?, ?, ?> cd) {
136                // OJB stores the related class object name. We need to go into the repository and grab the table name.
137                Class<?> collectionElementClass = cd.getElementType().getJavaType();
138                EntityType<?> elementEntityType = entityManager.getMetamodel().entity(collectionElementClass);
139                // get table name behind element
140                if (elementEntityType instanceof EntityTypeImpl) {
141                        collection.setBackingObjectName(((EntityTypeImpl<?>) elementEntityType).getDescriptor().getTableName());
142                }
143
144                // Set to read only if store (save) operations should not be pushed through
145                PersistentAttributeType persistentAttributeType = cd.getPersistentAttributeType();
146
147                if (cd instanceof PluralAttributeImpl) {
148                        PluralAttributeImpl<?, ?, ?> coll = (PluralAttributeImpl<?, ?, ?>) cd;
149                        CollectionMapping collectionMapping = coll.getCollectionMapping();
150
151                        if (collectionMapping instanceof OneToManyMapping) {
152                                OneToManyMapping otm = (OneToManyMapping) collectionMapping;
153                                populateInverseRelationship(otm, collection);
154                                Map<DatabaseField, DatabaseField> keyMap = otm.getSourceKeysToTargetForeignKeys();
155                                List<DataObjectAttributeRelationship> attributeRelationships = new ArrayList<DataObjectAttributeRelationship>();
156                                for (Map.Entry<DatabaseField, DatabaseField> keyRel : keyMap.entrySet()) {
157                                        attributeRelationships.add(new DataObjectAttributeRelationshipImpl(
158                                                        getPropertyNameFromDatabaseColumnName(cd.getDeclaringType(), keyRel.getKey().getName()),
159                                                        getPropertyNameFromDatabaseColumnName(elementEntityType, keyRel.getValue().getName())));
160                                }
161                                collection.setAttributeRelationships(attributeRelationships);
162                        }
163
164                        collection.setReadOnly(collectionMapping.isReadOnly());
165                        collection.setSavedWithParent(collectionMapping.isCascadePersist());
166                        collection.setDeletedWithParent(collectionMapping.isCascadeRemove());
167                        collection.setLoadedAtParentLoadTime(collectionMapping.isCascadeRefresh() && !collectionMapping.isLazy());
168                        collection.setLoadedDynamicallyUponUse(collectionMapping.isCascadeRefresh() && collectionMapping.isLazy());
169                } else {
170                        // get what we can based on JPA values (note that we just set some to have values here)
171                        collection.setReadOnly(false);
172                        collection.setSavedWithParent(persistentAttributeType == PersistentAttributeType.ONE_TO_MANY);
173                        collection.setDeletedWithParent(persistentAttributeType == PersistentAttributeType.ONE_TO_MANY);
174                        collection.setLoadedAtParentLoadTime(true);
175                        collection.setLoadedDynamicallyUponUse(false);
176                }
177
178                // We need to detect the case of a intermediate mapping table. These tables are not directly mapped
179                // in OJB, but are referenced by their table and column names.
180                // The attributes referenced are assumed to be in the order of the PK fields of the parent and child objects
181                // as there is no way to identify the attributes/columns on the linked classes.
182
183                // Extract the default sort order for the collection
184                List<DataObjectCollectionSortAttribute> sortAttributes = new ArrayList<DataObjectCollectionSortAttribute>();
185                if (cd instanceof PluralAttributeImpl) {
186                        PluralAttributeImpl<?, ?, ?> coll = (PluralAttributeImpl<?, ?, ?>) cd;
187                        CollectionMapping collectionMapping = coll.getCollectionMapping();
188                        if (collectionMapping.getSelectionQuery() instanceof ObjectLevelReadQuery) {
189                                ObjectLevelReadQuery readQuery = (ObjectLevelReadQuery) collectionMapping.getSelectionQuery();
190                                List<Expression> orderByExpressions = readQuery.getOrderByExpressions();
191                                for (Expression expression : orderByExpressions) {
192                                        if (expression instanceof FunctionExpression) {
193                                                String attributeName = ((FunctionExpression) expression).getBaseExpression().getName();
194                                                SortDirection direction = SortDirection.ASCENDING;
195                                                if (expression.getOperator().isOrderOperator()) {
196                                                        if (StringUtils
197                                                                        .containsIgnoreCase(expression.getOperator().getDatabaseStrings()[0], "DESC")) {
198                                                                direction = SortDirection.DESCENDING;
199                                                        }
200                                                }
201                                                sortAttributes.add(new DataObjectCollectionSortAttributeImpl(attributeName, direction));
202                                        }
203                                }
204                        }
205
206                }
207                collection.setDefaultCollectionOrderingAttributeNames(sortAttributes);
208        }
209
210        /**
211         * Returns the property name on the given entity type which the given database column is mapped to.
212         *
213         * <p>
214         * If no field on the given type is mapped to this field (which is common in cases of a JPA relationship without an
215         * actual {@link javax.persistence.Column} annotated field to represent the foreign key) then this method will
216         * return null.
217         * </p>
218         *
219         * @param entityType the entity type on which to search for a property that is mapped to the given column
220         * @param databaseColumnName the name of the database column
221         *
222         * @return the name of the property on the given entity type which maps to the given column, or null if no such
223         *         mapping exists
224         */
225        @SuppressWarnings({ "unchecked", "rawtypes" })
226        protected String getPropertyNameFromDatabaseColumnName(ManagedType entityType, String databaseColumnName) {
227                for (SingularAttributeImpl attr : (Set<SingularAttributeImpl>) entityType.getSingularAttributes()) {
228                        if (!attr.isAssociation()) {
229                                if (!(attr.getClass().isAssignableFrom(EmbeddableTypeImpl.class)) &&
230                                                !(attr.getMapping().getClass().isAssignableFrom(AggregateObjectMapping.class)) &&
231                                                attr.getMapping().getField().getName().equals(databaseColumnName)) {
232                                        return attr.getName();
233                                }
234                        }
235                }
236                return null;
237        }
238
239        /**
240         * {@inheritDoc}
241         */
242        @Override
243        protected void populateImplementationSpecificRelationshipLevelMetadata(DataObjectRelationshipImpl relationship,
244                                                                                                                                                   SingularAttribute<?, ?> rd) {
245                // We need to go into the repository and grab the table name.
246                Class<?> referencedClass = rd.getBindableJavaType();
247                EntityType<?> referencedEntityType = entityManager.getMetamodel().entity(referencedClass);
248                if (referencedEntityType instanceof EntityTypeImpl) {
249                        relationship
250                                        .setBackingObjectName(((EntityTypeImpl<?>) referencedEntityType).getDescriptor().getTableName());
251                }
252                // Set to read only if store (save) operations should not be pushed through
253                PersistentAttributeType persistentAttributeType = rd.getPersistentAttributeType();
254
255                if (rd instanceof SingularAttributeImpl) {
256                        SingularAttributeImpl<?, ?> rel = (SingularAttributeImpl<?, ?>) rd;
257
258                        OneToOneMapping relationshipMapping = (OneToOneMapping) rel.getMapping();
259                        relationship.setReadOnly(relationshipMapping.isReadOnly());
260                        relationship.setSavedWithParent(relationshipMapping.isCascadePersist());
261                        relationship.setDeletedWithParent(relationshipMapping.isCascadeRemove());
262                        relationship.setLoadedAtParentLoadTime(relationshipMapping.isCascadeRefresh()
263                                        && !relationshipMapping.isLazy());
264                        relationship.setLoadedDynamicallyUponUse(relationshipMapping.isCascadeRefresh()
265                                        && relationshipMapping.isLazy());
266
267                        List<DataObjectAttributeRelationship> attributeRelationships = new ArrayList<DataObjectAttributeRelationship>();
268                        List<String> referencedEntityPkFields = getPrimaryKeyAttributeNames(referencedEntityType);
269
270                        for (String referencedEntityPkField : referencedEntityPkFields) {
271                                for (Map.Entry<DatabaseField, DatabaseField> entry :
272                                                relationshipMapping.getTargetToSourceKeyFields().entrySet()) {
273                                        DatabaseField childDatabaseField = entry.getKey();
274                                        String childFieldName = getPropertyNameFromDatabaseColumnName(referencedEntityType,
275                                                        childDatabaseField.getName());
276
277                                        if (referencedEntityPkField.equalsIgnoreCase(childFieldName)) {
278                                                DatabaseField parentDatabaseField = entry.getValue();
279                                                String parentFieldName = getPropertyNameFromDatabaseColumnName(rd.getDeclaringType(),
280                                                                parentDatabaseField.getName());
281
282                                                if (parentFieldName != null) {
283                                                        attributeRelationships
284                                                                        .add(new DataObjectAttributeRelationshipImpl(parentFieldName, childFieldName));
285                                                        break;
286                                                } else {
287                                                        LOG.warn("Unable to find parent field reference.  There may be a JPA mapping problem on " +
288                                                                        referencedEntityType.getJavaType() + ": " + relationship);
289                                                }
290                                        }
291                                }
292                        }
293
294                        relationship.setAttributeRelationships(attributeRelationships);
295
296                        populateInverseRelationship(relationshipMapping, relationship);
297
298                } else {
299                        // get what we can based on JPA values (note that we just set some to have values here)
300                        relationship.setReadOnly(persistentAttributeType == PersistentAttributeType.MANY_TO_ONE);
301                        relationship.setSavedWithParent(persistentAttributeType == PersistentAttributeType.ONE_TO_ONE);
302                        relationship.setDeletedWithParent(persistentAttributeType == PersistentAttributeType.ONE_TO_ONE);
303                        relationship.setLoadedAtParentLoadTime(true);
304                        relationship.setLoadedDynamicallyUponUse(false);
305                }
306        }
307
308        /**
309         * Populates the inverse relationship for a given relationship.
310         *
311         * @param mapping the {@link DatabaseMapping} that defines the relationship.
312         * @param relationship the relationship of which to populate the other side.
313         */
314        protected void populateInverseRelationship(DatabaseMapping mapping, MetadataChildBase relationship) {
315                DatabaseMapping relationshipPartner = findRelationshipPartner(mapping);
316                if (relationshipPartner != null) {
317                        Class<?> partnerType = relationshipPartner.getDescriptor().getJavaClass();
318                        DataObjectMetadata partnerMetadata = masterMetadataMap.get(partnerType);
319                        // if the target metadata is not null, it means that entity has already been processed,
320                        // so we can go ahead and establish the inverse relationship
321                        if (partnerMetadata != null) {
322                                // first check if it's a relationship
323                                MetadataChildBase relationshipPartnerMetadata =
324                                                (MetadataChildBase)partnerMetadata.getRelationship(relationshipPartner.getAttributeName());
325                                if (relationshipPartnerMetadata == null) {
326                                        relationshipPartnerMetadata =
327                                                        (MetadataChildBase)partnerMetadata.getCollection(relationshipPartner.getAttributeName());
328                                }
329                                if (relationshipPartnerMetadata != null) {
330                                        relationshipPartnerMetadata.setInverseRelationship(relationship);
331                                        relationship.setInverseRelationship(relationshipPartnerMetadata);
332                                }
333
334                        }
335                }
336        }
337
338        /**
339         * Gets the inverse mapping of the given {@link DatabaseMapping}.
340         *
341         * @param databaseMapping the {@link DatabaseMapping} of which to get the inverse.
342         * @return the inverse mapping of the given {@link DatabaseMapping}.
343         */
344        protected DatabaseMapping findRelationshipPartner(DatabaseMapping databaseMapping) {
345                if (databaseMapping instanceof OneToManyMapping) {
346                        OneToManyMapping mapping = (OneToManyMapping)databaseMapping;
347                        if (mapping.getMappedBy() != null) {
348                                Class<?> referenceClass = mapping.getReferenceClass();
349                                ClassDescriptor referenceClassDescriptor = getClassDescriptor(referenceClass);
350                                return referenceClassDescriptor.getMappingForAttributeName(mapping.getMappedBy());
351                        }
352                } else if (databaseMapping instanceof ManyToOneMapping) {
353                        // one odd thing just to note here, for ManyToOne mappings with an inverse OneToMany, for some reason the
354                        // getMappedBy method still returns the mappedBy from the OneToMany side, so we can't use nullness of
355                        // mappedBy to infer which side of the relationship we are on, oddly enough, that's not the way it works
356                        // for OneToOne mappings (see below)...go figure
357                        //
358                        // I have to assume this is some sort of bug in EclipseLink metadata
359                        ManyToOneMapping mapping = (ManyToOneMapping)databaseMapping;
360                        Class<?> referenceClass = mapping.getReferenceClass();
361                        ClassDescriptor referenceClassDescriptor = getClassDescriptor(referenceClass);
362                        // find the OneToMany mapping which points back to this ManyToOne
363                        for (DatabaseMapping referenceMapping : referenceClassDescriptor.getMappings()) {
364                                if (referenceMapping instanceof OneToManyMapping) {
365                                        OneToManyMapping oneToManyMapping = (OneToManyMapping)referenceMapping;
366                                        if (mapping.getAttributeName().equals(oneToManyMapping.getMappedBy())) {
367                                                return oneToManyMapping;
368                                        }
369                                }
370                        }
371                } else if (databaseMapping instanceof OneToOneMapping) {
372                        OneToOneMapping mapping = (OneToOneMapping)databaseMapping;
373                        // well for reasons I can't quite fathom, mappedBy is always null on OneToOne relationships,
374                        // thankfully it's OneToOne so it's pretty easy to figure out the inverse
375                        ClassDescriptor referenceClassDescriptor = getClassDescriptor(mapping.getReferenceClass());
376                        // let's check if theres a OneToOne pointing back to us
377                        for (DatabaseMapping referenceMapping : referenceClassDescriptor.getMappings()) {
378                                if (referenceMapping instanceof OneToOneMapping) {
379                                        OneToOneMapping oneToOneMapping = (OneToOneMapping)referenceMapping;
380                                        if (oneToOneMapping.getReferenceClass().equals(mapping.getDescriptor().getJavaClass())) {
381                                                return oneToOneMapping;
382                                        }
383                                }
384                        }
385                }
386                // TODO need to implement for bi-directional OneToOne and ManyToMany
387                return null;
388        }
389
390        /**
391         * {@inheritDoc}
392         */
393        @Override
394        public DataObjectRelationship addExtensionRelationship(Class<?> entityClass, String extensionPropertyName,
395                                                                                                                   Class<?> extensionEntityClass) {
396                ClassDescriptor entityDescriptor = getClassDescriptor(entityClass);
397                ClassDescriptor extensionEntityDescriptor = getClassDescriptor(extensionEntityClass);
398
399                if (LOG.isDebugEnabled()) {
400                        LOG.debug("About to attempt to inject a 1:1 relationship on PKs between " + entityDescriptor + " and "
401                                        + extensionEntityDescriptor);
402                }
403                OneToOneMapping dm = (OneToOneMapping) entityDescriptor.newOneToOneMapping();
404                dm.setAttributeName(extensionPropertyName);
405                dm.setReferenceClass(extensionEntityClass);
406                dm.setDescriptor(entityDescriptor);
407                dm.setIsPrivateOwned(true);
408                dm.setJoinFetch(ForeignReferenceMapping.OUTER_JOIN);
409                dm.setCascadeAll(true);
410                dm.setIsLazy(false);
411                dm.dontUseIndirection();
412                dm.setIsOneToOneRelationship(true);
413                dm.setRequiresTransientWeavedFields(false);
414
415                OneToOneMapping inverse = findExtensionInverse(extensionEntityDescriptor, entityClass);
416                dm.setMappedBy(inverse.getAttributeName());
417                for (DatabaseField sourceField : inverse.getSourceToTargetKeyFields().keySet()) {
418                        DatabaseField targetField = inverse.getSourceToTargetKeyFields().get(sourceField);
419                        // reverse them, pass the source from the inverse as our target and the target from the inverse as our source
420                        dm.addTargetForeignKeyField(sourceField, targetField);
421                }
422
423                dm.preInitialize(getEclipseLinkEntityManager().getDatabaseSession());
424                dm.initialize(getEclipseLinkEntityManager().getDatabaseSession());
425                entityDescriptor.addMapping(dm);
426                entityDescriptor.getObjectBuilder().initialize(getEclipseLinkEntityManager().getDatabaseSession());
427
428                // build the data object relationship
429                ManagedTypeImpl<?> managedType = (ManagedTypeImpl<?>)getEntityManager().getMetamodel().managedType(entityClass);
430                SingularAttributeImpl<?, ?> singularAttribute = new SingularAttributeLocal(managedType, dm);
431                return getRelationshipMetadata(singularAttribute);
432        }
433
434        /**
435         * Provides a local implementation of {@link SingularAttributeImpl}.
436         */
437        class SingularAttributeLocal extends SingularAttributeImpl {
438
439                /**
440                 * Creates a local implementation of {@link SingularAttributeImpl}.
441                 *
442                 * @param managedType the {@link ManagedType}.
443                 * @param mapping the {@link DatabaseMapping}.
444                 */
445                SingularAttributeLocal(ManagedTypeImpl managedType, DatabaseMapping mapping) {
446                        super(managedType, mapping);
447                }
448        }
449
450        /**
451         * Gets the inverse extension of the given {@link ClassDescriptor}.
452         *
453         * @param extensionEntityDescriptor the {@link ClassDescriptor} of which to get the inverse.
454         * @param entityType the type of the entity.
455         * @return the inverse extension of the given {@link ClassDescriptor}.
456         */
457        protected OneToOneMapping findExtensionInverse(ClassDescriptor extensionEntityDescriptor, Class<?> entityType) {
458                Collection<DatabaseMapping> derivedIdMappings = extensionEntityDescriptor.getDerivesIdMappinps();
459                String extensionInfo = "(" + extensionEntityDescriptor.getJavaClass().getName() + " -> " + entityType.getName()
460                                + ")";
461                if (derivedIdMappings == null || derivedIdMappings.isEmpty()) {
462                        throw new MetadataConfigurationException("Attempting to use extension framework, but extension "
463                                        + extensionInfo + " does not have a valid inverse OneToOne Id mapping back to the extended data "
464                                        + "object. Please ensure it is annotated property for use of the extension framework with JPA.");
465                } else if (derivedIdMappings.size() > 1) {
466                        throw new MetadataConfigurationException("When attempting to determine the inverse relationship for use "
467                                        + "with extension framework " + extensionInfo + " encountered more than one 'derived id' mapping, "
468                                        + "there should be only one!");
469                }
470                DatabaseMapping inverseMapping = derivedIdMappings.iterator().next();
471                if (!(inverseMapping instanceof OneToOneMapping)) {
472                        throw new MetadataConfigurationException("Identified an inverse derived id mapping for extension "
473                                        + "relationship " + extensionInfo + " but it was not a one-to-one mapping: " + inverseMapping);
474                }
475                return (OneToOneMapping)inverseMapping;
476        }
477
478        /**
479         * Gets the descriptor for the entity type.
480         *
481         * @param entityClass the type of the enty.
482         * @return the descriptor for the entity type.
483         */
484        protected ClassDescriptor getClassDescriptor(Class<?> entityClass) {
485                return getEclipseLinkEntityManager().getDatabaseSession().getDescriptor(entityClass);
486        }
487
488        /**
489         * The entity manager for interacting with the database.
490         * @return the entity manager for interacting with the database.
491         */
492        protected JpaEntityManager getEclipseLinkEntityManager() {
493                return (JpaEntityManager) entityManager;
494        }
495}